วิธีง่ายๆในการสร้างเลย์เอาต์แบบไดนามิก แต่เป็นสี่เหลี่ยมจัตุรัส


84

ฉันใช้GridViewเพื่อแสดงจำนวนการดูซึ่งโดยพื้นฐานLinearLayoutsแล้ว ฉันต้องการLinearLayoutsให้ทั้งหมดเป็นสี่เหลี่ยมจัตุรัส แต่ฉันก็ต้องการให้มีขนาดแบบไดนามิกนั่นคือมีสองคอลัมน์และฉันต้องการLinearLayoutsให้ยืดออกโดยขึ้นอยู่กับขนาดของหน้าจอ แต่ยังคงเป็นสี่เหลี่ยมจัตุรัส มีวิธีทำผ่านxmlเค้าโครงหรือไม่หรือต้องตั้งค่าความสูงและความกว้างโดยใช้โปรแกรม

คำตอบ:


113

วิธีแก้ปัญหาอย่างเป็นระเบียบสำหรับGridViewรายการสี่เหลี่ยมจัตุรัสคือการขยายRelativeLayoutหรือLinearLayoutและแทนที่onMeasureดังนี้:

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

2
นี่คือการใช้ข้อมูลจำเพาะการวัดแทนที่จะใช้ setMeasuredDimension () ดังนั้นจึงเป็นสิ่งที่ดี แต่มันไม่ยืดหยุ่นมากนักเนื่องจากต้องใช้ความกว้างเสมอ
อดัม

3
ดังที่ @DavidMerriman ได้กล่าวไว้ข้างต้นหากมุมมองกว้างกว่าความสูงสิ่งนี้จะทำให้มุมมองขยายออกไปด้านล่างพื้นที่รับชมซึ่งจะตัดส่วนหนึ่งของมุมมองออกไป
vcapra1

5
สิ่งนี้จะมีความยืดหยุ่นมากขึ้นหากส่งผ่านค่าของ Math.min (widthMeasureSpec, heightMeasureSpec) ลงในอาร์กิวเมนต์ของ super.onMeasure ()
Paul Wintz

สิ่งนี้จะทำงานร่วมกับกิจกรรมได้อย่างง่ายดาย แต่จะใช้ไม่ได้กับวิดเจ็ตของแอป
Apollo-Roboto

73

อาจจะตอบช้า แต่ก็ยังเป็นประโยชน์สำหรับผู้เยี่ยมชมใหม่

ด้วยConstraintLayoutใหม่ที่เปิดตัวใน Android Studio 2.3 ตอนนี้มันค่อนข้างง่ายในการสร้างเลย์เอาต์ที่ตอบสนอง

ในแม่แบบ ConstraintLayout หากต้องการสร้างมุมมอง / เลย์เอาต์ของลูก ๆ แบบไดนามิกให้เพิ่มแอตทริบิวต์นี้

app:layout_constraintDimensionRatio="w,1:1"

w คือการระบุข้อ จำกัด ด้านความกว้างและอัตราส่วน 1: 1 เพื่อให้แน่ใจว่าเค้าโครงสี่เหลี่ยมจัตุรัส


9
นี่คือคำตอบของราชา
itzhar

2
คำตอบยอดนิยมหากคุณต้องการแสดงเค้าโครงที่มีอยู่เป็นสี่เหลี่ยมจัตุรัส!
Quentin Klein

9
อย่าลืมตั้งค่า "0dp" สำหรับความกว้างและความสูงของเค้าโครงในเค้าโครงลูก มิฉะนั้นจะไม่ทำงาน
thilina Kj

มันใช้งานได้กับมุมมองด้วยเช่นกันฉันหมายความว่าถ้าคุณต้องการให้ลูกของ contrantLayout เป็นสี่เหลี่ยมคุณสามารถกำหนดแอตทริบิวต์ layout_constraintDimensionRatio ได้โดยตรงในมุมมองลูก
Plinio.Santos

1
ขอบคุณมากสำหรับการแก้ปัญหา; ฉันได้ทำถ้ามันช่วยให้ส่วนสำคัญทั้งหมดของรหัสของฉันโดยใช้โซลูชันของคุณ: gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71

44

ไม่มีสิ่งใดใน xml ที่จะให้คุณเชื่อมโยงคุณสมบัติความกว้างและความสูง อาจเป็นสิ่งที่ง่ายที่สุดที่จะทำคือซับคลาสLinearLayoutและลบล้าง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);
}

ฉันเคยใช้สิ่งนี้เพื่อสร้างมุมมองที่มักจะเป็นสี่เหลี่ยมจัตุรัสมาก่อน มันควรจะยังคงทำงานสำหรับLinearLayout.

ข้อมูลเพิ่มเติมที่จะช่วยในการดำเนินการนี้: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html


ใช่ฉันจบลงด้วยการทำสิ่งที่คล้ายกัน ขอบคุณ!
Catherine

1
หากสิ่งนี้ได้ผลสำหรับคุณคุณช่วยทำเครื่องหมายว่าเป็นคำตอบที่ถูกต้องได้ไหม
David Merriman

1
@ แคทเธอรีนเฮ้แคทเธอรีนโพสต์ตัวอย่างของคุณได้ไหม บางทีมันอาจจะมีประโยชน์สำหรับคนอื่น ๆ :-)
Marek Sebera

3
อย่าลืมเพิ่ม: super.onMeasure(widthMeasureSpec, widthMeasureSpec);!
deKajoo

