Android的RecyclerView.ItemDecoration使用


前言

项目的需要,需要实现一个类似如图所示的效果,就是在列表中添加节点的效果,
并且最终显示走到节点的进度的效果(下图没有如此效果,后续加上)。
在这里插入图片描述


一、RecyclerView.ItemDecoration是什么?

RecyclerView.ItemDecoration就是RecyclerView的装饰器,RecyclerView的分隔线也是使用ItemDecoration实现。

例如Android默认实现的分隔线DividerItemDecoration,我们直接参考实现。

RecyclerView.ItemDecoration的源码,过时的方法已经剔除,就不列举了。

public abstract static class ItemDecoration {
    
    
       /**
          * 在提供给 RecyclerView 的 Canvas 中绘制任何适当的装饰。
          * 任何通过该方法绘制的内容都会在项目视图绘制之前被绘制,
          * 因此将出现在视图下方。
          *
          * @param c 要绘制的画布
          * @param parent RecyclerView 这个 ItemDecoration 正在绘制
          * @param state RecyclerView 的当前状态
          */
        public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
    
    
            onDraw(c, parent);
        }

        /**
          * 在提供给 RecyclerView 的 Canvas 中绘制任何适当的装饰。
          * 该方法绘制的任何内容都会在item view绘制完成后绘制
          * 因此将出现在视图上。
          *
          * @param c 要绘制的画布
          * @param parent RecyclerView 这个 ItemDecoration 正在绘制
          * @param state RecyclerView 的当前状态。
          */
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
                @NonNull State state) {
    
    
            onDrawOver(c, parent);
        }

       /**
          * 检索给定项目的任何偏移量。 <code>outRect</code> 的每个字段指定
          * 项目视图应插入的像素数,类似于填充或边距。
          * 默认实现将 outRect 的边界设置为 0 并返回。
          *
          * <p>
          * 如果这个ItemDecoration不影响itemview的定位,应该设置
          * <code>outRect</code> 的四个字段(左、上、右、下)全部归零
          * 在返回之前。
          *
          * <p>
          * 如果需要访问 Adapter 获取更多数据,可以调用
          * {@link RecyclerView#getChildAdapterPosition(View)} 获取适配器的位置
          * 看法。
          *
          * @param outRect 接收输出的矩形。
          * @param view 要装饰的子视图
          * @param parent RecyclerView 这个 ItemDecoration 正在装饰
          * @param state RecyclerView 的当前状态。
          */
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
                @NonNull RecyclerView parent, @NonNull State state) {
    
    
            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }
    }

我们最终需要实现的方法就是上述这三个。

我们单独讲下getItemOffsets中的Rect outRect和View view(ItemView)
在这里插入图片描述

二、使用步骤

1.引入库

创建项目之后,RecyclerView跟ViewPager这些一样,应该是包含在android源码中引入的,我们也可以单独做引入。

代码如下(示例):

implementation 'androidx.recyclerview:recyclerview:1.2.1'

2.设置装饰器

2.1 在Activity或者Fragment中设置装饰器

代码如下(示例):

private fun init() {
    
    
        val list: MutableList<MaintItem> = arrayListOf()
        for (i in 0..20) {
    
    
            list.add(MaintItem("张${i + 1}丰", "${(i + 1) * 100}km/${(i + 1) * 0.5}year", (i + 1) * 100))
        }
        val adapter = RecyclerAdapter(list) {
    
    
            Log.e(TAG, "data:: $it")
        }
        // 分隔线的图片
        val drawable = ContextCompat.getDrawable(this, R.drawable.item_space)
        // 创建装饰器象
        val itemDecoration = MaintenanceItemDecoration(this, list)
        itemDecoration.setDrawable(drawable!!)
        // 将装饰器传递给RecyclerView
        rv.addItemDecoration(itemDecoration)
        val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        rv.layoutManager = layoutManager
        rv.adapter = adapter
    }

item_space代码如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size android:width="5dp"
        android:height="5dp"/>
    <solid android:color="#00E5FF"/>
</shape>

2.2 实现MaintenanceItemDecoration,继承于RecyclerView.ItemDecoration()

1、实现getItemOffsets方法,代码如下

