ItemDecoration 是item的装饰器,item间的分割线就是使用她来实现的,但她还可以搞出其他事来, 因为她有canvas,我们可以像自定义view一样,来使用她。
比如我们可以实现如下图的效果:
这个列表显示了每天的数据,数据按时间倒序排列,数据按天的单位进行分割,并显示出日期,每天的数据个数是不一样的。相信这样的效果大家会经常碰到,实现方法很多也很容易,但我相信使用ItemDecoration来实现一定是最简单且优雅的(另外一种通过多item类型实现的方式也很优雅)。
以前我使用的一种比较傻的方式:
从数据库查询出数据,然后遍历数据,将同一天的数据放到一个list中,每一天都对应一个list,再使用LinkedHashMap存储起来,key为每一天的日期,value为这一天的数据list。显示数据的时候,就在onBindViewHolder里面获取position对应的list,然后对list for循环,在循环里面 inflate 子view布局,添加在itemVIew中。
这样的方式很明显效率很低,在onBindViewHolder中做了太多的操作,导致滑动的时候卡顿严重,而且每一天的数据都用一个list存储是很浪费内存的
现在我们使用ItemDecoration来实现,思路大概是这样的:
1、首先我们从数据库查询数据的时候,以时间倒叙来查询,这样查出来的数据,同一天的数据自然就在一起,且天的日期都是按日期倒叙排列好的,这样就不用在遍历数据按天进行分组了;
2、onBindViewHolder中不需要做什么处理,只要绑定position对应得数据即可。关键在ItemDecoration的getItemOffsets和onDraw两个方法中,
在getItemOffsets中,我们需要比较当前itemView与上一个itemView的日期是不是一样的,如果不一样就将他们隔开,并且将日期通过itemView的setTag保存起来。在onDraw方法中遍历子view,如果view.getTag不为空就在Tag使用canvas.drawText画出来。
下面是具体实现代码:
inner class WLItemDecoration : RecyclerView.ItemDecoration() {
private var paint: Paint
private var itemGap = 0
private var baseLine = 0f
init {
itemGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, resources.displayMetrics).toInt()
paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.textAlign = Paint.Align.LEFT
paint.textSize = itemGap / 2f
paint.color = Color.GRAY
val fontMetrics = paint.fontMetrics
baseLine = itemGap / 2f - (fontMetrics.bottom - fontMetrics.top) / 2f + fontMetrics.bottom
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
for (i in 0 until parent.childCount) {
val view = parent.getChildAt(i) ?: return
val tag = view.tag ?: return
if (tag is String) {
log_i("TAG", "WLItemDecoration onDraw i $i tag $tag ")
canvas.drawText(tag, itemGap.toFloat(), view.top - baseLine, paint)
}
}
}
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
log_i("TAG", "WLItemDecoration getItemOffsets position $position")
if (position == 0) {//第一个
outRect.set(0, itemGap, 0, 0)
val date = yyyyMMddSdf.format(dataList[position].createTime)
view.tag = date
} else {
val nowDate = yyyyMMddSdf.format(dataList[position].createTime)
val beforeDate = yyyyMMddSdf.format(dataList[position - 1].createTime)
if (beforeDate != nowDate) {
outRect.set(0, itemGap, 0, 0)
view.tag = nowDate
} else {
outRect.set(0, 2, 0, 0)
}
}
}
}
getItemOffsets中通过getChildAdapterPosition(view)来获取当前itemView对应的position,若是第一个,就通过outRect设置top 位置的偏移,并设置tag,若不是第一个,就和上一个的日期比较,若不一样,同样也设置偏移和tag。最后面else代码中是给同一天的数据之间设置间隔。
onDraw方法中遍历子View,如果view有tag,就画出来。并通过baseLine将文字绘制在间隔的垂直方向中部。
另外说一个优化布局的小技巧:对于简单的itemView,只是展示一些信息,完全可以使用自定义view将信息绘制显示,肯定比在布局文件中堆几个TextView或ImageView,更有效率更节省内存。