Cách đơn giản để tạo bố cục động nhưng vuông vắn


84

Tôi đang sử dụng một GridViewđể hiển thị một loạt các chế độ xem về cơ bản LinearLayouts. Tôi muốn LinearLayoutstất cả đều là hình vuông, nhưng tôi cũng muốn chúng có kích thước động - nghĩa là, có hai cột và tôi muốn LinearLayoutskéo dài tùy thuộc vào kích thước của màn hình nhưng vẫn là hình vuông. Có cách nào để thực hiện việc này thông qua xmlbố cục hay tôi phải đặt chiều cao và chiều rộng theo lập trình?

Câu trả lời:


113

Một giải pháp gọn gàng cho GridViewcác mục hình vuông là mở rộng RelativeLayouthoặc LinearLayoutghi đè onMeasurenhư vậy:

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}

2
Điều này đang sử dụng thông số đo lường thay vì sử dụng setMeasuredDimension () để điều đó tốt. Nhưng nó không linh hoạt lắm vì nó luôn chiếm chiều rộng.
Adam

3
Như @DavidMerriman đã nói ở trên, nếu tầm nhìn rộng hơn chiều cao, điều này sẽ làm cho tầm nhìn bị kéo dài ra bên dưới vùng xem, cắt đi một phần của chế độ xem.
vcapra1

5
Điều này sẽ được linh hoạt hơn nếu nó được thông qua giá trị của Math.min (widthMeasureSpec, heightMeasureSpec) vào cả hai lập luận của super.onMeasure ()
Paul Wintz

Điều này sẽ hoạt động dễ dàng với các hoạt động nhưng nó sẽ không hoạt động với tiện ích ứng dụng.
Apollo-Roboto

73

Có thể là muộn để trả lời nhưng vẫn có thể hữu ích cho những khách mới.

Với ConstraintLayout mới được giới thiệu trong Android Studio 2.3, giờ đây việc tạo bố cục đáp ứng trở nên khá dễ dàng.

Trong ConstraintLayout chính, để làm cho bất kỳ phần nào con của nó xem / bố cục hình vuông động, hãy thêm thuộc tính này

app:layout_constraintDimensionRatio="w,1:1"

w là xác định các ràng buộc theo chiều rộng và tỷ lệ 1: 1 đảm bảo bố cục hình vuông.


9
đây là câu trả lời của vua
itzhar

2
Câu trả lời hàng đầu nếu bạn muốn hiển thị bố cục hiện có dưới dạng hình vuông!
Quentin Klein

9
Đảm bảo đặt "0dp" cho chiều rộng và chiều cao của bố cục trong bố cục con. Nếu không nó sẽ không hoạt động.
thilina Kj

Nó cũng làm việc cho quan điểm riêng của mình, tôi có nghĩa là nếu bạn muốn con của contrantLayout là hình vuông, bạn có thể định nghĩa các thuộc tính layout_constraintDimensionRatio trực tiếp trong giao diện trẻ
Plinio.Santos

1
Cảm ơn bạn rất nhiều về giải pháp; Tôi đã thực hiện nếu nó giúp cho ý chính hoàn chỉnh mã của tôi bằng giải pháp của bạn: gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71

44

Không có gì trong xml cho phép bạn liên kết các thuộc tính chiều rộng và chiều cao. Có lẽ điều dễ dàng nhất để làm là phân lớp LinearLayoutvà ghi đèonMeasure

@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int size = width > height ? height : width;
    setMeasuredDimension(size, size);
}

Tôi đã sử dụng điều này để tạo các khung nhìn luôn là hình vuông trước đây. Nó vẫn sẽ hoạt động cho a LinearLayout.

Thông tin khác sẽ giúp thực hiện việc này: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html


Vâng, tôi đã kết thúc một việc tương tự. Cảm ơn!
Catherine

1
Nếu điều này hiệu quả với bạn, bạn có thể đánh dấu đây là câu trả lời đúng không?
David Merriman

1
@Catherine này Catherine, bạn có thể đăng đoạn mã của mình không? Có lẽ nó muốn được hữu ích cho những người khác :-)
Marek Sebera

3
Đừng quên thêm super.onMeasure(widthMeasureSpec, widthMeasureSpec);:!
deKajoo

1
@deKajoo Sử dụng super.onMeasure(widthMeasureSpec, widthMeasureSpec);cung cấp cho bạn một hình vuông, nhưng nó luôn là chiều rộng x chiều rộng. Nếu các số đo được đưa ra rộng hơn chiều cao, bạn sẽ gặp vấn đề. Giải pháp của tôi sử dụng số đo nhỏ hơn trong hai số đo, để bạn luôn nhận được hình vuông lớn nhất sẽ nằm trong hình chữ nhật đã cho.
David Merriman

