Square layout on GridLayoutManager for RecyclerView

给你一囗甜甜゛ 提交于 2019-12-28 03:29:09

问题


I try to make a grid-layout with square images. I thought that it must be possible to manipulate the GridLayoutManager by manipulating onMeasure to do a

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

instead of

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

but unfortunately, that didn't work.

Any ideas?


回答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).




回答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.




回答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);
}



回答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




回答5:


Please, try this extension of the FrameLayout. It performs double measuring to improve consistency. It also supports custom XML properties to set-up required aspect ration 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 fo the 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>



回答6:


Once again, I recommend the relatively recent 'percent' 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>

It's probably much faster than ConstraintLayout, though someday we probably won't care anymore.




回答7:


I had similar problem and I had to inflate the view which would be square in Grid of recycler view. Below is my way of doing it.

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

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




回答8:


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.




回答9:


I don't like chosen answer so let me provide mine: Instead of wrapping entire item layout in SomeDammyLayoutWithFixedAspectRatio you can hack GridLayoutManager and rewrite code inside measureChild. I've 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;
    }

It seems to work fine.

Don't get me wrong, this is quite messy too, but at least this solution doesn't hurt app performance by extending view hierarchy



来源:https://stackoverflow.com/questions/26566954/square-layout-on-gridlayoutmanager-for-recyclerview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!