kotlin版水波纹进度条

111 阅读1分钟

效果图:

video2.gif

代码:

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
import com.blankj.utilcode.util.ConvertUtils
import kotlin.math.min
import kotlin.math.roundToInt

class WaveView(context: Context, attributeSet: AttributeSet?,defStyleAttr : Int) : View(context, attributeSet,defStyleAttr) {
    constructor(context: Context) : this(context,null,0)
    constructor(context: Context, attributeSet: AttributeSet) : this(context,attributeSet,0)
    private var ringPaint = Paint()
    private var circlePaint = Paint()
    private var wavePaint = Paint()
    private var wavePath = Path()

    private lateinit var ringRectF : RectF
    private var circlePath = Path()

    /**
     * 波长
     */
    private var waveLength = 0
    private var offset = 0
    /**
     * 当前高度
     */
    private var curHeight = 0
    private var viewHeight = 0
    private var viewWidth = 0
    private var waveCount = 0
    /**
     * 波峰
     */
    private var waveCrest = 30

    init {
        ringPaint.color = Color.WHITE
        ringPaint.alpha = 50
        ringPaint.style = Paint.Style.STROKE
        ringPaint.isAntiAlias = true

        circlePaint.color = Color.WHITE
        circlePaint.style = Paint.Style.STROKE
        ringPaint.isAntiAlias = true

        wavePaint.strokeWidth = ConvertUtils.dp2px(1f).toFloat()
        wavePaint.color = Color.WHITE
        wavePaint.style = Paint.Style.FILL
        wavePaint.alpha = 50
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //var width = min(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec))
        val width = min(measure(widthMeasureSpec,true),measure(heightMeasureSpec,false))

        ringPaint.strokeWidth = (width / 20).toFloat()
        circlePaint.strokeWidth = ringPaint.strokeWidth / 8

        ringRectF = RectF(0f+ringPaint.strokeWidth/2,0f+ringPaint.strokeWidth/2,
            width.toFloat()-ringPaint.strokeWidth/2,width.toFloat()-ringPaint.strokeWidth/2)

        var circleRectF = RectF(0f+ringPaint.strokeWidth+circlePaint.strokeWidth/2,0f+ringPaint.strokeWidth+circlePaint.strokeWidth/2,
            width.toFloat()-ringPaint.strokeWidth-circlePaint.strokeWidth,width.toFloat()-ringPaint.strokeWidth-circlePaint.strokeWidth)
        circlePath.addOval(circleRectF,Path.Direction.CW)


        viewHeight = width
        viewWidth = width
        waveLength = (width * 3f).roundToInt()
        waveCrest = width / 12
        waveCount = (viewWidth / waveLength + 1.5).roundToInt()
        curHeight = viewHeight / 2

        //super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(width,width)
    }

    private fun measure( measureSpec :Int,  isWidth : Boolean) : Int{
        var result = 0
        val mode = MeasureSpec.getMode(measureSpec);
        val size = MeasureSpec.getSize(measureSpec);
        val padding = if(isWidth)  getPaddingLeft() + getPaddingRight()
        else getPaddingTop() + getPaddingBottom()
        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = if(isWidth) getSuggestedMinimumWidth()
            else getSuggestedMinimumHeight();
            result += padding;
            if (mode == MeasureSpec.AT_MOST) {
                if (isWidth) {
                    result = Math.max(result, size);
                } else {
                    result = Math.min(result, size);
                }
            }
        }
        return result
    }
    override fun onDraw(canvas: Canvas?) {
        canvas?.let{
            it.drawOval(ringRectF,ringPaint)
            it.drawPath(circlePath,circlePaint)

            wavePath.reset()
            wavePath.moveTo((-waveLength + offset).toFloat(), curHeight.toFloat());
            for (i in 0 until waveCount) {
                wavePath.quadTo(
                    ((-waveLength * 3 / 4) + (i * waveLength) + offset).toFloat(),
                    (curHeight + waveCrest).toFloat(),
                    ((-waveLength / 2) + (i * waveLength) + offset).toFloat(), curHeight.toFloat()
                )
                wavePath.quadTo(
                    ((-waveLength / 4) + (i * waveLength) + offset).toFloat(),
                    (curHeight - waveCrest).toFloat(),
                    (i * waveLength + offset).toFloat(), curHeight.toFloat()
                )
            }
            wavePath.lineTo(viewWidth.toFloat(), viewHeight.toFloat())
            wavePath.lineTo(0f, viewHeight.toFloat())
            wavePath.close()
            wavePath.op(circlePath,Path.Op.INTERSECT)

            it.drawPath(wavePath,wavePaint)

            startAnimal()
        }
    }
    
    fun setProgress(progress: Int){
        var pro = progress
        if (pro < 0) pro = 0
        if (pro > 100) pro = 100
        curHeight = viewHeight - viewHeight * pro / 100
    }

    /**
     * 动画是否开始
     */
    private var isStart = false
    @Synchronized
    private fun startAnimal(){
        if(isStart) return
        isStart = true
        val animator = ValueAnimator.ofInt(0,waveLength)
        animator.duration = 1000
        animator.repeatCount = ValueAnimator.INFINITE
        animator.interpolator = LinearInterpolator()
        animator.addUpdateListener {
            offset = it.animatedValue as Int
            postInvalidate()
        }
        animator.start()
    }
}