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

这次给大家带来的是一篇关于自定义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源码地址传送门


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • 本文主要内容为贝塞尔曲线原理解析并用 SurfaceView 实现其展示动画 关于SurfaceView 的使用,...
    涤生_Woo阅读 13,186评论 5 94
  • 有曰:大千世界无奇不有。众以为,这个“大千世界”经常仅被当作一个词汇用以形容广大无边的人世而已。即使穷尽我所有的想...
    雨儿rain阅读 1,057评论 3 2
  • 宝贝才十个半月,院子里有好几个小朋友大他有二三个月,但是在运动能力上确实我家宝宝要比他们高很多。 滑滑梯:几个月前...
    海豚的微笑阅读 274评论 0 0
  • 看窗外,暖阳静好。微风拂面,树枝轻摇,路边柳树发一层莹莹绿色,墙跟也有小草冒出嫩芽。春天说来就来了。再回头看屋内,...
    张枫霞阅读 2,031评论 0 0