Как нарисовать вид в 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 шага:
- Мера (
view.measure()
) - Макет (
view.layout()
) - Рисовать (
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 будут отличаться в зависимости от вашего варианта использования. Это другая тема, поэтому я не буду объяснять здесь.