How to have equal size of width and height [square shape] using layout_weight?

11,709

Solution 1

You have to override the onMeasure method set the same height as the height. Please see this question: Simple way to do dynamic but square layout

This is the complete solution that I come up with:

SquareLinearLayout.java :

public class SquareLinearLayout extends LinearLayout {
public SquareLinearLayout(Context context) {
    super(context);
}

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

public SquareLinearLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

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

    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 = getContext().getResources().getDimensionPixelSize(R.dimen.default_size); // Use your own default size, for example 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);
 }
}

activity_my.xml :

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:listitem="@layout/item"
tools:context=".MyActivity">

</ListView>

item.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="match_parent">

        <com.appsrise.squarelinearlayout.SquareLinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_blue_bright">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="Test 1"
                android:textSize="24sp" />
        </com.appsrise.squarelinearlayout.SquareLinearLayout>

        <com.appsrise.squarelinearlayout.SquareLinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@android:color/holo_green_light">
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="Test 2"
                android:textSize="24sp" />
        </com.appsrise.squarelinearlayout.SquareLinearLayout>

</LinearLayout>

MyActivity.java :

public class MyActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);

    ListView listView = (ListView) findViewById(R.id.listview);
    listView.setAdapter(new BaseAdapter() {

        private LayoutInflater mInflater = LayoutInflater.from(MyActivity.this);

        @Override
        public int getCount() {
            return 100;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null)
                convertView = mInflater.inflate(R.layout.item, parent, false);
            return convertView;
        }
    });
 }
}

Solution 2

Simpler solution is to pass the width measurement specification into the height specification when calling onMeasure (or the other way around if you want the height to determine the square size).

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;

public class SquareLinearLayout extends LinearLayout{
    public SquareLinearLayout(Context context) {
        super(context);
    }

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

    public SquareLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
        // or super.onMeasure(heightMeasureSpec, heightMeasureSpec);
    }
}

Solution 3

Xamarin version

[Register("SquareFrameLayout")]
public class SquareFrameLayout : LinearLayout
{
    public SquareFrameLayout(Context context) : base(context)
    {
    }

    public SquareFrameLayout(Context context, IAttributeSet attrs) : base(context, attrs)
    {
    }

    public SquareFrameLayout(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
    {
    }

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

        var width = MeasureSpec.GetSize(widthMeasureSpec);
        var height = MeasureSpec.GetSize(heightMeasureSpec);
        var widthDesc = MeasureSpec.GetMode(widthMeasureSpec);
        var heightDesc = MeasureSpec.GetMode(heightMeasureSpec);

        if (widthDesc == MeasureSpecMode.Unspecified && heightDesc == MeasureSpecMode.Unspecified)
        {
            //size = Context.Resources.GetDimensionPixelSize(Resource.Dimension.default_size); // Use your own default size, for example 125dp
            height = width = 0;
        }
        else if(heightDesc != MeasureSpecMode.Unspecified && widthDesc == MeasureSpecMode.Unspecified)
        {
            width = height;
        }
        else if (widthDesc != MeasureSpecMode.Unspecified && heightDesc == MeasureSpecMode.Unspecified)
        {
            height = width;
        }
        else
        {
            //In all other cases both dimensions have been specified so we choose the smaller of the two
            var size = width > height ? height : width;
            width = height = size;
        }

        SetMeasuredDimension(width, height);
    }

and

[Register("SquareLinearLayout")]
public class SquareLinearLayout : LinearLayout
{
    public SquareLinearLayout(Context context) : base(context)
    {
    }

    public SquareLinearLayout(Context context, IAttributeSet attrs) : base(context, attrs)
    {
    }

    public SquareLinearLayout(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
    {
    }

    protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

        var width = MeasureSpec.GetSize(widthMeasureSpec);
        var height = MeasureSpec.GetSize(heightMeasureSpec);
        var widthDesc = MeasureSpec.GetMode(widthMeasureSpec);
        var heightDesc = MeasureSpec.GetMode(heightMeasureSpec);

        if (widthDesc == MeasureSpecMode.Unspecified && heightDesc == MeasureSpecMode.Unspecified)
        {
            //size = Context.Resources.GetDimensionPixelSize(Resource.Dimension.default_size); // Use your own default size, for example 125dp
            height = width = 0;
        }
        else if(heightDesc != MeasureSpecMode.Unspecified && widthDesc == MeasureSpecMode.Unspecified)
        {
            width = height;
        }
        else if (widthDesc != MeasureSpecMode.Unspecified && heightDesc == MeasureSpecMode.Unspecified)
        {
            height = width;
        }
        else
        {
            //In all other cases both dimensions have been specified so we choose the smaller of the two
            var size = width > height ? height : width;
            width = height = size;
        }

        SetMeasuredDimension(width, height);
    }
}
Share:
11,709
Rendy
Author by

Rendy

A mobile tech enthusiast in hope to help other people's life

Updated on June 09, 2022

Comments

  • Rendy
    Rendy about 2 years

    I have following code:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Test 1"
            android:textSize="24sp" />
    </LinearLayout>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="Test 2"
            android:textSize="24sp" />
    </LinearLayout>
    

    that results:

    enter image description here

    How can I have equal size of View's height with its width (that is resulted by layout_weight)?

  • Rendy
    Rendy almost 10 years
    I think this one will make equally divided LinearLayout vertically, while what I want is height is same as the width resulted from layout_weight horizontally.
  • Lal
    Lal almost 10 years
    oops sorry..I've changed it to horizontal.But, what is your question actually?? you want height to be equal to width???
  • Rendy
    Rendy almost 10 years
    Yes, to be precise, I want square shape :)
  • Rendy
    Rendy almost 10 years
    Actually my question is for listview item and I never try OnMeasure for listview item, but I think I should try it.
  • Lal
    Lal almost 10 years
    try changing the height and width of TextView to fill_parent. But i dont think it will be square..
  • Rendy
    Rendy almost 10 years
    Yes, it will take whole screen height instead.
  • Lal
    Lal almost 10 years
    i would suggest you to divide the layouts until you get a square shaped layout and then you insert the TextViews in that square shaped layouts..Not sure if this is the right solution..
  • Rendy
    Rendy almost 10 years
    Hi Edy, I tried for OnMeasure but I am not sure why it doesn't work. After I tried code from your link, I use ViewTreeObserver and OnPreDrawListener but it gives me 0 for both width and height. I have found other solution that works.
  • Eduard B.
    Eduard B. almost 10 years
    Please present your solution then, as it might help others too.
  • Eduard B.
    Eduard B. almost 10 years
    Ok, I'll try later to implement the solution that I proposed, and if I succeed then we should compare the performances.
  • Rendy
    Rendy almost 10 years
    Sorry I think my solution is wrong, since when I tried to use margin, the View becomes rectangle, not square. I will delete my answer.
  • Rendy
    Rendy almost 10 years
    Your solution works great! However, previously I tried to use by extending TextView but it gives me undefinite height, but when in LinearLayout, it works.. Btw, would you please mind explain a bit why we need to declare default size first?
  • MCLLC
    MCLLC about 7 years
    The issue here is that you're adding more views in the View hierarchy and that causes overdraw issues, which can be a performance problem. I think the earlier answers using onMeasure() are better for that reason.
  • ajthyng
    ajthyng about 7 years
    You could simplify the else if portion of your statement using a bitwise XOR operator ^, otherwise great solution and I thank you.