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

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

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

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

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

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

【问题讨论】:

标签: android android-recyclerview android-wrap-content


【解决方案1】:

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

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

【讨论】:

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

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

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

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

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

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

【讨论】:

【解决方案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_width="match_parent"
   android:layout_height="wrap_content" />

当列表中的新元素可见时调用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 中创建了一个解决方案并提交了代码,请随意尝试。 stackoverflow.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);
            }
        }
    });

【讨论】:

  • 欢迎来到 Stackoverflow。请解释您的代码的作用。让其他人更容易理解。
  • 我也在 GitHub 中创建了一个解决方案并提交了代码,请随意尝试。 stackoverflow.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://stackoverflow.com/a/67403898/4828650

【讨论】:

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

你应该尝试不同的 item_view 类型

【讨论】:

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

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

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

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-23
  • 2017-07-30
  • 1970-01-01
  • 2017-05-22
  • 1970-01-01
  • 2016-07-04
  • 1970-01-01
相关资源
最近更新 更多