请先阅读​​Android RecyclerView 吸顶效果(一)​​,推荐参考第一篇文章的方案来实现,这里只是帖一下第二种方案的代码,简单的介绍一下ItemDecoration

实现效果:

Android RecyclerView 吸顶效果(二)_bundle

ItemDecoration

Android RecyclerView 吸顶效果(二)_数据_02


太懒了,截个图,如有侵权,请告知,会进行删除!

主要代码

StickyItemDecoration

class StickyItemDecoration(val context: Context, val mList: MutableList<Bean> = mutableListOf()) :
RecyclerView.ItemDecoration() {

private var paint: Paint? = null
private var textPaint: Paint = Paint()
private var dividerHeight: Float = dp2Px(50).toFloat()

init {
paint = Paint()
paint?.setColor(context.resources.getColor(R.color.colorAccent))
paint?.style = Paint.Style.FILL

textPaint?.setColor(context.resources.getColor(R.color.FF333333))
textPaint?.style = Paint.Style.FILL
textPaint?.textSize = sp2Px(14).toFloat()
}

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val position = parent.getChildAdapterPosition(view)
if (isGroupFirst(position)) {
outRect.top = dividerHeight.toInt()
} else {
outRect.top = 0
}
}

override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(canvas, parent, state)
val childCount = parent.childCount
for (i in 0 until childCount) {
val childView = parent.getChildAt(i)
val childAdapterPos = parent.getChildAdapterPosition(childView)
val groupName = getGroupName(childAdapterPos)
val left = childView.paddingLeft
val right = childView.paddingRight
paint?.let {
if (isGroupFirst(childAdapterPos)) {
val top = childView.top - dividerHeight
val bottom = childView.top.toFloat()
canvas.drawRect(left.toFloat(), top, (childView.width - right).toFloat(), bottom, it)
groupName ?: return
val baseLine = (top + bottom) / 2f - (textPaint.descent() + textPaint.ascent()) / 2f
canvas.drawText(groupName, left + dp2Px(10).toFloat(), baseLine, textPaint)
}
}
}
}

override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(canvas, parent, state)
val firstVisiblePos = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
// 这里必须获取itemView 不能通过parent.getChildAt的方式,原因是getChildAt的方式会出现top准确的情况
val view = parent.findViewHolderForAdapterPosition(firstVisiblePos)?.itemView
view ?: return
val top = parent.paddingTop
val left = parent.paddingLeft
val right = parent.width - view.paddingRight
paint?.let {
if (isGroupFirst(firstVisiblePos)) {
val bottom = Math.min(dividerHeight, view.bottom.toFloat())
canvas.drawRect(left.toFloat(), top + view.top - dividerHeight, right.toFloat(), top + bottom, it)
val baseLine = ((top + bottom) - (textPaint.ascent() + textPaint.descent())) / 2f
canvas.drawText(getGroupName(firstVisiblePos)!!, left + dp2Px(10).toFloat(), baseLine, textPaint)
} else {
canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), top.toFloat() + dividerHeight, it)
val baseLine = ((top + top + dividerHeight) - (textPaint.ascent() + textPaint.descent())) / 2f
canvas.drawText(getGroupName(firstVisiblePos)!!, left + dp2Px(10).toFloat(), baseLine, textPaint)
}
}
}

private fun getGroupName(position: Int): String? {
return mList[position].groupName
}

private fun isGroupFirst(position: Int): Boolean {
return if (position == 0) {
true
} else {
val lastGroupName = mList[position - 1].groupName
val currentGroupName = mList[position].groupName
lastGroupName != currentGroupName
}
}

private fun dp2Px(dpValue: Int): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue.toFloat(), context.resources.displayMetrics).toInt()
}

private fun sp2Px(spValue: Int): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue.toFloat(), context.resources.displayMetrics).toInt()
}
}

数据

class Bean(val text: String, val groupName: String)

Activity

class SimpleActivity : AppCompatActivity() {
private val beanList = mutableListOf<Bean>()
private var recyclerView: RecyclerView? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sample_2)
initData()
initRecyclerView()
}

private fun initRecyclerView() {
recyclerView = findViewById(R.id.recyclerView)
recyclerView?.adapter = SampleAdapter2(beanList)
recyclerView?.layoutManager = LinearLayoutManager(this)
recyclerView?.layoutParams?.width = ViewGroup.LayoutParams.MATCH_PARENT
recyclerView?.layoutParams?.height = ViewGroup.LayoutParams.WRAP_CONTENT
recyclerView?.addItemDecoration(StickyItemDecoration(this, beanList))
}

private fun initData() {
for (i in 0..5) {
beanList.add(Bean("第一组 ${i + 1} 号", "第一组"))
}
for (i in 0..5) {
beanList.add(Bean("第二组 ${i + 1} 号", "第二组"))
}
for (i in 0..5) {
beanList.add(Bean("第三组 ${i + 1} 号", "第三组"))
}
for (i in 0..5) {
beanList.add(Bean("第四组 ${i + 1} 号", "第四组"))
}
for (i in 0..5) {
beanList.add(Bean("第五组 ${i + 1} 号", "第五组"))
}
}
}

Adapter

class SampleAdapter2(var data: MutableList<Bean>? = mutableListOf()) : RecyclerView.Adapter<Holder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
return Holder(LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false))
}

override fun getItemCount(): Int {
return data?.size ?: 0
}

override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindData(data?.get(position)?.text)
}
}

class Holder(view: View) : RecyclerView.ViewHolder(view) {
private var textView: TextView? = null

init {
textView = view.findViewById(R.id.tv)
}

fun bindData(s: String?) {
s ?: return
textView?.text = s
}
}