自定义view-滑动进度条

Posted da_caoyuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义view-滑动进度条相关的知识,希望对你有一定的参考价值。

好久没有写文章啦。记录一下此时的心情,哈哈。先上效果图:
布局
样式:

<declare-styleable name="SlidingScaleBarView">
        <!--刻度文字的大小-->
        <attr name="scaleTextSize" format="dimension" />
        <!--刻度文字的颜色-->
        <attr name="scaleTextColor" format="color" />

        <!--最大值-->
        <attr name="maxScaleValue" format="integer" />
        <!--step-->
        <attr name="step" format="integer" />
        <!--精度,默认小数点0位-->
        <attr name="precision" format="integer" />
        <!--单位-->
        <attr name="unit" format="string" />
        <!--改变的步幅大小 默认1,如果 precision 精度不等于0,changeStep始终等于1 -->
        <attr name="changeStep" format="integer" />


    </declare-styleable>

布局文件:

  <com.ypk.tablayout.view.fram.SlidingScaleBarView
            android:id="@+id/scaleBarView5"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            app:changeStep="100"
            app:maxScaleValue="40"
            app:precision="0"
            app:scaleTextColor="@color/green"
            app:scaleTextSize="12sp"
            app:step="10"
            app:unit="" />

自定义view完整代码 SlidingScaleBarView:

package com.ypk.tablayout.view.fram

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.ConvertUtils
import com.ypk.tablayout.R

/**
 * Created by ypk on 2022/7/4
 * 滑动刻度条
 *
 * 点击任意位置,就可以滑动
 */
