Android自定义View实现防刻度seekbar

需求分析

  • UI效果如下
    UI效果图
    这里的目的是实现一个类似刻度尺一样的自定义View,看起来总共分为三部分
    三角形,蓝色圆角矩形,白色圆角矩形,实际上是两个部分-蓝色和白色

实现

因此这里通过自定义属性来设置背景和进度的颜色

  • 自定义属性如下
    分别是背景色,前景进度色,进度的最小值,进度的最大值,进度的当前值
<declare-styleable name="SeekStripeView">
       <attr name="progressBgColor" format="color" />
       <attr name="progressFgColor" format="color" />
       <attr name="progress_min" format="integer" />
       <attr name="progress_max" format="integer" />
       <attr name="progress_cur" format="integer" />
</declare-styleable>
  • 然后自定义view的代码获取设置的属性值(知识点-context.withStyledAttributes方法是一个扩展函数)
init {
        context.withStyledAttributes(attributeSet, R.styleable.SeekStripeView) {
            setMin(getInt(R.styleable.SeekStripeView_progress_min, mMin))
            setMax(getInt(R.styleable.SeekStripeView_progress_max, mMax))
            setProgress(getInt(R.styleable.SeekStripeView_progress_cur, mProgress))
            progressBgColor = getInt(R.styleable.SeekStripeView_progressBgColor, progressBgColor)
            progressFgColor = getInt(R.styleable.SeekStripeView_progressFgColor, progressFgColor)
        }
        mPaint.style = Paint.Style.FILL
        mPaint.color = progressBgColor
        bgPath = Path()
        fgPath = Path()
    }
 //扩展函数 Context.withStyledAttributes的源码
 /**
 * Executes [block] on a [TypedArray] receiver. The [TypedArray] holds the attribute
 * values in [set] that are listed in [attrs]. In addition, if the given [AttributeSet]
 * specifies a style class (through the `style` attribute), that style will be applied
 * on top of the base attributes it defines.
 *
 * @param set The base set of attribute values.
 * @param attrs The desired attributes to be retrieved. These attribute IDs must be
 *              sorted in ascending order.
 * @param defStyleAttr An attribute in the current theme that contains a reference to
 *                     a style resource that supplies defaults values for the [TypedArray].
 *                     Can be 0 to not look for defaults.
 * @param defStyleRes A resource identifier of a style resource that supplies default values
 *                    for the [TypedArray], used only if [defStyleAttr] is 0 or can not be found
 *                     in the theme. Can be 0 to not look for defaults.
 *
 * @see Context.obtainStyledAttributes
 * @see android.content.res.Resources.Theme.obtainStyledAttributes
 * 这里主要就是帮你添加了回收的方法,避免写代码时忘记导致内存泄露,点赞..
 */
public inline fun Context.withStyledAttributes(
    set: AttributeSet? = null,
    attrs: IntArray,
    @AttrRes defStyleAttr: Int = 0,
    @StyleRes defStyleRes: Int = 0,
    block: TypedArray.() -> Unit
) {
    obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes).apply(block).recycle()
}
  • 添加好属性之后需要根据控件的宽高计算所需要画出的格子数,这里我们在onSizeChanged方法得到控件的宽高,然后最主要的方法是computeProgressPath方法
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mWidth = (w + paddingLeft + paddingRight)
        mHeight = (h + paddingTop + paddingBottom)
        computeProgressPath()
    }
    
private fun computeProgressPath() {
		//根据进度和最大最小值得到当前的百分比
        val percentWidth: Float = (mProgress - mMin) / (mMax - mMin).toFloat()
        LogInfo(TAG, "percentWidth = $percentWidth")
        //根据控件的宽高和每个小矩形的宽高计算出要总共要多少个矩形-这个就是背景色了
        val totalSum = (mWidth / (mBlockSpace + mBlockWidth))
        //然后根据进度百分比得到进度有多少个矩形 
        val progressSum = (percentWidth * totalSum).roundToInt()
        LogInfo(TAG, "sum = $totalSum ,progressSum = $progressSum")
        //每次设置路径前都需要先重置,否则不会生效
        fgPath.reset()
        bgPath.reset()
        //为了避免重复绘制,所以这里有分成两个path 一个是进度一个是背景
        for (i in 0..totalSum) {
            val left = (mBlockWidth + mBlockSpace) * i
            val right = left + mBlockWidth
            val top = thumbHeight
            val bottom = mHeight
            val rectF = RectF(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
            when {
                i < progressSum -> fgPath.addRoundRect(rectF, 5f, 5f, Path.Direction.CCW)
                i == progressSum -> {
                fgPath.addRoundRect(rectF, 5f, 5f, Path.Direction.CCW)
                    //三角标的位置在最后一个进度矩形上
                    fgPath.moveTo(left.toFloat(), 0f)
                    fgPath.lineTo(right.toFloat(), 0f)
                    fgPath.lineTo(right - (mBlockWidth.toFloat() / 2), thumbHeight.toFloat())
                }
                else -> {
                    bgPath.addRoundRect(rectF, 5f, 5f, Path.Direction.CCW)
                }
            }
        }

        bgPath.close()
        fgPath.close()
    }
  • 路径添加完成之后,就是绘制路径了方法很简单重新onDraw方法即可
override fun onDraw(canvas: Canvas) {
      mPaint.color = progressBgColor
       canvas.drawPath(bgPath, mPaint)
       mPaint.color = progressFgColor
       canvas.drawPath(fgPath, mPaint)
    }
  • 因为它是一个可滑动的view 所以当然少不了重写触摸事件了
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
        if (!isEnabled) {
            return false
        }
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                mOnProgressChangeListener?.onStartTrackingTouch(this)
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                mOnProgressChangeListener?.onStopTrackingTouch(this)
            }
            MotionEvent.ACTION_MOVE -> {
                var x = event.x
                if (x > mWidth) {
                    x = mWidth.toFloat()
                }
                if (x < 0) {
                    x = 0f
                }
                /**
                 * (progress-mMin)/(mMax-mMin) = x/width
                 * so progress = (x *(max-min)/width)+min
                 */
                var ctrlProgress =
                    (x * (mMax - mMin) / mWidth).toInt() + mMin
                if (ctrlProgress < mMin) {
                    ctrlProgress = mMin
                }
                if (ctrlProgress != mProgress) {
                    mOnProgressChangeListener?.onProgressChanged(this, ctrlProgress, true)
                    mProgress = ctrlProgress
                    computeProgressPath()
                    postInvalidate()
                }
            }
        }
        return true
    }

实现的效果

最终效果
哈哈,对自定义View的印象又加深了一点,感谢观看,欢迎点赞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值