override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    
    
        super.getItemOffsets(outRect, view, parent, state)
        // 判断是否设置了分隔线图片,如果未设置,不处理
        if (mDrawable == null) {
    
    
            outRect.set(0, 0, 0, 0)
            return
        }
        mDrawable?.let {
    
     drawable ->
            if (isVertical()) {
    
    
                outRect.set(0, 0, 0, drawable.intrinsicHeight)
            } else {
    
    
                // left、 top、right(分隔线的宽度)、bottom(给装饰器底部留白)
                outRect.set(0, 0, drawable.intrinsicWidth, mOutRectHeight)
            }
        }
    }

效果图如下:可以看到ItemView的右侧和底部都有留白
在这里插入图片描述

2、给底部留白部分加上节点,实现onDraw方法

代码如下:在onDraw中实现绘制

override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    
    
        super.onDraw(c, parent, state)
        if (!isVertical()) {
    
    
            drawHorizontal(c, parent)
        } else {
    
    
            drawVertical(c, parent)
        }
    }
private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
    
    
        val childCount = parent.childCount
        mDrawable?.let {
    
     drawable ->
            mPaint?.let {
    
     paint ->
                // 注意:
                // 当前childCount获取的是当前可见的ItemView
                for (i in 0 until childCount) {
    
    
                    val child = parent.getChildAt(i)

                    // 此处为了获取当前的ItemView在列表中真正的位置
                    val childLayoutPosition = parent.getChildLayoutPosition(child)
                    Log.i("drawHorizontal", "drawHorizontal childLayoutPosition:: $childLayoutPosition")
                    if (childLayoutPosition == RecyclerView.NO_POSITION) {
    
    
                        continue
                    }

                    val centerX: Float = child.left + (child.right - child.left).toFloat() / 2
                    val centerY: Float = child.bottom + DEFAULT_RECT_HEIGHT.toFloat() / 2
                    Log.e("drawHorizontal", "centerX:: $centerX, centerY:: $centerY")
                    canvas.drawLine(
                            child.left.toFloat(),
                            centerY,
                            child.right.toFloat() + drawable.intrinsicWidth,
                            centerY, paint)
                    canvas.drawCircle(centerX, centerY, mCircleRadius, paint)

                    // 此处我们需要使用正确的position在列表中获取数据
                    val text = list[childLayoutPosition].mark
                    val textWidth = paint.measureText(text)
                    Log.e("drawHorizontal", "text:: $text")
                    val textY: Float = child.bottom + DEFAULT_RECT_HEIGHT.toFloat() * 2
                    val startX = centerX - textWidth / 2
                    val startY = textY + (paint.descent() - paint.ascent()) / 2
                    canvas.drawText(text, startX, startY, paint)
                }
            }
        }
    }

此处已经完成绘制截图:
在这里插入图片描述
需要注意代码中划线的部分,我们正确获取当前ItemView中正确的数据
在这里插入图片描述

3、MaintenanceItemDecoration完整代码

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.dh.daynight.MaintItem
import com.dh.daynight.widget.SizeUtils
import java.lang.IllegalArgumentException

