我如何在RecyclerView WRAP_CONTENT工作 [英] How do I make WRAP_CONTENT work on a RecyclerView

查看:807
本文介绍了我如何在RecyclerView WRAP_CONTENT工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 DialogFragment 包含 RecyclerView (一卡列表)。

在此 RecyclerView 有1个或多个 CardViews ,可以有任何的高度。

我想给这个 DialogFragment 的基础上正确的高度 CardViews 被包含在其中。

通常情况下,这将是简单的,我会​​成立 WRAP_CONTENT RecyclerView 是这样的。

 < android.support.v7.widget.RecyclerView ...
    的xmlns:工具=htt​​p://schemas.android.com/tool​​s
    机器人:ID =@ + ID / recycler_view
    机器人:layout_width =match_parent
    机器人:layout_height =WRAP_CONTENT
    机器人:可点击=真
    机器人:滚动条=垂直>

< /android.support.v7.widget.RecyclerView>
 

由于我使用的是 RecyclerView 这不工作,请参阅:

<一个href="https://$c$c.google.com/p/android/issues/detail?id=74772">https://$c$c.google.com/p/android/issues/detail?id=74772

<一个href="http://stackoverflow.com/questions/26649406/nested-recycler-view-height-doesnt-wrap-its-content">Nested回收站视图高度不把它包装的内容

在这两个页面的人建议延长 LinearLayoutManager 和重写的 onMeasure()

我第一次使用的布局管理,在第一个环节提供人:

 公共静态类WrappingLayoutManager扩展LinearLayoutManager {

        公共WrappingLayoutManager(上下文的背景下){
            超(上下文);
        }

        私人INT [] mMeasuredDimension =新INT [2];

        @覆盖
        公共无效onMeasure(RecyclerView.Recycler回收,RecyclerView.State状态,
                              INT widthSpec,诠释heightSpec){
            最终诠释widthMode = View.MeasureSpec.getMode(widthSpec);
            最终诠释heightMode = View.MeasureSpec.getMode(heightSpec);
            最终诠释widthSize = View.MeasureSpec.getSize(widthSpec);
            最终诠释heightSize = View.MeasureSpec.getSize(heightSpec);

            measureScrapChild(回收,0,
                    View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            INT宽度= mMeasuredDimension [0];
            INT高度= mMeasuredDimension [1];

            开关(widthMode){
                案例View.MeasureSpec.EXACTLY:
                案例View.MeasureSpec.AT_MOST:
                    宽度= widthSize;
                    打破;
                案例View.MeasureSpec.UNSPECIFIED:
            }

            开关(heightMode){
                案例View.MeasureSpec.EXACTLY:
                案例View.MeasureSpec.AT_MOST:
                    身高= heightSize;
                    打破;
                案例View.MeasureSpec.UNSPECIFIED:
            }

            setMeasuredDimension(宽度,高度);
        }

        私人无效measureScrapChild(RecyclerView.Recycler回收,INT地位,诠释widthSpec,
                                       INT heightSpec,INT [] measuredDimension){
            查看查看= recycler.getViewForPosition(位置);
            如果(查看!= NULL){
                RecyclerView.LayoutParams P =(RecyclerView.LayoutParams)view.getLayoutParams();
                INT childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                        getPaddingLeft()+ getPaddingRight(),p.width);
                INT childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                        getPaddingTop()+ getPaddingBottom(),p.height);
                view.measure(childWidthSpec,childHeightSpec);
                measuredDimension [0] = view.getMeasuredWidth();
                measuredDimension [1] = view.getMeasuredHeight();
                recycler.recycleView(视图);
            }
        }
    }
 

不过,这并没有工作,因为

heightSize = View.MeasureSpec.getSize(heightSpec);

返回似乎与 match_parent 一个非常大的价值。

通过评论高度= heightSize; (第二开关的情况下),我设法使高空作业,但只有在的TextView 子里面的 CardView 不换自己的文字(长句)。

