Square layout on GridLayoutManager for RecyclerView
Solution 1
To have the square elements in my RecyclerView, I provide a simple wrapper for my root View element; I use the following SquareRelativeLayout
in place of RelativeLayout
.
package net.simplyadvanced.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
/** A RelativeLayout that will always be square -- same width and height,
* where the height is based off the width. */
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);
}
@TargetApi(VERSION_CODES.LOLLIPOP)
public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Set a square layout.
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
Then, in my XML layout for the adapter, I've just referenced the custom view as shown in the following. Though, you can do this programmatically also.
<?xml version="1.0" encoding="utf-8"?>
<net.simplyadvanced.widget.SquareRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/elementRootView"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- More widgets here. -->
</net.simplyadvanced.widget.SquareRelativeLayout>
Note: Depending on which orientation your grid is, then you may want to have the width based off of height (GridLayoutManager.HORIZONTAL
) instead of the height being based off the width (GridLayoutManager.VERTICAL
).
Solution 2
Constraint layout solves this problem. Use app:layout_constraintDimensionRatio="H,1:1"
recyclerview_grid_layout.xml
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/imageview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,1:1"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
EDIT
Set ImageView width to 0dp
. match_parent
is now deprecated for ConstraintLayout.
Solution 3
In case someone would like to scale the view differently - this is how you do it:
private static final double WIDTH_RATIO = 3;
private static final double HEIGHT_RATIO = 4;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = (int) (HEIGHT_RATIO / WIDTH_RATIO * widthSize);
int newHeightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, newHeightSpec);
}
Solution 4
Starting API 26 (Support Library 26.0), one can use ConstraintLayout that exposes aspect ratio property to force views to be squared: https://developer.android.com/training/constraint-layout/index.htm
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
...
}
...
dependencies {
compile 'com.android.support:appcompat-v7:26.0.2'
compile 'com.android.support.constraint:constraint-layout:1.1.0-beta1' //use whatever version is current
}
Example of layout I'm using in GridLayoutManager:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/margin_small"
android:background="@drawable/border_gray"
android:gravity="center">
<android.support.constraint.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- place your content here -->
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
app:layout_constraintDimensionRatio="h,1:1"
is the key attribute here
Solution 5
A small update for ConstraintLayout for androidx.
Include this line to your build.gradle:
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
I wanted to get a RecycleView with GridLayoutManager with square CardViews and I used such a layout for items:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
>
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="0dp"
android:layout_height="0dp"
card_view:cardElevation="4dp"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
>
On the ConstraintLayout
- layout_width="match_parent" is important to let the item fill as much space as RecyclerView provides
- layout_height="wrap_content" do not let the item to fill all the height given by RecyclerView, but use the constrained height, provided by ConstraintLayout. In my case, when I used FrameLayout or LinearLayout, the items were "tall".
On the child node, in my case CardView
- limiting size to zero is important: layout_width="0dp" and layout_height="0dp" it means, that width and height are contrained
- layout_constraintDimensionRatio="H,1:1" makes the desired effect, by setting H you define that height is to be constrained 1:1 is the ratio.
See some detailed explanations on the offsite.
![Paul Woitaschek](https://i.stack.imgur.com/uypve.jpg?s=256&g=1)
Paul Woitaschek
Updated on December 02, 2020Comments
-
Paul Woitaschek over 3 years
I try to make a grid-layout with square images. I thought that it must be possible to manipulate the
GridLayoutManager
by manipulatingonMeasure
to do asuper.onMeasure(recycler, state, widthSpec, widthSpec);
instead of
super.onMeasure(recycler, state, widthSpec, heightSpec);
but unfortunately, that didn't work.
Any ideas?
-
Nauman Afzaal about 9 yearsThanks alot for this help
-
Muhammad Naderi almost 9 yearsawesome job, quick note though, there is no need to your footnote, since GridLayoutManager takes care of it
-
tccpg288 almost 8 yearsWhy do you use RelativeLayout? Wouldn't LinearLayout make more sense?
-
Anonsage almost 8 yearsYou should be able to use any root view that you want. The key is to override the
onMeasure(...)
-
Mateus over 7 yearsDear, not work for me... I don't know why... After update build tools, never more "square" items at GridLayoutManager.
-
wonsuc about 7 yearsWork with Glide perfactly.
-
Pelanes almost 7 yearsWorked, best solution to maintain code short and clean. Thanks
-
Mavamaarten almost 7 yearsThank you for this additional bit of info. Multiplying the widthMeasureSpec by the desired aspect ratio seemed logical, but obviously did not work.
-
Teffi almost 7 years@andryr I agree with you. I had to set mine to 0dp width also. Probably its due to some recent updates with constraint layout.
-
Ryan Amaral almost 7 yearsIf you need a full visible square in landscape mode, you can do that by getting the minimum value (from width and height) and set that value to both sizes. Example: onMeasure(...) { int size = Math.min(widthMeasureSpec, heightMeasureSpec); super.onMeasure(size, size); }
-
jpoppe over 6 yearsAs of release 26.0.0, the Percent Support library is deprecated. Clients of this module should migrate to the new ConstraintLayout widget, which is provided as a separate artifact in SDK Manager.
-
Sven over 5 yearsWorks with
app:layout_constraintDimensionRatio="W, 1:1"
-
blueware over 5 yearsThis should be the accepted answer as we need the most updates solution. Current accepted answer needs more boilerplate code to be written.
-
hkchakladar about 5 yearsbest solutions.
-
Yamashiro Rion almost 5 yearsThank you, the solution is simple and elegant!
-
Sergei Buvaka over 4 yearsthis mast be approved answer
-
Leonardo Sibela over 4 yearsThe constructor targeting the lollipop api did not work for me. It couldn't find the constructor.