43

Tôi đã làm theo cách này:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int size;
    if(widthMode == MeasureSpec.EXACTLY && widthSize > 0){
        size = widthSize;
    }
    else if(heightMode == MeasureSpec.EXACTLY && heightSize > 0){
        size = heightSize;
    }
    else{
        size = widthSize < heightSize ? widthSize : heightSize;
    }

    int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
    super.onMeasure(finalMeasureSpec, finalMeasureSpec);
}

Với cách triển khai này, bố cục của bạn sẽ là hình vuông, giả sử kích thước thấp hơn giữa chiều rộng và chiều cao. Và nó thậm chí có thể được đặt bằng các giá trị động, như sử dụng trọng số bên trong LinearLayout.


phương thức "onMeasure" sẽ được ghi đè trong một lớp mở rộng GridView?
An-droid

Tt được ghi đè trong lớp mà bạn muốn nó là hình vuông. Đối với trường hợp của câu hỏi, đó là LinearLayouts bên trong GridView.
Fernando Camargo

Điều này đã giúp tôi khắc phục một số vấn đề lạ khi sử dụng chế độ xem hình vuông trong StprisredGridLayout. Việc thực hiện ngây thơ hơn của tôi không đủ tốt. Cảm ơn!
Peterdk

10

Chúng ta có thể làm điều đó với một cách rất đơn giản - chỉ cần gọi super.onMeasure()hai lần.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width = getMeasuredWidth();
    int height = getMeasuredHeight();
    int squareLen = Math.min(width, height);

    super.onMeasure(
        MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY), 
        MeasureSpec.makeMeasureSpec(squareLen, MeasureSpec.EXACTLY));
}

Bằng cách gọi super.onMeasure()hai lần, điều này kém hiệu quả hơn về mặt quy trình vẽ, nhưng nó là một cách đơn giản để khắc phục các vấn đề về bố cục mà các câu trả lời khác có thể gây ra.


2
thay vì gọi super.onMeasure hai lần, lần thứ hai bạn chỉ cần gọi setMeasuredDimension (squareLen, squareLen);
user3802077

Gọi super.onMeasure()hai lần để đảm bảo rằng bố cục được thực hiện chính xác với kích thước mới của dạng xem. Nếu không có điều này, chúng tôi cần kích hoạt bố cục theo một cách khác hoặc xử lý bố cục không chính xác.
Richard Le Mesurier

4

Nó đơn giản như:

public class SquareRelativeLayout extends RelativeLayout {

    public SquareRelativeLayout(Context context) {
        super(context);
    }

    public SquareRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    {
        if (widthMeasureSpec < heightMeasureSpec) 
            super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec);
    }
}

1

Đây là một giải pháp hoạt động cho tất cả các thông số bố cục có thể được đặt để xem hoặc nhóm xem:

    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int widthDesc = MeasureSpec.getMode(widthMeasureSpec);
    int heightDesc = MeasureSpec.getMode(heightMeasureSpec);
    int size = 0;
    if (widthDesc == MeasureSpec.UNSPECIFIED
            && heightDesc == MeasureSpec.UNSPECIFIED) {
        size = DP(defaultSize); // Use your own default size, in our case
                                // it's 125dp
    } else if ((widthDesc == MeasureSpec.UNSPECIFIED || heightDesc == MeasureSpec.UNSPECIFIED)
            && !(widthDesc == MeasureSpec.UNSPECIFIED && heightDesc == MeasureSpec.UNSPECIFIED)) {
                    //Only one of the dimensions has been specified so we choose the dimension that has a value (in the case of unspecified, the value assigned is 0)
        size = width > height ? width : height;
    } else {
                    //In all other cases both dimensions have been specified so we choose the smaller of the two
        size = width > height ? height : width;
    }
    setMeasuredDimension(size, size);

Chúc mừng


Câu trả lời này có một số đặc điểm thú vị và có khả năng mạnh mẽ hơn, nhưng cần làm việc và nên xây dựng các thông số đo lường được chuyển đến super.onMeasure () chứ không phải setMeasuredDimension () vì nó sẽ cho phép nhiều siêu lớp.
Adam

1

Đề xuất của tôi là tạo một lớp bố cục tùy chỉnh kế thừa từ FrameLayout. Ghi đè phương thức OnMeasure () và đặt bất kỳ điều khiển nào bạn muốn thành hình vuông bên trong SquareFrameLayout đó.

