如何在ItemDecoration中绘制视图?

4

我已经知道如何在ItemDecoration中绘制物品,但现在我想在ItemDecoration中绘制一个View

由于设置有点复杂,我创建了一个示例项目来重现这个问题。

我的目标

我有一个包含20个项目的RecyclerView,仅显示数字。我想在第5个项目上方添加一个黑色标题,上面写着"This is Number 5"。

当然,这是我真正问题的简化版本,在我的真实问题中,我必须通过ItemDecoration来实现这一点,请不要提供不使用ItemDecoration的替代方法。

问题

如下截图所示,装饰的大小是正确的,xml的第一层(其中android:background="@color/black")可以绘制;但不包括应该显示"This is Number 5"的TextView在内的子视图。

enter image description here

我现在如何做到这一点

FiveHeaderDecoration.kt:

class FiveHeaderDecoration: RecyclerView.ItemDecoration() {

    private var header: Bitmap? = null
    private val paint = Paint()

    override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
        val params = view?.layoutParams as? RecyclerView.LayoutParams
        if (params == null || parent == null) {
            super.getItemOffsets(outRect, view, parent, state)
        } else {
            val position = params.viewAdapterPosition
            val number = (parent.adapter as? JustAnAdapter)?.itemList?.getOrNull(position)
            if (number == 5) {
                outRect?.set(0, 48.dp(), 0, 0)
            } else {
                super.getItemOffsets(outRect, view, parent, state)
            }
        }
    }

    override fun onDraw(c: Canvas?, parent: RecyclerView?, state: RecyclerView.State?) {
        initHeader(parent)
        if (parent == null) return
        val childCount = parent.childCount
        for (i in 0 until childCount) {
            val view = parent.getChildAt(i)
            val position = parent.getChildAdapterPosition(view)
            val number = (parent.adapter as? JustAnAdapter)?.itemList?.getOrNull(position)
            if (number == 5) {
                header?.let {
                    c?.drawBitmap(it, 0.toFloat(), view.top.toFloat() - 48.dp(), paint)
                }
            } else {
                super.onDraw(c, parent, state)
            }
        }
    }

    private fun initHeader(parent: RecyclerView?) {
        if (header == null) {
            val view = parent?.context?.inflate(R.layout.decoration, parent, false)
            val bitmap = Bitmap.createBitmap(parent?.width?:0, 40.dp(), Bitmap.Config.ARGB_8888)
            val canvas = Canvas(bitmap)
            view?.layout(0, 0, parent.width, 40.dp())
            view?.draw(canvas)
            header = bitmap
        }
    }

}

你可以在示例项目中找到其他类,但我想它们并不真正相关。
如你所见,我试图先将视图布局和绘制到位图上。这是因为我只能在onDraw()中向画布绘制内容,而不能膨胀视图(我甚至没有一个ViewGroupaddView())。
通过使用调试器,我已经发现initHeader()中生成的位图只是一块黑色的区域。所以问题可能在于我如何初始化initHeader()

1
在布局和绘制之前,您可能还想测量视图,view?.measure(MeasureSpec.makeMeasureSpec(parent.width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(40.dp(), MeasureSpec.EXACTLY)) - user
@Luksprog 我也刚刚弄明白了。但是仍在尝试将这些子视图定位到正确的位置。 - Sira Lam
1个回答

11

已经弄明白了(哎呀我的奖金)。

为了正确创建View,需要完成三个步骤:

  1. 测量(view.measure()
  2. 布局(view.layout()
  3. 绘制(view.draw()

通常这些步骤由父ViewGroup完成,或者在addView()中完成。但是现在因为我们没有执行任何这些操作,所以需要自己调用它们。

问题显然是我漏掉了第一步。

因此,initHeader方法应该是:

private fun initHeader(parent: RecyclerView?) {
    if (header == null) {
        val view = parent?.context?.inflate(R.layout.decoration, parent, false)
        val bitmap = Bitmap.createBitmap(parent?.width?:0, 40.dp(), Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        val widthSpec = View.MeasureSpec.makeMeasureSpec(parent?.width ?: 0, View.MeasureSpec.EXACTLY)
        val heightSpec = View.MeasureSpec.makeMeasureSpec(40.dp(), View.MeasureSpec.EXACTLY)
        view?.measure(widthSpec, heightSpec)
        view?.layout(0, 0, parent.width, 40.dp())
        view?.draw(canvas)
        header = bitmap
    }
}

请注意,widthSpec和heightSpec将根据您的使用情况而有所不同。 这是另一个话题,我在这里不做解释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接