Как нарисовать вид в ItemDecoration?

Я уже знаю, как рисовать вещи в ItemDecoration, но теперь я хочу нарисовать View в ItemDecoration,

Поскольку настройка немного сложна, я создал пример проекта, который может воспроизвести проблему.

Чего я хочу достичь

у меня есть RecyclerView с 20 предметов, отображая только цифры. Я хочу добавить черный заголовок с текстом "Это номер 5" выше пункта 5.

Конечно, это упрощенная версия моей реальной проблемы, и в моей реальной проблеме я должен сделать это с помощью ItemDecoration, поэтому, пожалуйста, не давайте альтернатив, которые не используют ItemDecoration.

Эта проблема

Как показано на скриншоте ниже, украшение имеет правильный размер и первый слой XML (который имеет android:background="@color/black") можно нарисовать; но не дочерние взгляды, которые включают TextView который должен отображать "Это номер 5".

Как я это делаю сейчас

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() но не раздувать мнение (у меня даже нет ViewGroup в addView()).

И с помощью отладчика, я уже вижу, что растровое изображение, сгенерированное в initHeader() это просто блок черного. Так что проблема, вероятно, заключается в том, как я initHeader(),

1 ответ

Решение

Разобрался (Ой, моя щедрость)

Для того, чтобы View для правильного создания необходимо 3 шага:

  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 будут отличаться в зависимости от вашего варианта использования. Это другая тема, поэтому я не буду объяснять здесь.

Другие вопросы по тегам