【问题标题】:How do I make WRAP_CONTENT work on a RecyclerView如何使 WRAP_CONTENT 在 RecyclerView 上工作
【发布时间】:2015-02-13 00:36:56
【问题描述】:

我有一个DialogFragment,其中包含一个RecyclerView(卡片列表)。

在这个RecyclerView 中有一个或多个CardViews,可以有任何高度。

我想根据其中包含的CardViews 为这个DialogFragment 提供正确的高度。

通常这很简单,我会像这样在RecyclerView 上设置wrap_content

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

因为我使用的是RecyclerView,所以这不起作用:

https://issuetracker.google.com/issues/37001674

Nested Recycler view height doesn't wrap its content

在这两个页面上,人们建议扩展 LinearLayoutManager 并覆盖 onMeasure()

我首先使用了某人在第一个链接中提供的 LayoutManager

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

但是这不起作用因为

heightSize = View.MeasureSpec.getSize(heightSpec);

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

通过评论 height = heightSize;(在第二个 switch 案例中)我设法使高度起作用,但前提是 CardView 内的 TextView 子级不换行自己的文本(长句)。

一旦TextView 包装它自己的文本,高度应该增加,但它不会。它将长句子的高度计算为单行,而不是换行(2 行或更多行)。

关于我应该如何改进LayoutManager 以便我的RecyclerViewWRAP_CONTENT 一起使用的任何建议?

编辑:这个布局管理器可能适用于大多数人,但它仍然存在滚动和计算包装文本视图高度的问题

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

【问题讨论】:

  • 好像谷歌finally managed to fix that:Jan 22, 2016: This has been merged into the internal tree, should be available with the next version of support library.

标签: android android-layout android-fragments android-recyclerview


【解决方案1】:

Android Support Library 23.2.1 更新,所有WRAP_CONTENT 应该都能正常工作。

请更新gradle文件中的库版本以进一步:

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

解决了一些问题,例如修复了与各种测量规范方法相关的错误

查看http://developer.android.com/tools/support-library/features.html#v7-recyclerview

您可以查看Support Library revision history

【讨论】:

  • 我正在使用 LayoutManager,然后我立即更新为 23.2.0 它开始使我的应用程序崩溃。所以,我听从了你的回答,我的 RecyclerView 没有换行。为什么?
  • 已修复。我将自定义 LayoutManager 编写到 Recycler View 中项目的 wrap_content 中,但在更新后它开始因基于索引的异常而崩溃。当我删除它时,它并没有再次崩溃,但我的视图没有换行,直到我使用 LinearLayoutManager 初始化 Recycler 视图。
  • 不要忘记调用mRecyclerView.setNestedScrollingEnabled(false); 否则回收器视图仍将自己处理滚动而不是将事件传递给父级。
  • RecyclerView 的包装内容尚不完全受支持。查看medium.com/@elye.project/…
  • 这不是给定问题的解决方案。即使在第 27 版的支持中也不起作用。解决方案在 orange01 的下一个答案中被告知,将 RecyclerView 包装在 RelativeLayout 中。
【解决方案2】:

更新 02.07.2020
此方法可能会阻止回收,并且不应在大型数据集上使用

更新 05.07.2019

如果您在ScrollView 中使用RecyclerView,只需将ScrollView 更改为androidx.core.widget.NestedScrollView。在此视图中,无需将 RecyclerView 打包到 RelativeLayout 中。

<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <!-- other views -->

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!-- other views -->

    </LinearLayout>

</androidx.core.widget.NestedScrollView>

终于找到了解决这个问题的办法。

您需要做的就是将RecyclerView 包装在RelativeLayout 中。也许还有其他视图也可以工作。

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

