美文网首页
ItemDecoration源码分析

ItemDecoration源码分析

作者: 壹元伍角叁分 | 来源:发表于2021-10-22 08:54 被阅读0次

先来看下ItemDecoration的源码

public abstract static class ItemDecoration {
       
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
        onDraw(c, parent);
    }
    
    @Deprecated
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {
    }
     
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
        onDrawOver(c, parent);
    }
      
    @Deprecated
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent) {
    }
       
    @Deprecated
    public void getItemOffsets(@NonNull Rect outRect, int itemPosition, @NonNull RecyclerView parent) {
        outRect.set(0, 0, 0, 0);
    }
       
    // 预留分割线的位置
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);
    }
}

onDraw()和onDrawOver()都是绘制,区别在于绘制的流程。那我们看下分别在什么时候调用的

RecyclerView.draw(Canvas c)
    // 先是调用父类的draw()方法,也就是View.draw()
--> super.draw(c);
        // 第一步,绘制背景
    --> drawBackground(canvas);@View
        // 第二步,绘制自己的内容
    --> onDraw(canvas);          
            // RecyclerView重写了onDraw()方法。RecyclerView.onDraw()中调用了ItemDecoration.onDraw()
        --> mItemDecorations.get(i).onDraw(c, this, mState);@RecyclerView
        // 第三步,绘制子view
    --> dispatchDraw(canvas); 
        // 第四步,绘制前景
    --> onDrawForeground(canvas);
    // ItemDecoration.onDrawOver() 在 ItemDecoration.onDraw()之后绘制。相当于覆盖在onDraw上面
--> mItemDecorations.get(i).onDrawOver(c, this, mState);

接下来,我们给RecyclerView的item之间添加分割线。
系统给我们提供了DividerItemDecoration这样一个类,实现了RecyclerView.ItemDecoration。

 mRecyclerView.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.HORIZONTAL));
--> fill(recycler, mLayoutState, state, false);@LinearLayoutManager
    --> layoutChunk(recycler, state, layoutState, layoutChunkResult);  
        --> measureChildWithMargins(view, 0, 0);
                // 获取到分割线的区域
            --> final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
                    // 分割线的区域是由我们自己定义的,通过重写ItemDecoration的getItemOffsets()方法。
                --> mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);@RecyclerView
                    --> getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent);@RecyclerView.ItemDecoration
                            // 默认的分割线是没有的
                        --> outRect.set(0, 0, 0, 0);

我们来看下DividerItemDecoration中getItemOffsets()的具体实现
//根据RecyclerView的方向,设置分割线

if (mOrientation == VERTICAL) {
    outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
    outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}

既然知道了分割线的具体处理位置是在getItemOffsets()中,那么来尝试自定义一个ItemDecoration。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        recycler_view.layoutManager = LinearLayoutManager(this)
        recycler_view.addItemDecoration(StudentItemDecoration())
        recycler_view.adapter = StudentAdapter(this, initStudentList())
    }
    
    // 模拟数据
    private fun initStudentList(): MutableList<Student>? {
        val studentList = mutableListOf<Student>()
        for (i in 0..3) {
            for (j in 0..6) {
                studentList.add(Student("班级$i 学生$j", "班级$i"))
            }
        }
        return studentList
    }
}
class StudentAdapter(private val context: Context, private val list: MutableList<Student>?) :
    RecyclerView.Adapter<StudentAdapter.MyViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflate = LayoutInflater.from(context).inflate(
            R.layout.layout_item_recycler_student,
            null
        )
        return MyViewHolder(inflate)
    }
    
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        list?.run {
            holder.tvName.text = "$position ---- ${get(position).name}"
        }
    }
    
    override fun getItemCount(): Int = list?.size ?: 0
    
    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tvName: TextView = itemView.findViewById<TextView>(R.id.tv_student_name)
    }
    
    /**
     * 通过position判断view是不是这个类别的第一个
     */
    fun isFirstView(position: Int): Boolean {
        if (list == null) {
            return false
        }
        if (position == 0) {
            return true
        }
    
        return list[position - 1].groupName != list[position].groupName
    }
    
    fun getClassName(position: Int): String = list?.get(position)?.groupName ?: ""
    fun getStudentName(position: Int): String = list?.get(position)?.name ?: ""
    
     /**
     * 通过屏幕上第一个position和范围,获取范围内的最后一个数据的index
     */
    fun getLastIndex(startIndex: Int, endIndex: Int): Int {
        if (list == null) {
            return -1
        }
    
        for (index in startIndex..endIndex) {
            //遍历,直到班级名不相等为止。
            if (list[index].groupName != list[index + 1].groupName) {
                return index
            }
        }
    
        return -1
    
    }
}

定义一个Student数据类

data class Student(val name: String, val groupName: String)

定义一个StudentItemDecoration,实现ItemDecoration

