Skip to content

Commit b29a08c

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents e96d29f + 2964c23 commit b29a08c

File tree

2 files changed

+87
-20
lines changed

2 files changed

+87
-20
lines changed

README.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,75 @@
11
# WaveLineView
2-
a beatiful wave line animation
2+
## 一款内存友好的录音漂亮的波浪动画
3+
4+
# 效果图(实际效果更好)
5+
6+
![image](https://github.com/Jay-Goo/WaveLineView/blob/master/pictures/%E6%95%88%E6%9E%9C.gif)
7+
8+
----------
9+
10+
# Usage
11+
## Step1
12+
```
13+
allprojects {
14+
repositories {
15+
...
16+
maven { url 'https://jitpack.io' }
17+
}
18+
}
19+
20+
dependencies {
21+
compile 'com.github.Jay-Goo:WaveLineView:v1.0.2'
22+
}
23+
```
24+
## Step2
25+
26+
```
27+
<jaygoo.widget.wlv.WaveLineView
28+
android:id="@+id/waveLineView"
29+
android:layout_width="match_parent"
30+
android:layout_height="120dp"
31+
app:wlvBackgroundColor="@android:color/white"
32+
app:wlvMoveSpeed="290"
33+
/>
34+
```
35+
## Step3
36+
37+
```
38+
waveLineView.startAnim();
39+
40+
waveLineView.stopAnim();
41+
```
42+
43+
```
44+
@Override
45+
protected void onResume() {
46+
super.onResume();
47+
waveLineView.onResume();
48+
}
49+
50+
@Override
51+
protected void onPause() {
52+
super.onPause();
53+
waveLineView.onPause();
54+
}
55+
56+
@Override
57+
protected void onDestroy() {
58+
super.onDestroy();
59+
waveLineView.release();
60+
}
61+
```
62+
63+
----------
64+
# Attributes
65+
attr | format | description
66+
-------- | ---|---
67+
backgroundColor|color|背景色
68+
wlvLineColor|color|波浪线的颜色
69+
wlvThickLineWidth|dimension|中间粗波浪曲线的宽度
70+
wlvFineLineWidth|dimension|三条细波浪曲线的宽度
71+
wlvMoveSpeed|float|波浪线移动的速度,默认值为290F,方向从左向右,你可以使用负数改变移动方向
72+
wlvSamplingSize|integer|采样率,动画效果越大越精细,默认64
73+
wlvSensibility|integer|灵敏度,范围[1,10],越大越灵敏,默认值为5
74+
75+
## [原理讲解传送门](https://github.com/Jay-Goo/WaveLineView/blob/master/blog.md)

blog.md

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
![这里写图片描述](https://github.com/Jay-Goo/WaveLineView/blob/master/pictures/logo.jpg)
22

33

4-
看到咱们智课技术订阅号推送了好几遍精彩的技术分享了,写的都非常好,不过还没有移动端的文章,所以今天我这里也总结了一下我最近在做的一些东西,希望能抛砖引玉,吸引我们优秀的移动端小伙伴投稿。
5-
4+
# 前言
65
本文实战性较强,主要目的是通过一个自定义控件的开发,引出我对自定义控件性能优化的一些思考和实践,欢迎各位喜欢移动开发的小伙伴来拍砖~
76

87
本文由于篇幅有限,只讲解思路,并没有放出大量源代码,如果对本项目感兴趣,文末会放出Demo,可以自行去Github上fork和star。
@@ -26,7 +25,7 @@
2625

2726
整个绘制过程通过一个Choreographer定时器驱动调用更新,每16ms会刷新一次,通过树状结构存储的 ViewGroup,依次递归的调用到每个 View 的 onMeasure、onLayout、onDraw 方法,从而最后将每个 View 都绘制出来(为了保证绘制效率,并不是每个View的这些方法每个绘制周期都会调用,那些没有变化的不会被重绘)。
2827

29-
但是由于普通的 View 都处于主线程中,Android 除了绘制之外,在主线程中还需要处理用户的各种点击事件。很多情况,在主线程中还需要运行额外的用户处理逻辑、轮询消息事件等。 如果主线程过于繁忙,不能及时的处理和响应用户的输入,会让用户的体验急剧降低。如果更严重的情况,当主线程延迟时间达到5s的时候,还会触发 ANR(Application Not Responding)。 如果界面的绘制和动画比较复杂,计算量比较大的情况,就不再适合使用 View 这种方式来绘制了。
28+
但是由于普通的 View 都处于主线程中,Android 除了绘制之外,在主线程中还需要处理用户的各种点击事件。很多情况,在主线程中还需要运行额外的用户处理逻辑、轮询消息事件等。 如果主线程过于繁忙,不能及时的处理和响应用户的输入,会让用户的体验急剧降低。如果更严重的情况,当主线程延迟时间达到5s的时候,还会触发 ANR(Application Not Responding)。 如果界面的绘制和动画比较复杂,计算量比较大的情况,就不再适合使用 View 这种方式来绘制了。
3029

3130
Android考虑到这种场景,提出了SurfaceView机制,它可以在非主线程进行图形绘制,释放了主线程的压力,所以我们可以把View的绘制放到SurfaceView中完成。如果对SurfaceView不太熟悉,可以自行百度,或参看demo中封装好的RenderView,这里由于篇幅限制,这里就不作详细介绍了。
3231

@@ -37,13 +36,11 @@ Android考虑到这种场景,提出了SurfaceView机制,它可以在非主
3736
# 动画实现
3837
看了一下要实现的效果,感觉是由四条振幅不等的正弦曲线组成,这些曲线振幅中间比较高,两边比较低,应该有一个对称的衰减系数,然后这些曲线根据声音的大小上下波动,保持一个速率向右运动。
3938

40-
这里再取回还给高中老师的数学知识,下面是正弦曲线的公式:y=Asin(ωx+φ)+k
41-
42-
A 代表的是振幅,对应的波峰和波谷的高度,即 y 轴上的距离;ω 是角速度,换成频率是 2πf,能够控制波形的宽度;φ 是初始相位,能够决定正弦曲线的初始 x 轴位置;k 是偏距,能够控制在 y 轴上的偏移量
39+
这里再取回还给高中老师的数学知识,下面是正弦曲线的公式:y=Asin(ωx+φ)+k,其中A 代表的是振幅,对应的波峰和波谷的高度,即 y 轴上的距离;ω 是角速度,换成频率是 2πf,能够控制波形的宽度;φ 是初始相位,能够决定正弦曲线的初始 x 轴位置;k 是偏距,能够控制在 y 轴上的偏移量。
4340

4441
那么我们只需根据时间改变φ,那么曲线就可以实现移动,通过一个对称衰减函数乘以A,就可实现曲线衰减变化,通过改变A的值可以实现上下波动。恩,完美,开干!
4542

46-
趁着给我们智课教育云的销售小伙伴们培训开会的功夫,我绘制调整了下大致的曲线函数,这里我要推荐大家一个绘图网站 [https://www.desmos.com/calculator](https://www.desmos.com/calculator),它可以帮你将函数转换成相应的图形,十分方便。
43+
这里我要推荐大家一个绘图网站 [https://www.desmos.com/calculator](https://www.desmos.com/calculator) ,它可以帮你将函数转换成相应的图形,十分方便。
4744
函数很简单,正弦就行,主要是衰减函数的选取,这里要找一个对称的衰减函数,如图所示:
4845
![衰减函数](https://github.com/Jay-Goo/WaveLineView/blob/master/pictures/%E8%A1%B0%E5%87%8F%E5%87%BD%E6%95%B0%E5%9B%BE.png)
4946
我们只要将每个点的x分别映射到衰减函数的一个对称区间,根据函数计算出相应的衰减系数,就可以实现振幅不同的波动曲线了。
@@ -52,10 +49,9 @@ A 代表的是振幅,对应的波峰和波谷的高度,即 y 轴上的距离
5249
接下来,我们只需要在 SurfaceView 中使用 Path,通过上面的公式计算出一个个的点,然后画直线连接起来就行啦!效果如图所示:
5350
![曲线函数](https://github.com/Jay-Goo/WaveLineView/blob/master/pictures/%E9%9D%99%E6%80%81%E6%95%88%E6%9E%9C%E5%9B%BE.png)
5451

55-
然后就是让它动起来了,前面也说了,可以根据时间改变曲线的相位值φ来实现移动,我在封装好的RenderView中实现了一个叫做onRender的方法,它主要是代替onDraw工作的,我们传入时间,然后让时间除以一个位移系数当φ,于是就让曲线实现了位移效果。然后再给一个volume变量,volume乘以一个初始振幅amplitude和根据横坐标算出的衰减系数,作为纵坐标,便实现了曲线形状和根据声音大小波动的效果。
52+
然后就是让它动起来了,前面也说了,可以根据时间改变曲线的相位值φ来实现移动,我在封装好的RenderView中实现了一个叫做onRender的方法,它主要是代替onDraw工作的,我们传入时间millisPassed,定义位移系数offsetSpeed,那么相位值φ = π * millisPassed / offsetSpeed,每次渲染周期都将φ代入函数就可以让曲线实现位移效果了。最后再给一个volume变量,volume乘以一个初始振幅amplitude和根据横坐标算出的衰减系数,作为纵坐标,便实现了曲线形状和根据声音大小波动的效果。
5653

5754
这里再总结下大致实现步骤:
58-
5955
- 计算出函数曲线和对称衰减函数
6056
- 根据函数计算出需要绘制的点,通过Path连线
6157
- 根据时间改变曲线的φ,实现曲线位移效果
@@ -72,7 +68,7 @@ A 代表的是振幅,对应的波峰和波谷的高度,即 y 轴上的距离
7268
## 降低绘制密度
7369
Ok,让我们review下绘制过程,第二步的时候我们需要计算曲线的点,然后通过Path连线这些点,而现在的手机屏幕1960x1080已经成为了标配,如果我们把宽度的像素点叫做采样点,每次我们要把每个采样点的x代入函数求出y,然后调用lineTo连线,那么我们每16ms都需要做出大量的计算。
7470

75-
但是事实上人的肉眼是有一定容忍度的,特别是快速运动的动画,一些失真的地方,肉眼很难分辨,所以我们没必要把1080个点每个都算出来,经过试验发现我们只要在60个以上的采样点,效果还是十分的平滑,粗略计算,这样做可以将计算量减少到原来的1/16,于是可以释放大量的CPU时间(由于采样点的减少,图形会出现锯齿,我们可以通过Paint的抗锯齿属性优化)
71+
但是事实上人的肉眼是有一定容忍度的,特别是快速运动的动画,一些失真的地方,肉眼很难分辨,所以我们没必要把1080个点每个都算出来,经过试验发现我们只要在60个以上的采样点,效果还是十分的平滑,粗略计算,这样做可以将计算量减少到原来的1/16,于是可以释放大量的CPU时间(由于采样点的减少,图形会出现锯齿,我们可以通过Paint的抗锯齿属性优化)
7672

7773
总结:通过动态调节自定义的绘制密度,在绘制密度与最终实现效果中找到一个平衡点(即不影响最后的视觉效果,同时还能最大限度的减少计算量),这个是最直接,也最简单的优化方法。
7874

@@ -85,12 +81,12 @@ Ok,让我们review下绘制过程,第二步的时候我们需要计算曲线
8581

8682
学过计算机的都知道,CPU在计算加减乘是非常快的,但是除法是比较慢的,特别是浮点数除法,我们可以将这些浮点运算转换成整数除法,除数、被除数乘以一个统一的精确度,用到时再除以精确度,这个方法在大量浮点计算时是很有效的,但是注意处理整形溢出。另外还要避免一些乘方、开平方根等运算的重复计算。
8783

88-
就本例来讲,calcValue方法是为了计算每个点代表的衰减系数,但其实我们计算衰减函数的时候对于每次固定的x,我们算出的衰减系数都是一样,这就会产生大量重复的计算。
89-
我们可以把这些计算好的值直接放入表中,然后通过查表的方式,下次就不需要重复计算这些复杂运算了。关于存储,如果数量不是很多建议使用SparseArray,它可以避免自动装箱,节约不少时间。理论上是这样的,但其实由于本例的衰减函数不是很复杂,这种做法的优化空间并不是很大,而且由于前面已经降低了绘制密度,已经减少了大量的计算,统计了下,耗时节约了几ms左右,但这确实也是一个优化的好方向,特别是一些复杂的运算,还是很有意义的。
84+
就本例来讲,calcValue方法是为了计算每个点代表的衰减系数,但其实我们计算衰减函数的时候对于每次固定的x,我们算出的衰减系数都是一样,这就会产生大量重复的计算。我们可以把这些计算好的值直接放入表中,然后通过查表的方式,下次就不需要重复计算这些复杂运算了。关于存储,如果数量不是很多建议使用SparseArray,它可以避免自动装箱,节约不少时间。理论上是这样的,但其实由于本例的衰减函数不是很复杂,这种做法的优化空间并不是很大,而且由于前面已经降低了绘制密度,已经减少了大量的计算,统计了下,耗时节约了几ms左右,但这确实也是一个优化的好方向,特别是一些复杂的运算,还是很有意义的。
9085

9186
总结:尽量减少重复运算,对重复复杂的计算,可以适当使用空间换时间。尽量减少浮点数除法运算。
9287

9388
经过前两步的优化,再看一下目前的CPU trace,发现已经降低了很多,动画也流畅了起来。
89+
9490
![这里写图片描述](https://github.com/Jay-Goo/WaveLineView/blob/master/pictures/%E4%BC%98%E5%8C%96%E5%90%8E%E6%95%88%E6%9E%9C%E5%9B%BE.png)
9591

9692
## 内存泄漏
@@ -114,6 +110,7 @@ RenderThread持有了Activity的隐式context,导致Activity不能释放资源
114110
}
115111
```
116112
在 Java 中,非静态匿名内部类会持有其外部类的隐式引用,所以RenderThread所持有的context就是holder和它的Runable方法持有的引用,而Activity销毁时,因为Thread的持有强引用导致无法及时的释放掉内存,从而导致内存泄漏。
113+
117114
解决方案就是,将RenderThread改为私有的静态内部类,这样它便不会持有其外部类的引用,另外可以对surfaceHolder使用弱引用,确保GC可以及时释放掉holder。
118115

119116
```
@@ -131,20 +128,17 @@ RenderThread持有了Activity的隐式context,导致Activity不能释放资源
131128
1. 减少对象的重复创建,例如Paint,Path,Rect等
132129
2. 减少大量临时对象的创建,对于那些无法避免,每次又必须分配的,我们可以采用对象池模型的方式来分配对象。对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。
133130
3. 减少一些资源操作,例如getColor,这个方法中会创建多个 StringBuilder 的变量
131+
134132
# 总结
135133
通过一系列的优化,动画在中端手机上CPU稳定在2~3%左右,内存在2MB左右,在一些低端手机CPU占有率在控制在10%左右,内存在15MB左右(为什么内存这么高?我还没有研究),不过欣慰的是两者动画都十分流畅。
136134

137135
本文介绍了从需求开始,如何一步步开发一个自定义控件,并通过降低绘制密度、减少重复实时计算量、避免和解决内存泄漏、如何优化内存等四方面对控件的性能进行了优化,希望能给大家平时开发工作带来一些启发和帮助,也希望大家可以提出更多更好的优化方案~
138136

139-
限于笔者的水平和经验有限,本文如果有纰漏和错误的地方,欢迎大家指出。如果大家有更多更好的建议,欢迎一起分享讨论,共同进步。同时我也是咱们智课技术订阅号的运维人员,如果大家有好的文章,欢迎踊跃投稿!
137+
限于笔者的水平和经验有限,本文如果有纰漏和错误的地方,欢迎大家指出。如果大家有更多更好的建议,欢迎一起分享讨论,共同进步。
138+
140139
# Github
141140
[https://github.com/Jay-Goo/WaveLineView](https://github.com/Jay-Goo/WaveLineView)
142-
# 关于作者
143-
144-
- 部门:智课教育云事业部
145-
- 项目:负责Android智课批改App的开发与维护
146-
- 作者:谷进杰
147-
- QQ:1015121748
141+
欢迎各位Star,Mark
148142

149143

150144

0 commit comments

Comments
 (0)