class SlidingScaleBarView : View 

    private var viewWidth = 0f
    private var viewHeight = 0f

    private lateinit var bgPaint: Paint
    private lateinit var bgSlidePaint: Paint
    private lateinit var linePaint: Paint
    private lateinit var scaleTextPaint: Paint
    private lateinit var auxiliaryLinePaint: Paint

    lateinit var bgRect: RectF

    //背景进度条的高度
    private var bgRectHeight = 0f

    //背景矩形圆角大小
    private var bgRoundedCorners = ConvertUtils.dp2px(10f).toFloat()

    private var bgRectMarginHorizontal = 0f

    //改变的步幅大小
    var changeStep = 1

    //最大刻度值
    var maxScaleValue = 100;
    var step = 10

    //精度
    var precision = 0

    //分的份数
    var number = 0

    //分割后,每个间隔的宽度
    var spaceWidth = 0f

    //刻度线的高度
    private var lineHeight = ConvertUtils.dp2px(8f).toFloat()

    //刻度线的宽度
    private var lineWidth = ConvertUtils.dp2px(2f).toFloat()

    //刻度数字距离背景条的间距
    private var scaleTextMarginTop = ConvertUtils.dp2px(3f).toFloat()

    //滑块
    private var sliceSpace = ConvertUtils.dp2px(5f).toFloat()
    private var sliceWidth = ConvertUtils.dp2px(50f).toFloat()
    private var sliceHeight = ConvertUtils.dp2px(25f).toFloat()

    //刻度指示器图标
    lateinit var bitmapIndicator: Bitmap
    lateinit var bitmapSlide: Bitmap

    //百分比单位
    private var percentUnit = "%"


    var colorArray = intArrayOf(
        ContextCompat.getColor(context, R.color.green),
        ContextCompat.getColor(context, R.color.tab_select_color),
        ContextCompat.getColor(context, R.color.red_light)
    )
    var positionArray = floatArrayOf(0f, 0.8f, 1f)

    var maxValue = 0f
    var minValue = 0f


    private var customizeViewDefaultHeight = ConvertUtils.dp2px(45f).toFloat() //自定义的控件默认高度

    var mContext: Context

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) 
        this.mContext = context
        initAttr(context, attrs, defStyleAttr);
        initData()
    

    private var scaleTextSize = ConvertUtils.sp2px(15f).toFloat()
    private var scaleTextColor = ContextCompat.getColor(context, R.color.white)

    private fun initData() 
        //如果 precision 精度不等于0,changeStep要始终等于1
        if (precision > 0) 
            changeStep = 1;
        
        currentPercentageProgress = (maxScaleValue / 2).toFloat()
        number = maxScaleValue / step

        //控制条背景画笔
        bgPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        bgPaint.isAntiAlias = true
        bgPaint.style = Paint.Style.FILL
        //bgPaint.color = ContextCompat.getColor(context, R.color.task_type_bg)

        bgSlidePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        bgSlidePaint.isAntiAlias = true
        bgSlidePaint.alpha = 180


        linePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        linePaint.isAntiAlias = true
        linePaint.style = Paint.Style.STROKE
        linePaint.strokeWidth = lineWidth
        linePaint.color = ContextCompat.getColor(context, R.color.white)


        //刻度文字
        scaleTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        scaleTextPaint.isAntiAlias = true
        //scaleTextPaint.style = Paint.Style.FILL
        //caleTextPaint.strokeWidth = lineWidth
        scaleTextPaint.color = scaleTextColor
        scaleTextPaint.textSize = scaleTextSize

        bitmapIndicator = bitmapScale(
            ConvertUtils.drawable2Bitmap(resources.getDrawable(R.mipmap.ic_slide_indicator2)),
            1.4f
        )


        //辅助线画笔
        auxiliaryLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        auxiliaryLinePaint.isAntiAlias = true
        auxiliaryLinePaint.style = Paint.Style.STROKE
        //auxiliaryLinePaint.strokeWidth = ringWidth
        auxiliaryLinePaint.color = Color.RED

        val rectText = Rect()
        scaleTextPaint.getTextBounds("0", 0, 1, rectText)
        customizeViewDefaultHeight =
            sliceHeight + sliceSpace + bitmapIndicator.height + rectText.height() + scaleTextMarginTop


    

    private fun initAttr(context: Context, attrs: AttributeSet?, defStyleAttr: Int) 
        val typedArray =
            context.obtainStyledAttributes(attrs, R.styleable.SlidingScaleBarView, defStyleAttr, 0)
        val indexCount = typedArray.indexCount
        for (i in 0 until indexCount) 
            val index = typedArray.getIndex(i)
            when (index) 
                R.styleable.SlidingScaleBarView_scaleTextSize -> 
                    scaleTextSize = typedArray.getDimension(index, scaleTextSize)
                
                R.styleable.SlidingScaleBarView_scaleTextColor -> 
                    scaleTextColor = typedArray.getColor(index, scaleTextColor)
                
                R.styleable.SlidingScaleBarView_maxScaleValue -> 
                    maxScaleValue = typedArray.getInteger(index, maxScaleValue)
                
                R.styleable.SlidingScaleBarView_step -> 
                    step = typedArray.getInteger(index, step)
                
                R.styleable.SlidingScaleBarView_precision -> 
                    precision = typedArray.getInteger(index, precision)
                
                R.styleable.SlidingScaleBarView_unit -> 
                    percentUnit = typedArray.getString(index).toString()
                
                R.styleable.SlidingScaleBarView_changeStep -> 
                    changeStep = typedArray.getInteger(index, 1)
                
            
        
        typedArray.recycle()
    

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) 
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(widthMeasureSpec, measureHeight(heightMeasureSpec))
    

    private fun measureHeight(measureSpec: Int): Int 
        var result = 0
        val mode = MeasureSpec.getMode(measureSpec)
        val size = MeasureSpec.getSize(measureSpec)
        if (mode == MeasureSpec.EXACTLY)  //表示父控件已经确切的指定了子View的大小。
            result = size
            //  System.out.println("YPKTabLayoutView.measureHeight result1=" + result);
         else if (mode == MeasureSpec.AT_MOST)  //表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。
            result = Math.min(customizeViewDefaultHeight.toInt(), size)
            // System.out.println("YPKTabLayoutView.measureHeight result2=" + result);
         else if (mode == MeasureSpec.UNSPECIFIED)  //默认值,父控件没有给子view任何限制,子View可以设置为任意大小。
            result = customizeViewDefaultHeight.toInt()
            //System.out.println("YPKTabLayoutView.measureHeight result3=" + result);
        
        return result
    


    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) 
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = (w - oldw).toFloat()
        viewHeight = (h - oldh).toFloat()

        //进度条的高度和指示器一样
        bgRectHeight = bitmapIndicator.height.toFloat()

        bgRectMarginHorizontal = sliceWidth / 2

        spaceWidth = (viewWidth - bgRectMarginHorizontal * 2) / number
        //spaceWidth = (viewWidth - bgRectMarginHorizontal * 2 - lineWidth * (number - 1)) / number

        //进度条
        bgRect = RectF(
            bgRectMarginHorizontal,
            sliceHeight + sliceSpace,
            viewWidth - bgRectMarginHorizontal,
            sliceHeight + sliceSpace + bgRectHeight
        )
        val linearGradient = LinearGradient(
            0f,
            0f,
            bgRect.right,
            bgRect.bottom,
            colorArray,
            positionArray,
            Shader.TileMode.MIRROR
        )
        bgPaint.shader = linearGradient

        maxValue = viewWidth - bgRectMarginHorizontal
        minValue = bgRectMarginHorizontal

        val tv = TextView(context)
        // tv.setBackgroundColor(ContextCompat.getColor(context, R.color.gray))
        tv.background = ContextCompat.getDrawable(context, R.drawable.shap_corners10_bg)
        tv.width = sliceWidth.toInt()
        tv.height = sliceHeight.toInt()
        bitmapSlide = ConvertUtils.view2Bitmap(tv)

        //设置默认值
        setSlideProgress(currentPercentageProgress)
    


    override fun onDraw(canvas: Canvas) 
        super.onDraw(canvas)
        //绘制背景圆角矩形,也就是进度条
        canvas.drawRoundRect(bgRect, bgRoundedCorners, bgRoundedCorners, bgPaint)

        //绘制刻度线
        for (i in 1 until number) 
            canvas.drawLine(
                bgRectMarginHorizontal + spaceWidth * i,
                sliceHeight + sliceSpace + bgRectHeight - lineHeight,
                bgRectMarginHorizontal + spaceWidth * i,
                sliceHeight + sliceSpace + bgRectHeight,
                linePaint
            )
        
        //绘制数字刻度
        drawNumberScaleBar(canvas)


        //绘制指示器
        canvas.drawBitmap(
            bitmapIndicator,
            currentProgress - (bitmapIndicator.width / 2),
            sliceHeight + sliceSpace, null
        )


        //绘制top滑块
        canvas.drawBitmap(
            bitmapSlide,
            currentProgress - (bitmapSlide.width / 2),
            0f, bgSlidePaint
        )

        //绘制滑块中文字百分比
        val formatValue = String.format("%.$precisionf", progressPercent)
        val strPercentage = if (precision == 0) 
            "$formatValue.toInt() * changeStep$percentUnit"
         else 
            "$formatValue$percentUnit"
        
        val rectText = Rect()
        scaleTextPaint.getTextBounds(strPercentage, 0, strPercentage.length, rectText)
        canvas.drawText(
            strPercentage,
            currentProgress - rectText.width() / 2,
            sliceHeight / 2 + rectText.height() / 2,
            scaleTextPaint
        )
    


    /**
     * 绘制数字刻度
     */
    private fun drawNumberScaleBar(canvas: Canvas) 
        for (i in 0..number) 
            val scaleText = "$i * step * changeStep"
            when (i) 
                0 -> 
                    val rectText = Rect()
                    scaleTextPaint.getTextBounds(scaleText, 0, scaleText.length, rectText)
                    canvas.drawText(
                        scaleText,
                        bgRectMarginHorizontal + spaceWidth * i,
                        viewHeight,
                        scaleTextPaint
                    )
                
                number -> 
                    val rectText = Rect()
                    scaleTextPaint.getTextBounds(scaleText, 0, scaleText.length, rectText)
                    canvas.drawText(
                        scaleText,
                        bgRectMarginHorizontal + spaceWidth * i - rectText.width() / 2,
                        viewHeight,
                        scaleTextPaint
                    )
                
                else -> 
                    val rectText = Rect()
                    scaleTextPaint.getTextBounds(scaleText, 0, scaleText.length, rectText)
                    canvas.drawText(
                        scaleText,
                        bgRectMarginHorizontal + spaceWidth * i - rectText.width() / 2,
                        viewHeight,
                        scaleTextPaint
                    )
                
            
        
    


    override fun onTouchEvent(event: MotionEvent): Boolean 
        if (event.x < minValue) 
            setProgress(minValue)
            return true
         else if (event.x > maxValue) 
            setProgress(maxValue)
            return true
         else 
            when (event.action) 
                MotionEvent.ACTION_DOWN -> 
                    setProgress(event.x)
                    return true
                
                MotionEvent.ACTION_MOVE -> 
                    setProgress(event.x)
                    return true
                
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> 

                
                else -> 
                
            
        


        return super.onTouchEvent(event)
    


    /**
     * 计算触摸点的百分比
     *
     * @param eventX
     * @return
     */
    private fun calculateProgress(eventX: Float): Float 
        val proResult: Float =
            (eventX - bgRectMarginHorizontal) / (viewWidth - bgRectMarginHorizontal * 2)
        return proResult * maxScaleValue
    

    //滑块的当前进度坐标
    var currentProgress = 0f

    //进度百分比
    private var progressPercent: Float = 0f
    private fun setProgress(x: Float) 
        currentProgress = x
        val progress = calculateProgress(currentProgress)
        progressPercent = progress

        val formatValue = String.format("%.$precisionf", progress)
        progressChangeListener?.invoke(formatValue)
        invalidate()
        // println("ScaleBarView2.setProgress progress=$progress")
    

    var progressChangeListener: ((progress: String) -> Unit)? = null

    var currentPercentageProgress = 0f
    public fun setSlideProgress(percentageProgress: Float) 
        currentPercentageProgress = percentageProgress
        //百分比进度
        if (percentageProgress > maxScaleValue) 
            currentPercentageProgress = maxScaleValue.toFloat()
         else if (percentageProgress < 0) 
            currentPercentageProgress = 0f
        
        val currentProgress =
            currentPercentageProgress / maxScaleValue * (viewWidth - bgRectMarginHorizontal * 2) + bgRectMarginHorizontal
        setProgress(currentProgress)
    


    private fun bitmapScale(bm: Bitmap, scale: Float): Bitmap 
        val m = Matrix()
        m.setScale(scale, scale)
        val bmpSrc = Bitmap.createBitmap(bm, 0, 0, bm.width, bm.height, m, true)
        return bmpSrc
    


以上是关于自定义view-滑动进度条的主要内容,如果未能解决你的问题,请参考以下文章

自定义view-滑动进度条

Compose自定义条形进度条

Compose自定义条形进度条

Compose自定义条形进度条

自定义View实现钟摆效果进度条PendulumView

Android自定义View实现可拖拽的进度条