如何将回收器高度设置为 recyclerView 中的最高项目?

Posted

技术标签:

【中文标题】如何将回收器高度设置为 recyclerView 中的最高项目?【英文标题】:How to set recycler height to highest item in recyclerView? 【发布时间】:2020-01-13 08:33:43 【问题描述】:

我需要确保水平 recyclerView 高度与最大项目的高度相同。

项目可以有不同的高度(项目 = 始终相同的图像 + 标题 + 副标题,标题和副标题可以有无限长)。 当我为我的 recyclerView 设置 wrap_content 时,它会根据可见项目的高度调整大小,这会使 recyclerView 下面的内容跳转,这是我想要避免的。

我想要达到的目标: 灰色区域是可见视口。

所以基本上我想以某种方式获得最大项目的高度,然后将 recyclerView 高度设置为该数字。

我已经尝试过的是基于标题 + 副标题长度的近似高项目,但它非常不准确,因为例如,即使两个标题具有相同的文本长度,它们也可能具有不同的宽度,因为我使用的字体不是等宽字体。

【问题讨论】:

How do I make WRAP_CONTENT work on a RecyclerView的可能重复 不是,因为在我的情况下 WRAP_CONTENT “有效”。我的意思是它做了它应该做的事情,但我想获得与 WRAP_CONTENT 应该给出的结果不同的结果。 ***.com/a/49667381/7972851 这对我有用, 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650 【参考方案1】:

我也遇到了这个问题。我的解决方案是:

    将 RecyclerView 包装在 ConstraintLayout 中。 将 ConstraintLayout 的 layout_height 设置为 wrap_content。 将项目视图添加到 ConstraintLayout 并使用您期望最高的项目数据填充它,例如,基于其标题的长度。 将项目视图的可见性设置为不可见。 将 RecyclerView 的 layout_height 设置为零,并使其顶部和底部约束与项目视图的约束匹配。

【讨论】:

我最近做了一些类似的事情,虽然它看起来像一个 hacky 解决方案,但它允许您不必在整个 RecyclerView 上设置静态高度,并且始终具有基于最高 ViewHolder 的动态高度。 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650【参考方案2】:

我不认为这是开箱即用的。

让我们思考一下 RecyclerView 的工作原理。为了节省内存,它重用相同的 View 对象,并在用户滚动时将它们绑定到列表中的新数据。因此,例如,如果用户看到项目的 0 和 1,那么系统只测量并布置了 2 个项目(可能还有一两个以帮助提高滚动性能)。

但是假设你的高项目是列表中的第 50 位,当 RecyclerView 绑定前几个项目时,它根本不知道第 50 项是否存在,更不用说它有多高了。

但是,您可以做一些有点 hacky 的事情。例如,您可以在绑定后测量每个项目的高度,跟踪最高的,然后手动将 RecyclerView 高度设置为该大小。使用该机制,您可以隐藏 RecyclerView,然后手动滚动到列表的末尾,滚动回列表的开头,然后显示 RecyclerView。

不是最优雅的解决方案,但应该可以。

【讨论】:

我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650【参考方案3】:

回答为时已晚,但也许这会对某人有所帮助。

我在同样的问题上苦苦挣扎,找不到可接受的解决方案。

通过以下方式解决:

1) 首先,需要从 RecyclerView 重写 onMeasure 以保存最大元素高度:

class CustomRecycleView(ctx: Context, attrs: AttributeSet) : RecyclerView(ctx, attrs) 

    private var biggestHeight: Int = 0

    override fun onMeasure(widthSpec: Int, heightSpec: Int) 
        for (i in 0 until childCount) 
            val child = getChildAt(i)
            child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
            val h = child.measuredHeight
            if (h > biggestHeight) biggestHeight = h
        

        super.onMeasure(widthSpec, MeasureSpec.makeMeasureSpec(biggestHeight, MeasureSpec.EXACTLY))
    


2) 在你的布局中,用这个 CustomRecycleView 替换 RecycleView:

<CustomRecycleView
   android:layout_
   android:layout_ />

当列表中的新元素可见时调用onMeasure,如果元素是最高的,那么我们保存这个值。例如:如果第一个元素的高度最低,但lates 的高度最高,那么在开始时 RecycleView 的高度将与第一个元素匹配,但在滚动后它将保持与最高元素的匹配。 如果您不需要在开始时使 RecycleView 高度与最高项目匹配,那么您可以在这里停止。

3)要在开始时执行此操作,您必须进行 hack(基于@MidasLefko 的建议): 要初步了解最高元素的高度是多少,您需要在结尾和开头添加滚动机制。我是这样做的:

private fun initRecycleView(items: ArrayList<Object>) 
    val adapter = Adapter()
    rv.visibility = View.INVISIBLE
    rv.vadapter = adapter
    rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
    rv.setHasFixedSize(true)
    rv.smoothScrollToPosition(pinnedPosts.size)
    Handler().postDelayed(
        rv.smoothScrollToPosition(0)
    , 300)
    Handler().postDelayed(
        rv.visibility = View.VISIBLE
    , 700)

