先上效果图:
可以从图中看到,增加一个粘性头部,也相当于一个直观的分组效果,有比较多的实现方式,例如:
本文是通过重写RecyclerView的ItemDecoration来实现,如果想使用BaseSectionQuickAdapter可以参考使用BRVAH
https://github.com/CymChad/BaseRecyclerViewAdapterHelper
在贴代码之前先了解一下ItemDecoration
介绍:
官方介绍
An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.
All ItemDecorations are drawn in the order they were added, before the item views (in onDraw() and after the items (in onDrawOver(Canvas, RecyclerView, RecyclerView.State).
官方的说明定义还是很清晰精准,大概的意思是
ItemDecoration 允许应用给具体的View添加具体的图画或者layout的偏移,对于绘制View之间的分割线,视觉分组边界等等是非常有用的。所有的ItemDecorations按照被添加的顺序在itemview之前(如果通过重写`onDraw()`)或者itemview之后(如果通过重写 `onDrawOver(Canvas, RecyclerView, RecyclerView.State)`)绘制。
所以整体的实现思路就是重写ItemDecoration,通过接口回调从数据data数组中获取对应item的日期,通过日期来区分分组设置一个item的偏移,再通过onDrawOver绘制一个分组显示。
ItemDecoration 除去被标记为过时的外,只剩如下三个方法:
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
其中三个方法的用途
通过调用RecyclerView的addItemDecoration()便可以添加
ItemDecoration,ItemDecoration会被add到集合中,然后RecyclerView会根据add的顺序依次调用(getItemOffsets
->onDraw
->onDrawOver
)的方法。
贴上实现的代码:
适配器,也可按需继承使用其他adapter:
class UndoneTodoListAdapter (val context: Context, datas: MutableList) :
BaseQuickAdapter(R.layout.undone_todo_item, datas) {
private var d : MutableList = datas
override fun getItemCount(): Int {
return d.size
}
override fun convert(helper: BaseViewHolder?, item: TodoListResponse.Data.Datas?) {
item ?: return
var p : String = "一般"
when {
item.priority == 1 -> p = "重要"
item.priority == 2 -> p = "一般"
}
if (helper != null) {
@Suppress("DEPRECATION")
helper.setText(R.id.item_title, item.title)
.setText(R.id.item_content, item.content)
.setText(R.id.todo_p, "优先级:$p")
.addOnClickListener(R.id.delete_todo)
.addOnClickListener(R.id.update_status_todo)
when {
item.type == 1 -> helper.setImageResource(R.id.item_type, R.drawable.work)
item.type == 2 -> helper.setImageResource(R.id.item_type, R.drawable.sport)
item.type == 3 -> helper.setImageResource(R.id.item_type, R.drawable.play)
}
}
}
}
item布局代码:
自定义列表间隔 StickyDecoration
class StickyDecoration(context: Context, decorationCallback: DecorationCallback) : RecyclerView.ItemDecoration() {
private var callback: DecorationCallback? = decorationCallback
private var textPaint: TextPaint? = null
private var paint: Paint? = null
private var topHead: Int = 0
private var topHeadD: Int = 0
init {
paint = Paint()
paint!!.color = ContextCompat.getColor(context, R.color.bg_header)
textPaint = TextPaint()
textPaint!!.typeface = Typeface.DEFAULT
textPaint!!.isFakeBoldText = false
textPaint!!.isAntiAlias = true
textPaint!!.textSize = 35f
textPaint!!.color = ContextCompat.getColor(context, R.color.colorPrimary)
textPaint!!.textAlign = Paint.Align.LEFT
topHead = context.resources.getDimensionPixelSize(R.dimen.head_top)
topHeadD = context.resources.getDimensionPixelSize(R.dimen.head_top_d)
}
override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent!!.getChildAdapterPosition(view)
val data : String = callback!!.getData(position)
if (TextUtils.isEmpty(data) || TextUtils.equals(data,"")) {
return
}
//同组的第一个添加日期padding
if (position == 0 || isHeader(position)) {
outRect!!.top = topHead
} else {
outRect!!.top = topHeadD
}
}
override fun onDrawOver(c: Canvas?, parent: RecyclerView?, state: RecyclerView.State?) {
super.onDrawOver(c, parent, state)
//获取当前可见的item的数量
val childCount = parent!!.childCount
//获取所有的的item个数
// val itemCount = state!!.itemCount
val itemCount = parent.adapter.itemCount
val left : Int = parent.left + parent.paddingLeft
val right : Int = parent.right + parent.paddingRight
var preData : String?
var currentDate : String? = null
for (i in 0 until childCount) {
val view = parent.getChildAt(i)
val position = parent.getChildAdapterPosition(view)
val textLine = callback!!.getData(position)
preData = currentDate
currentDate = callback!!.getData(position)
if (TextUtils.isEmpty(currentDate) || TextUtils.equals(currentDate, preData)) {
continue
}
if (TextUtils.isEmpty(textLine)) {
continue
}
val viewBottom = view.bottom
var textY = max(topHead, view.top).toFloat()
//下一个和当前不一样移动当前
if (position + 1 < itemCount) {
val nextData = callback!!.getData(position + 1)
if (currentDate != nextData && viewBottom < textY) {
//组内最后一个view进入了header
textY = viewBottom.toFloat()
}
}
val rect = Rect(left, textY.toInt() - topHead, right, textY.toInt())
c!!.drawRect(rect, paint!!)
//绘制文字基线,文字的的绘制是从绘制的矩形底部开始的
val fontMetrics = textPaint!!.fontMetrics
val baseline = ((rect.bottom + rect.top).toFloat() - fontMetrics.bottom - fontMetrics.top) / 2
textPaint!!.textAlign = Paint.Align.CENTER//文字居中
//绘制文本
c.drawText(textLine, 100F, baseline, textPaint!!)
}
}
//判断是否为头部
private fun isHeader(pos: Int): Boolean {
return if (pos == 0) {
true
} else {
val preData = callback!!.getData(pos - 1)
val data = callback!!.getData(pos)
preData != data
}
}
interface DecorationCallback {
fun getData(position: Int): String
}
}
使用添加
todo_rv.run {
layoutManager = LinearLayoutManager(context)
adapter = todoListAdapter
addItemDecoration(StickyDecoration(context, object : StickyDecoration.DecorationCallback {
override fun getData(position: Int): String {
return datas[position].dateStr
}
}))
}