实现一个水波纹上移效果

735 阅读4分钟

想要效果需求如下:

1.实现水波纹水平滚动效果。

2.实现多组水波纹。

3.实现水波纹上移效果。

WechatIMG179.jpg

实现上述功能需要用到的技能点:

1.通过 Canvas 绘制贝塞尔曲线2阶

2.通过属性动画实现 起始点位置偏移量;

想法:

1.首先通过自定义 view 绘制出一个静态的水波纹图像。

2.然后实现静态水波纹的平移效果;

3.实现两组静态水波纹平移效果;

4.通过色彩组实现 n 组水波纹平移效果;

需要了解绘制波的起始点、中点、终点如何设置:

1.起始点 x轴:-waveWidth * i (从屏幕外一个波宽进行绘制,避免移动的时候出现没有布局的情况) i 属于第几个波;

2.中点 x轴:上波 waveWidth/4 + 起始点 (属于起始点 +1/4波宽), 下波 起始点+waveWidth*(3/4) (属于起始点 +3/4波宽)

  1. 终点 x轴: 起始点+waveWidth;
  2. 基准线:属于水波纹的y轴中线,有了基准线,后面绘制就有了参考点。
  3. 起始点 y 轴: 基准线的值;针对水波纹上移动 ,也是通过更改基准线的值进行实现
  4. 中点 y轴:上波 基准线值 - waveHeight / 2 (减 的原因属于 坐标原点在手机左上角,向下是 y轴增加值)下波 基准线值 +waveHeight / 2
  5. 终点 y轴: 基准线值;

后续需要设置的值:

1.水波纹宽度 waveWidth ,waveHeight, paint, path

2.后续需要设置的值,offsetX,offsetY 属于每个水波纹的x,y 轴偏移量

绘制水波纹静态效果:

    private fun drawWave(canvas: Canvas, color: Int, baseLineY: Float) {
        path.reset()

        canvas.save()
        val startPointX = -waveWidth;
        path.moveTo(
            startPointX,
            baseLineY
        )
        //判断能够绘制多少个波
        for (start: Int in -waveWidth.toInt() until (width + waveWidth).toInt() step waveWidth.toInt()) {
            val startX = start //每个波的起始点
            path.quadTo(
                (startX + waveWidth / 4.0f),
                baseLineY - waveHeight / 2.0f,
                (startX + waveWidth / 2.0f),
                baseLineY
            )

            //下波
            path.quadTo(
                (startX + waveWidth * (3 / 4.0f)),
                baseLineY + waveHeight / 2.0f,
                (startX + waveWidth),
                baseLineY
            )
        }
        path.lineTo(width.toFloat(), height.toFloat())
        path.lineTo(0f, height.toFloat())
        path.close()
        paint.setColor(color)
        canvas.drawPath(path, paint)
        canvas.restore()
    }

实现动画效果:

1.如何实现水波纹的水平移动效果呢?其实就是通过更改起始点位置来实现水波纹的平移效果。

代码如下:

    private var anim: ValueAnimator? = null

    @SuppressLint("Recycle")
    private fun startAnim() {
        if (anim != null && anim?.isStarted == true) {
            return
        }
        anim = ValueAnimator.ofInt(0, waveWidth.toInt())
        anim?.repeatCount = ValueAnimator.INFINITE
        anim?.interpolator = LinearInterpolator()
        anim?.duration = 1000
        anim?.addUpdateListener {
            dx = it.animatedValue as Int
            dy += 0.5f
            invalidate()
        }
        anim?.start()
    }

同时需要更改起始点的位置信息如下:

val startPointX = -waveWidth + dx //dx这个地方是每次0-waveWidth 的值,用于实现起始点 -waveWidth -0 位置的移动。

val startX = start + dx //是实现每个波位置都移动相同的位置。实现整个 view 波纹的移动。

根据色值绘制多个波纹:

     private val colors = arrayOf(
        context.resources.getColor(R.color.purple_200),
        context.resources.getColor(R.color.f0purple_200),
        context.resources.getColor(R.color.a0purple_200)
    )

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