【讨论】:

  • 我不知道为什么您的解决方案是正确的。但它对我有用。非常感谢
  • Same.. 尝试了很多东西,但不知何故这奏效了。不知道为什么或如何。非常感谢! FrameLayout 对我不起作用,但 RelativeLayout 对我有用。
  • 当我在 ConstraintLayout 中使用 RecyclerView 时,这也适用于我。
  • 这对我不起作用,直到我将 recyclerview 的高度和宽度都设置为“match_parent”
  • 你说它阻止回收。在不阻止回收的情况下这样做的正确方法是什么?
【解决方案3】:

这是该类的改进版本,它似乎可以工作并且没有其他解决方案存在的问题:

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

这也可以作为library 使用。链接到relevant class

【讨论】:

  • 对我来说完美无缺。谢谢。
  • 请使用github 的最新版本,因为自从我发布答案以来它发生了很大变化。
  • 感谢您的工作。我在与不同身高的孩子一起工作时遇到了麻烦。如果我有 10 个 150dp 的孩子,它可以工作;如果其中一个是 300dp,则最后一个将被隐藏。有什么想法吗?
  • 更具体地说,我觉得它在调用onBindViewHolder() 之前测量了孩子。这很糟糕,因为那时我打电话给holder.textView.setText(longText),这样孩子就变得更高了,但它并没有反映在回收站的高度上。如果您有任何想法(例如快速更改适配器),我将不胜感激。
  • 谢谢。您的答案/库帮助我克服了horizontal RecyclerView inside vertical RecyclerView 的高度问题。
【解决方案4】:

更新

通过 Android 支持库 23.2 更新,所有 WRAP_CONTENT 都应该可以正常工作。

请更新 gradle 文件中的库版本。

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

原答案

正如对其他问题的回答,当您的回收站视图高度大于屏幕高度时,您需要使用原始的 onMeasure() 方法。这个布局管理器可以计算 ItemDecoration 并且可以滚动更多。

    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];
            }
        }
    }

    // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
    if (height < heightSize && width < widthSize) {

        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);
    } else {
        super.onMeasure(recycler, state, widthSpec, heightSpec);
    }
}

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

   View view = recycler.getViewForPosition(position);

   // For adding Item Decor Insets to view
   super.measureChildWithMargins(view, 0, 0);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height);
            view.measure(childWidthSpec, childHeightSpec);

            // Get decorated measurements
            measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
            measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

原答案:https://stackoverflow.com/a/28510031/1577792

【讨论】:

  • 考虑到跨度计数,我们如何才能为 gridlayoutmanagerstaggeredgridlayoutmanager 实现相同的目标
【解决方案5】:

这里是 单声道 android 的 c# 版本

/* 
* Ported by Jagadeesh Govindaraj (@jaganjan)
 *Copyright 2015 serso aka se.solovyev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Contact details
 *
 * Email: se.solovyev @gmail.com
 * Site:  http://se.solovyev.org
 */


using Android.Content;
using Android.Graphics;
using Android.Support.V4.View;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Java.Lang;
using Java.Lang.Reflect;
using System;
using Math = Java.Lang.Math;

namespace Droid.Helper
{
    public class WrapLayoutManager : LinearLayoutManager
    {
        private const int DefaultChildSize = 100;
        private static readonly Rect TmpRect = new Rect();
        private int _childSize = DefaultChildSize;
        private static bool _canMakeInsetsDirty = true;
        private static readonly int[] ChildDimensions = new int[2];
        private const int ChildHeight = 1;
        private const int ChildWidth = 0;
        private static bool _hasChildSize;
        private static  Field InsetsDirtyField = null;
        private static int _overScrollMode = ViewCompat.OverScrollAlways;
        private static RecyclerView _view;

        public WrapLayoutManager(Context context, int orientation, bool reverseLayout)
            : base(context, orientation, reverseLayout)
        {
            _view = null;
        }

        public WrapLayoutManager(Context context) : base(context)
        {
            _view = null;
        }

        public WrapLayoutManager(RecyclerView view) : base(view.Context)
        {
            _view = view;
            _overScrollMode = ViewCompat.GetOverScrollMode(view);
        }

