Adaptive Waterfall Height Using RecyclerView

  • 2021-12-19 07:03:00
  • OfStack

The waterfall flow is highly adaptive using RecyclerView for your reference. The specific contents are as follows

BACKGROUND: When using RecyclerView, a custom ScrollView is nested outside, so RecyclerView needs to be highly adaptive. Because many methods can't be realized on the waterfall format network, or the dynamic calculation is highly inaccurate. It is estimated that everyone knows that the height of recyclerview content is not controlled by recyclerview but set by LayoutManager. Let me talk about my solution:

Use in layout


<android.support.v7.widget.RecyclerView
                android:id="@+id/rcv_indexfragment_article_list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/dimen12"
                android:padding="@dimen/dimen4"
                android:background="@color/bg_white"/>

Method 1

Personally, I think the simplest and most effective method is the final choice to solve the problem.

Official website blog translation materials:

The RecyclerView control provides a flexible basic solution for creating lists and grids, and also supports animation. This release brings a very exciting new feature to LayoutManager API: Automatic measurement! Let RecyclerView adjust itself according to the size of its content. This means that previous unsolvable scenarios, such as using WRAP_CONTENT for one dimension of RecyclerView, are possible. You will find that all built-in LayoutManager now support automatic measurement.
Because of this change, you have to carefully check layout parameters of item view: layout parameters, which was automatically ignored before (such as MATCH_PARENT in the scroll direction), will now be fully considered. If you have a custom LayoutManager that does not inherit from the built-in LayoutManager, then this is an optional API-you need to call setAutoMeasureEnabled (true) and make a slight change as described in Javadoc for this method.
Note that although RecyclerView can animate its own child View, it cannot animate its own boundary changes. If you want RecyclerView boundary changes to be animated as well, you can use Transition API.

Configured version:


compile 'com.android.support:recyclerview-v7:23.2.1'

If you want the content to change with the height, you need to set it: (Note: This method is introduced in Android Support Library 23.2. If you configure the previous version, you will report an error ~)


layoutManager.setAutoMeasureEnabled(true);

Use in Activity:


recyclerview= ViewFindUtils.find(view, R.id.recyclerview);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
        layoutManager.setAutoMeasureEnabled(true);
recyclerview.setLayoutManager(layoutManager);
recyclerview.setHasFixedSize(true);
recyclerview.setNestedScrollingEnabled(false);
SpacesItemDecoration decoration = new SpacesItemDecoration(6);
recyclerview.addItemDecoration(decoration);

Method 2

The custom CustomStaggeredGridLayoutManager class inherits from StaggeredGridLayoutManager. (Note: This method is inaccurate in the calculation in my project, and there is always a blank paragraph at the back of the list, so I gave up ~ but you can try it, it may be a problem with my layout nesting)

Configured version (the latest version is recommended):


compile 'com.android.support:recyclerview-v7:23.1.1'

Use in Activity:


recyclerview= ViewFindUtils.find(view, R.id.recyclerview);
CustomStaggeredGridLayoutManager layoutManager = new CustomStaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL);
recyclerview.setLayoutManager(layoutManager);
recyclerview.setHasFixedSize(true);
recyclerview.setNestedScrollingEnabled(false);
SpacesItemDecoration decoration = new SpacesItemDecoration(6);
recyclerview.addItemDecoration(decoration);

SpacesItemDecoration.class


public class SpacesItemDecoration extends RecyclerView.ItemDecoration {

        private int space;

        public SpacesItemDecoration(int space) {
            this.space = space;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            outRect.top = space;
            outRect.bottom = space;
            outRect.left = space;
            outRect.right = space;
        }
    }

CustomStaggeredGridLayoutManager.class


package com.sunny.demo.widget;

import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
import android.view.ViewGroup;

import com.parkmecn.ehc.utils.LogUtil;

/**
 *  Solve ScrollView Nesting RecyclerView Hour RecyclerView Problems requiring a high degree of adaptation 
 */