1
@deKajoo การใช้super.onMeasure(widthMeasureSpec, widthMeasureSpec);จะทำให้คุณได้รูปสี่เหลี่ยมจัตุรัส แต่กว้าง x กว้างเสมอ หากการวัดที่ให้ไว้กว้างกว่าความสูงคุณจะมีปัญหา วิธีแก้ปัญหาของฉันใช้การวัดสองขนาดที่เล็กกว่าเพื่อให้คุณได้สี่เหลี่ยมจัตุรัสที่ใหญ่ที่สุดที่จะพอดีกับสี่เหลี่ยมที่กำหนด
David Merriman

43

ฉันทำแบบนี้แล้ว:

@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);
}

ด้วยการใช้งานนี้เลย์เอาต์ของคุณจะเป็นสี่เหลี่ยมจัตุรัสโดยสมมติว่ามีขนาดต่ำกว่าระหว่างความกว้างและความสูง และยังสามารถตั้งค่าด้วยค่าไดนามิกเช่นการใช้น้ำหนักภายใน LinearLayout


วิธีการ "onMeasure" จะถูกแทนที่ในคลาสที่ขยาย GridView?
An-droid

Tt ถูกลบล้างในคลาสที่คุณต้องการให้เป็นสี่เหลี่ยมจัตุรัส สำหรับกรณีของคำถามมันคือ LinearLayouts ภายใน GridView
Fernando Camargo

สิ่งนี้ช่วยฉันแก้ไขปัญหาแปลก ๆ ในการใช้มุมมองสี่เหลี่ยมใน StaggeredGridLayout การนำไปใช้งานที่ไร้เดียงสาของฉันไม่ดีพอ ขอบคุณ!
Peterdk

10

เราสามารถทำได้ด้วยวิธีง่ายๆเพียงโทรหาsuper.onMeasure()สองครั้ง

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));
}

การโทรsuper.onMeasure()สองครั้งจะมีประสิทธิภาพน้อยกว่าในแง่ของกระบวนการวาดภาพ แต่เป็นวิธีง่ายๆในการแก้ไขปัญหาเค้าโครงที่คำตอบอื่น ๆ อาจทำให้เกิดได้


2
แทนที่จะเรียก super.onMeasure สองครั้งครั้งที่สองคุณต้องเรียกใช้ setMeasuredDimension (squareLen, squareLen);
user3802077

การโทรsuper.onMeasure()สองครั้งทำให้แน่ใจว่าเค้าโครงเสร็จสิ้นอย่างถูกต้องตามขนาดใหม่ของมุมมอง หากไม่มีสิ่งนี้เราจำเป็นต้องทริกเกอร์เลย์เอาต์ด้วยวิธีอื่นหรือจัดการกับเค้าโครงที่ไม่ถูกต้อง
Richard Le Mesurier

4

มันง่ายเหมือน:

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);
    }
}

2

เพิ่มบรรทัดต่อไปนี้ใน XML:

app:layout_constraintDimensionRatio="1:1"

1
ฉันต้องการโพสต์คำตอบเดียวกันเพราะมันดีกว่า app: layout_constraintDimensionRatio = "w, 1: 1" เพราะมันจะรักษาอัตราส่วนไว้เสมอหากคุณเปลี่ยนการกำหนดค่า
Alexey Simchenko

1

นี่คือโซลูชันที่ใช้ได้กับพารามิเตอร์โครงร่างทั้งหมดที่สามารถตั้งค่าให้ดูหรือดูกลุ่ม:

    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);

ไชโย


คำตอบนี้มีลักษณะที่น่าสนใจและอาจมีประสิทธิภาพมากกว่า แต่จำเป็นต้องใช้งานได้และควรสร้างข้อกำหนดการวัดที่ส่งต่อไปยัง super.onMeasure () แทนที่จะเป็น setMeasuredDimension () เนื่องจากจะอนุญาตให้มีซูเปอร์คลาสที่หลากหลาย
อดัม

1

คำแนะนำของฉันคือการสร้างคลาสเลย์เอาต์แบบกำหนดเองที่สืบทอดมาจาก FrameLayout แทนที่เมธอด OnMeasure () และวางคอนโทรลที่คุณต้องการให้เป็นสี่เหลี่ยมจัตุรัสภายใน SquareFrameLayout นั้น

นี่คือวิธีการทำใน Xamarin Android:

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);
    }
}

หากคุณต้องการให้มุมมอง (หรือส่วนควบคุมอื่น ๆ ) เป็นสี่เหลี่ยมจัตุรัส (และอยู่ตรงกลาง) ให้เพิ่มลงในเค้าโครงของคุณด้วยวิธีต่อไปนี้

<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>

0

ลองใช้SquareLayoutซึ่งเป็นไลบรารี Androidที่มีคลาส Wrapper สำหรับเลย์เอาต์ที่แตกต่างกันโดยแสดงผลแบบ Squared ที่มีขนาดโดยไม่สูญเสียฟังก์ชันหลักใด ๆ

มิติจะถูกคำนวณก่อนที่จะมีการแสดงเค้าโครงจึงไม่มีการแสดงผลใหม่หรืออะไรเช่นการปรับครั้งดูจะได้รับ

ในการใช้ไลบรารีให้เพิ่มสิ่งนี้ใน build.gradle ของคุณ:

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

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

คนที่คุณต้องการคือSquareLinearLayout


ใช้งานไม่ได้สำหรับฉันบนอุปกรณ์จริง :( แสดงผลได้ดีในการดูตัวอย่าง Android Studio แต่ไม่ทำงานบนอุปกรณ์
Eugene Voronoy

0

สำหรับทุกคนที่ต้องการแก้ปัญหาด้วย Kotlin 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

ลองใช้รหัสนี้:

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);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.