借助RecyclerView 的 ItemDecoration
ItemDecoration 允许应用给具体的View添加具体的图画或者layout的偏移,对于绘制View之间的分割线,视觉分组边界等等是非常有用的。
当我们调用addItemDecoration()方法添加decoration的时候,RecyclerView就会调用该类的onDraw方法去绘制分隔线,也就是说:分隔线是绘制出来的。
RecyclerView.ItemDecoration,该类为抽象类,官方目前只提供了一个实现类DividerItemDecoration。
三个方法
onDraw
onDrawOver
getItemOffsets
绘制顺序
onDraw------itemView----onDrawOver
源码
我用kotlin实现
StarDecoration 分割线
class StarDecoration(val context: Context) : RecyclerView.ItemDecoration() {
private val groupHeaderHeight: Int = dp2px(context,
100f)
private val headPaint: Paint = Paint()
private val textPaint: Paint = Paint()
private val textRect: Rect = Rect()
init {
headPaint.color = Color.RED
textPaint.textSize = 50f
textPaint.color = Color.WHITE
}
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
if (parent.adapter is StarAdapter) {
val starAdapter = parent.adapter as StarAdapter
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
var count = parent.childCount
for (i in 0 until count) {
// curView
val view = parent.getChildAt(i)
val position = parent.getChildAdapterPosition(view)
val isGroupHead = starAdapter.isGroupHeader(position)
if (isGroupHead && view.top - groupHeaderHeight - parent.paddingTop >= 0) {
c.drawRect(left.toFloat(), (view.top - groupHeaderHeight).toFloat(), right.toFloat(), view.bottom.toFloat(), headPaint)
val groupName = starAdapter.getGroupName(position)
textPaint.getTextBounds(groupName, 0, groupName.length, textRect);
c.drawText(groupName, (left + 20).toFloat(),
(view.top - groupHeaderHeight / 2 + textRect.height() / 2).toFloat(), textPaint)
} else if(view.top - groupHeaderHeight - parent.paddingTop >= 0){
c.drawRect(left.toFloat(), (view.top - 4).toFloat(), right.toFloat(), view.top.toFloat(), headPaint);
}
}
}
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
if (parent.adapter is StarAdapter) {
val starAdapter = parent.adapter as StarAdapter
val position = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
val itemView: View? = parent.findViewHolderForAdapterPosition(position)?.itemView
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val top = parent.paddingTop
val isGroupHead = starAdapter.isGroupHeader(position + 1)
if (isGroupHead) {
val groupName = starAdapter.getGroupName(position)
val bottom: Int = Math.min(groupHeaderHeight, itemView?.bottom
?: 0 - parent.paddingTop)
c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(),
(bottom + top).toFloat(), headPaint)
textPaint.getTextBounds(groupName, 0, groupName.length, textRect);
c.drawText(groupName, (left + 20).toFloat(),
(top - groupHeaderHeight / 2 + textRect.height() / 2 + bottom).toFloat(),
textPaint)
} else {
val groupName = starAdapter.getGroupName(position)
c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(),
(top + groupHeaderHeight).toFloat(), headPaint)
textPaint.getTextBounds(groupName, 0, groupName.length, textRect);
c.drawText(groupName, (left + 20).toFloat(),
(top + groupHeaderHeight / 2 + textRect.height() / 2).toFloat(), textPaint)
}
}
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
if (parent.adapter is StarAdapter) {
val starAdapter = parent.adapter as StarAdapter
val position: Int = parent.getChildLayoutPosition(view)
val isGroupHeader = starAdapter.isGroupHeader(position)
if (isGroupHeader) {
outRect.set(0, groupHeaderHeight, 0, 0)
} else {
outRect.set(0, 0, 4, 0)
}
}
}
}
入口Activity
class RecyclerViewActivity : AppCompatActivity() {
val starList = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_recycler_view)
initData()
rv_list.layoutManager = LinearLayoutManager(this)
rv_list.addItemDecoration(StarDecoration(this))
rv_list.adapter = StarAdapter(starList,this)
}
private fun initData() {
for(i in 0 until 4){
for(j in 0 until 20){
if(i % 2 == 0){
starList.add(Star("何XXA$j","快乐家族$i"))
}else {
starList.add(Star("汪XXA$j","天天兄弟$i"))
}
}
}
}
}
布局
Adapter & item
class StarAdapter(val starList: MutableList, val context: Context) : RecyclerView.Adapter() {
// TODO: 2020/7/22 inner class & class ??????
class StarViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tv: TextView = itemView.findViewById(R.id. iv_start)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StarViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.rv_item_star, null)
return StarViewHolder(view)
}
override fun getItemCount(): Int = starList.size
override fun onBindViewHolder(holder: StarViewHolder, position: Int) {
holder.tv.text = starList[position].name
}
fun isGroupHeader(position: Int): Boolean {
return if (position == 0)
true
else {
val curGroupName = getGroupName(position)
val preGroupName = getGroupName(position -1)
return preGroupName != curGroupName
}
}
fun getGroupName(position: Int): String {
return
starList[position].groupName
}
}
原理
拿到分割线,分割先可以有多个
addItemDecoration 调用了requestLayout
之后会计算
measureChildWithMargins->getItemDecorInsetsForChild->getItemOffsets
我们的RecyclerView 的onDraw方法说明了绘制的顺序
draw()->super.onDraw()->mItemDecorations.get(i).onDraw
draw()->mItemDecorations.get(i).onDrawOver()