将回收视图的可见性设置为不可见,并在 700 毫秒后设置为可见,以使此过程对用户不可见。此外,滚动开始的延迟为 300 毫秒,因为如果没有一些延迟,它可能无法正常工作。就我而言,这对于 3 个元素的列表是必需的,而这些延迟对我来说是最佳的。

还要记得去掉onStop()中的所有Handler回调

【讨论】:

这是一个非常的好答案。幸运的是,对于我的用例,我只需要第 1 步和第 2 步,但这有助于解决我们多年来遇到的问题! 再次以防万一它对其他人有帮助,我不得不在第 2 步中做一个非常小的调整来替换这一行:child.measure(widthSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) with child.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) 这基本上只是因为 ViewHolder 的宽度被孩子间歇性弄乱了,因为它正在使用 widthSpec整个recyclerview,而不是仅仅计算ViewHolder的宽度。 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650【参考方案4】:

你可以试试这个:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() 
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) 
            super.onScrollStateChanged(recyclerView, newState);
            final int newHeight = recyclerView.getMeasuredHeight();
            if (0 != newHeight && minHeight < newHeight) 
            // keep track the height and prevent recycler view optimizing by resizing
                minHeight = newHeight;
                recyclerView.setMinimumHeight(minHeight);
            
        
    );

【讨论】:

欢迎来到 ***。请解释您的代码的作用。让其他人更容易理解。 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650【参考方案5】:

创建了一个方法来计算 textView 的投影高度,方法是尝试列表中的所有描述以获得最高高度。

 public static int getHeightOfLargestDescription(final Context context, final CharSequence text, TextView textView) 
    final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    final Point displaySize = new Point();
    wm.getDefaultDisplay().getSize(displaySize);
    final int deviceWidth = displaySize.x;
    textView.setTypeface(Typeface.DEFAULT);
    textView.setText(text, TextView.BufferType.SPANNABLE);
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    textView.measure(widthMeasureSpec, heightMeasureSpec);
    return textView.getMeasuredHeight();

然后使用此方法在onCreateViewHolder 中准备好在绑定视图时使用的最高高度。

        MyViewHolder myViewHolder = new MyViewHolder(itemView);
    for (Model m : modelList) 
        currentItemHeight = getHeightOfLargestDescription(context, m.description, myViewHolder.description);
        if (currentItemHeight > highestHeight) 
            highestHeight = currentItemHeight;
        
    

然后在onBindViewHolder`中使用这个highestHeight来设置描述TexView的高度,这样所有的视图总是有相同的高度,等于最高的高度。

 viewHolder.description.setHeight(highestHeight);

代码在 https://github.com/dk19121991/HorizontalRecyclerWithDynamicHeight

如果这能解决您的问题,请告诉我,如果您还有其他问题,请随时提出。

谢谢

要查看有关此解决方案的完整讨论,请参见下文 https://***.com/a/67403898/4828650

【讨论】:

太棒了,你救了我,这正是我一直在寻找的东西。非常感谢。【参考方案6】:

你应该尝试不同的 item_view 类型

【讨论】:

你能解释一下你的意思吗? 我的意思是你可以在 recyclerview 中使用不同的视图。例如,在任何聊天列表适配器中,我们在单个适配器中都有不同的视图,例如简单的文本、图像、视频。为此,我们根据需要使用不同的 viewType。供参考,请参阅***.com/questions/26245139/… 哦,我明白了,但事实并非如此。只有一种类型的项目:CustomView,其中包含 Image + title + subtitle。 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650【参考方案7】:

试试这个

    @Override
     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 

     View itemView = mLayoutInflater.inflate(R.layout.view_item, parent, false);
    // work here if you need to control height of your items
    // keep in mind that parent is RecyclerView in this case
    int height = parent.getMeasuredHeight() / 4;
    itemView.setMinimumHeight(height);
    return new ItemViewHolder(itemView);        
    

或者你也可以试试这个

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View itemView = inflater.inflate(R.layout.itemview, parent, false);

    ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
    layoutParams.height = (int) (parent.getHeight() * 0.3);
    itemView.setLayoutParams(layoutParams);

    return new MyViewHolder(itemView);
   

您还可以将 itemView 设置为固定高度。

【讨论】:

我不确定我的解释是否足够好,但我需要让我的 recyclerView 匹配它的项目(我不知道它们会有多高)。 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650【参考方案8】:

我disabled the recycling in recycler view 解决了这个问题。

recyclerView.getRecycledViewPool().setMaxRecycledViews(TYPE_CAROUSEL, 0);

如果有很多项目,此解决方案可能会出现性能问题,但对于少数项目可以正常工作,比如说 5 到 20,这对我来说就是这种情况。

【讨论】:

我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 ***.com/a/67403898/4828650

以上是关于如何将回收器高度设置为 recyclerView 中的最高项目?的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView 项目高度和重力不起作用

如何将 RecyclerView 添加到 LinearLayout(高度问题)

具有固定高度的 Recyclerview 不会在 nestedscrollview 内滚动

如何在屏幕上设置两个相同大小的RecyclerView,保持高度换行内容以避免空白

如何为android recyclerview中的最大字符串设置textview高度?

Vertical RecyclerView 中的 Horizo​​ntal RecyclerViews 丢失位置/状态