class MaintenanceItemDecoration(
        val context: Context,
        val list: MutableList<MaintItem>
) : RecyclerView.ItemDecoration() {
    
    
    companion object {
    
    
        private val ATTRS: IntArray = intArrayOf(android.R.attr.listDivider)
        private const val DEFAULT_RECT_HEIGHT = 50
        private const val DEFAULT_COLOR = "#404040"
        private const val DEFAULT_CIRCLE_RADIUS = 20f
        private const val DEFAULT_TXT_SIZE = 40f

        const val HORIZONTAL = LinearLayoutManager.HORIZONTAL
        const val VERTICAL = LinearLayoutManager.VERTICAL
    }
    private var mDrawable: Drawable? = null
    private var mOrientation: Int = HORIZONTAL
    private var mPaint: Paint? = null
    private var mOutRectHeight: Int = DEFAULT_RECT_HEIGHT * 3
    private var mCircleRadius: Float = DEFAULT_CIRCLE_RADIUS

    init {
    
    
        val typedArray = context.obtainStyledAttributes(ATTRS)
        mDrawable = typedArray.getDrawable(0)
        typedArray.recycle()
        setOrientation(HORIZONTAL)
        initData()
    }

    private fun initData() {
    
    
        mPaint = Paint()
        mPaint?.isAntiAlias = true
        mPaint?.color = Color.parseColor(DEFAULT_COLOR)
        mPaint?.style = Paint.Style.FILL
        mPaint?.textSize = DEFAULT_TXT_SIZE
    }

    fun setDrawable(drawable: Drawable) {
    
    
        this.mDrawable = drawable
    }

    fun setOrientation(orientation: Int) {
    
    
        if (orientation != HORIZONTAL || orientation == VERTICAL) {
    
    
            throw IllegalArgumentException(
                    "Invalid orientation. It should be either HORIZONTAL or VERTICAL")
        }
        this.mOrientation = orientation
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    
    
        super.onDraw(c, parent, state)
        if (!isVertical()) {
    
    
            drawHorizontal(c, parent)
        } else {
    
    
            drawVertical(c, parent)
        }
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    
    
        super.getItemOffsets(outRect, view, parent, state)
        if (mDrawable == null) {
    
    
            outRect.set(0, 0, 0, 0)
            return
        }
        mDrawable?.let {
    
     drawable ->
            if (isVertical()) {
    
    
                outRect.set(0, 0, 0, drawable.intrinsicHeight)
            } else {
    
    
                outRect.set(0, 0, drawable.intrinsicWidth, mOutRectHeight)
            }
        }
    }

    private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
    
    
        val childCount = parent.childCount
        mDrawable?.let {
    
     drawable ->
            mPaint?.let {
    
     paint ->
                // 注意:
                // 当前childCount获取的是当前可见的ItemView
                for (i in 0 until childCount) {
    
    
                    val child = parent.getChildAt(i)

                    // 此处为了获取当前的ItemView在列表中真正的位置
                    val childLayoutPosition = parent.getChildLayoutPosition(child)
                    Log.i("drawHorizontal", "drawHorizontal childLayoutPosition:: $childLayoutPosition")
                    if (childLayoutPosition == RecyclerView.NO_POSITION) {
    
    
                        continue
                    }

                    val centerX: Float = child.left + (child.right - child.left).toFloat() / 2
                    val centerY: Float = child.bottom + DEFAULT_RECT_HEIGHT.toFloat() / 2
                    Log.e("drawHorizontal", "centerX:: $centerX, centerY:: $centerY")
                    canvas.drawLine(
                            child.left.toFloat(),
                            centerY,
                            child.right.toFloat() + drawable.intrinsicWidth,
                            centerY, paint)
                    canvas.drawCircle(centerX, centerY, mCircleRadius, paint)

                    // 此处我们需要使用正确的position在列表中获取数据
                    val text = list[childLayoutPosition].mark
                    val textWidth = paint.measureText(text)
                    Log.e("drawHorizontal", "text:: $text")
                    val textY: Float = child.bottom + DEFAULT_RECT_HEIGHT.toFloat() * 2
                    val startX = centerX - textWidth / 2
                    val startY = textY + (paint.descent() - paint.ascent()) / 2
                    canvas.drawText(text, startX, startY, paint)
                }
            }
        }
    }

    private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
    
    

    }

    private fun isVertical(): Boolean {
    
    
        return mOrientation == VERTICAL
    }
}

3.完成最终版带进度更新的装饰器

3.1 设置装饰器

使用中关注下述代码中带注释部分,谢谢

private fun init() {
    
    
        val list: MutableList<MaintItem> = arrayListOf()
        for (i in 0..20) {
    
    
            // 最后一个属性传入节点代表的值
            list.add(MaintItem("张${i + 1}丰", "${(i + 1) * 100}km/${(i + 1) * 0.5}year", (i + 1) * 100))
        }
        val adapter = RecyclerAdapter(list) {
    
    
            Log.e(TAG, "data:: $it")
        }
        val drawable = ContextCompat.getDrawable(this, R.drawable.item_space)
        // 最后参数传递当前进度需要走到的值
        val itemDecoration = MaintenanceItemDecoration(this, list, 500)
        itemDecoration.setDrawable(drawable!!)
        rv.addItemDecoration(itemDecoration)
        val layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        rv.layoutManager = layoutManager
        rv.adapter = adapter
    }

