妙用RecyclerView的ItemDecoration

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,更有效率更节省内存。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值