美文网首页
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