RecyclerView的实现有以下几个步骤
实现Adapter
实现ViewHolder
实现分割线ItemDecoration
本期的吸顶效果主要就是围绕ItemDecoration 做文章
首先先定义一个bean
data class Star(
val name: String,
val group: String
)
然后是Adapter的实现
class StickyAdapter(val list: List<Star>): RecyclerView.Adapter<StickyAdapter.StickyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StickyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_star, null)
return StickyViewHolder(view)
}
override fun onBindViewHolder(holder: StickyViewHolder, position: Int) {
holder.tvStar.text = list[position].name
}
override fun getItemCount(): Int {
return list.size
}
class StickyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tvStar: TextView = itemView.findViewById(R.id.tv_star)
}
}
接下来是定义分割线
class StickyDecoration(val context: Context) : RecyclerView.ItemDecoration() {
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
}
}
通过 RecyclerView
的 addItemDecoration
可以添加自定义的分割效果,这里的吸顶效果主要就在 StickyDecoration
中实现
首先我们要理解这3个待实现方法的含义
getItemOffsets
的含义就是设置留白区域,outRect.set(0, 1, 0, 0)
此段代码的含义就是设置每个itemView上方的留白区域为1像素高度。
onDraw
就是在留白区域绘制,如果超出留白区域就会被其他的item 覆盖
onDrawOver
就是在任何区域绘制。会覆盖其他区域
不难想到我们要实现吸顶效果需要的三个步骤:
- 设置留白区域
getItemOffsets
//获取当前view的位置
val position = parent.getChildAdapterPosition(view)
val adapter = parent.adapter
if (adapter is StickyAdapter) {
val firstGroup = adapter.isFirstGroup(position)
// 如果是第一个位置则预留头部高度
if (firstGroup) {
outRect.set(0, headerHeight, 0, 0)
} else {
outRect.set(0, 1, 0, 0)
}
}
- 在留白区域绘制分割效果
onDraw
val left: Float = parent.paddingLeft.toFloat()
val right: Float = (parent.width - parent.paddingRight).toFloat()
var bottom: Float
var top: Float
val adapter = parent.adapter as StickyAdapter
// 在分割线处画一个底色
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val pos = parent.getChildLayoutPosition(child)
val first = adapter.isFirstGroup(pos)
val star = adapter.list[pos]
// 对应的留白区域画上颜色
if (first) {
bottom = child.top.toFloat()
top = (child.top - headerHeight).toFloat()
c.drawRect(left, top, right, bottom, dividerPaint)
// 找到文字的x, y 坐标,
val rect = Rect()
//获取文字的矩形区域
textPaint.getTextBounds(star.group, 0, star.group.length, rect)
// 画文字
c.drawText(
star.group,
left + 2,
top + headerHeight / 2 + rect.height() / 2,
textPaint
)
} else {
// 找到上下位置
bottom = child.top.toFloat()
top = (child.top - 1).toFloat()
// 画上分割线
c.drawRect(left, top, right, bottom, dividerPaint)
}
}
- 吸顶绘制
onDrawOver
// 画吸顶效果
val adapter = parent.adapter as StickyAdapter
// 拿到第一个可见item的位置
val firstVisiblePos =
(parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
// 找到第一个view
val view = parent.findViewHolderForAdapterPosition(firstVisiblePos)?.itemView
val firstViewBottom = view?.bottom?:return
val star = adapter.list[firstVisiblePos]
// 吸顶应该展示的位置
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val top = parent.paddingTop
// 是否为顶部
val isFirstGroup = adapter.isFirstGroup(firstVisiblePos + 1)
if (isFirstGroup){
// 底部为 高度和 第一个可见view的最小值
val bottom = top + Math.min(headerHeight, firstViewBottom)
c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), dividerPaint)
// 找到文字的x, y 坐标,
val rect = Rect()
//获取文字的矩形区域
textPaint.getTextBounds(star.group, 0, star.group.length, rect)
// 画文字
// 设置区域 防止文字超出范围
c.clipRect(left, top, right, bottom)
c.drawText(
star.group,
left + 2f,
firstViewBottom - headerHeight / 2f + rect.height() / 2,
textPaint
)
} else {
// 普通吸顶效果
val bottom = top + headerHeight
c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), dividerPaint)
// 找到文字的x, y 坐标,
val rect = Rect()
//获取文字的矩形区域
textPaint.getTextBounds(star.group, 0, star.group.length, rect)
// 画文字
c.drawText(
star.group,
left + 2f,
top + headerHeight / 2f + rect.height() / 2,
textPaint
)
}