1. 概述

最近开始学习自定义View,看到现在公司项目上的一个动画效果,顿时想到其实可以自己画,于是就开始着手优(zhuang)化(bi)这个动画。

动画如下:

其实很简单对不对,但初学者的我还是要思考一下。

2. 动画分解

动画有两部分,

  • 第一个是背景,这个直接画bitmap就可以了。
  • 第二个就是这个波浪,我们仔细观察这个波浪其实是一个有规律的,基于每一个点的原点Y轴方向的不断拉伸。 但是,每一个点,其拉伸的量不一致,而且时间也有差错。

根据UI提供的详细动画细节,可以知道:

  • 动效总共时间为2S,之后反复循环,每秒帧数为24帧,其中圆形元素控件大小为6px,拉伸都是以圆心为拉伸中心点进行拉伸。 0-1s为从左到右的拉伸动画,1s-2s为从右到左的拉伸动画,之后为循环。

  • 每一个点,都有5种不同的拉伸量,这里我们把对应的拉伸后的Y的高度命名为:

public float maxHeight;
public float threeHeight;
public float halfHeight;
public float oneHeight;
复制代码

其中还有一种拉伸量为0。maxHeight是最大的高度,threeHeight为次最高高度。

列出几个关键帧(约定,从左到右将元素命名为元素1、元素2、元素3、元素4、元素5)
  • 第1帧: 初始化,每一个的点均为 6 px * 6 px

  • 第2帧: 元素1 高7.3px 对应 oneHeight,其他元素保持初始状态。

  • 第3帧: 元素1 高11px 对应 halfHeight,其他元素保持初始状态

  • 第4帧: 元素1 高14.7px 对应 threeHeight,元素2 高8.3px 对应 oneHeight,其他元素保持初始状态。

  • 第5帧: 元素1 高16px 对应 maxHeight,元素2 高15px 对应 halfHeight,其他元素保持初始状态。

  • 第6帧: 元素1 高14.7px 对应 threeHeight,元素2 高21.7px 对应 threeHeight,元素3 高10.1px 对应 oneHeight,其他元素保持初始状态。

... 通过观察,我...先定义一个类,来存储这些点的四种拉伸量的高度(我们也称之为状态)和中心位置的坐标:

public class VoiceAnimPoint {public int centerX, centerY;public float maxHeight;public float threeHeight;public float halfHeight;public float oneHeight;public VoiceAnimPoint(int centerX, int centerY, float maxHeight, float threeHeight, float halfHeight, float oneHeight) {this.centerX = centerX;this.centerY = centerY;this.maxHeight = maxHeight;this.threeHeight = threeHeight;this.halfHeight = halfHeight;this.oneHeight = oneHeight;}
}
复制代码

然后在 onSizeChanged 中初始化这五个点:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);points = new VoiceAnimPoint[5];points[0] = new VoiceAnimPoint(getWidth()/2-24,getHeight()/2,16f,14.7f,11f,7.3f);points[1] = new VoiceAnimPoint(getWidth()/2-12,getHeight()/2,24f,21.7f,15f, 8.3f);points[2] = new VoiceAnimPoint(getWidth()/2,getHeight()/2,38f,33.9f,22f,10.1f);points[3] = new VoiceAnimPoint(getWidth()/2+12,getHeight()/2,24f,21.7f,15f,8.3f);points[4] = new VoiceAnimPoint(getWidth()/2+24,getHeight()/2,16f,14.7f,11f,7.3f);
}
复制代码

3. 波浪动效实现

对于元素1,我们看到有这样的一个变化过程:

那么我们可以构造这样的一个函数: x暂时可以看成是第几帧,x=0,第1帧时候,y=0,代表原始状态,x=1,第2帧的时候,y=1,代表元素1 高7.3px 对应 oneHeight,y=2,代表元素1 高11px 对应 halfHeight,y=3,代表元素1 高11px 对应 threeHeight,y=4,代表元素1 高16px 对应 maxHeight,,,

那么其他的元素呢,我们把他们的变化过程放在一起:

发现,每一个元素的变化都是一样的,只不过是x轴的位移,而相邻的元素的x相差2,也就是假如 元素1在 oneHeight,x=8 状态的时候,对应的 相邻元素2 就是元素1在x-2=6的时候的状态,也就是 元素2的 threeHeight 状态,而,元素2相邻的元素3就是在元素1的x-2-2=4的时候的状态,为 元素3的 threeHeight,,,如此类推。所以我们只用元素1当前的位置和上面的函数图,就可以推断出其他元素的情况。

那么onDraw中可以这样来:pointIndex 代表着元素1绘制的第 N 帧,然后依次按照如上所分析的去得到其他元素的对应帧。