3.2 书写完整的装饰器

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.dh.daynight.MaintItem
import com.dh.daynight.R
import java.lang.IllegalArgumentException

class MaintenanceItemDecoration1(
        val context: Context,
        val list: MutableList<MaintItem>,
        val currentMileage: Int = 0
) : RecyclerView.ItemDecoration() {
    
    
    companion object {
    
    
        private const val TAG = "MaintenanceItemDecoration"
        private val ATTRS: IntArray = intArrayOf(android.R.attr.listDivider)
        private const val HALF = 2f

        const val HORIZONTAL = LinearLayoutManager.HORIZONTAL
        const val VERTICAL = LinearLayoutManager.VERTICAL
    }
    private var mDrawable: Drawable? = null
    private var mOrientation: Int = HORIZONTAL
    //private var mPaint: Paint? = null
    private lateinit var mTextPaint: Paint
    private lateinit var mLineNormalPaint: Paint
    private lateinit var mLineProgressPaint: Paint
    private lateinit var mNodeCirclePaint: Paint
    private lateinit var mNodeCircleProgressPaint: Paint
    private var mOutRectHeight: Int = 0
    private var mCircleRadius: Float = 0f
    private var mCircleTextSpace: Int = 0

    init {
    
    
        val typedArray = context.obtainStyledAttributes(ATTRS)
        mDrawable = typedArray.getDrawable(0)
        typedArray.recycle()
        setOrientation(HORIZONTAL)
        initData()
    }

    /**
     * Init data.
     */
    private fun initData() {
    
    
        setTextPaint()
        setLineNormalPaint()
        setCirclePaint()
        setLineGradientPaint()
        setCircleProgressPaint()

        mOutRectHeight = context.resources.getDimension(R.dimen.maint_item_rect_h).toInt()
        mCircleRadius = context.resources.getDimension(R.dimen.maint_item_circle_radius)

        mCircleTextSpace = context.resources.getDimension(R.dimen.maint_item_circle_text_space).toInt()
    }

    /**
     * Set the item decoration drawable.
     *
     * @param drawable drawable
     */
    fun setDrawable(drawable: Drawable) {
    
    
        this.mDrawable = drawable
    }

    /**
     * Set the display direction of RecyclerView.
     *
     * @param orientation
     * @see HORIZONTAL
     * @see VERTICAL
     */
    fun setOrientation(orientation: Int) {
    
    
        if (orientation != HORIZONTAL || orientation == VERTICAL) {
    
    
            throw IllegalArgumentException(
                    "Invalid orientation. It should be either HORIZONTAL or VERTICAL")
        }
        this.mOrientation = orientation
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    
    
        super.onDraw(c, parent, state)
        if (!isVertical()) {
    
    
            drawHorizontal(c, parent)
        } else {
    
    
            drawVertical(c, parent)
        }
    }

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    
    
        super.getItemOffsets(outRect, view, parent, state)
        if (mDrawable == null) {
    
    
            outRect.set(0, 0, 0, 0)
            return
        }
        mDrawable?.let {
    
     drawable ->
            if (isVertical()) {
    
    
                outRect.set(0, 0, 0, drawable.intrinsicHeight)
            } else {
    
    
                outRect.set(0, 0, drawable.intrinsicWidth, mOutRectHeight)
            }
        }
    }

    /**
     * When RecyclerView is displayed horizontally, draw item decoration.
     *
     * @param canvas Canvas
     * @param parent RecyclerView
     */
    @SuppressLint("LongLogTag")
    private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
    
    
        val childCount = parent.childCount
        Log.d(TAG, "drawHorizontal childCount:: $childCount, currentMileage:: $currentMileage")
        mDrawable?.let {
    
     drawable ->
            for (i in 0 until childCount) {
    
    
                val child = parent.getChildAt(i)
                val childLayoutPosition = parent.getChildLayoutPosition(child)
                Log.i(TAG, "drawHorizontal childLayoutPosition:: $childLayoutPosition")
                if (childLayoutPosition == RecyclerView.NO_POSITION) {
    
    
                    continue
                }
                val centerX: Float = child.left + (child.right - child.left).toFloat() / 2
                val centerY: Float = child.bottom + mCircleRadius
                Log.i(TAG, "drawHorizontal centerX:: $centerX, centerY:: $centerY")
                // 绘制保养刻度
                canvas.drawLine(
                    child.left.toFloat(),
                    centerY,
                    child.right.toFloat() + drawable.intrinsicWidth,
                    centerY, mLineNormalPaint)
                // 绘制保养进度
                drawProgress(canvas, child, childLayoutPosition, drawable, centerX, centerY)

                // 绘制保养节点
                drawNodeCircle(canvas, centerX, centerY, childLayoutPosition)

                // 绘制保养节点文本
                val text = list[childLayoutPosition].mark
                val textWidth = mTextPaint.measureText(text)
                Log.i(TAG, "drawHorizontal text:: $text")
                val textY: Float = child.bottom.toFloat() + mCircleRadius * Companion.HALF + mCircleTextSpace
                val startX = centerX - textWidth / Companion.HALF
                val startY = textY + (mTextPaint.descent() - mTextPaint.ascent())
                canvas.drawText(text, startX, startY, mTextPaint)
            }
        }
    }

    /**
     * Draw maintenance progress.
     *
     * @param canvas Canvas
     * @param child RecyclerView item view
     * @param childLayoutPosition Visible item's position for lit
     * @param drawable divider line
     * @param centerX item center point
     * @param centerY
     */
    private fun drawProgress(
        canvas: Canvas,
        child: View,
        childLayoutPosition: Int,
        drawable: Drawable,
        centerX: Float,
        centerY: Float
    ) {
    
    
        val nodeMileage = list[childLayoutPosition].mileage
        if (nodeMileage <= 0 || currentMileage <= 0) {
    
    
            return
        }
        when {
    
    
            currentMileage > nodeMileage -> {
    
    
                canvas.drawLine(
                    child.left.toFloat(),
                    centerY,
                    child.right.toFloat() + drawable.intrinsicWidth,
                    centerY, mLineProgressPaint)
            }
            currentMileage < nodeMileage -> {
    
    
                drawLessThanProgress(
                    canvas,
                    child,
                    childLayoutPosition,
                    centerY,
                    nodeMileage
                )
            }
            else -> {
    
    
                canvas.drawLine(
                    child.left.toFloat(),
                    centerY,
                    centerX,
                    centerY, mLineProgressPaint)
            }
        }
    }

    /**
     * The current mileage is less than the mileage of the current node.
     *
     * @param canvas Canvas
     * @param child Child view
     * @param childLayoutPosition Child view position for list
     * @param centerY
     * @param nodeMileage Current node mileage
     */
    @SuppressLint("LongLogTag")
    private fun drawLessThanProgress(
        canvas: Canvas,
        child: View,
        childLayoutPosition: Int,
        centerY: Float,
        nodeMileage: Int
    ) {
    
    
        val itemWidth = child.right.toFloat() - child.left.toFloat()
        val itemHalfWidth = itemWidth / Companion.HALF
        if (childLayoutPosition == 0) {
    
    
            val stopX = itemHalfWidth * (currentMileage.toFloat() / nodeMileage) + child.left
            canvas.drawLine(
                0f,
                centerY,
                stopX,
                centerY, mLineProgressPaint)
        } else {
    
    
            val preNodeMileage = list[childLayoutPosition - 1].mileage
            if (currentMileage !in (preNodeMileage + 1) until nodeMileage) {
    
    
                return
            }
            val percent = (currentMileage - preNodeMileage).toFloat() / (nodeMileage - preNodeMileage)
            val percentWidth = itemHalfWidth * percent
            val startX = if (child.left > 0) {
    
    
                child.left.toFloat()
            } else {
    
    
                0f
            }
            val stopX = child.left + percentWidth
            Log.e(TAG, "drawProgress left:: ${child.left}, startX:: $startX, stopX:: $stopX")
            canvas.drawLine(
                startX,
                centerY,
                stopX,
                centerY, mLineProgressPaint)
        }
    }

    private fun drawNodeCircle(
        canvas: Canvas,
        centerX: Float,
        centerY: Float,
        childLayoutPosition: Int
    ) {
    
    
        val nodeMileage = list[childLayoutPosition].mileage
        if (nodeMileage <= 0 || currentMileage <= 0) {
    
    
            canvas.drawCircle(centerX, centerY, mCircleRadius, mNodeCirclePaint)
            return
        }
        if (currentMileage >= nodeMileage) {
    
    
            canvas.drawCircle(centerX, centerY, mCircleRadius, mNodeCircleProgressPaint)
        } else {
    
    
            canvas.drawCircle(centerX, centerY, mCircleRadius, mNodeCirclePaint)
        }
    }

    private fun drawVertical(canvas: Canvas, parent: RecyclerView) {
    
    
        // TODO
    }

    /**
     * Determine whether it is vertical.
     */
    private fun isVertical(): Boolean {
    
    
        return mOrientation == VERTICAL
    }

    /**
     * Modify the Paint style to be the horizontal line of the item decorator.
     */
    private fun setCirclePaint() {
    
    
        mNodeCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mNodeCirclePaint.color = ContextCompat.getColor(context, R.color.maint_item_node_line_color)
        mNodeCirclePaint.style = Paint.Style.FILL
    }

    /**
     * Modify the Paint style to be the horizontal line of the item decorator.
     */
    private fun setCircleProgressPaint() {
    
    
        mNodeCircleProgressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mNodeCircleProgressPaint.color = ContextCompat.getColor(context, R.color.node_progress_color)
        mNodeCircleProgressPaint.style = Paint.Style.FILL
    }

    /**
     * Modify the Paint style to the text of the item decorator.
     */
    private fun setTextPaint() {
    
    
        mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mTextPaint.color = ContextCompat.getColor(context, R.color.black)
        mTextPaint.style = Paint.Style.FILL
        mTextPaint.textSize = context.resources.getDimension(R.dimen.txt_node_size)
    }

    /**
     * Set the Paint style of the line.
     */
    private fun setLineGradientPaint() {
    
    
        mLineProgressPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mLineProgressPaint.style = Paint.Style.STROKE
        mLineProgressPaint.strokeWidth = context.resources.getDimension(R.dimen.node_progress_line__h)
        mLineProgressPaint.color = ContextCompat.getColor(context, R.color.node_progress_color)
    }

    /**
     * Set the Paint style of the line.
     */
    private fun setLineNormalPaint() {
    
    
        mLineNormalPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mLineNormalPaint.style = Paint.Style.STROKE
        mLineNormalPaint.color = ContextCompat.getColor(context, R.color.maint_item_node_line_color)
        mLineNormalPaint.strokeWidth = context.resources.getDimension(R.dimen.node_line_h)
    }
}