一旦这个的TextView 封装它自己的文字高度应增加,但事实并非如此。它计算出的高度为长句子作为一个单一的线,而不是一个缠绕线(2个或更多)。

这是我应该怎么改善这个布局管理所以我的 RecyclerView 适用于 WRAP_CONTENT任何意见

编辑:此布局管理器可能适用于大多数人,但它仍然有问题,滚动和包装textviews的计算高度

 公共类MyLinearLayoutManager扩展LinearLayoutManager {

公共MyLinearLayoutManager(上下文的背景下,INT方向,布尔reverseLayout){
    超(背景下,定向,reverseLayout);
}

私人INT [] mMeasuredDimension =新INT [2];

@覆盖
公共无效onMeasure(RecyclerView.Recycler回收,RecyclerView.State状态,
                      INT widthSpec,诠释heightSpec){
    最终诠释widthMode = View.MeasureSpec.getMode(widthSpec);
    最终诠释heightMode = View.MeasureSpec.getMode(heightSpec);
    最终诠释widthSize = View.MeasureSpec.getSize(widthSpec);
    最终诠释heightSize = View.MeasureSpec.getSize(heightSpec);
    INT宽度= 0;
    INT高= 0;
    的for(int i = 0; I&LT; getItemCount();我++){
        measureScrapChild(回收,我,
                View.MeasureSpec.makeMeasureSpec(ⅰ,View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(ⅰ,View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        如果(getOrientation()==水平){
            宽度=宽度+ mMeasuredDimension [0];
            如果(我== 0){
                高度= mMeasuredDimension [1];
            }
        } 其他 {
            高度=身高+ mMeasuredDimension [1];
            如果(我== 0){
                宽度= mMeasuredDimension [0];
            }
        }
    }
    开关(widthMode){
        案例View.MeasureSpec.EXACTLY:
            宽度= widthSize;
        案例View.MeasureSpec.AT_MOST:
        案例View.MeasureSpec.UNSPECIFIED:
    }

    开关(heightMode){
        案例View.MeasureSpec.EXACTLY:
            身高= heightSize;
        案例View.MeasureSpec.AT_MOST:
        案例View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(宽度,高度);
}

    私人无效measureScrapChild(RecyclerView.Recycler回收,INT地位,诠释widthSpec,
                                   INT heightSpec,INT [] measuredDimension){
        查看查看= recycler.getViewForPosition(位置);
        如果(查看!= NULL){
            RecyclerView.LayoutParams P =(RecyclerView.LayoutParams)view.getLayoutParams();
            INT childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft()+ getPaddingRight(),p.width);
            INT childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop()+ getPaddingBottom(),p.height);
            view.measure(childWidthSpec,childHeightSpec);
            measuredDimension [0] = view.getMeasuredWidth()+ p.leftMargin + p.rightMargin;
            measuredDimension [1] = view.getMeasuredHeight()+ p.bottomMargin + p.topMargin;
            recycler.recycleView(视图);
        }
    }
}
 

解决方案

下面是这似乎是工作,没有问题之类的改良版本其他的解决方案有:

 包org.solovyev.android.views.llm;

进口android.content.Context;
进口android.support.v7.widget.RecyclerView;
进口android.util.Log;
进口android.view.View;

/ **
 * {@link android.support.v7.widget.LinearLayoutManager}它包装的内容。请注意,这个类将始终
 *无论{@link android.support.v7.widget.RecyclerView}布局参数包裹的内容。
 *
 *现在是不可能的运行添加/删除动画与具有任意尺寸的子视图(高度
 *垂直方向和宽度水平)。但是,如果孩子的看法有固定的尺寸
 * {@link #setChildSize(INT)}方法可以用来让布局管理器知道他们有多大将是。
 *如果动画没有被使用,然后正常测量过程将运行和孩子的意见会期间进行测量
 *测量通。
 * /
公共类LinearLayoutManager扩展android.support.v7.widget.LinearLayoutManager {

    私有静态最终诠释CHILD_WIDTH = 0;
    私有静态最终诠释CHILD_HEIGHT = 1;
    私有静态最终诠释DEFAULT_CHILD_SIZE = 100;

    私人最终诠释[] childDimensions =新INT [2];

    私人诠释childSize = DEFAULT_CHILD_SIZE;
    私人布尔hasChildSize;

    @燮pressWarnings(UnusedDeclaration)
    公共LinearLayoutManager(上下文的背景下){
        超(上下文);
    }

    @燮pressWarnings(UnusedDeclaration)
    公共LinearLayoutManager(上下文的背景下,INT方向,布尔reverseLayout){
        超(背景下,定向,reverseLayout);
    }

    公共静态INT makeUnspecifiedSpec(){
        返回View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
    }

    @覆盖
    公共无效onMeasure(RecyclerView.Recycler回收,RecyclerView.State状态,INT widthSpec,诠释heightSpec){
        最终诠释widthMode = View.MeasureSpec.getMode(widthSpec);
        最终诠释heightMode = View.MeasureSpec.getMode(heightSpec);

        最终诠释widthSize = View.MeasureSpec.getSize(widthSpec);
        最终诠释heightSize = View.MeasureSpec.getSize(heightSpec);

        最终布尔exactWidth = widthMode == View.MeasureSpec.EXACTLY;
        最终布尔exactHeight = heightMode == View.MeasureSpec.EXACTLY;

        最终诠释未指定= makeUnspecifiedSpec();

        如果(exactWidth&安培;&安培; exactHeight){
            //如果精确计算这两个维度,让我们使用默认的onMeasure实施
            super.onMeasure(回收,州,widthSpec,heightSpec);
            返回;
        }

        最终布尔垂直= getOrientation()==垂直;

        initChildDimensions(widthSize,heightSize,垂直);

        INT宽度= 0;
        INT高= 0;

        //有可能得到回收废品的观点这必将对旧的(无效)适配器的实体。本
        //是因为它们失效后onMeasure的方法发生。作为一种变通方法,让我们清除
        //现在回收(它应该不会造成任何性能问题,同时滚动的onMeasure永不
        //所谓的田地滚动)
        recycler.clear();

        最终诠释stateItemCount = state.getItemCount();
        最终诠释adapterItemCount = getItemCount();
        //适配器始终包含实际的数据,而国家可能包含旧数据(FE数据的动画前
        //完成)。因为我们希望以测量与我们必须使用的数据从适配器,而不是从实际数据的视图
        // 州
        的for(int i = 0; I&LT; adapterItemCount;我++){
            如果(垂直){
                如果(!hasChildSize){
                    如果(ⅰ&其中; stateItemCount){
                        //我们不应该超过国家的数量,否则我们会得到IndexOutOfBoundsException异常。对于这样的项目
                        //我们将使用previously计算维度
                        measureChild(回收,我,widthSpec,不确定的,childDimensions);
                    } 其他 {
                        logMeasureWarning(ⅰ);
                    }
                }
                身高+ = childDimensions [CHILD_HEIGHT]
                如果(我== 0){
                    宽度= childDimensions [CHILD_WIDTH]
                }
                如果(高度&GT; = heightSize){
                    打破;
                }
            } 其他 {
                如果(!hasChildSize){
                    如果(ⅰ&其中; stateItemCount){
                        //我们不应该超过国家的数量,否则我们会得到IndexOutOfBoundsException异常。对于这样的项目
                        //我们将使用previously计算维度
                        measureChild(回收,我不确定,heightSpec,childDimensions);
                    } 其他 {
                        logMeasureWarning(ⅰ);
                    }
                }
                宽+ = childDimensions [CHILD_WIDTH]
                如果(我== 0){
                    身高= childDimensions [CHILD_HEIGHT]
                }
                如果(宽&GT; = widthSize){
                    打破;
                }
            }
        }

        如果((垂直和放大器;&功放;高&LT;!heightSize)||(垂直和放大器;&安培;宽&LT; widthSize)){
            //我们真的应该换视图的内容,让我们做吧

            如果(exactWidth){
                宽度= widthSize;
            } 其他 {
                宽+ = getPaddingLeft()+ getPaddingRight();
            }

            如果(exactHeight){
                身高= heightSize;
            } 其他 {
                身高+ = getPaddingTop()+ getPaddingBottom();
            }

            setMeasuredDimension(宽度,高度);
        } 其他 {
            //如果计算出的高度/宽度超过要求的高度/宽度让我们使用默认的onMeasure实施
            super.onMeasure(回收,州,widthSpec,heightSpec);
        }
    }

    私人无效logMeasureWarning(INT子){
        如果(BuildConfig.DEBUG){
            Log.w(LinearLayoutManager,无法测量的孩子#+儿童+,previously使用尺寸将被重用。+
                    要删除此消息要么使用#setChildSize()方法或不运行RecyclerView动画);
        }
    }

    私人无效initChildDimensions(INT宽度,高度INT,布尔型垂直){
        如果(childDimensions [CHILD_WIDTH]!= 0 || childDimensions [CHILD_HEIGHT]!= 0){
            //已初始化,跳绳
            返回;
        }
        如果(垂直){
            childDimensions [CHILD_WIDTH] =宽度;
            childDimensions [CHILD_HEIGHT] = childSize;
        } 其他 {
            childDimensions [CHILD_WIDTH] = childSize;
            childDimensions [CHILD_HEIGHT] =高度;
        }
    }

    @覆盖
    公共无效setOrientation(INT方向){
        //可以被称为这个类的构造函数被调用之前
        // noinspection ConstantConditions
        如果(childDimensions!= NULL){
            如果(getOrientation()!=取向){
                childDimensions [CHILD_WIDTH] = 0;
                childDimensions [CHILD_HEIGHT] = 0;
            }
        }
        super.setOrientation(方向);
    }

    公共无效clearChildSize(){
        hasChildSize = FALSE;
        setChildSize(DEFAULT_CHILD_SIZE);
    }

    公共无效setChildSize(INT childSize){
        hasChildSize = TRUE;
        如果(this.childSize!= childSize){
            this.childSize = childSize;
            requestLayout();
        }
    }

    私人无效measureChild(RecyclerView.Recycler回收,INT位置,INT widthSpec,诠释heightSpec,INT []维){
        最后查看孩子= recycler.getViewForPosition(位置);

        最后RecyclerView.LayoutParams P =(RecyclerView.LayoutParams)child.getLayoutParams();

        最终诠释hPadding = getPaddingLeft()+ getPaddingRight();
        最终诠释vPadding = getPaddingTop()+ getPaddingBottom();

        最终诠释hMargin = p.leftMargin + p.rightMargin;
        最终诠释vMargin = p.topMargin + p.bottomMargin;

        最终诠释hDecoration = getRightDecorationWidth(子)+ getLeftDecorationWidth(子);
        最终诠释vDecoration = getTopDecorationHeight(子)+ getBottomDecorationHeight(子);

        最终诠释childWidthSpec = getChildMeasureSpec(widthSpec,hPadding + hMargin + hDecoration,p.width,canScrollHorizo​​ntally());
        最终诠释childHeightSpec = getChildMeasureSpec(heightSpec,vPadding + vMargin + vDecoration,p.height,canScrollVertically());

        child.measure(childWidthSpec,childHeightSpec);

        尺寸[CHILD_WIDTH] = getDecoratedMeasuredWidth(子)+ p.leftMargin + p.rightMargin;
        尺寸[CHILD_HEIGHT] = getDecoratedMeasuredHeight(子)+ p.bottomMargin + p.topMargin;

        recycler.recycleView(子);
    }
}
 

这也可以作为

I have a DialogFragment that contains a RecyclerView (a list of cards).

Within this RecyclerView are 1 or more CardViews that can have any height.

I want to give this DialogFragment the correct height based on the CardViews that are contained within.

Normally this would be simple, I would set wrap_content on the RecyclerView like this.

<android.support.v7.widget.RecyclerView ...
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"   
    android:clickable="true"   
    android:scrollbars="vertical" >

</android.support.v7.widget.RecyclerView>

Because I am using a RecyclerView this does not work see:

https://code.google.com/p/android/issues/detail?id=74772

and

Nested Recycler view height doesn't wrap it's content

On both of these pages people suggest to extend LinearLayoutManager and to override onMeasure()

I first used the LayoutManager that someone provided in the first link:

public static class WrappingLayoutManager extends LinearLayoutManager {

        public WrappingLayoutManager(Context context) {
            super(context);
        }

        private int[] mMeasuredDimension = new int[2];

        @Override
        public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                              int widthSpec, int heightSpec) {
            final int widthMode = View.MeasureSpec.getMode(widthSpec);
            final int heightMode = View.MeasureSpec.getMode(heightSpec);
            final int widthSize = View.MeasureSpec.getSize(widthSpec);
            final int heightSize = View.MeasureSpec.getSize(heightSpec);

            measureScrapChild(recycler, 0,
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            int width = mMeasuredDimension[0];
            int height = mMeasuredDimension[1];

            switch (widthMode) {
                case View.MeasureSpec.EXACTLY:
                case View.MeasureSpec.AT_MOST:
                    width = widthSize;
                    break;
                case View.MeasureSpec.UNSPECIFIED:
            }

            switch (heightMode) {
                case View.MeasureSpec.EXACTLY:
                case View.MeasureSpec.AT_MOST:
                    height = heightSize;
                    break;
                case View.MeasureSpec.UNSPECIFIED:
            }

            setMeasuredDimension(width, height);
        }

        private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                       int heightSpec, int[] measuredDimension) {
            View view = recycler.getViewForPosition(position);
            if (view != null) {
                RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
                int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                        getPaddingLeft() + getPaddingRight(), p.width);
                int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                        getPaddingTop() + getPaddingBottom(), p.height);
                view.measure(childWidthSpec, childHeightSpec);
                measuredDimension[0] = view.getMeasuredWidth();
                measuredDimension[1] = view.getMeasuredHeight();
                recycler.recycleView(view);
            }
        }
    }

However this did not work because

heightSize = View.MeasureSpec.getSize(heightSpec);

returns a very large value that appear to be related to match_parent.

By commenting height = heightSize; (in the second switch case) I managed to make the height work but only if a TextView child inside the CardView does not wrap its own text (a long sentence).

As soon as that TextView wraps it's own text the height SHOULD increase but it doesn't. It calculated the height for that long sentence as a single line, not a wrapped line (2 or more).

Any advice on how I should improve this LayoutManager so my RecyclerView works with WRAP_CONTENT?

Edit: This layout manager might work for most people, but it still has problems with scrolling and calculating heights of wrapping textviews

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

解决方案

Here is the refined version of the class which seems to work and lacks problems other solutions have:

package org.solovyev.android.views.llm;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

/**
 * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
 * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
 *
 * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
 * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
 * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
 * If animations are not used at all then a normal measuring procedure will run and child views will be measured during
 * the measure pass.
 */
public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

    private static final int CHILD_WIDTH = 0;
    private static final int CHILD_HEIGHT = 1;
    private static final int DEFAULT_CHILD_SIZE = 100;

    private final int[] childDimensions = new int[2];

    private int childSize = DEFAULT_CHILD_SIZE;
    private boolean hasChildSize;

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context) {
        super(context);
    }

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public static int makeUnspecifiedSpec() {
        return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    }

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);

        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
        final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

        final int unspecified = makeUnspecifiedSpec();

        if (exactWidth && exactHeight) {
            // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
            super.onMeasure(recycler, state, widthSpec, heightSpec);
            return;
        }

        final boolean vertical = getOrientation() == VERTICAL;

        initChildDimensions(widthSize, heightSize, vertical);

        int width = 0;
        int height = 0;

        // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
        // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
        // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
        // called whiles scrolling)
        recycler.clear();

        final int stateItemCount = state.getItemCount();
        final int adapterItemCount = getItemCount();
        // adapter always contains actual data while state might contain old data (f.e. data before the animation is
        // done). As we want to measure the view with actual data we must use data from the adapter and not from  the
        // state
        for (int i = 0; i < adapterItemCount; i++) {
            if (vertical) {
                if (!hasChildSize) {
                    if (i < stateItemCount) {
                        // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                        // we will use previously calculated dimensions
                        measureChild(recycler, i, widthSpec, unspecified, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                height += childDimensions[CHILD_HEIGHT];
                if (i == 0) {
                    width = childDimensions[CHILD_WIDTH];
                }
                if (height >= heightSize) {
                    break;
                }
            } else {
                if (!hasChildSize) {
                    if (i < stateItemCount) {
                        // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                        // we will use previously calculated dimensions
                        measureChild(recycler, i, unspecified, heightSpec, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                width += childDimensions[CHILD_WIDTH];
                if (i == 0) {
                    height = childDimensions[CHILD_HEIGHT];
                }
                if (width >= widthSize) {
                    break;
                }
            }
        }

        if ((vertical && height < heightSize) || (!vertical && width < widthSize)) {
            // we really should wrap the contents of the view, let's do it

            if (exactWidth) {
                width = widthSize;
            } else {
                width += getPaddingLeft() + getPaddingRight();
            }

            if (exactHeight) {
                height = heightSize;
            } else {
                height += getPaddingTop() + getPaddingBottom();
            }

            setMeasuredDimension(width, height);
        } else {
            // if calculated height/width exceeds requested height/width let's use default "onMeasure" implementation
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        }
    }

    private void logMeasureWarning(int child) {
        if (BuildConfig.DEBUG) {
            Log.w("LinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
                    "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
        }
    }

    private void initChildDimensions(int width, int height, boolean vertical) {
        if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
            // already initialized, skipping
            return;
        }
        if (vertical) {
            childDimensions[CHILD_WIDTH] = width;
            childDimensions[CHILD_HEIGHT] = childSize;
        } else {
            childDimensions[CHILD_WIDTH] = childSize;
            childDimensions[CHILD_HEIGHT] = height;
        }
    }

    @Override
    public void setOrientation(int orientation) {
        // might be called before the constructor of this class is called
        //noinspection ConstantConditions
        if (childDimensions != null) {
            if (getOrientation() != orientation) {
                childDimensions[CHILD_WIDTH] = 0;
                childDimensions[CHILD_HEIGHT] = 0;
            }
        }
        super.setOrientation(orientation);
    }

    public void clearChildSize() {
        hasChildSize = false;
        setChildSize(DEFAULT_CHILD_SIZE);
    }

    public void setChildSize(int childSize) {
        hasChildSize = true;
        if (this.childSize != childSize) {
            this.childSize = childSize;
            requestLayout();
        }
    }

    private void measureChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] dimensions) {
        final View child = recycler.getViewForPosition(position);

        final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

        final int hPadding = getPaddingLeft() + getPaddingRight();
        final int vPadding = getPaddingTop() + getPaddingBottom();

        final int hMargin = p.leftMargin + p.rightMargin;
        final int vMargin = p.topMargin + p.bottomMargin;

        final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
        final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

        final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
        final int childHeightSpec = getChildMeasureSpec(heightSpec, vPadding + vMargin + vDecoration, p.height, canScrollVertically());

        child.measure(childWidthSpec, childHeightSpec);

        dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
        dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

        recycler.recycleView(child);
    }
}

This is also available as a library.

这篇关于我如何在RecyclerView WRAP_CONTENT工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