如何在 ItemDecoration 中绘制视图?
How to draw a view in an ItemDecoration?
我已经知道如何在 ItemDecoration
中绘制东西,但现在我想在 ItemDecoration
中绘制 View
。
由于设置有点复杂,我创建了一个可以重现问题的sample project
我想达到的目标
我有一个 RecyclerView
有 20 个项目,只显示数字。我想在第 5 项上方添加一个带有文本 "This is Number 5" 的黑色 header。
当然这是我真实问题的简化版,在我的真实问题中我必须通过ItemDecoration来做,所以请不要给出没有使用ItemDecoration的替代方案。
问题
如下图所示,装饰尺寸正确,可以绘制xml的第一层(其中有android:background="@color/black"
);但不包含应该显示 "This is Number 5".
的 TextView
的 child 视图
我现在怎么做
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()
中绘制一些东西到 canvas 而不能膨胀视图(我什至没有 ViewGroup
到 addView()
)。
并且通过调试器,我已经可以看到在initHeader()
中生成的位图只是一块黑色。所以问题可能出在我如何 initHeader()
.
想通了(哎呀我的赏金)
为了正确创建 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 将根据您的用例而有所不同。那是另外一个话题,这里就不解释了。
我已经知道如何在 ItemDecoration
中绘制东西,但现在我想在 ItemDecoration
中绘制 View
。
由于设置有点复杂,我创建了一个可以重现问题的sample project
我想达到的目标
我有一个 RecyclerView
有 20 个项目,只显示数字。我想在第 5 项上方添加一个带有文本 "This is Number 5" 的黑色 header。
当然这是我真实问题的简化版,在我的真实问题中我必须通过ItemDecoration来做,所以请不要给出没有使用ItemDecoration的替代方案。
问题
如下图所示,装饰尺寸正确,可以绘制xml的第一层(其中有android:background="@color/black"
);但不包含应该显示 "This is Number 5".
TextView
的 child 视图
我现在怎么做
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()
中绘制一些东西到 canvas 而不能膨胀视图(我什至没有 ViewGroup
到 addView()
)。
并且通过调试器,我已经可以看到在initHeader()
中生成的位图只是一块黑色。所以问题可能出在我如何 initHeader()
.
想通了(哎呀我的赏金)
为了正确创建 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 将根据您的用例而有所不同。那是另外一个话题,这里就不解释了。