        public WrapLayoutManager(RecyclerView view, int orientation, bool reverseLayout)
            : base(view.Context, orientation, reverseLayout)
        {
            _view = view;
            _overScrollMode = ViewCompat.GetOverScrollMode(view);
        }

        public void SetOverScrollMode(int overScrollMode)
        {
            if (overScrollMode < ViewCompat.OverScrollAlways || overScrollMode > ViewCompat.OverScrollNever)
                throw new ArgumentException("Unknown overscroll mode: " + overScrollMode);
            if (_view == null) throw new ArgumentNullException(nameof(_view));
            _overScrollMode = overScrollMode;
            ViewCompat.SetOverScrollMode(_view, overScrollMode);
        }

        public static int MakeUnspecifiedSpec()
        {
            return View.MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
        }

        public override void OnMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
            int heightSpec)
        {
            var widthMode = View.MeasureSpec.GetMode(widthSpec);
            var heightMode = View.MeasureSpec.GetMode(heightSpec);

            var widthSize = View.MeasureSpec.GetSize(widthSpec);
            var heightSize = View.MeasureSpec.GetSize(heightSpec);

            var hasWidthSize = widthMode != MeasureSpecMode.Unspecified;
            var hasHeightSize = heightMode != MeasureSpecMode.Unspecified;

            var exactWidth = widthMode == MeasureSpecMode.Exactly;
            var exactHeight = heightMode == MeasureSpecMode.Exactly;

            var unspecified = MakeUnspecifiedSpec();

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

            var vertical = Orientation == Vertical;

            InitChildDimensions(widthSize, heightSize, vertical);

            var width = 0;
            var 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();

            var stateItemCount = state.ItemCount;
            var adapterItemCount = ItemCount;
            // 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 (var 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, widthSize, unspecified, ChildDimensions);
                        }
                        else
                        {
                            LogMeasureWarning(i);
                        }
                    }
                    height += ChildDimensions[ChildHeight];
                    if (i == 0)
                    {
                        width = ChildDimensions[ChildWidth];
                    }
                    if (hasHeightSize && 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, heightSize, ChildDimensions);
                        }
                        else
                        {
                            LogMeasureWarning(i);
                        }
                    }
                    width += ChildDimensions[ChildWidth];
                    if (i == 0)
                    {
                        height = ChildDimensions[ChildHeight];
                    }
                    if (hasWidthSize && width >= widthSize)
                    {
                        break;
                    }
                }
            }

            if (exactWidth)
            {
                width = widthSize;
            }
            else
            {
                width += PaddingLeft + PaddingRight;
                if (hasWidthSize)
                {
                    width = Math.Min(width, widthSize);
                }
            }

            if (exactHeight)
            {
                height = heightSize;
            }
            else
            {
                height += PaddingTop + PaddingBottom;
                if (hasHeightSize)
                {
                    height = Math.Min(height, heightSize);
                }
            }

            SetMeasuredDimension(width, height);

            if (_view == null || _overScrollMode != ViewCompat.OverScrollIfContentScrolls) return;
            var fit = (vertical && (!hasHeightSize || height < heightSize))
                      || (!vertical && (!hasWidthSize || width < widthSize));

            ViewCompat.SetOverScrollMode(_view, fit ? ViewCompat.OverScrollNever : ViewCompat.OverScrollAlways);
        }

        private void LogMeasureWarning(int child)
        {
#if DEBUG
            Log.WriteLine(LogPriority.Warn, "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");
#endif
        }

        private void InitChildDimensions(int width, int height, bool vertical)
        {
            if (ChildDimensions[ChildWidth] != 0 || ChildDimensions[ChildHeight] != 0)
            {
                // already initialized, skipping
                return;
            }
            if (vertical)
            {
                ChildDimensions[ChildWidth] = width;
                ChildDimensions[ChildHeight] = _childSize;
            }
            else
            {
                ChildDimensions[ChildWidth] = _childSize;
                ChildDimensions[ChildHeight] = height;
            }
        }

        public void ClearChildSize()
        {
            _hasChildSize = false;
            SetChildSize(DefaultChildSize);
        }

        public void SetChildSize(int size)
        {
            _hasChildSize = true;
            if (_childSize == size) return;
            _childSize = size;
            RequestLayout();
        }

        private void MeasureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize,
            int[] dimensions)
        {
            View child = null;
            try
            {
                child = recycler.GetViewForPosition(position);
            }
            catch (IndexOutOfRangeException e)
            {
                Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
                    "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
            }

            if (child != null)
            {
                var p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()

                var hPadding = PaddingLeft + PaddingRight;
                var vPadding = PaddingTop + PaddingBottom;

                var hMargin = p.LeftMargin + p.RightMargin;
                var vMargin = p.TopMargin + p.BottomMargin;

                // we must make insets dirty in order calculateItemDecorationsForChild to work
                MakeInsetsDirty(p);
                // this method should be called before any getXxxDecorationXxx() methods
                CalculateItemDecorationsForChild(child, TmpRect);

                var hDecoration = GetRightDecorationWidth(child) + GetLeftDecorationWidth(child);
                var vDecoration = GetTopDecorationHeight(child) + GetBottomDecorationHeight(child);

                var childWidthSpec = GetChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.Width,
                    CanScrollHorizontally());
                var childHeightSpec = GetChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.Height,
                    CanScrollVertically());

                child.Measure(childWidthSpec, childHeightSpec);

                dimensions[ChildWidth] = GetDecoratedMeasuredWidth(child) + p.LeftMargin + p.RightMargin;
                dimensions[ChildHeight] = GetDecoratedMeasuredHeight(child) + p.BottomMargin + p.TopMargin;

                // as view is recycled let's not keep old measured values
                MakeInsetsDirty(p);
            }
            recycler.RecycleView(child);
        }

        private static void MakeInsetsDirty(RecyclerView.LayoutParams p)
        {
            if (!_canMakeInsetsDirty)
            {
                return;
            }
            try
            {
                if (InsetsDirtyField == null)
                {
                   var klass = Java.Lang.Class.FromType (typeof (RecyclerView.LayoutParams));
                    InsetsDirtyField = klass.GetDeclaredField("mInsetsDirty");
                    InsetsDirtyField.Accessible = true;
                }
                InsetsDirtyField.Set(p, true);
            }
            catch (NoSuchFieldException e)
            {
                OnMakeInsertDirtyFailed();
            }
            catch (IllegalAccessException e)
            {
                OnMakeInsertDirtyFailed();
            }
        }

        private static void OnMakeInsertDirtyFailed()
        {
            _canMakeInsetsDirty = false;
#if DEBUG
            Log.Warn("LinearLayoutManager",
                "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
#endif
        }
    }
}

