Android——贝塞尔曲线的水波浪效果实现

使用贝塞尔曲线实现的水波浪效果,在很多杀毒软件的进度条显示中都有应用:

这个效果呢以前在有一个项目中需要实现过,当时是使用的正弦曲线做的效果,不过后来发现贝塞尔曲线也可以做出相同的效果,并且代码更为优雅。

那么对于初学者来说呢,首先需要了解什么是贝塞尔曲线,网上有很多教程,这里我找到了比较容易看懂的博客:

贝塞尔曲线原理(实现图真漂亮)

我们常用的是二阶贝塞尔曲线,其推导公式为:

但是在Android中,Path类提供了qudto方法去绘制一段贝塞尔曲线,因此,只要知道3个点就可以绘制出一段贝塞尔曲线,只需要6个点就可以模拟出正弦波浪曲线。

实现思路

1.首先大体上需要绘制两段横版S型曲线,然后使用动画效果不断左平移来实现波浪的滑动效果。

2.每一段S型曲线需要确定5个点的位置:

其中对应两段贝塞尔曲线,即P1-P3,P3-P5

3.绘制过程为首先new一个path,将path移动到P1点,然后计算P2,P3的坐标,通过path的rQuadTo(float dx1, float dy1, float dx2, float dy2)方法绘制出P1-P3的贝塞尔曲线,以此类推绘制P3-P5以及之后的另外两条贝塞尔曲线。

实现代码


class WaveView : View {

    private var color: Int
    private var paint: Paint
    private var path: Path
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    private var cX: Float = 0f
    private var cY: Float = 0f
    private var xOffset = 0f

    private var progress = 50
    private val clipPath: Path
    private var left: Float = 0f
    private var top: Float = 0f
    private var right: Float = 0f
    private var bottom: Float = 0f
    private var xOffsetAnimator: ValueAnimator? = null


    fun startAnima() {
        if (xOffsetAnimator == null) {
            xOffsetAnimator = ValueAnimator.ofInt(0, width)
            xOffsetAnimator?.addUpdateListener { animation ->
                val value = animation.animatedValue as Int
                xOffset = (-value).toFloat()
                postInvalidate()
            }
            xOffsetAnimator?.interpolator = LinearInterpolator()
            xOffsetAnimator?.duration = 1500
            xOffsetAnimator?.repeatCount = ValueAnimator.INFINITE
        }
        LogUtils.d("开始动画")
        xOffsetAnimator?.start()
    }


    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        val array = context?.obtainStyledAttributes(attrs, R.styleable.WaveView)
        this.color = array!!.getColor(R.styleable.WaveView_WaveView_color, Color.BLUE)
        paint = Paint()
        path = Path()
        paint.color = this.color;
        clipPath = Path()
        array.recycle()

    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        val dx = mWidth / 4f
        val dy = mHeight / 8f
        val yOffset = (100 - progress) / 100f * height
        val p1 = floatArrayOf(xOffset, yOffset)
        val p2 = floatArrayOf(dx, -dy)
        val p3 = floatArrayOf(2 * dx, 0f)
        val p4 = floatArrayOf(dx, dy)
        val p5 = floatArrayOf(2 * dx, 0f)
        val p6 = floatArrayOf(dx, -dy)
        val p7 = floatArrayOf(2 * dx, 0f)
        val p8 = floatArrayOf(dx, dy)
        val p9 = floatArrayOf(2 * dx, 0f)

        paint.style = Paint.Style.FILL

        path.reset()
        path.moveTo(p1[0], p1[1])
        path.rQuadTo(p2[0], p2[1], p3[0], p3[1])
        path.rQuadTo(p4[0], p4[1], p5[0], p5[1])
        path.rQuadTo(p6[0], p6[1], p7[0], p7[1])
        path.rQuadTo(p8[0], p8[1], p9[0], p9[1])

        path.lineTo(width.toFloat(), height.toFloat())
        path.lineTo(0f, height.toFloat())
        path.close()

        clipPath.reset()
        left = 0f
        top = 0f
        right = mWidth.toFloat()
        bottom = height.toFloat()
        clipPath.addArc(left, top, right, bottom, 0f, 360f)

        // canvas!!.clipPath(clipPath)
        canvas!!.drawPath(path, paint)

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = measuredWidth
        mHeight = measuredHeight
        cX = (mWidth / 2).toFloat()
        cY = (mHeight / 2).toFloat()

    }
}

Github项目地址:

https://github.com/jiangzhengnan/UI

最近会把实现的UI效果都集中到一个库里面,求start~

发布了70 篇原创文章 · 获赞 75 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/qq_22770457/article/details/90139954