5

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
  • 5,572
  • 15
  • 52
  • 95

5 Answers5

9

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;
        }
    });
 }
}
amin
  • 561
  • 6
  • 18
Eduard B.
  • 6,735
  • 4
  • 27
  • 38
  • Actually my question is for listview item and I never try OnMeasure for listview item, but I think I should try it. – Rendy Aug 05 '14 at 15:57
  • 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. – Rendy Aug 06 '14 at 13:01
  • Please present your solution then, as it might help others too. – Eduard B. Aug 06 '14 at 13:10
  • Ok, I'll try later to implement the solution that I proposed, and if I succeed then we should compare the performances. – Eduard B. Aug 06 '14 at 14:42
  • 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 Aug 06 '14 at 14:58
  • 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? – Rendy Aug 06 '14 at 15:42
  • You could simplify the else if portion of your statement using a bitwise XOR operator ^, otherwise great solution and I thank you. – ajthyng Jun 14 '17 at 14:46
6

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);
    }
}
sixones
  • 1,953
  • 4
  • 21
  • 25
1

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);
    }
}
Softlion
  • 12,281
  • 11
  • 58
  • 88
0

You can do this also, If you need only one TextView needs to be square :

ViewTreeObserver vto = textview.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    textview.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    int width = textview.getMeasuredWidth();
                    int height = textview.getMeasuredHeight();
                    ViewGroup.LayoutParams params = textview.getLayoutParams();
                    if (width > height)
                        params.height = width;
                    else
                        params.width = height;
                    textview.setLayoutParams(params);

                }
            });
Vignesh KM
  • 1,979
  • 1
  • 18
  • 24
-3

You can divide a LinearLayout into two equal LinearLayout like this..

<LinearLayout android:orientation="horizontal" android:layout_height="fill_parent" android:layout_width="fill_parent">
  <LinearLayout android:layout_weight="1" android:layout_height="fill_parent" android:layout_width="fill_parent">

      <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_weight="1" android:layout_height="fill_parent" android:layout_width="fill_parent">

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

  </LinearLayout>
</LinearLayout>

This will divide the LinearLayout into two equal LinearLayouts and each of these LinearLayouts will contain the TextViews

Lal
  • 14,726
  • 4
  • 45
  • 70
  • 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. – Rendy Aug 05 '14 at 15:53
  • oops sorry..I've changed it to horizontal.But, what is your question actually?? you want height to be equal to width??? – Lal Aug 05 '14 at 15:55
  • Yes, to be precise, I want square shape :) – Rendy Aug 05 '14 at 15:57
  • try changing the height and width of `TextView` to `fill_parent`. But i dont think it will be square.. – Lal Aug 05 '14 at 15:59
  • Yes, it will take whole screen height instead. – Rendy Aug 05 '14 at 15:59
  • i would suggest you to divide the layouts until you get a square shaped layout and then you insert the `TextView`s in that square shaped layouts..Not sure if this is the right solution.. – Lal Aug 05 '14 at 16:01
  • 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. – MCLLC May 20 '17 at 14:55