【讨论】:

  • 几乎...用var p = child.LayoutParameters.JavaCast&lt;RecyclerView.LayoutParams&gt;()替换var p = (RecyclerView.LayoutParams) child.LayoutParameters
  • 为什么要将成员变量声明为static?
  • @esskar 我认为该变量用于静态方法,如果您有任何疑问,请查看 java 版本
【解决方案6】:

把recyclerview放到任何其他布局中(相对布局是 更可取)。然后将recyclerview的高度/宽度更改为匹配父级 到该布局并将父布局的高度/宽度设置为换行 内容。

来源:This comment.

【讨论】:

  • 我还为我的项目添加了match_parent
【解决方案7】:

RecyclerView23.2.0 中添加了对 wrap_content 的支持,这是错误的,23.2.1 刚刚稳定,因此您可以使用:

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

您可以在此处查看修订历史记录:

https://developer.android.com/topic/libraries/support-library/revisions.html

注意:

另请注意,更新支持库后RecyclerView 将尊重wrap_content 以及match_parent,因此如果您将RecyclerView 的项目视图设置为match_parent,则单个视图将填满整个屏幕

【讨论】:

  • @Yvette 确定
【解决方案8】:

只需将您的 RecyclerView 放入 NestedScrollView。完美运行

<android.support.v4.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:layout_marginBottom="25dp">
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/kliste"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.widget.NestedScrollView>