@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);for (int i = 0; i < points.length; i++) {VoiceAnimPoint point = points[i];int y = indexChangeFunc(pointIndex - i*2);switch (y) {case 0:canvas.drawLine(point.centerX,point.centerY, point.centerX,point.centerY+0.01f,paint);break;case 2:canvas.drawLine(point.centerX,point.centerY-point.halfHeight/2+pointWidth/2,point.centerX,point.centerY+point.halfHeight/2-pointWidth/2,paint);break;case 4:canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);break;case 1:canvas.drawLine(point.centerX,point.centerY-point.oneHeight/2+pointWidth/2,point.centerX,point.centerY+point.oneHeight/2-pointWidth/2,paint);break;case 3:canvas.drawLine(point.centerX,point.centerY-point.threeHeight/2+pointWidth/2,point.centerX,point.centerY+point.threeHeight/2-pointWidth/2,paint);break;}}
}
复制代码

其中的0-4状态就是上面的函数的Y值,通过X得到相应的Y,而Y则对应则元素的5中状态,onDraw中就是根据5中状态去绘制相应的高度:

/*** 动画轨迹其实符合一个函数* 这里传入对应的x,返回函数的y* @param x 位置* @return y 4 : 最大, 3:threeHeight, 2: 一半, 1:oneHeight, 0 :0 。*/
private int indexChangeFunc(int x) {if (x<0)return 0;else if (x<4)return x;else if (x<8)return -x + 8;elsereturn 0;
}
复制代码

4. 几个关键的地方

  • 这里的波浪条是用drawLine画,而且上下端是圆角的,所以我们要设置Paint的线帽为圆形,
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xffC2E379);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(pointWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
复制代码

注意的一点,假如你的Paint宽10px,而同时你又设置了线帽为圆形,画了20px的line,那其实你画的如下:

所以才有上面的onDraw中的,高度要减去线帽:

canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
复制代码
  • 最后还有这个动效要倒叙播放,那么我们可以让 pointIndex 自增代表正序播放,pointIndex 自减代表倒叙播放。
@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawBitmap(bgBitmap, getWidth() / 2 - bgBitmap.getWidth() / 2, getHeight() / 2 - bgBitmap.getHeight() / 2, paint);for (int i = 0; i < points.length; i++) {VoiceAnimPoint point = points[i];int y = indexChangeFunc(pointIndex - i*2);switch (y) {case 0:canvas.drawLine(point.centerX,point.centerY, point.centerX,point.centerY+0.01f,paint);break;case 2:canvas.drawLine(point.centerX,point.centerY-point.halfHeight/2+pointWidth/2,point.centerX,point.centerY+point.halfHeight/2-pointWidth/2,paint);break;case 4:canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);break;case 1:canvas.drawLine(point.centerX,point.centerY-point.oneHeight/2+pointWidth/2,point.centerX,point.centerY+point.oneHeight/2-pointWidth/2,paint);break;case 3:canvas.drawLine(point.centerX,point.centerY-point.threeHeight/2+pointWidth/2,point.centerX,point.centerY+point.threeHeight/2-pointWidth/2,paint);break;}}if (!isRevert) {pointIndex++;}else {pointIndex--;}if (pointIndex == 23) {isRevert = true;pointIndex = 17;}else if (pointIndex == -6) {pointIndex = 0;isRevert = false;}
}
复制代码
  • 暴露开启动画,暂停动画的接口,根据动效的时间描述,我们应该每1000/24=42毫秒去重新绘制,也就是调用:invalidate()方法。
private Runnable r = new Runnable() {@Overridepublic void run() {VoiceAnimView.this.invalidate();VoiceAnimView.this.postDelayed(r, 42);}
};
public void startAnim() {if (!isStart) {isStart = true;this.post(r);}
}
public void stopAnim() {if (isStart) {isStart = false;this.removeCallbacks(r);}
}
复制代码
  • 对比原来的动画 原来的是用lottie直接去实现,可谓方便快捷:

加载动画后,大概用了1M多,每一论的波浪,都吃15%cpu。

用了自定义动画后:

加载动画后,大概用了不到1M,每一论的波浪,都吃不到5%cpu。

5. 最后

这算是自己的第一个自定义View,可能实现思路上有问题,或者大家有更好的思路,欢迎一起讨论! 还有几个问题:

  • 一般测试动画性能是怎么去测试的?上面的测试我觉得有些粗糙
  • 关于优化自定义的动画性能,也就是那几点,其中有一点,说的是减少调用invalidate()的无参数方法,使用有参数的方法,但我查了一下官方文档,在API21后,有参数的方法就不管用了。还有什么别的方法优化自己的自定义动画?

源代码在此