class StudentItemDecoration : RecyclerView.ItemDecoration() {
    var paintBack: Paint = Paint()
    var paintText: Paint
    val headerHeight = 200 // 头部的高度
    val dividerHeight = 2 // 分隔线高度

    init {
        paintBack.isDither = true
        paintBack.isAntiAlias = true
        paintBack.color = Color.RED
    
        paintText = Paint()
        paintText.isDither = true
        paintText.isAntiAlias = true
        paintText.textSize = 30f
        paintText.color = Color.WHITE
    }
    
    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        super.onDraw(c, parent, state)
    
        val adapter: StudentAdapter = parent.adapter as StudentAdapter
        //获取到recyclerView的padding值
        val rectLeft = parent.paddingLeft.toFloat()
        val rectTop = parent.paddingTop.toFloat()
        val rectRight = parent.width - parent.paddingRight.toFloat()
    
        //这里绘制头布局。
        val childCount = parent.childCount
        //遍历当前可见的所有view
        for (childIndex in 0..childCount) {
            val childView = parent.getChildAt(childIndex)
            if (childView != null) {
                // 通过view获取它的位置。这里要区分getChildLayoutPosition()和getChildAdapterPosition()。
                // 这里用getChildAdapterPosition() 会索引越界
                val childAdapterPosition = parent.getChildLayoutPosition(childView)
                if (adapter.isFirstView(childAdapterPosition)) {
                    // 如果view是第一个,那就绘制在它的上面
                    c.drawRect(
                        rectLeft,
                        childView.top - headerHeight + rectTop,
                        rectRight,
                        childView.top + rectTop,
                        paintBack
                    )
    
                    // 接下来是绘制标题
                    c.drawText(
                        adapter.getClassName(childAdapterPosition),
                        rectLeft,
                        childView.top - headerHeight + rectTop + 30,
                        paintText
                    )
                }
            }
        }
    }
    
    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        super.onDrawOver(c, parent, state)
    
        val adapter: StudentAdapter = parent.adapter as StudentAdapter
        //获取到recyclerView的padding值
        val rectLeft = parent.paddingLeft.toFloat()
        val rectTop = parent.paddingTop.toFloat()
        val rectRight = parent.width - parent.paddingRight.toFloat()
    
        // 获取到第一个view
        val layoutManager = parent.layoutManager as LinearLayoutManager
        val itemHeight = parent[0].height
        val findFirstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
    
        // 接下来是绘制标题
//        if (headerHeight > itemHeight){
        //如果头布局的高度大于item的高度。那就需要加
//            val childView = parent.getChildAt( )
//        }

        val endIndexedValue = findFirstVisibleItemPosition + headerHeight / itemHeight
        val lastViewIndex = adapter.getLastIndex(
            findFirstVisibleItemPosition,
            endIndexedValue
        )
        println("onDrawOver: lastViewIndex=$lastViewIndex start=$findFirstVisibleItemPosition  endIndexedValue=$endIndexedValue")
   
        // 这里需要判断固定在头部的头布局的高度是否需要变动
        if (lastViewIndex != -1
        ) {
            val childView = parent.getChildAt(lastViewIndex - findFirstVisibleItemPosition)
            //这里绘制悬浮停留的效果
            c.drawRect(
                rectLeft,
                ((childView.bottom - headerHeight).toFloat() + rectTop).coerceAtMost(rectTop),
                rectRight,
                (childView.bottom.toFloat() + rectTop).coerceAtMost(headerHeight + rectTop),
                paintBack
            )
    
            c.drawText(
                adapter.getStudentName(findFirstVisibleItemPosition),
                rectLeft,
                (childView.bottom - headerHeight + rectTop + 30f).coerceAtMost(rectTop + 30),
                paintText
            )
        } else {
            //这里绘制悬浮停留的效果
            c.drawRect(
                rectLeft,
                rectTop,
                rectRight,
                rectTop + headerHeight,
                paintBack
            )
    
            c.drawText(
                adapter.getStudentName(findFirstVisibleItemPosition),
                rectLeft,
                rectTop + 30,
                paintText
            )
        }
    }
    
    override fun getItemOffsets(
        outRect: Rect,
        view: View,
        parent: RecyclerView,
        state: RecyclerView.State
    ) {
        outRect.set(0, 10, 0, 0)
    
        //班级之间的分隔大一些
        //获取到当前的位置
        val childAdapterPosition = parent.getChildAdapterPosition(view)
        val adapter: StudentAdapter = parent.adapter as StudentAdapter
    
        if (adapter.isFirstView(childAdapterPosition)) {
            outRect.set(0, headerHeight, 0, 0)
    
        } else {
            outRect.set(0, dividerHeight, 0, 0)
        }
    }
}

相关文章

网友评论

      本文标题:ItemDecoration源码分析

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