【讨论】:

    【解决方案9】:

    滚动和文本换行的问题是这段代码假设宽度和高度都设置为wrap_content。但是,LayoutManager 需要知道水平宽度受到限制。所以不要为每个子视图创建自己的widthSpec,只需使用原始的widthSpec

    @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,
                    widthSpec,
                    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 childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                        getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(widthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
    

    【讨论】:

      【解决方案10】:

      试试这个(这是一个令人讨厌的解决方案,但它可能会起作用): 在ActivityonCreate 方法或片段的onViewCreated 方法中。设置回调准备在RecyclerView 首次渲染时触发,如下所示:

      vRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                  @Override
                  public void onGlobalLayout() {
                      calculeRecyclerViewFullHeight();
                  }
              });
      

      calculeRecyclerViewFullHeight 中,根据其子项的高度计算RecyclerView 的全高。

      protected void calculateSwipeRefreshFullHeight() {
              int height = 0;
              for (int idx = 0; idx < getRecyclerView().getChildCount(); idx++ ) {
                  View v = getRecyclerView().getChildAt(idx);
                  height += v.getHeight();
              }
              SwipeRefreshLayout.LayoutParams params = getSwipeRefresh().getLayoutParams();
              params.height = height;
              getSwipeRefresh().setLayoutParams(params);
          }
      

      就我而言,我的RecyclerView 包含在SwipeRefreshLayout 中,因此我将高度设置为SwipeRefreshView 而不是RecyclerView,但如果您没有任何SwipeRefreshView,那么您可以将高度设置为RecyclerView

      如果这对你有帮助,请告诉我。

      【讨论】:

      • 如何获取getRecyclerView()方法?
      • @asubanovsky 这是一个只返回您的RecyclerView 实例的方法。
      • 记得在 onGlobalLayout() 中移除你的 globalLayoutListener
      【解决方案11】:

      正如post 中所述,由于他们在 23.2 版中发布了此版本,因此现在可以正常使用。 引用官方blogpost

      此版本为 LayoutManager API 带来了一项激动人心的新功能:自动测量!这允许 RecyclerView 根据其内容的大小来调整自己的大小。这意味着以前不可用的场景,例如将 WRAP_CONTENT 用于 RecyclerView 的维度,现在是可能的。您会发现所有内置的 LayoutManagers 现在都支持自动测量。

      【讨论】:

        【解决方案12】:

        我使用了上述一些解决方案,但它适用于width,但适用于height

        1. 如果你指定的compileSdkVersion大于23,你可以直接使用各自的recycler view支持库中提供的RecyclerView,比如23强> 它将是'com.android.support:recyclerview-v7:23.2.1'。这些支持库支持wrap_content的宽度和高度属性。

        你必须将它添加到你的依赖项中

        compile 'com.android.support:recyclerview-v7:23.2.1'
        
        1. 如果你的compileSdkVersion小于23,你可以使用下面提到的解决方案。

        我发现这个Google thread 关于这个问题。在这个线程中,有一个贡献导致了LinearLayoutManager 的实现。

        我已经对它的 heightwidth 进行了测试,并且在这两种情况下对我来说都很好。

        /*
         * Copyright 2015 serso aka se.solovyev
         *
         * Licensed under the Apache License, Version 2.0 (the "License");
         * you may not use this file except in compliance with the License.
         * You may obtain a copy of the License at
         *
         *    http://www.apache.org/licenses/LICENSE-2.0
         *
         * Unless required by applicable law or agreed to in writing, software
         * distributed under the License is distributed on an "AS IS" BASIS,
         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
         * See the License for the specific language governing permissions and
         * limitations under the License.
         *
         * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         *
         * Contact details
         *
         * Email: se.solovyev@gmail.com
         * Site:  http://se.solovyev.org
         */
        
        package org.solovyev.android.views.llm;
        
        import android.content.Context;
        import android.graphics.Rect;
        import android.support.v4.view.ViewCompat;
        import android.support.v7.widget.RecyclerView;
        import android.util.Log;
        import android.view.View;
        
        import java.lang.reflect.Field;
        
        /**
         * {@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.
         * <p/>
         * 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 boolean canMakeInsetsDirty = true;
            private static Field insetsDirtyField = null;
        
            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 final RecyclerView view;
        
            private int childSize = DEFAULT_CHILD_SIZE;
            private boolean hasChildSize;
            private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
            private final Rect tmpRect = new Rect();
        
            @SuppressWarnings("UnusedDeclaration")
            public LinearLayoutManager(Context context) {
                super(context);
                this.view = null;
            }
        
            @SuppressWarnings("UnusedDeclaration")
            public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
                super(context, orientation, reverseLayout);
                this.view = null;
            }
        
            @SuppressWarnings("UnusedDeclaration")
            public LinearLayoutManager(RecyclerView view) {
                super(view.getContext());
                this.view = view;
                this.overScrollMode = ViewCompat.getOverScrollMode(view);
            }
        
            @SuppressWarnings("UnusedDeclaration")
            public LinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
                super(view.getContext(), orientation, reverseLayout);
                this.view = view;
                this.overScrollMode = ViewCompat.getOverScrollMode(view);
            }
        
            public void setOverScrollMode(int overScrollMode) {
                if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
                    throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
                if (this.view == null) throw new IllegalStateException("view == null");
                this.overScrollMode = overScrollMode;
                ViewCompat.setOverScrollMode(view, overScrollMode);
            }
        
            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 hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
                final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;
        
                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, widthSize, unspecified, childDimensions);
                            } else {
                                logMeasureWarning(i);
                            }
                        }
                        height += childDimensions[CHILD_HEIGHT];
                        if (i == 0) {
                            width = childDimensions[CHILD_WIDTH];
                        }
                        if (hasHeightSize && 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, heightSize, childDimensions);
                            } else {
                                logMeasureWarning(i);
                            }
                        }
                        width += childDimensions[CHILD_WIDTH];
                        if (i == 0) {
                            height = childDimensions[CHILD_HEIGHT];
                        }
                        if (hasWidthSize && width >= widthSize) {
                            break;
                        }
                    }
                }
        
                if (exactWidth) {
                    width = widthSize;
                } else {
                    width += getPaddingLeft() + getPaddingRight();
                    if (hasWidthSize) {
                        width = Math.min(width, widthSize);
                    }
                }
        
                if (exactHeight) {
                    height = heightSize;
                } else {
                    height += getPaddingTop() + getPaddingBottom();
                    if (hasHeightSize) {
                        height = Math.min(height, heightSize);
                    }
                }
        
                setMeasuredDimension(width, height);
        
                if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
                    final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
                            || (!vertical && (!hasWidthSize || width < widthSize));
        
                    ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
                }
            }
        
            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 widthSize, int heightSize, int[] dimensions) {
                final View child;
                try {
                    child = recycler.getViewForPosition(position);
                } catch (IndexOutOfBoundsException e) {
                    if (BuildConfig.DEBUG) {
                        Log.w("LinearLayoutManager", "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
                    }
                    return;
                }
        
                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;
        
                // we must make insets dirty in order calculateItemDecorationsForChild to work
                makeInsetsDirty(p);
                // this method should be called before any getXxxDecorationXxx() methods
                calculateItemDecorationsForChild(child, tmpRect);
        
                final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
                final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
        
                final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
                final int childHeightSpec = getChildMeasureSpec(heightSize, 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;
        
                // as view is recycled let's not keep old measured values
                makeInsetsDirty(p);
                recycler.recycleView(child);
            }
        
            private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
                if (!canMakeInsetsDirty) {
                    return;
                }
                try {
                    if (insetsDirtyField == null) {
                        insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
                        insetsDirtyField.setAccessible(true);
                    }
                    insetsDirtyField.set(p, true);
                } catch (NoSuchFieldException e) {
                    onMakeInsertDirtyFailed();
                } catch (IllegalAccessException e) {
                    onMakeInsertDirtyFailed();
                }
            }
        
            private static void onMakeInsertDirtyFailed() {
                canMakeInsetsDirty = false;
                if (BuildConfig.DEBUG) {
                    Log.w("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
                }
            }
        }
        

        【讨论】:

          【解决方案13】:

          我建议您将 recyclerview 放在任何其他布局中(最好是相对布局)。然后将 recyclerview 的高度/宽度更改为与该布局匹配的父级,并将父布局的高度/宽度设置为包装内容。它对我有用

          【讨论】:

            【解决方案14】:

            在新版本发布之前,最简单的解决方案是打开b.android.com/74772,而不是使用任何库。您可以在那里轻松找到迄今为止已知的最佳解决方案。

            PS:b.android.com/74772#c50 为我工作

            【讨论】:

              【解决方案15】:

              在 Adapter viewholder onCreateViewHolder 方法中使用 null 值而不是父视图组更新您的视图。

              @Override
              public AdapterItemSku.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              
                  View view = inflator.inflate(R.layout.layout_item, null, false);
                  return new MyViewHolder(view);
              }
              

              【讨论】:

                【解决方案16】:

                还要检查布局管理器上是否启用了自动测量。如果没有:layoutManager.setAutoMeasureEnabled(true);

                【讨论】:

                  【解决方案17】:

                  替换measureScrapChild 关注代码:

                  private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                          int heightSpec, int[] measuredDimension)
                      {
                          View view = recycler.GetViewForPosition(position);
                          if (view != null)
                          {
                              MeasureChildWithMargins(view, widthSpec, heightSpec);
                              measuredDimension[0] = view.MeasuredWidth;
                              measuredDimension[1] = view.MeasuredHeight;
                              recycler.RecycleView(view);
                          }
                      }
                  

                  我使用 xamarin,所以这是 c# 代码。我认为这可以很容易地“翻译”成 Java。

                  【讨论】:

                    【解决方案18】:

                    您必须将 FrameLayout 作为主视图,然后放入带有 ScrollView 和至少您的 RecyclerView 的 RelativeLayout,它对我有用。

                    这里真正的技巧是RelativeLayout...

                    乐于助人。

                    【讨论】:

                      【解决方案19】:

                      我有和你类似的问题,我可以通过使用LayoutManager 作为StaggeredGridLayoutManager 来解决它,而不是尝试通过计算屏幕宽度或GridLayoutManager 来应用LinearLayoutManager

                      请在下面找到示例代码,无需您进行任何其他自定义

                      StaggeredGridLayoutManager horizontalManager = new StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.HORIZONTAL);
                      mRecyclerView.setLayoutManager(horizontalManager);
                      
                      

                      注意:spanCount 是每行的项目数,但如果当前 RecyclerView 项目达到屏幕宽度,它将自动为您包装内容。

                      希望对你有帮助,祝你好运!!!

                      【讨论】:

                        【解决方案20】:

                        我没有解决我的答案,但我知道它的方式是 StaggridLayoutManager 没有。网格 1 可以解决您的问题,因为 StaggridLayout 会根据内容的大小自动调整其高度和宽度。如果它有效,请不要忘记将其作为正确答案进行检查。干杯..

                        【讨论】:

                          猜你喜欢
                          • 2016-02-29
                          • 1970-01-01
                          • 2015-05-06
                          • 1970-01-01
                          • 1970-01-01
                          • 2015-08-24
                          • 1970-01-01
                          • 2017-10-26
                          • 1970-01-01
                          相关资源
                          最近更新 更多