//        val baseLineY = height - waveHeight / 2.0f - dy
//        if (baseLineY <= -waveHeight) {
            //如果滚动到了顶部就移除动画。
//            closeAnim()
//      }

//        遍历绘制
        colors.forEachIndexed { index, color ->
            drawWave(
                canvas,
                dx - offsetX * index,
                color, baseLineY - offsetY * index
            )
        }
    }

实现水波纹上移效果是在动画中 的dy 在每次指定动画时就自动增加值。同时基准线减去 dy 来实现上移效果(baseLineY - offsetY);

总的代码如下:

class WaveView @JvmOverloads constructor(
    context: Context,
    attributeSet: AttributeSet? = null,
    def: Int
) : View(context, attributeSet, def) {
    private val waveWidth = 80f.dp()
    private val waveHeight = 40.dp()

    //定义绘制的线条以及设置
    private var paint: Paint = Paint()
    private val path = Path()

    private var dx = 0
    private var dy = 0f

    private val offsetX = 25.dp()//属于水波纹的偏移量
    private val offsetY = 5.dp()

    //创建色值的集合
    private val colors = arrayOf(
        context.resources.getColor(R.color.purple_200),
        context.resources.getColor(R.color.f0purple_200),
        context.resources.getColor(R.color.a0purple_200)
    )

    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0) {
        //设置画笔的宽度和颜色
        paint.setColor(context.resources.getColor(R.color.purple_200))
        paint.isAntiAlias = true
        paint.setTypeface(Typeface.DEFAULT)
        paint.style = Paint.Style.FILL
        startAnim()
    }

    private var anim: ValueAnimator? = null

    @SuppressLint("Recycle")
    private fun startAnim() {
        if (anim != null && anim?.isStarted == true) {
            return
        }
        anim = ValueAnimator.ofInt(0, waveWidth.toInt())
        anim?.repeatCount = ValueAnimator.INFINITE
        anim?.interpolator = LinearInterpolator()
        anim?.duration = 1000
        anim?.addUpdateListener {
            dx = it.animatedValue as Int
            dy += 0.5f
            invalidate()
        }
        anim?.start()
    }


    //    绘制水波纹
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //设置绘制的起始点
        //height - waveHeight / 2.0f :属于在view 最底部 waveHeight / 2.0f处
        // - dy 属于根据 dy 值的增加逐渐的更改基准线的位置,属于 将基准线向 y 轴 原点处偏移;

        val baseLineY = height - waveHeight / 2.0f - dy
        if (baseLineY <= -waveHeight) {
            //如果滚动到了顶部就移除动画。
            closeAnim()
        }

        /*   drawWave(
               canvas,
               dx,
               context.resources.getColor(R.color.f0purple_200), baseLineY
           )

           drawWave(
               canvas,
               dx - offsetX,
               context.resources.getColor(R.color.purple_200),
               baseLineY - offsetY
           )*/

//        遍历绘制
        colors.forEachIndexed { index, color ->
            drawWave(
                canvas,
                dx - offsetX * index,
                color, baseLineY - offsetY * index
            )
        }
    }

    private fun drawWave(canvas: Canvas, offset: Int, color: Int, baseLineY: Float) {
        path.reset()

        canvas.save()
        val startPointX = -waveWidth + offset;
        path.moveTo(
            startPointX,
            baseLineY
        )
        //判断能够绘制多少个波
        for (start: Int in -waveWidth.toInt() until (width + waveWidth).toInt() step waveWidth.toInt()) {
            val startX = start + offset
            path.quadTo(
                (startX + waveWidth / 4.0f),
                baseLineY - waveHeight / 2.0f,
                (startX + waveWidth / 2.0f),
                baseLineY
            )

            //下波
            path.quadTo(
                (startX + waveWidth * (3 / 4.0f)),
                baseLineY + waveHeight / 2.0f,
                (startX + waveWidth),
                baseLineY
            )
        }
        path.lineTo(width.toFloat(), height.toFloat())
        path.lineTo(0f, height.toFloat())
        path.close()
        paint.setColor(color)
        canvas.drawPath(path, paint)
        canvas.restore()
    }

    private fun closeAnim() {
        anim?.cancel()
        anim = null
    }

}