Đây là cách nó được thực hiện trong Xamarin.

public class SquareFrameLayout : FrameLayout
{
    private const string _tag = "SquareFrameLayout";

    public SquareFrameLayout(Android.Content.Context context):base(context) {}
    public SquareFrameLayout(IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer):base(javaReference, transfer) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs):base(context, attrs) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr):base(context, attrs, defStyleAttr) {}
    public SquareFrameLayout(Android.Content.Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes):base(context, attrs, defStyleAttr, defStyleRes) {}

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        var widthMode = MeasureSpec.GetMode(widthMeasureSpec);
        int widthSize = MeasureSpec.GetSize(widthMeasureSpec);
        var heightMode = MeasureSpec.GetMode(heightMeasureSpec);
        int heightSize = MeasureSpec.GetSize(heightMeasureSpec);

        int width, height;

        switch (widthMode)
        {
            case MeasureSpecMode.Exactly:
                width = widthSize;
                break;
            case MeasureSpecMode.AtMost:
                width = Math.Min(widthSize, heightSize);
                break;
            default:
                width = 100;
                break;
        }

        switch (heightMode)
        {
            case MeasureSpecMode.Exactly:
                height = heightSize;
                break;
            case MeasureSpecMode.AtMost:
                height = Math.Min(widthSize, heightSize);
                break;
            default:
                height = 100;
                break;
        }

        Log.Debug(_tag, $"OnMeasure({widthMeasureSpec}, {heightMeasureSpec}) => Width mode: {widthMode}, Width: {widthSize}/{width}, Height mode: {heightMode}, Height: {heightSize}/{height}");
        var size = Math.Min(width, height);
        var newMeasureSpec = MeasureSpec.MakeMeasureSpec(size, MeasureSpecMode.Exactly);
        base.OnMeasure(newMeasureSpec, newMeasureSpec);
    }
}

Nếu bạn muốn Chế độ xem (hoặc bất kỳ điều khiển nào khác) là hình vuông (và căn giữa), chỉ cần thêm nó vào bố cục của bạn theo cách sau:

<your.namespace.SquareFrameLayout
    android:id="@+id/squareContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center">
    <View
        android:id="@+id/squareContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</your.namespace.SquareFrameLayout>

1

Thêm dòng sau vào XML:

app:layout_constraintDimensionRatio="1:1"

1
Tôi muốn đăng câu trả lời tương tự vì nó tốt hơn app: layout_constraintDimensionRatio = "w, 1: 1" vì nó luôn giữ tỷ lệ nếu bạn thay đổi cấu hình
Alexey Simchenko

0

Hãy xem SquareLayout , một Thư viện Android cung cấp lớp trình bao bọc cho các Bố cục khác nhau, hiển thị chúng bằng kích thước Hình vuông mà không làm mất bất kỳ chức năng cốt lõi nào.

Các kích thước được tính toán ngay trước khi Bố cục được hiển thị , do đó không có kết xuất lại hoặc bất kỳ thứ gì tương tự để điều chỉnh sau khi có được Chế độ xem.

Để sử dụng Thư viện, hãy thêm cái này vào build.gradle của bạn:

repositories {
    maven {
        url "https://maven.google.com"
    }
}

dependencies {
    compile 'com.github.kaushikthedeveloper:squarelayout:0.0.3'
}

Cái bạn yêu cầu là SquareLinearLayout .


Không hoạt động với tôi trên thiết bị thực :( Hiển thị tốt trên bản xem trước Android Studio, nhưng không hoạt động trên thiết bị
Eugene Voronoy

0

Đối với bất kỳ ai muốn giải pháp Với Kotlin, đây là những gì tôi đã làm với FrameLayout.

package your.package.name

import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout

class SquareLayout: FrameLayout {

    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (widthMeasureSpec < heightMeasureSpec)
            super.onMeasure(widthMeasureSpec, widthMeasureSpec)
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec)
    }
}

0

Hãy thử mã này:

public class SquareRelativeLayout extends RelativeLayout {
    public SquareRelativeLayout(Context context) {
        super(context);
    }

    public SquareRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int size;
        if (widthMode == MeasureSpec.EXACTLY && widthSize > 0) {
            size = widthSize;
        } else if (heightMode == MeasureSpec.EXACTLY && heightSize > 0) {
            size = heightSize;
        } else {
            size = widthSize < heightSize ? widthSize : heightSize;
        }

        int finalMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        super.onMeasure(finalMeasureSpec, finalMeasureSpec);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.