colors.xml如下

<color name="black">#FF000000</color>
<!--节点和连接线的颜色-->
<color name="maint_item_node_line_color">#565656</color>
<!--已走进度的节点和连接线的颜色值-->
<color name="node_progress_color">#00F4FF</color>

dimens.xml如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--底部留白的高度-->
    <dimen name="maint_item_rect_h">100dp</dimen>
    <!--圆形节点的半径-->
    <dimen name="maint_item_circle_radius">10dp</dimen>
    <!--圆形节点和文本之间的间距-->
    <dimen name="maint_item_circle_text_space">20dp</dimen>
    <!--节点文本大小-->
    <dimen name="txt_node_size">25sp</dimen>
    <!--进度连接线的高度-->
    <dimen name="node_progress_line__h">8dp</dimen>
    <!--节点连接线高度-->
    <dimen name="node_line_h">4dp</dimen>
</resources>

最终效果:
在这里插入图片描述
在这里插入图片描述

其实主要就是操作如下两个方法:

1、通过getItemOffsets()在itemView顶部撑出来一片区域 2、通过onDraw()方法来在撑出的区域绘制自己想要的内容

参考

1、解析RecyclerView.ItemDecoration
2、自定义ItemDecoration分割线的高度、颜色、偏移,看完这个你就懂了
3、玩Android上收录的很多关于ItemDecoration的文章

总结

这篇文章主要还是代码,如果有需要如此效果的可以直接在代码山修改,因为确实除了代码这几个方法没什么可讲述的。

如果大家有问题,欢迎在评论区讨论,谢谢

猜你喜欢

转载自blog.csdn.net/u013855006/article/details/124768882