【问题标题】:How to set RecyclerView item max height relative to parent如何设置 RecyclerView 项目相对于父项的最大高度
【发布时间】:2021-02-23 13:59:57
【问题描述】:

按照这个问题中的建议,我几乎已经完成了这项工作

Android percent screen width in RecyclerView item

但是,这为所有视图设置了一个高度,无论内容如何,​​高度都相同。就我而言,我只是想限制RecyclerView 中的项目与其高度相关的高度。

在我的 RV 中,我有不同高度的物品,其中一些物品甚至比 RV 还要高。

我的目标是告诉该项目最多可以达到RecyclerView 高度的 60%,到目前为止,我发现实现这一点的唯一方法是在新项目绑定到视图时手动设置最大高度持有人使用此逻辑:

constraintSet.constrainMaxHeight(viewId, (int) (recyclerView.getMeasuredHeight() * 0.6));

recyclerView 是对托管 RecyclerView 的引用,我使用适配器的方法 onAttachedToRecyclerView 传递给适配器,viewId 是 RV 项目内的主视图 ConstraintLayout 根视图,我使用它控制它可以采取的最大高度。

虽然这可以满足我的需要,但我想为此找到一个更简单/更优化的解决方案,在简单性方面类似于这个:https://stackoverflow.com/a/51224889/524695

这意味着对ViewTreeObserver 的任何使用都被排除在外。

有什么想法吗?

【问题讨论】:

  • 为什么 Ben P 的 answer 不适合您?代码将在创建视图持有者时运行,而不是每次重新绑定时运行,这更简单。
  • @Cheticamp 我在第二段中确实提到了它对我不起作用的原因;当我只想设置最大高度时,他的答案为所有项目设置了相同的固定高度。即我不希望所有项目都具有相同的高度,它们可以变化,我只想通过设置最大高度来限制它们可以变化的程度。
  • 我看到了 RecyclerView 的项目没有“最大高度”设置,这是我的想法。太糟糕了。您可能可以使用RecyclerView.LayoutManager#measureChild 执行某些操作以强制设置最大高度。不过,不确定这是否会更简单。

标签: android android-recyclerview android-constraintlayout


【解决方案1】:

覆盖measureChildWithMargins 是可行的方法,但要使其真正发挥作用,您需要自定义LayoutParams,这样您就可以从适配器“继承”数据或直接从XML 中增加所需的百分比。

这个布局管理器会处理它:

open class PercentLinearLayoutManager : LinearLayoutManager {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)

    // where we actually force view to be measured based on custom layout params
    override fun measureChildWithMargins(child: View, widthUsed: Int, heightUsed: Int) {
        val pp = child.layoutParams as PercentParams
        if (pp.maxPercentHeight <= 0.0f)
            super.measureChildWithMargins(child, widthUsed, heightUsed)
        else {
            val widthSpec = getChildMeasureSpec(width, widthMode,
                    paddingLeft + paddingRight + pp.leftMargin + pp.rightMargin + widthUsed, pp.width,
                    canScrollHorizontally())
            val maxHeight = (height * pp.maxPercentHeight).toInt()
            val heightSpec = when (pp.height) {
                ViewGroup.LayoutParams.MATCH_PARENT -> View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.EXACTLY)
                ViewGroup.LayoutParams.WRAP_CONTENT -> View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
                else -> View.MeasureSpec.makeMeasureSpec(min(pp.height, maxHeight), View.MeasureSpec.AT_MOST)
            }
            child.measure(widthSpec, heightSpec)
        }
    }

    // everything below is needed to generate custom params
    override fun checkLayoutParams(lp: RecyclerView.LayoutParams) = lp is PercentParams

    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return PercentParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    override fun generateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams {
        return PercentParams(lp)
    }

    override fun generateLayoutParams(c: Context, attrs: AttributeSet): RecyclerView.LayoutParams {
        return PercentParams(c, attrs)
    }

    class PercentParams : RecyclerView.LayoutParams {
        /** Max percent height of recyclerview this item can have. If height is `match_parent` this size is enforced. */
        var maxPercentHeight = 0.0f

        constructor(c: Context, attrs: AttributeSet) : super(c, attrs){
            val t = c.obtainStyledAttributes(attrs, R.styleable.PercentLinearLayoutManager_Layout)
            maxPercentHeight = t.getFloat(R.styleable.PercentLinearLayoutManager_Layout_maxPercentHeight, 0f)
            t.recycle()
        }
        constructor(width: Int, height: Int) : super(width, height)
        constructor(source: MarginLayoutParams?) : super(source)
        constructor(source: ViewGroup.LayoutParams?) : super(source)
        constructor(source: RecyclerView.LayoutParams?) : super(source)
    }
}

要处理来自 XML 自定义属性的膨胀,请将其添加到 values/attrs.xml

<declare-styleable name="PercentLinearLayoutManager_Layout">
    <attr name="maxPercentHeight" format="float"/>
</declare-styleable>

那么你有两个选择:

1 - 更改 onBindViewHolder 中的自定义参数以控制每个项目:

val lp = holder.itemView.layoutParams as PercentLinearLayoutManager.PercentParams
if(modifyThisViewHolderHeight) {
    lp.maxPercentHeight = 0.6f
} else {
    lp.maxPercentHeight = 0.0f // clear percentage in case viewholder is reused
}

2 - 在视图布局中使用自定义属性:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:maxPercentHeight ="0.6">

    <!-- rest of layout-->
</FrameLayout>

【讨论】:

  • 我不确定您是否真的阅读了我在问题中解释的内容,或者您​​是否只是忽略了它,但我非常清楚地表明,我想找到一个比我已经在使用的一个。你的建议正好相反。
  • @Shadow 默认布局管理器不支持百分比大小,这就是为什么您必须编写自定义布局管理器才能正确处理它。任何在绑定 viewholder 时依赖快照 recyclerViews 高度的解决方案都是有缺陷的,因为它无法正确处理 RecyclerView 本身被调整大小的情况。
  • 我明白你在说什么,但正如我所提到的,这与我所要求的完全相反。但是,我相信这对于想要这种级别的功能的人来说会非常方便。您能否编辑您的答案并在开头提及,以便我可以投票?
  • @Pawel 请告诉我 if(modifyThisViewHolderHeight) { 我在这里写什么“modifythisViewHolderHeight”
  • @Meerz 这只是您可能想要检查的任何条件的伪代码,以防您更改每个项目的百分比。如果你想将它应用于所有项目,你可以使用 XML 属性。
【解决方案2】:

我会推荐我在链接答案中使用的大致策略:在创建 ViewHolder 时设置最大高度。但是,通用 View 不支持 maxHeight 属性,因此我们将利用 ConstraintLayout 来实现我们想要的。

如果您的项目视图的根视图已经是ConstraintLayout,那太好了,否则您可以包装其中的任何内容。在这个人为的示例中,我使用了这种布局(FrameLayout 在 XML 中使用 0dp 宽度和 wrap_content 高度):

<androidx.constraintlayout.widget.ConstraintLayout ...>
    <FrameLayout ...>
        <TextView .../>
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

在创建时,我将FrameLayout 的最大高度设置为父级高度的 60%:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val itemView = inflater.inflate(R.layout.recycler_item, parent, false)
    val holder = MyViewHolder(itemView)

    val params = holder.frame.layoutParams as ConstraintLayout.LayoutParams
    params.matchConstraintMaxHeight = (parent.height * 0.6).toInt()
    holder.frame.layoutParams = params

    return holder
}

【讨论】:

  • 这感觉就像我已经在做的那样,但在项目的根视图内有一个额外的层,但它可能比我目前实现它的方式更好,并且“更好“是我想要的。我试试这个看看,谢谢!
  • 是的,就整体战略而言非常相似。基本上,它只是从 onBind 移动到 onCreate。
  • 请注意,某些视图(如TextView)本身就支持最大高度;在这些情况下,您可以避免使用 ConstraintLayout 包装器,而只需在 onCreateViewHolder() 中调用 setMaxHeight()
  • 明白了,幸好我喂的项目的所有根视图都是ConstraintLayout所以目前没有问题。
猜你喜欢
  • 1970-01-01
  • 2017-07-30
  • 2017-05-22
  • 2020-01-13
  • 1970-01-01
  • 2013-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多