GridLayoutManager Square Layout for RecyclerView

I am trying to make a grid layout with square images. I thought it should be possible to control the GridLayoutManager by manipulating onMeasure to do

 super.onMeasure(recycler, state, widthSpec, widthSpec); 

instead

 super.onMeasure(recycler, state, widthSpec, heightSpec); 

but unfortunately this did not work.

Any ideas?

+39
android android-recyclerview gridlayoutmanager
Oct 25 '14 at 20:31
source share
8 answers

To have square elements in my RecyclerView, I provide a simple wrapper for my View element for root; Instead of RelativeLayout I use SquareRelativeLayout .

 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 simply referred to the user view, as shown below. Although you can also do this programmatically.

 <?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 the orientation of your grid, you may have a width based on height ( GridLayoutManager.HORIZONTAL ) rather than a height based on width ( GridLayoutManager.VERTICAL ).

+112
Oct 26 '14 at 17:25
source share

If someone wants to scale the view differently, here's how to 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); } 
+18
Mar 28 '15 at 18:15
source share

Constraint design 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="match_parent" 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> 
+15
Jan 19 '17 at 10:58
source share

Please try this FrameLayout extension. It performs a double measurement to improve consistency. It also supports custom XML properties to customize the desired aspect of the diet from layouts.

 public class StableAspectFrameLayout extends FrameLayout { private int aspectWidth = 1; private int aspectHeight = 1; public StableAspectFrameLayout(Context context) { this(context, null, 0); } public StableAspectFrameLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public StableAspectFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); extractCustomAttrs(context, attrs); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public StableAspectFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); extractCustomAttrs(context, attrs); } private void extractCustomAttrs(Context context, AttributeSet attrs) { if (attrs == null) return; TypedArray a = context.getResources().obtainAttributes(attrs, R.styleable.StableAspectFrameLayout); try { aspectWidth = a.getInteger(R.styleable.StableAspectFrameLayout_aspect_width, 1); aspectHeight = a.getInteger(R.styleable.StableAspectFrameLayout_aspect_height, 1); } finally { a.recycle(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int newSpecWidth = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); int newH = Math.round(((float) getMeasuredWidth()) * aspectHeight / aspectWidth); int newSpecHeigh = MeasureSpec.makeMeasureSpec(newH, MeasureSpec.EXACTLY); super.onMeasure(newSpecWidth, newSpecHeigh); } } 

And the content for attrs.xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <!-- StableAspectFrameLayout --> <declare-styleable name="StableAspectFrameLayout"> <attr name="aspect_width" format="integer"/> <attr name="aspect_height" format="integer"/> </declare-styleable> </resources> 
+1
Oct 28 '16 at 12:14
source share

Once again, I recommend the relatively recent "percentage" layouts. Using the dependency 'com.android.support:percent:25.2.0' , you can do something like this:

 <android.support.percent.PercentFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/image" app:layout_widthPercent="100%" app:layout_aspectRatio="100%" android:padding="10dp" android:scaleType="centerCrop" android:cropToPadding="true" tools:background="#efdbed" /> </android.support.percent.PercentFrameLayout> 

This is probably much faster than ConstraintLayout, although someday we probably won't like it anyway.

+1
Mar 16 '17 at 3:38 on
source share

Starting API 26 (Support Library 26.0), you can use ConstraintLayout, which provides an aspect ratio property to force views to be viewed: 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 } 

An example of the layout that I use 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

0
Nov 05 '17 at 20:07 on
source share

I had a similar problem and I had to inflate a view that would be square in the recycler view grid. Below is my way to do this.

Inside the onCreateViewHolder method, I used ViewTreeObserver and GlobalLayoutListener to get the measured layout width. The layout has a match_parent value in the width attribute. Any kind of my recycler has a horizontal layout.

 final View view = LayoutInflater.from(mActivity).inflate(R.layout.list_item_deals, parent, false); view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int side = view.getMeasuredWidth(); ViewGroup.LayoutParams lp = view.getLayoutParams(); lp.width = side; lp.height = side; view.setLayoutParams(lp); } }); 

reference image

0
Dec 22 '17 at 5:25
source share

I don’t like the answer chosen, so let me provide mine: instead of wrapping the entire layout of the element in SomeDammyLayoutWithFixedAspectRatio, you can hack the GridLayoutManager and rewrite the code inside measureChild. I replaced these lines:

 if (mOrientation == VERTICAL) { wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, horizontalInsets, lp.width, false); hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(), verticalInsets, lp.height, true); } else { hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, verticalInsets, lp.height, false); wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(), horizontalInsets, lp.width, true); } 

to:

 if (mOrientation == VERTICAL) { wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, horizontalInsets, lp.width, false); hSpec = wSpec; } else { hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, verticalInsets, lp.height, false); wSpec = hSpec; } 

Everything seems to be fine.

Don't get me wrong, this is also pretty dirty, but at least this solution will not hurt application performance by expanding the view hierarchy

-one
Jul 05 '17 at 19:01
source share



All Articles