美文网首页Android 自定义view安卓View学习Android界面端
Android实用View------水波动画效果多种实现方式详

Android实用View------水波动画效果多种实现方式详

作者: lygttpod | 来源:发表于2017-06-20 17:54 被阅读2981次

    这次给大家带来的是一篇关于自定义View实现水波动画效果的文章,其实在去年项目中使用过类似的动画,当时就自定义View也实现了预期的效果,最近项目中又使用了相似的效果,于是对代码重新整理了一下并且记录下来,便于以后有类似需求可以当作参考!
    按照惯例,无图无真相

    image.png

    实现方式:
    1、正余弦函数实现
    2、贝塞尔曲线实现

    开篇

    看到上边的两种实现方式是不是感觉都和数学公式有关呐,这对于毕业多年之后的我们来说如果当初数学基础不是很好现在估计也全部还给老师了吧,所以一提到相关的数学计算公式只能用一个表情表达了。。。

    这TM都是什么.jpg

    1、正余弦函数实现

    正余弦的函数不知道大家还记不记得,我们温习一下相关参数的意义

    y=A*sin(ωx+φ)+k
     A—振幅越大,波形在y轴上最大与最小值的差值越大
     ω—角速度, 控制正弦周期(单位角度内震动的次数)
     φ—初相,反映在坐标系上则为图像的左右移动。这里通过不断改变φ,达到波浪移动效果
     k—偏距,反映在坐标系上则为图像的上移或下移。
    
    正余弦函数图.png

    我们要实现移动的波形首先是先画出静态的波形,那么怎么来绘制一个波形图呐,Math函数里已经提供了相应的方法,我们可以直接使用

    A* Math.sin(ω * x + φ ) + K)
    开始绘制之前首先定义相关画笔之类的参数,在此就不做过多说明了,根据上边的公式我们知道需要哪些参数,首先是A,这是振幅,就是波形最高和最低点的差值,我们可以设置定值或者外界传入;其次是ω,角速度,给一个定制或者外界传入;φ,相位,我们就是根据不断改变相位来达到波形移动的效果,每次移动多少可以从外界传入,便于控制速度;K,波形偏移上下的距离,知道了以上各个参数的具体使用意义,下边就可以直接通过代码看下具体实现效果了,毕竟公式都有了,参数也发给你了,剩下的就是根据公式填写以下相应参数就ok了

      private void drawSin(Canvas canvas) {
    
            φ -= 0.03;
            float y;
    
            path.reset();
            path.moveTo(0, getHeight());
    
            for (float x = 0; x <= getWidth(); x += 20) {
                y = (float) (A * Math.sin(ω * x + φ ) + K);
                path.lineTo(x, getHeight() - y);
            }
    
            canvas.drawPath(path, paint);
    
        }
    
    静态的波形图.png

    静态的波形出来之后我们就要借助属性动画来让波形动起来

        private void initAnimation() {
            valueAnimator = ValueAnimator.ofInt(0, getWidth());
            valueAnimator.setDuration(1000);
            valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            valueAnimator.setInterpolator(new LinearInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    /**
                     * 刷新页面调取onDraw方法,通过变更φ 达到移动效果
                     */
                    invalidate();
                }
            });
            if (waveStart) {
                valueAnimator.start();
            }
        }
    

    开启动画之后再运行一下看看效果吧

    gif录制不流畅,真机效果很好哦.gif

    看到这里只是一个单纯的波形,我们一般使用的时候并不是这样的,而是一个封闭的波形,可以向上封闭也可以向下封闭,我们在波形绘制完成之后

    .........省略部分代码
    //填充矩形
            path.lineTo(getWidth(), getHeight());
            path.lineTo(0, getHeight());
            path.close();
    

    这样就绘制出封闭的波形了,然后画笔设成填充就ok

    image.png

    代码中我已经对向下密封还是向上密封封装了方法,在此就不再赘述,需要的可以看源码哦,除此之外还有其他的参数都进行了可配置话,可以通过xml进行设置,至此通过正余弦函数进行绘制波形图已经介绍完毕了。

    2、贝塞尔曲线实现

    对贝塞尔曲线不是很了解的可以自行百度,概念性的东东就不在此赘述,我们使用二阶的贝塞尔进行绘制,为什么选择二阶的呐,看一个图就知道啦

    二阶贝塞尔曲线.png 二阶贝塞尔曲线.gif

    一段完整的波形其实就是两个二阶的贝塞尔组成的,来看下代码

        /**
         * sin函数图像的波形
         *
         * @param canvas
         */
        private void drawSinPath(Canvas canvas) {
            mWavePath.reset();
    
            mWavePath.moveTo(-mWaveLength + mOffset, mWaveAmplitude);
    
             //相信很多人会疑惑为什么控制点的纵坐标是以下值,
             //是根据公式计算出来的,具体计算方法情况文章内容
    
            for (int i = 0; i < mWaveCount; i++) {
    
                //第一个控制点的坐标为(-mWaveLength * 3 / 4,-mWaveAmplitude)
                mWavePath.quadTo(-mWaveLength * 3 / 4 + mOffset + i * mWaveLength,
                        -mWaveAmplitude,
                        -mWaveLength / 2 + mOffset + i * mWaveLength,
                        mWaveAmplitude);
    
                //第二个控制点的坐标为(-mWaveLength / 4,3 * mWaveAmplitude)
                mWavePath.quadTo(-mWaveLength / 4 + mOffset + i * mWaveLength,
                        3 * mWaveAmplitude,
                        mOffset + i * mWaveLength,
                        mWaveAmplitude);
            }
    
            mWavePath.lineTo(getWidth(), getHeight());
            mWavePath.lineTo(0, getHeight());
            mWavePath.close();
    
            canvas.drawPath(mWavePath, mWavePaint);
        }
    
    当时的手绘内容,可能有点乱,只是大致描述一下.png 计算控制点纵坐标的方式.png

    根据计算得到起点和控制点坐标之后就可以写代码运行了效果和上边的运行效果一样就不再展示了,上边的计算内容就解释了代码提出的问题

    代码中提出的问题.png

    3、两种方式对比总结

    图像的绘制其实都不复杂,不过关键点还是有几个的。
    正余弦函数的波形使用是根据相位控制的,而贝塞尔曲线实现的波形效果是不断改变波的起始位置控制的,并且使用贝塞尔曲线的话需要先在屏幕外边绘制一个完整的波形,保证在平移的过程中可以看到图像不间断的移动来达到移动的波形效果。

    最终效果--录制模拟感觉有点卡顿,请真机测试哦.gif

    最后

    看到这里你是你会感觉到这边文章的内容其实很简单,只要中间的几个点注意一下就可以实现相应的效果了,建议朋友们动手敲一遍代码,加深一下印象,毕竟真是做出来和知道理论没有实践还是有很大区别的!


    github源码地址传送门


    谨以此篇来记录自己项目中遇到的问题,献给需要类似功能的小伙伴们。如果你有好的建议欢迎评论指出,大家一起讨论、学习、进步!

    相关文章

      网友评论

      • 闪电代码手:么么哒么么哒,正好要用,谢谢分享
      • 性冷淡_:mark 一波
      • 76ffef5441ab:omsure.onsizange有何区别吗,为什么有的人在sizechange获得宽高
        lygttpod: @会编程的_宁采臣 onsizechange在onmesure之后调用,都是可以设置宽高的
      • da1b76f3ca12:楼主你好!本人菜鸟,想请问源码当中的 构造方法中给valueanimation设置了监听事件,里面用了invalidate(),根据我学的知识范围内,invalidate会触发ondraw,而view的生命周期应该是构造方法--onFinishFlate---onAttachToWindow---onMeasure---onLayout---onDraw---onDetachFromWindow,我就想问下直接在构造方法invalidate(),后面会怎么执行?
        da1b76f3ca12:@lygttpod 谢谢楼主!受教了
        lygttpod: @崔永昌 这位兄弟基础很扎实哦,这些方法有的我都记不住,哈哈!下边解决你的疑惑:在构造方法里只是初始化动画,在动画里边调用invalidate方法,这个方法确实是会调onDraw方法的,但是有一点你可能没注意到,在构造方法中只是初始化动画,并没有启动动画,所以构造方法中并没有走invalidate方法,只有启动动画的时候才会走invalidate方法重绘页面
        da1b76f3ca12:在onMeasure后面漏了onSizeChange
      • da1b76f3ca12:楼主你好!我看了下源码,那个startperiod变量是什么呢?注释是开始位置相差多少个周期
        lygttpod: @崔永昌 就是波形绘制的起点是从一个完整波得哪个位置开始的
      • 采姑娘的小蘑菇_fa4f:学霸,受我一拜
      • dcdc87be3c57:上面的数学符号是怎么弄得,,我打出来了报错呢!!!比如∮
      • 澜秋:大佬,无法表达我的心情,看了好多自定义View系列的文章,几乎所有数学计算的部分都是一笔带过,只有你详细的讲了讲,不说别的了,一定要坚持这种写作风格,你知道那种想学却看不懂的心情是多么的痛苦! :joy: 谢谢LZ, 谢谢LZ 谢谢LZ ! 重要的事情说三遍!
        lygttpod: @澜秋 非常感谢对我的肯定😄,会继续努力的!
      • 爱编程的八戒:我爱你:yum:
        lygttpod: @爱编程的八戒 😀😀😀

      本文标题:Android实用View------水波动画效果多种实现方式详

      本文链接:https://www.haomeiwen.com/subject/kzagqxtx.html