Simple way to do dynamic but square layout

Android

Android Problem Overview


I'm using a GridView to display a bunch of views which are essentially LinearLayouts. I want the LinearLayouts to all be square, but I also want them to be dynamically sized--that is, there are two columns and I want the LinearLayouts to stretch depending on the size of the screen but remain square. Is there a way to do this through the xml layout or do I have to set the heights and widths programmatically?

Android Solutions


Solution 1 - Android

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

Solution 2 - Android

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.

Solution 3 - Android

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.

Solution 4 - Android

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.html#onMeasure(int, int)> http://developer.android.com/reference/android/view/View.MeasureSpec.html

Solution 5 - Android

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.

Solution 6 - Android

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

Solution 7 - Android

Add the following line in XML:

app:layout_constraintDimensionRatio="1:1"

Solution 8 - Android

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

Solution 9 - Android

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>

Solution 10 - Android

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.

Solution 11 - Android

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

Solution 12 - Android

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

Solution 13 - Android

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCatherineView Question on Stackoverflow
Solution 1 - AndroidjdamcdView Answer on Stackoverflow
Solution 2 - AndroidHimanshu ChauhanView Answer on Stackoverflow
Solution 3 - AndroidFernando CamargoView Answer on Stackoverflow
Solution 4 - AndroidDavid MerrimanView Answer on Stackoverflow
Solution 5 - AndroidpasserbywhuView Answer on Stackoverflow
Solution 6 - AndroidAbdul WasaeView Answer on Stackoverflow
Solution 7 - AndroidAnurag BhalekarView Answer on Stackoverflow
Solution 8 - AndroidZaid DaghestaniView Answer on Stackoverflow
Solution 9 - AndroidsdipplView Answer on Stackoverflow
Solution 10 - AndroidKaushik NPView Answer on Stackoverflow
Solution 11 - AndroidWaterView Answer on Stackoverflow
Solution 12 - Androidreza_khalafiView Answer on Stackoverflow
Solution 13 - AndroidMina FaridView Answer on Stackoverflow