public class CustomStaggeredGridLayoutManager extends StaggeredGridLayoutManager {
    public CustomStaggeredGridLayoutManager(int spanCount, int orientation) {
        super(spanCount, orientation);
    }

    //  An array of dimensions, [0] Is wide, [1] Is high 
    private int[] measuredDimension = new int[2];

    //  Used to compare peers / List that item Leniency for sins / Gao 
    private int[] dimension;


    @Override

    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        //  Wide mode+size
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        //  Tall mode + size
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        //  Initial value of its own width and height 
        int width = 0;
        int height = 0;
        // item Number of 
        int count = getItemCount();
        // item Number of columns of 
        int span = getSpanCount();
        //  Create an array based on the number of rows or columns 
        dimension = new int[span];

        for (int i = 0; i < count; i++) {
            measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), View
                    .MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), measuredDimension);

            //  If it is a vertical list, calculate the item Otherwise, calculate the width 
//            LogUtil.d("LISTENER", "position " + i + " height = " + measuredDimension[1]);
            if (getOrientation() == VERTICAL) {
                dimension[findMinIndex(dimension)] += measuredDimension[1];
            } else {
                dimension[findMinIndex(dimension)] += measuredDimension[0];
            }
        }
        if (getOrientation() == VERTICAL) {
            height = findMax(dimension);
        } else {
            width = findMax(dimension);
        }


        switch (widthMode) {
            //  When the control width is match_parent The width is the width of the parent control 
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case View.MeasureSpec.AT_MOST:
                break;
            case View.MeasureSpec.UNSPECIFIED:
                break;
        }
        switch (heightMode) {
            //  When the control height is match_parent The height is the height of the parent control 
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case View.MeasureSpec.AT_MOST:
                break;
            case View.MeasureSpec.UNSPECIFIED:
                break;
        }
        //  Set measurement dimensions 
        setMeasuredDimension(width, height);
        LogUtil.e("setMeasuredDimension(width, height)--->height==" + height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[]
            measuredDimension) {

        //  Traverse all of them one by one item
        if (position < getItemCount()) {
            try {
                View view = recycler.getViewForPosition(position);//fix  Dynamic Add Times IndexOutOfBoundsException

                if (view != null) {
                    RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
                    int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight
                            (), lp.width);
                    LogUtil.e(position + "--->heightSpec=" + heightSpec + ";getPaddingTop()=" + getPaddingTop() + ";" +
                            "getPaddingBottom()" + getPaddingBottom() + ";lp.height=" + lp.height);
                    int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() +
                            getPaddingBottom(), lp.height);
                    LogUtil.e(position + "--->viewchildHeightSpec=" + childHeightSpec);
                    //  Sub view Make measurements, and then you can pass the getMeasuredWidth() Obtain the similarity of width and height of measurement 
                    view.measure(childWidthSpec, childHeightSpec);
                    //  Will item Put the width and height of into the array 
                    measuredDimension[0] = view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
                    //FIXME  The calculated height here is always higher than the actual height 1 Some, leading to the end RecycerView The height calculation of is incorrect, and the last one is left 1 The section is blank, and the problem has not been found for the time being. Wait for the great God to solve it ~
                    measuredDimension[1] = view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
                    LogUtil.e(position + "--->view.getMeasuredHeight()=" + view.getMeasuredHeight() + ";lp" +
                            ".topMargin=" + lp.topMargin + ";lp.bottomMargin=" + lp.bottomMargin);
                    recycler.recycleView(view);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private int findMax(int[] array) {
        int max = array[0];
        for (int value : array) {
            if (value > max) {
                max = value;
            }
        }
        return max;
    }

    /**
     *  Get the subscript of the smallest element in the smallest array 
     *
     * @param array
     * @return
     */
    private int findMinIndex(int[] array) {
        int index = 0;
        int min = array[0];
        for (int i = 0; i < array.length; i++) {
            if (array[i] < min) {
                min = array[i];
                index = i;
            }
        }
        return index;
    }


}

Related articles: