【问题标题】:Dynamically update the layout of Recyclerview item without changing scroll position在不改变滚动位置的情况下动态更新 Recyclerview 项目的布局
【发布时间】:2021-09-18 08:51:03
【问题描述】:

场景: 我有一个卡片列表,基本上是一个 RecyclerView,Recycler 视图中只有两个项目。我也在扩展 RecyclerView.LayoutManager() 并覆盖 LayoutManager 类的 onLayoutChildren() 方法。 最初第二张卡片在底部,当我向上滑动时,第二张卡片滚动到顶部,就在第一张卡片下方一点点。

单击第二张卡片(列表中的第二项)中的“生成条形码”按钮时,我隐藏该按钮并显示替换该按钮的条形码图像。更新卡片布局时,会自动调用 onLayoutChildren() 方法。此方法被覆盖以在回收器视图启动时在特定位置显示卡片。这会导致布局管理器重绘子视图。因此,第二张卡片正在滚动回初始位置。

预期行为:当我们尝试更新第二张卡片布局时,第二张卡片不应向下滚动。

StackCardLayoutManager.kt

class StackCardLayoutManager(
    private val maxItemCount: Int
) : RecyclerView.LayoutManager() {
private val addedChildren: List<View>
    get() = (0 until childCount).map { getChildAt(it) ?: throw NullPointerException() }
private var firstTime: Boolean = true

init {
    Log.d(TAG_K, "StackCardLayoutManager.init()")
}

override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams =
        RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.MATCH_PARENT)

override fun isAutoMeasureEnabled(): Boolean = true

override fun onLayoutChildren(
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
) {
    Log.d(TAG_K, "StackCardLayoutManager.onLayoutChildren(itemcount : ${state.itemCount}) addedChildren Size : ${addedChildren.size} firstTime : $firstTime")
    firstTime = false
    if (state.itemCount == 0) {
        return
    }

    if (state.itemCount > maxItemCount) {
        throw RuntimeException("Can not set more Item than $maxItemCount")
    }
    detachAndScrapAttachedViews(recycler)

    for (i in 0 until state.itemCount) {
        Log.d(TAG_K, "StackCardLayoutManager.onLayoutChildren($i) - layoutDecorated")
        val view = recycler.getViewForPosition(i)
        measureChild(view, 0, 0)
        addView(view)
        val layoutParams = view.layoutParams as RecyclerView.LayoutParams
        val left = layoutParams.marginStart
        Log.d(TAG_K, "StackCardLayoutManager.onLayoutChildren() left  : $left")
        val top = (view.measuredHeight * i * 1.15).toInt()
        Log.d(TAG_K, "StackCardLayoutManager.onLayoutChildren() top  : $top")
        val right = view.measuredWidth + layoutParams.marginEnd
        Log.d(TAG_K, "StackCardLayoutManager.onLayoutChildren() right  : $right")
        val bottom = top + view.measuredHeight
        Log.d(TAG_K, "StackCardLayoutManager.onLayoutChildren() bottom  : $bottom")
        layoutDecorated(view, left, top, right, bottom)
        view.setTag(InitializedPosition.TOP.key, top)
    }
}

override fun canScrollVertically(): Boolean = true

override fun scrollVerticallyBy(
        dy: Int,
        recycler: RecyclerView.Recycler,
        state: RecyclerView.State
): Int = dy.also { deltaY ->
    Log.d("stackcardlayout", "scrollVerticallyBy: $deltaY")
    if (childCount == 0) {
        Log.d("stackcardlayout", "scrollVerticallyBy: child count is 0")
        return@also
    }
    var deltaY1 = 0
    addedChildren.forEachIndexed { index, view ->
        val initializedTop = view.getTag(InitializedPosition.TOP.key) as Int
        val layoutParams = view.layoutParams as RecyclerView.LayoutParams
        val left = layoutParams.marginStart

        if(deltaY < 0){
            deltaY1 = -500
        }else {
            deltaY1 = 500
        }

        val top = min(max((view.top - deltaY1), index * dpToPx(70)), initializedTop)
        val right = view.measuredWidth + layoutParams.marginEnd
        val bottom = top + view.measuredHeight
        layoutDecorated(view, left, top, right, bottom)
    }
}

private enum class InitializedPosition(val key: Int) {
    TOP(R.integer.top)
}
}

我尝试在这个解决方案周围搜索并在Stackoverflow 上发现了类似的问题,但是该解决方案对 androidx Recyclerview 无效,但仅对 android v7 Recycyler 视图支持库有效。

【问题讨论】:

    标签: android android-layout android-recyclerview


    【解决方案1】:

    我看到了答案。建议您按照eatRequestLayout() 方法来实现你的自定义视图requestLayout方法。

    // RecyclerView requestlayout
    @Override
        public void requestLayout() {
            if (!mEatRequestLayout) {
                super.requestLayout();
            } else {
                mLayoutRequestEaten = true;
            }
        }
    //RecyclerView.eatRequestLayout
    void eatRequestLayout() {
            if (!mEatRequestLayout) {
                mEatRequestLayout = true;
                mLayoutRequestEaten = false;
            }
        }
    
    

    由于文档中提到的requestLayout,视图根据它重新绘制

    当某些事情发生变化而导致该视图的布局无效时调用它。这将安排视图树的布局传递。

    在您的自定义视图requestLayout中,您可以根据自己的情况,设置不同的标志来防止重绘。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-13
      • 2021-10-04
      相关资源
      最近更新 更多