RecyclerView 吸顶实现

作者: zcwfeng | 来源:发表于2020-07-22 17:04 被阅读0次

借助RecyclerView 的 ItemDecoration

ItemDecoration 允许应用给具体的View添加具体的图画或者layout的偏移,对于绘制View之间的分割线,视觉分组边界等等是非常有用的。

当我们调用addItemDecoration()方法添加decoration的时候,RecyclerView就会调用该类的onDraw方法去绘制分隔线,也就是说:分隔线是绘制出来的。

RecyclerView.ItemDecoration,该类为抽象类,官方目前只提供了一个实现类DividerItemDecoration。

三个方法

onDraw
onDrawOver
getItemOffsets

绘制顺序

onDraw------itemView----onDrawOver

源码

我用kotlin实现

StarDecoration 分割线

class StarDecoration(val context: Context) : RecyclerView.ItemDecoration() {
    private val groupHeaderHeight: Int = dp2px(context,
            100f)
    private val headPaint: Paint = Paint()
    private val textPaint: Paint = Paint()
    private val textRect: Rect = Rect()

    init {
        headPaint.color = Color.RED
        textPaint.textSize = 50f
        textPaint.color = Color.WHITE
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        super.onDraw(c, parent, state)
        if (parent.adapter is StarAdapter) {
            val starAdapter = parent.adapter as StarAdapter
            val left = parent.paddingLeft
            val right = parent.width - parent.paddingRight
            var count = parent.childCount
            for (i in 0 until count) {
                // curView
                val view = parent.getChildAt(i)
                val position = parent.getChildAdapterPosition(view)
                val isGroupHead = starAdapter.isGroupHeader(position)
                if (isGroupHead && view.top - groupHeaderHeight - parent.paddingTop >= 0) {
                    c.drawRect(left.toFloat(), (view.top - groupHeaderHeight).toFloat(), right.toFloat(), view.bottom.toFloat(), headPaint)
                    val groupName = starAdapter.getGroupName(position)
                    textPaint.getTextBounds(groupName, 0, groupName.length, textRect);
                    c.drawText(groupName, (left + 20).toFloat(),
                            (view.top - groupHeaderHeight / 2 + textRect.height() / 2).toFloat(), textPaint)
                } else if(view.top - groupHeaderHeight - parent.paddingTop >= 0){
                    c.drawRect(left.toFloat(), (view.top - 4).toFloat(), right.toFloat(), view.top.toFloat(), headPaint);
                }

            }
        }
    }

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        super.onDrawOver(c, parent, state)
        if (parent.adapter is StarAdapter) {
            val starAdapter = parent.adapter as StarAdapter
            val position = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
            val itemView: View? = parent.findViewHolderForAdapterPosition(position)?.itemView
            val left = parent.paddingLeft
            val right = parent.width - parent.paddingRight
            val top = parent.paddingTop
            val isGroupHead = starAdapter.isGroupHeader(position + 1)
            if (isGroupHead) {
                val groupName = starAdapter.getGroupName(position)
                val bottom: Int = Math.min(groupHeaderHeight, itemView?.bottom
                        ?: 0 - parent.paddingTop)
                c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(),
                        (bottom + top).toFloat(), headPaint)
                textPaint.getTextBounds(groupName, 0, groupName.length, textRect);
                c.drawText(groupName, (left + 20).toFloat(),
                        (top - groupHeaderHeight / 2 + textRect.height() / 2 + bottom).toFloat(),
                        textPaint)

            } else {
                val groupName = starAdapter.getGroupName(position)
                c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(),
                        (top + groupHeaderHeight).toFloat(), headPaint)
                textPaint.getTextBounds(groupName, 0, groupName.length, textRect);
                c.drawText(groupName, (left + 20).toFloat(),
                        (top + groupHeaderHeight / 2 + textRect.height() / 2).toFloat(), textPaint)
            }

        }
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        super.getItemOffsets(outRect, view, parent, state)
        if (parent.adapter is StarAdapter) {
            val starAdapter = parent.adapter as StarAdapter
            val position: Int = parent.getChildLayoutPosition(view)
            val isGroupHeader = starAdapter.isGroupHeader(position)
            if (isGroupHeader) {
                outRect.set(0, groupHeaderHeight, 0, 0)
            } else {
                outRect.set(0, 0, 4, 0)
            }

        }
    }
}

入口Activity

class RecyclerViewActivity : AppCompatActivity() {
    val starList = mutableListOf<Star>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view)
        initData()
        rv_list.layoutManager = LinearLayoutManager(this)
        rv_list.addItemDecoration(StarDecoration(this))
        rv_list.adapter = StarAdapter(starList,this)

    }

    private fun initData() {

        for(i in 0 until 4){
            for(j in 0 until 20){
                if(i % 2 == 0){
                    starList.add(Star("何XXA$j","快乐家族$i"))
                }else {
                    starList.add(Star("汪XXA$j","天天兄弟$i"))

                }
            }
        }
    }
}

布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".baseui.view.activity.RecyclerViewActivity">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_list"
        android:paddingTop="150dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

Adapter & item



class StarAdapter(val starList: MutableList<Star>, val context: Context) : RecyclerView.Adapter<StarAdapter.StarViewHolder>() {


    // TODO: 2020/7/22 inner class & class ??????
    class StarViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tv: TextView = itemView.findViewById(R.id.  iv_start)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StarViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.rv_item_star, null)
        return StarViewHolder(view)
    }

    override fun getItemCount(): Int = starList.size

    override fun onBindViewHolder(holder: StarViewHolder, position: Int) {
        holder.tv.text = starList[position].name
    }

    fun isGroupHeader(position: Int): Boolean {
        return if (position == 0)
            true
        else {
            val curGroupName = getGroupName(position)
            val preGroupName = getGroupName(position -1)
            return preGroupName != curGroupName
        }
    }

    fun getGroupName(position: Int): String {
        return 
            starList[position].groupName
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <TextView
        android:id="@+id/iv_start"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center"
        android:textSize="20sp"/>

</RelativeLayout>

原理

拿到分割线,分割先可以有多个

addItemDecoration 调用了requestLayout

之后会计算

measureChildWithMargins->getItemDecorInsetsForChild->getItemOffsets

我们的RecyclerView 的onDraw方法说明了绘制的顺序

draw()->super.onDraw()->mItemDecorations.get(i).onDraw

draw()->mItemDecorations.get(i).onDrawOver()

相关文章

网友评论

    本文标题:RecyclerView 吸顶实现

    本文链接:https://www.haomeiwen.com/subject/martlktx.html