进行动态但正方形布局的简单方法


84

我正在使用aGridView来显示一堆基本上是的视图LinearLayouts。我希望LinearLayouts它们都为正方形,但我也希望它们为动态大小-也就是说,有两列,我希望LinearLayouts根据屏幕的大小进行拉伸,但保持正方形。有没有办法通过xml布局来做到这一点,还是必须以编程方式设置高度和宽度?

Answers:


113

方形GridView项目的一个很好的解决方案是像这样扩展RelativeLayoutLinearLayout覆盖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()的两个参数
保罗Wintz

这将很容易与活动配合使用,但不适用于应用程序小部件。
Apollo-Roboto

72

回答可能很晚,但仍然会对新访客有所帮助。

借助Android Studio 2.3中引入的新ConstraintLayout,现在可以轻松构建响应式布局。

在父ConstraintLayout中,要使其所有子视图或布局动态地正方形化,请添加此属性

app:layout_constraintDimensionRatio="w,1:1"

w是指定宽度方向的约束,并且1:1的比例可确保正方形布局。


9
这是国王的答案
itzhar,

2
如果您想将现有布局显示为正方形,则为最佳答案!
昆汀·克莱因

9
确保在子布局中将布局宽度和高度设置为“ 0dp”。否则它将无法正常工作。
蒂丽娜·凯(Thilina Kj)

它也适用于视图本身,我的意思是,如果您想让congenttLayout的子对象为正方形,则可以直接在子视图中定义layout_constraintDimensionRatio属性
Plinio.Santos

1
非常感谢您的解决方案;我已经确定是否可以使用您的解决方案帮助我编写代码:gist.github.com/nadar71/006699e31ef7451813e6c97dacfbc5e2
android_dev71

44

xml中没有任何内容可让您链接width和height属性。可能最简单的方法是继承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


是的,我最终做了类似的事情。谢谢!
凯瑟琳

1
如果这对您有用,您能否将其标记为正确答案?
David Merriman 2012年

1
@Catherine嘿,Catherine,您可以发布您的代码段吗?也许对其他人有用:-)
Marek Sebera 2013年

3
不要忘记添加:super.onMeasure(widthMeasureSpec, widthMeasureSpec);
deKajoo 2014年

1
@deKajoo Usingsuper.onMeasure(widthMeasureSpec, widthMeasureSpec);确实可以为您提供一个正方形,但始终为宽度x宽度。如果给出的尺寸比它们的高宽,您将遇到问题。我的解决方案使用两个测量值中较小的一个,以便始终获得适合给定矩形的最大正方形。
大卫·梅里曼

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的类中重写?
机器人2014年

Tt在您希望它是正方形的类中被覆盖。对于问题的情况,它是GridView中的LinearLayouts。
Fernando Camargo 2014年

这帮助我解决了在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 '16

调用super.onMeasure()两次可确保在给定新视图大小的情况下正确完成布局。没有这个,我们需要以其他方式触发布局或处理不正确的布局。
理查德·勒·梅修里尔

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

1

这是一个适用于可以设置为view或viewgroup的所有布局参数的解决方案:

    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>

1

在XML中添加以下行:

app:layout_constraintDimensionRatio="1:1"

1
我想发布相同的答案,因为它比app:layout_constraintDimensionRatio =“ w,1:1”更好,因为如果您更改配置,它始终保持比率
Alexey Simchenko

0

请查看SquareLayout,这是一个Android库,它为不同的Layout提供包装类,使它们以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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.