简单的方式做动态但方形布局

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

58267 次浏览

There's nothing in the xml that will let you link the width and height properties. Probably the easiest thing to do is to subclass LinearLayout and override 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);
}

I've used this to create views that are always square before. It should still work for a LinearLayout.

More info that will help doing this: http://developer.android.com/guide/topics/ui/custom-components.html http://developer.android.com/reference/android/view/View.MeasureSpec.html

A neat solution for square GridView items is to extend RelativeLayout or LinearLayout and override onMeasure like so:

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

Here's a solution that works for all layout parameters that can be set to view or 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);

Cheers

I've done this way:

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

With this implementation, your layout will be square, assuming the lower size between width and height. And it can even be set with dynamic values, like using weight inside a LinearLayout.

We can do it with a very simple way - just call super.onMeasure() twice.

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

By calling super.onMeasure() twice, this is less efficient in terms of the drawing process, but it is a simple way to fix layout issues that the other answers can cause.

My suggestion is to create a custom layout class that inherits from FrameLayout. Override the OnMeasure() method and put whatever control you want to be square inside that SquareFrameLayout.

This is how it's done in 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);
}
}

If you want a View (or any other control) to be square (and centered) just add it to your layout the following way:

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

With the new ConstraintLayout introduced in Android Studio 2.3, it is now quite easy to build responsive layouts.

In a parent ConstraintLayout, to make any of its children view/layout dynamically square, add this attribute

app:layout_constraintDimensionRatio="w,1:1"

w is to specify width-wise constraints and 1:1 ratio ensures square layout.

It is as simple as:

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

Check out SquareLayout, an Android Library which provides a wrapper class for different Layouts, rendering them Squared dimensioned without losing any core functionalities.

The dimensions are calculated just before the Layout is rendered, hence there is no re-rendering or anything as such to adjust once the View is obtained.

To use the Library, add this to your build.gradle:

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


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

The one you require is SquareLinearLayout.

For anyone wants solution With Kotlin, here's what I did with 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)
}
}

Try this code:

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

Add the following line in XML:

app:layout_constraintDimensionRatio="1:1"
                          <androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >


<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />




</androidx.constraintlayout.widget.ConstraintLayout>