Simple way to do dynamic but square layout
AndroidAndroid 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>