自定义View-波浪动效相关推荐

  1. Android自定义View高级动效之---安卓流星雨动效|Android流星雨专辑封面

    篇章目标要点 最近看到酷我音乐App出了一则<穹顶流星>动效,看完之后决定自己尝试一下实现,本文将围绕通过自定义View实现流星雨效果,可以看到流星雨环绕专辑图的高级动效.通过完成这项开发 ...

  2. Android自定义View高级动效---粒子动效实现|音乐播放器粒子动效|实现酷我网易云粒子动效

    篇章目标介绍 之前看到网易云,酷我音乐都发布过用于播放器页面粒子动效的效果,于是打算自己也动手做一个,产品目标是对标酷我手机app的动效设计,实现过程完全基于自身的推测理解予以实现.计划通过两次完全完 ...

  3. Android开发之自定义SurfaceView绘制动效音波图 | 动效音阶图 | Android自定义View

    老套路献上图: 第一张是通过播放歌曲拿到歌曲播放的数据进行动态展示的 第二张是通过定时器随机生成的数据动态展示的 先说下这个自定义view也不难很简单,就是绘制矩形,唯一的难点在于计算矩形的坐标 说下 ...

  4. android根据滑动字体颜色被填充,自定义View:02-滑动变色的字体

    效果图如下: 滑动文字.gif 一.自定义属性: 1.1.字体要变的颜色 1.2.字体不变的颜色 二.继承TextView 2.1.初始化画笔 两个字体画笔 :变色与不变色 2.2.onDraw() ...

  5. 干货分享|纯CSS绘制电池充电水波纹动效(uni-app|view组件版)

    前言 依托于CSS3提供的强大功能,我们可以充分发挥自己的想象力,制作出许多非常惊艳的动效,比如:接下来我要跟大家分享的一个完全用CSS绘制出来的电池充电水波纹动效,还是老规矩,小凡我依然分享的是un ...

  6. 干货分享|巧用CSS滤镜绘制安卓手机充电动效(uni-app|view组件版)

    前言 CSS3的滤镜真的是一个非常强大的功能,如果我们能够很好的利用它,并充分发挥我们的想象力,想要制作出非常惊艳的动效也是没有问题的哦.比如:接下来我要跟大家分享的一个巧妙使用CSS滤镜绘制出来的a ...

  7. android录音波浪动画_Android自定义View实现波浪动画

    本文实例为大家分享了Android自定义View实现波浪动画的具体代码,供大家参考,具体内容如下 效果演示 代码调用与实现效果 xml中调用 android:layout_width="ma ...

  8. android录音波浪动画_Android 自定义 view 实现波浪动画进度条

    最近在做项目时需要实现这样一种动画,类似于波浪形的进度动画,粗略的看了一下,发现好像类似于正余弦曲线实现的,但是Android 没有相关的API,所以需要我们动手画出来,所以现在在此记录一下学习过程, ...

  9. 自定义View、画波浪线

    三角函数公式:y = A sin(ωx + φ) + k A代表振幅,就是最大值减去最小值的一半. ω代表(角速度)收缩或伸长,这个只要记住:周期T=2*pi/w就可以了 φ代表在X上的平移,左移为正 ...

最新文章

  1. 宏基因组合种树第292期—侧柏、樟子松,为祖国绿化做贡献
  2. php实现适配器模式(转)
  3. 一般将来时语法课教案_【语法视频课】第43~45节(虚拟语气)
  4. erlang精要(19)-以函数作为参数的函数,返回函数的函数(2)
  5. BFS——广度优先算法(Breadth First Search)
  6. php大负荷,web大负载优化收集------php-fpm参数优化
  7. 使用一下SQL Server 2008中的新日期函数
  8. Python Web笔记之高性能网络编程
  9. 常用 Dos 命令+杂项-常用的命令符+常用的公式
  10. 性能优化–查找和解决僵尸对象
  11. Vs2010中水晶报表引用及打包
  12. 正则表达式--简单记忆一
  13. [原创]Ladon7.5大型内网渗透扫描器Cobalt Strike
  14. MISRA C 2004
  15. vim-airline use patched fonts
  16. html5课程总结500字,体育课心得体会500字(精选6篇)
  17. POJ 2856 Y2K Accounting Bug【简单暴力】
  18. FM模型及其在推荐系统中的应用
  19. 游戏后台搭建(基于cocoscreator+nodejs+linux-阿里云)
  20. 千锋Unity学习笔记

热门文章

  1. 2022最新网络安全零基础学习路线
  2. 上海交大开源GPGPU青花瓷仿真环境搭建
  3. HTTP状态码之500、501、502、503、504、505
  4. DELPHI BULK INSERT
  5. 微信公众平台的运营管理
  6. 敏捷项目用户故事地图
  7. 【机器学习】评价指标PSI
  8. Cobalt Strike详细使用教程
  9. gunicorn 安装部署详解
  10. Server-Sent Events 一种轻量级的Push方式