├── .classpath
├── .project
├── AndroidManifest.xml
├── README.md
├── bin
├── AndroidManifest.xml
├── VolumeView.apk
├── classes.dex
├── classes
│ └── com
│ │ └── example
│ │ └── volumeview
│ │ ├── BuildConfig.class
│ │ ├── MainActivity$RandomThreand.class
│ │ ├── MainActivity.class
│ │ ├── R$attr.class
│ │ ├── R$dimen.class
│ │ ├── R$drawable.class
│ │ ├── R$id.class
│ │ ├── R$layout.class
│ │ ├── R$menu.class
│ │ ├── R$string.class
│ │ ├── R$style.class
│ │ ├── R.class
│ │ ├── VolumeView$MoveThread.class
│ │ ├── VolumeView.class
│ │ ├── VolumeViewDoubleMoveWave$MoveThread.class
│ │ ├── VolumeViewDoubleMoveWave.class
│ │ ├── VolumeViewDoubleMoveWaveOpt$MoveThread.class
│ │ ├── VolumeViewDoubleMoveWaveOpt.class
│ │ ├── VolumeViewDoubleSine.class
│ │ ├── VolumeViewMoveWave$MoveThread.class
│ │ ├── VolumeViewMoveWave.class
│ │ ├── VolumeViewScaleWave.class
│ │ ├── VolumeViewScaleWaveOpt.class
│ │ ├── VolumeViewSine.class
│ │ └── VolumeViewWave.class
├── res
│ └── crunch
│ │ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ │ └── drawable-xxhdpi
│ │ └── ic_launcher.png
└── resources.ap_
├── gen
└── com
│ └── example
│ └── volumeview
│ ├── BuildConfig.java
│ └── R.java
├── ic_launcher-web.png
├── proguard-project.txt
├── project.properties
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
├── drawable-xxhdpi
│ └── ic_launcher.png
├── layout
│ └── activity_main.xml
├── menu
│ └── main.xml
├── values-v11
│ └── styles.xml
├── values-v14
│ └── styles.xml
├── values-w820dp
│ └── dimens.xml
└── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── src
└── com
└── example
└── volumeview
├── MainActivity.java
├── VolumeView.java
├── VolumeViewDoubleMoveWave.java
├── VolumeViewDoubleMoveWaveOpt.java
├── VolumeViewDoubleSine.java
├── VolumeViewMoveWave.java
├── VolumeViewScaleWave.java
├── VolumeViewScaleWaveOpt.java
├── VolumeViewSine.java
└── VolumeViewWave.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | VolumeView
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # (自定义view实现)音量波形图
2 |
3 | 标签: 自定义view 音量波形 音波
4 |
5 | ---
6 |
7 | 本文目的:主要是记录自己在实现自定义view的时候,一些思路和解决方案。
8 |
9 | ## 目标
10 |
11 | ![音量波形图][1]
12 |
13 | 绘制两个音量波形,并且能够向右运动,上面的波形移动速度慢,下面的波形移动速度快,并且振幅能够根据音量的高低进行改变。
14 |
15 | ## 分解目标
16 | 先考虑静止状态,上图有两个波形图,现只考虑一个波形图,每个波形图类似于两个正弦函数的闭合。所以我们第一步要绘制一个**正弦图形**。
17 |
18 | ![正弦函数图像][2]
19 |
20 | ## 绘制正弦函数
21 | 关于自定义view的图形绘制,一般都需要onMeasure,onLayout,onDraw三个步骤。由于是自定义view,而不是viewGroup,所以并不需要实现onLayout方法。
22 | 在绘制之前,要在onMeasure方法里,计算出画布的高度、宽度、中心点等需要计算的变量,这里就不详细说明了。
23 | 为了便于绘制图形正弦函数,要把画布的坐标原点移动到绘制view的中间位置。
24 | 也就是下图中标明的点,这样坐标原点(0,0),就位于view的中间,便于函数计算。
25 |
26 | 正弦函数方法参考:
27 |
28 | private double sine(float x, int period, float drawWidth) {
29 | return Math.sin(2 * Math.PI * period * x / drawWidth);
30 | }
31 |
32 | 其中period为在画布里有多少个周期,假设period为3,就是在画布里有三个周期。drawWidth为画布宽度。
33 |
34 | 在ondraw 方法里进行绘制。
35 | 这里调用drawsine方法
36 |
37 | private void drawSine(Canvas canvas, Path path, Paint paint, int period, float drawWidth, float amplitude) {
38 | float halfDrawWidth = drawWidth / 2f;
39 | path.reset();
40 | path.moveTo(-halfDrawWidth, 0);//将绘制的起点移动到最左边
41 | float y;
42 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
43 | y = (float) sine(x, period, drawWidth) * amplitude;
44 | path.lineTo(x, y);
45 | }
46 | canvas.drawPath(path, paint);
47 | canvas.save();
48 | canvas.restore();
49 | }
50 | amplitude 为振幅的高度,也就是半个画布的高度。绘制出的图形如下(在手机里,y轴正方形是向下的,x轴正方形是向右的)
51 |
52 | ![正弦函数图][3]
53 |
54 | ## 绘制两个关于y轴对称正弦函数
55 | 绘制反方向正弦函数,并且填充里面的内容。只是相当于将y值乘以-1,这里不详细列出具体代码
56 |
57 | ![两个正弦函数图][4]
58 |
59 | 进行内容填充 mPaint.setStyle(Style.FILL); 画笔的样式设置为填充,填充后的效果如下
60 |
61 | ![音波图][5]
62 |
63 | 这样勉强能算作一个波形图了。
64 | ## 缩放波形图
65 | 观察刚开始的效果图,发现每个波形的振幅并不相同,所以要考虑对波形图进行缩放。
66 | 采用缩放函数,就是按比例将振幅逐渐增大或者减小。
67 |
68 | double scaling;
69 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
70 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
71 | y = (float) (sine(x, period, drawWidth) * amplitude * (1) * Math
72 | .pow(scaling, 3));
73 | path.lineTo(x, y);
74 | }
75 | 为了更好的效果,我们缩放了三次,Math.pow(scaling, 3)
76 | 现在感觉和图一的效果差不多了。基本满足需求,就是每个波形之间的间隙还是很小。(后续会进行优化)
77 |
78 | ![音波缩放图][6]
79 |
80 | ## 让波形图动起来
81 | 在view里定义一个移动线程MoveThread,每隔一段时间就执行一次刷新postInvalidate(),每次刷新图像的时候,都会改变该图形的相位。
82 | 所谓相位,查看下图,一个函数是sin(x),另外一个函数是sin(x+0.5),两个函数之间就相差了0.5个相位。
83 | ![正弦函数相位 + 0.5 图][7]
84 | 相位变化了0.5,看起来就会向左移动0.5的距离。(图形右上角有标注函数)
85 | 在线程中不断更新相位的取值,这样不断的刷新图形,就会看起来形成一种移动的效果。(大家可以想象以前放电影时用的胶片,实现原理类似)这样我们的图形就能运动起来了。
86 | 修改后的sine函数
87 |
88 | private double sine(float x, int period, float drawWidth, double phase) {
89 | return Math.sin(2 * Math.PI * period * (x + phase) / drawWidth);
90 | }
91 | 定义一个MoveThread
92 |
93 | private class MoveThread extends Thread {
94 | private static final int MOVE_STOP = 1;
95 |
96 | private static final int MOVE_START = 0;
97 |
98 | private int state;
99 |
100 | @Override
101 | public void run() {
102 | mPhase = 0;
103 | state = MOVE_START;
104 | while (true) {
105 | if (state == MOVE_STOP) {
106 | break;
107 | }
108 | try {
109 | sleep(30);
110 | } catch (InterruptedException e) {
111 | }
112 | mPhase -= MOVE_DISTACE;
113 | postInvalidate();
114 | }
115 | }
116 | public void stopRunning() {
117 | state = MOVE_STOP;
118 | }
119 | }
120 |
121 | 这样当线程开启的时候,我们就能根据不断的改变sine函数的相位,就会形成不断右移动的效果。
122 |
123 | ## 绘制两个波形,并且设置不同的移动速度
124 | 两个波形的区别只是颜色不同,最大振幅不同,以及移动速度不同。
125 | 所谓移动速度不同,就是相位每次改变的值不同。可以在计算sine函数的时候,对固定相位值乘以不同的比例,就会得到不同的移动速度。从下图中的移动我们可以看到效果,已经很接近目标了。
126 |
127 | ![音波移动图形][8]
128 |
129 | (这里可以在图中看到不同的实现效果,为了便于有些同学学习和实践,将整个view进行了解剖,能更快的学习view的绘制过程)
130 |
131 | ## 根据音量改变波形图的振幅
132 | 通过音量设置波形图振幅,这样能够让波形图随着声音大小的变化而变化。
133 | 我们改变sin函数的振幅,图形就会升高或者下降。也就是在相同的x位置处,y的取值会发生变化。
134 |
135 | ![振幅不同,两个正弦的高度也不同][9]
136 |
137 | 但是,随着音频的变化,振幅的变动幅度变大,这样会造成一种图形的闪动。
138 |
139 | ## 解决图形闪动
140 | 当音量变化时,我们的振幅会发生变化,也就是这个图形,会随着振幅的变化按比例变大或者变小。如下图标记的两个点,如果我们刷新间隔为1s,就是1s之后,点1会突然变成点2的位置。这样就会造成闪动。
141 |
142 | ![点在不同的振幅,所在的高度不同][10]
143 |
144 | 我们的要求是图形要平滑的变动,意思就是不能这么快的进行变化,要怎么解决呢?
145 | 首先我们规定上升的最大速度为为1px每秒,现在的y值为1px,也就是当前1的位置。
146 | 现在只考虑点1的位置,假设我们每1s刷新一次,上升的最大速度为1px每秒,这样我们就可以计算出下一次变化y的最高位置为 1px + 1px/秒 * 1秒 = 2。
147 |
148 | - 如果当前音量发生变化,也就是振幅发生改变,得到的y值为3px,这个时候y值,3px >
149 | 我们计算的2px,这个时候就要用我们的2px。也就保证了最大速度不能超过我们规定的速度。
150 | - 如果当前音量发生变化,也就是振幅发生改变,得到的y值为1.5px,这个时候y值,1.5px <
151 | 我们计算的2px,这个时候就要用我们的1.5px。根据实际位置进行设定。
152 |
153 | 下降同理,这样我们就能保证上升或者下降的最大速度。
154 |
155 | // 计算当前时间下的振幅
156 | private float currentVolumeAmplitude(long curTime) {
157 | if (lastAmplitude == nextTargetAmplitude) {
158 | return nextTargetAmplitude;
159 | }
160 |
161 | if (curTime == amplitudeSetTime) {
162 | return lastAmplitude;
163 | }
164 |
165 | if (nextTargetAmplitude > lastAmplitude) {
166 | float target = lastAmplitude + mVerticalSpeed
167 | * (curTime - amplitudeSetTime) / 1000;
168 | if (target >= nextTargetAmplitude) {
169 | target = nextTargetAmplitude;
170 | lastAmplitude = nextTargetAmplitude;
171 | amplitudeSetTime = curTime;
172 | nextTargetAmplitude = mMinAmplitude;
173 | }
174 | return target;
175 | }
176 |
177 | if (nextTargetAmplitude < lastAmplitude) {
178 | float target = lastAmplitude - mVerticalRestoreSpeed
179 | * (curTime - amplitudeSetTime) / 1000;
180 | if (target <= nextTargetAmplitude) {
181 | target = nextTargetAmplitude;
182 | lastAmplitude = nextTargetAmplitude;
183 | amplitudeSetTime = curTime;
184 | nextTargetAmplitude = mMinAmplitude;
185 | }
186 | return target;
187 | }
188 |
189 | return mMinAmplitude;
190 | }
191 |
192 | ## 图形优化
193 | 因为中间的间隙过小,我们要把中间的间歇变大,类似于下图。这样效果可能会更好一点。
194 |
195 | ![优化后的波形图][11]
196 |
197 | 实施方案,将正弦函数上移,下面的正弦函数下移动,这样中间留有固定宽度的,通过缩放函数之后,效果如下:
198 |
199 | ![优化后的音量波形图][12]
200 |
201 | ## 实验过程中存在的问题以及解决方案:
202 | ### 中间线条的问题
203 | 横线的原因,是因为缩放造成了这 两个波形之间的点 x对应的值,y不等于0,会闭合不到中间的点。造成这个的现象是因为我们只是针对半个正弦曲线就进行填充了
204 |
205 | ![有瑕疵的波形图][13]
206 |
207 | 所以我们要将正反两个曲线画出来之后,把路径闭合之后再进行填充。这样就不会出现上面中间有横线的瑕疵
208 |
209 | ![修复后的波形图][14]
210 |
211 | ### 闪动问题
212 | 参考上文解决方案
213 |
214 | ## 可以直接使用的view
215 |
216 | 源代码地址:[https://github.com/duchao/VolumeView][15]
217 |
218 | ##可以直接使用的view
219 | VolumeView.java
220 | API: start() 开始
221 | stop() 结束
222 | setVolume(float volume) 设置音量
223 |
224 | [1]: http://upload-images.jianshu.io/upload_images/3050535-330c294cd7882d02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
225 | [2]: http://upload-images.jianshu.io/upload_images/3050535-eadb0d5bbea603bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
226 | [3]: http://upload-images.jianshu.io/upload_images/3050535-cf4afe45500e5c25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
227 | [4]: http://upload-images.jianshu.io/upload_images/3050535-2d83125173ad0cbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
228 | [5]: http://upload-images.jianshu.io/upload_images/3050535-d1a39b1e5ec3a2a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
229 | [6]: http://upload-images.jianshu.io/upload_images/3050535-462a1fd28452cc6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
230 | [7]: http://upload-images.jianshu.io/upload_images/3050535-b4cff2b8776c3600.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
231 | [8]: http://upload-images.jianshu.io/upload_images/3050535-5a9e12ec667ce851.gif?imageMogr2/auto-orient/strip
232 | [9]: http://upload-images.jianshu.io/upload_images/3050535-7c1c959115883a06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
233 | [10]: http://upload-images.jianshu.io/upload_images/3050535-283ad28a988d46ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
234 | [11]: http://upload-images.jianshu.io/upload_images/3050535-4804659eb60b780a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
235 | [12]: http://upload-images.jianshu.io/upload_images/3050535-1a47d6f71bb450f1.gif?imageMogr2/auto-orient/strip
236 | [13]: http://upload-images.jianshu.io/upload_images/3050535-c2fa84fa7e1157b0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
237 | [14]: http://upload-images.jianshu.io/upload_images/3050535-42955fd827a69809.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240
238 | [15]: https://github.com/duchao/VolumeView
239 |
--------------------------------------------------------------------------------
/bin/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/bin/VolumeView.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/VolumeView.apk
--------------------------------------------------------------------------------
/bin/classes.dex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes.dex
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/BuildConfig.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/BuildConfig.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/MainActivity$RandomThreand.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/MainActivity$RandomThreand.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/MainActivity.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/MainActivity.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$attr.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$attr.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$dimen.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$dimen.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$drawable.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$drawable.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$id.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$id.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$layout.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$layout.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$menu.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$menu.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$string.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$string.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R$style.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R$style.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/R.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/R.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeView$MoveThread.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeView$MoveThread.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeView.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeView.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWave$MoveThread.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWave$MoveThread.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWave.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWave.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWaveOpt$MoveThread.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWaveOpt$MoveThread.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWaveOpt.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewDoubleMoveWaveOpt.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewDoubleSine.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewDoubleSine.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewMoveWave$MoveThread.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewMoveWave$MoveThread.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewMoveWave.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewMoveWave.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewScaleWave.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewScaleWave.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewScaleWaveOpt.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewScaleWaveOpt.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewSine.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewSine.class
--------------------------------------------------------------------------------
/bin/classes/com/example/volumeview/VolumeViewWave.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/classes/com/example/volumeview/VolumeViewWave.class
--------------------------------------------------------------------------------
/bin/res/crunch/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/res/crunch/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bin/res/crunch/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/res/crunch/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bin/res/crunch/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/res/crunch/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bin/res/crunch/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/res/crunch/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bin/resources.ap_:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/bin/resources.ap_
--------------------------------------------------------------------------------
/gen/com/example/volumeview/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /** Automatically generated file. DO NOT MODIFY */
2 | package com.example.volumeview;
3 |
4 | public final class BuildConfig {
5 | public final static boolean DEBUG = true;
6 | }
--------------------------------------------------------------------------------
/gen/com/example/volumeview/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package com.example.volumeview;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class dimen {
14 | /** Default screen margins, per the Android Design guidelines.
15 |
16 | Example customization of dimensions originally defined in res/values/dimens.xml
17 | (such as screen margins) for screens with more than 820dp of available width. This
18 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
19 |
20 | */
21 | public static final int activity_horizontal_margin=0x7f040000;
22 | public static final int activity_vertical_margin=0x7f040001;
23 | }
24 | public static final class drawable {
25 | public static final int ic_launcher=0x7f020000;
26 | }
27 | public static final class id {
28 | public static final int action_settings=0x7f08000c;
29 | public static final int move_wave=0x7f080002;
30 | public static final int move_wave_double=0x7f080005;
31 | public static final int move_wave_double_start=0x7f080003;
32 | public static final int move_wave_double_stop=0x7f080004;
33 | public static final int move_wave_opt_double=0x7f080008;
34 | public static final int move_wave_opt_double_start=0x7f080006;
35 | public static final int move_wave_opt_double_stop=0x7f080007;
36 | public static final int move_wave_start=0x7f080000;
37 | public static final int move_wave_stop=0x7f080001;
38 | public static final int volume_view=0x7f08000b;
39 | public static final int volume_view_random_start=0x7f080009;
40 | public static final int volume_view_random_stop=0x7f08000a;
41 | }
42 | public static final class layout {
43 | public static final int activity_main=0x7f030000;
44 | }
45 | public static final class menu {
46 | public static final int main=0x7f070000;
47 | }
48 | public static final class string {
49 | public static final int action_settings=0x7f050002;
50 | public static final int app_name=0x7f050000;
51 | public static final int button_random_volume_start=0x7f05000e;
52 | public static final int button_random_volume_stop=0x7f05000f;
53 | public static final int button_start=0x7f05000c;
54 | public static final int button_stop=0x7f05000d;
55 | public static final int hello_world=0x7f050001;
56 | public static final int volume_view_double_move_wave_opt_tips=0x7f05000a;
57 | public static final int volume_view_double_move_wave_tips=0x7f050008;
58 | public static final int volume_view_double_sine_tips=0x7f050004;
59 | public static final int volume_view_move_wave_tips=0x7f050007;
60 | public static final int volume_view_scale_wave_opt_tips=0x7f050009;
61 | public static final int volume_view_scale_wave_tips=0x7f050006;
62 | public static final int volume_view_sine_tips=0x7f050003;
63 | public static final int volume_view_tips=0x7f05000b;
64 | public static final int volume_view_wave_tips=0x7f050005;
65 | }
66 | public static final class style {
67 | /**
68 | Base application theme, dependent on API level. This theme is replaced
69 | by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
70 |
71 |
72 | Theme customizations available in newer API levels can go in
73 | res/values-vXX/styles.xml, while customizations related to
74 | backward-compatibility can go here.
75 |
76 |
77 | Base application theme for API 11+. This theme completely replaces
78 | AppBaseTheme from res/values/styles.xml on API 11+ devices.
79 |
80 | API 11 theme customizations can go here.
81 |
82 | Base application theme for API 14+. This theme completely replaces
83 | AppBaseTheme from BOTH res/values/styles.xml and
84 | res/values-v11/styles.xml on API 14+ devices.
85 |
86 | API 14 theme customizations can go here.
87 | */
88 | public static final int AppBaseTheme=0x7f060000;
89 | /** Application theme.
90 | All customizations that are NOT specific to a particular API-level can go here.
91 | */
92 | public static final int AppTheme=0x7f060001;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/ic_launcher-web.png
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-23
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duchao/VolumeView/316a139a73d7fe39bb159ad61f67839e78ebde1c/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
15 |
16 |
17 |
18 |
23 |
24 |
28 |
29 |
30 |
31 |
36 |
37 |
41 |
42 |
43 |
44 |
49 |
50 |
54 |
55 |
56 |
57 |
62 |
63 |
67 |
68 |
69 |
70 |
75 |
76 |
80 |
81 |
87 |
88 |
94 |
95 |
96 |
97 |
98 |
103 |
104 |
109 |
110 |
114 |
115 |
121 |
122 |
128 |
129 |
130 |
135 |
136 |
137 |
138 |
143 |
144 |
148 |
149 |
150 |
151 |
156 |
157 |
161 |
162 |
168 |
169 |
175 |
176 |
177 |
182 |
183 |
184 |
185 |
190 |
191 |
195 |
196 |
202 |
203 |
209 |
210 |
211 |
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | VolumeView
5 | Hello world!
6 | Settings
7 | VolumeViewSine sine函数
8 | VolumeViewDoubleSine 两个sine函数
9 | VolumeViewWave 填充内容的波形图
10 | "VolumeViewScaleWave 对波形图进行两边的缩放
11 | VolumeViewMoveWave 波形图能够向右移动
12 | VolumeViewDoubleMoveWave 两个移动的波形图,速度不同
13 | VolumeViewScaleWaveOpt 对缩放的波形曲线进行优化
14 | VolumeViewDoubleMoveWaveOpt 优化后的移动图形
15 | 音量view,可以根据音量变化改变波形
16 | 开始
17 | 停止
18 | 开始随机音量
19 | 停止随机音量
20 |
21 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 | import java.util.Random;
4 |
5 | import android.app.Activity;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.view.View.OnClickListener;
9 | import android.widget.Button;
10 |
11 | public class MainActivity extends Activity implements OnClickListener {
12 | private Button mMoveViweStartButton;
13 |
14 | private Button mMoveViewStopButton;
15 |
16 | private VolumeViewMoveWave mVolumeViewMoveWave;
17 |
18 | private Button mDoubleMoveViweStartButton;
19 |
20 | private Button mDoubleMoveViewStopButton;
21 |
22 | private VolumeViewDoubleMoveWave mVolumeViewDoubleMoveWave;
23 |
24 | private Button mDoubleMoveViweStartOptButton;
25 |
26 | private Button mDoubleMoveViewStopOptButton;
27 |
28 | private VolumeViewDoubleMoveWaveOpt mVolumeViewDoubleMoveWaveOpt;
29 |
30 | private VolumeView mVolumeView;
31 |
32 | private Button mVolumeViewRandomStartButton;
33 |
34 | private Button mVolumeViewRandomStopButton;
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_main);
40 | init();
41 | }
42 |
43 | private void init() {
44 | mMoveViweStartButton = (Button) findViewById(R.id.move_wave_start);
45 | mMoveViweStartButton.setOnClickListener(this);
46 | mMoveViewStopButton = (Button) findViewById(R.id.move_wave_stop);
47 | mMoveViewStopButton.setOnClickListener(this);
48 | mVolumeViewMoveWave = (VolumeViewMoveWave) findViewById(R.id.move_wave);
49 |
50 | mDoubleMoveViweStartButton = (Button) findViewById(R.id.move_wave_double_start);
51 | mDoubleMoveViweStartButton.setOnClickListener(this);
52 | mDoubleMoveViewStopButton = (Button) findViewById(R.id.move_wave_double_stop);
53 | mDoubleMoveViewStopButton.setOnClickListener(this);
54 | mVolumeViewDoubleMoveWave = (VolumeViewDoubleMoveWave) findViewById(R.id.move_wave_double);
55 |
56 | mDoubleMoveViweStartOptButton = (Button) findViewById(R.id.move_wave_opt_double_start);
57 | mDoubleMoveViweStartOptButton.setOnClickListener(this);
58 | mDoubleMoveViewStopOptButton = (Button) findViewById(R.id.move_wave_opt_double_stop);
59 | mDoubleMoveViewStopOptButton.setOnClickListener(this);
60 | mVolumeViewDoubleMoveWaveOpt = (VolumeViewDoubleMoveWaveOpt) findViewById(R.id.move_wave_opt_double);
61 |
62 | mVolumeView = (VolumeView) findViewById(R.id.volume_view);
63 | mVolumeViewRandomStartButton = (Button) findViewById(R.id.volume_view_random_start);
64 | mVolumeViewRandomStartButton.setOnClickListener(this);
65 | mVolumeViewRandomStopButton = (Button) findViewById(R.id.volume_view_random_stop);
66 | mVolumeViewRandomStopButton.setOnClickListener(this);
67 | }
68 |
69 | @Override
70 | public void onClick(View v) {
71 | switch (v.getId()) {
72 | case R.id.move_wave_start:
73 | mVolumeViewMoveWave.start();
74 | break;
75 | case R.id.move_wave_stop:
76 | mVolumeViewMoveWave.stop();
77 | break;
78 |
79 | case R.id.move_wave_double_start:
80 | mVolumeViewDoubleMoveWave.start();
81 | break;
82 | case R.id.move_wave_double_stop:
83 | mVolumeViewDoubleMoveWave.stop();
84 | break;
85 |
86 | case R.id.move_wave_opt_double_start:
87 | mVolumeViewDoubleMoveWaveOpt.start();
88 | break;
89 | case R.id.move_wave_opt_double_stop:
90 | mVolumeViewDoubleMoveWaveOpt.stop();
91 | break;
92 |
93 | case R.id.volume_view_random_start:
94 | startRandomVolumeView();
95 | break;
96 | case R.id.volume_view_random_stop:
97 | stopRandmVolumeView();
98 | break;
99 | default:
100 | break;
101 | }
102 | }
103 |
104 | private void startRandomVolumeView() {
105 | mVolumeView.start();
106 |
107 | //随机设置音量大小.
108 | if (mRandomThreand != null) {
109 | mRandomThreand.stopRunning();
110 | mRandomThreand = null;
111 | }
112 | mRandomThreand = new RandomThreand();
113 | mRandomThreand.start();
114 | }
115 |
116 | private void stopRandmVolumeView() {
117 | if (mRandomThreand != null) {
118 | mRandomThreand.stopRunning();
119 | mRandomThreand = null;
120 | }
121 | mVolumeView.stop();
122 | }
123 |
124 | private RandomThreand mRandomThreand;
125 |
126 | private class RandomThreand extends Thread {
127 | private static final int MOVE_STOP = 1;
128 |
129 | private static final int MOVE_START = 0;
130 |
131 | private int state;
132 |
133 | @Override
134 | public void run() {
135 | state = MOVE_START;
136 |
137 | while (true) {
138 | if (state == MOVE_STOP) {
139 | break;
140 | }
141 | try {
142 | sleep(5);
143 | } catch (InterruptedException e) {
144 | e.printStackTrace();
145 | }
146 | mVolumeView.setVolume(getRandom(0, 100));
147 | }
148 | }
149 |
150 | public void stopRunning() {
151 | state = MOVE_STOP;
152 | }
153 | }
154 |
155 | private int getRandom(int min, int max) {
156 | Random random = new Random();
157 | int r = random.nextInt(max) % (max - min + 1) + min;
158 | return r;
159 | }
160 |
161 | }
162 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeView.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.graphics.Path;
7 | import android.graphics.Paint.Style;
8 | import android.util.AttributeSet;
9 | import android.util.DisplayMetrics;
10 | import android.util.TypedValue;
11 | import android.view.View;
12 | import android.view.WindowManager;
13 |
14 | public class VolumeView extends View {
15 |
16 | private Paint mPaint;
17 |
18 | private Path mPath;
19 |
20 | private float mDrawHeight;
21 |
22 | private float mDrawWidth;
23 |
24 | private float mViewHeight;
25 |
26 | private float mViewWidth;
27 |
28 | private float mCenterPointX;
29 |
30 | private float mCenterPointY;
31 |
32 | private double mPhase; // 相位
33 |
34 | private static final float MOVE_DISTACE = 5f;
35 |
36 | private MoveThread mMoveThread;
37 |
38 | // small wave 是外面的小波,big wave 是里面的大波
39 |
40 | private static final int SMALL_WAVE_COLOR = 0xFF77DAFD;//小波颜色
41 |
42 | private static final int BIG_WAVE_COLOR = 0xFF4DA6F3;//大波颜色
43 |
44 | private static final float SMALL_WAVE_SPEED = 1f;//小波速度
45 |
46 | private static final float BIG_WAVE_SPEED = 1.5f;//大波速度
47 |
48 | private static final int SMALL_WAVE_PERIOD = 5;//小波周期
49 |
50 | private static final int BIG_WAVE_PERIOD = 6;//大波周期
51 |
52 | private float mMaxBigWaveAmplitude;
53 |
54 | private float mMaxSmallWaveAmplitude;
55 |
56 | private static final float BIG_SINE_UP_AND_DOWN_MOVE = 1.8f;//大波 上移动或者下移动的比值
57 |
58 | private static final float SMALL_SINE_UP_AND_DOWN_MOVE = 1.3f;//小波 上移动或者下移动的比值
59 |
60 | private static final float SMALL_AMPLITUDE_SIZE_SCALE = 8f;
61 |
62 | private static final float BIG_AMPLITUDE_SIZE_SCALE = 6f;
63 |
64 | private float mVerticalSpeed; // (中间位置)垂直扩展时的移动速度,每秒移动像素数
65 |
66 | private float mVerticalRestoreSpeed; // (中间位置)垂直恢复时的移动速度,每秒移动像素数
67 |
68 | private static final float MAX_VALUE = 100;// 最大振幅对应的值。对应0到100分贝
69 |
70 | private float mMinAmplitude;// 最小振幅,这个可以手动修改。
71 |
72 | private float mVolumeAmplitude;
73 |
74 | public VolumeView(Context context, AttributeSet attrs) {
75 | super(context, attrs);
76 | init();
77 | }
78 |
79 | private void init() {
80 | mPath = new Path();
81 | mPaint = new Paint();
82 | mPaint.setAntiAlias(true);
83 | mPaint.setDither(true);
84 | mPaint.setStrokeWidth(1);
85 | mPaint.setStyle(Style.FILL);
86 |
87 | mVerticalSpeed = dip2px(0.1f);
88 | mVerticalRestoreSpeed = dip2px(0.05f);
89 | mMinAmplitude = 0.2f;
90 | mVolumeAmplitude = mMinAmplitude;
91 | }
92 |
93 | @Override
94 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
95 | widthMeasureSpec = measureWidth(widthMeasureSpec);
96 | heightMeasureSpec = measureHeight(heightMeasureSpec);
97 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
98 |
99 | int viewWidth = getWidth();
100 | int viewHeight = getHeight();
101 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
102 | return;
103 | }
104 | mViewWidth = viewWidth;
105 | mViewHeight = viewHeight;
106 |
107 | int paddingLeft = getPaddingLeft();
108 | int paddingRight = getPaddingRight();
109 | int paddingTop = getPaddingTop();
110 | int paddingBottom = getPaddingBottom();
111 |
112 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
113 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
114 |
115 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
116 | mCenterPointY = paddingTop + mDrawHeight / 2f;
117 |
118 | mMaxSmallWaveAmplitude = mDrawHeight / SMALL_AMPLITUDE_SIZE_SCALE;
119 | mMaxBigWaveAmplitude = mDrawHeight / BIG_AMPLITUDE_SIZE_SCALE;
120 | }
121 |
122 | private int measureWidth(int spec) {
123 | int mode = MeasureSpec.getMode(spec);
124 | if (mode == MeasureSpec.UNSPECIFIED) {
125 | WindowManager wm = (WindowManager) getContext().getSystemService(
126 | Context.WINDOW_SERVICE);
127 |
128 | @SuppressWarnings("deprecation")
129 | int width = wm.getDefaultDisplay().getWidth();
130 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
131 | } else if (mode == MeasureSpec.AT_MOST) {
132 | int value = MeasureSpec.getSize(spec);
133 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
134 | }
135 | return spec;
136 | }
137 |
138 | private int measureHeight(int spec) {
139 | int mode = MeasureSpec.getMode(spec);
140 | if (mode == MeasureSpec.EXACTLY) {
141 | return spec;
142 | }
143 |
144 | int height = (int) dip2px(50); // 其他模式下的最大高度
145 |
146 | if (mode == MeasureSpec.AT_MOST) {
147 | int preValue = MeasureSpec.getSize(spec);
148 | if (preValue < height) {
149 | height = preValue;
150 | }
151 | }
152 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
153 | return spec;
154 | }
155 |
156 | private float dip2px(float dp) {
157 | DisplayMetrics dm = getResources().getDisplayMetrics();
158 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
159 | }
160 |
161 | private double sine(float x, int period, float drawWidth, double phase) {
162 | return Math.sin(2 * Math.PI * period * (x + phase) / drawWidth);
163 | }
164 |
165 | private void drawSine(Canvas canvas, Path path, Paint paint, int period,
166 | float drawWidth, float amplitude, double phase, float upAndDownScale) {
167 | float halfDrawWidth = drawWidth / 2f;
168 | path.reset();
169 | path.moveTo(-halfDrawWidth, 0);
170 | float y;
171 | double scaling;
172 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
173 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
174 | y = (float) ((sine(x, period, drawWidth, phase) + upAndDownScale)
175 | * amplitude * (1) * Math.pow(scaling, 3));
176 | path.lineTo(x, y);
177 | }
178 |
179 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
180 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
181 | y = (float) ((sine(x, period, drawWidth, phase) + upAndDownScale)
182 | * amplitude * (-1) * Math.pow(scaling, 3));
183 | path.lineTo(x, y);
184 | }
185 |
186 | canvas.drawPath(path, paint);
187 | canvas.save();
188 | canvas.restore();
189 | }
190 |
191 | private void drawWave(Canvas canvas, Path path, Paint paint, int period,
192 | float drawWidth, double phase) {
193 | paint.setColor(BIG_WAVE_COLOR);
194 | drawSine(canvas, mPath, mPaint, BIG_WAVE_PERIOD, mDrawWidth,
195 | mVolumeAmplitude * mMaxBigWaveAmplitude, mPhase
196 | * BIG_WAVE_SPEED, BIG_SINE_UP_AND_DOWN_MOVE);
197 | paint.setColor(SMALL_WAVE_COLOR);
198 | drawSine(canvas, mPath, mPaint, SMALL_WAVE_PERIOD, mDrawWidth,
199 | mVolumeAmplitude * mMaxSmallWaveAmplitude, mPhase
200 | * SMALL_WAVE_SPEED, SMALL_SINE_UP_AND_DOWN_MOVE);
201 |
202 | }
203 |
204 | @Override
205 | protected void onDraw(Canvas canvas) {
206 | super.onDraw(canvas);
207 | canvas.translate(mCenterPointX, mCenterPointY);
208 | drawWave(canvas, mPath, mPaint, BIG_WAVE_PERIOD, mDrawWidth, mPhase);
209 |
210 | }
211 |
212 | private class MoveThread extends Thread {
213 | private static final int MOVE_STOP = 1;
214 |
215 | private static final int MOVE_START = 0;
216 |
217 | private int state;
218 |
219 | private long amplitudeSetTime;
220 |
221 | private float lastAmplitude;
222 |
223 | private float nextTargetAmplitude;
224 |
225 | private long curTime;
226 |
227 | private long lastTime;
228 |
229 | @Override
230 | public void run() {
231 | mPhase = 0;
232 | state = MOVE_START;
233 | lastTime = System.currentTimeMillis();
234 | amplitudeSetTime = lastTime;
235 | lastAmplitude = mVolumeAmplitude;
236 | nextTargetAmplitude = lastAmplitude;
237 | while (true) {
238 | if (state == MOVE_STOP) {
239 | break;
240 | }
241 | try {
242 | sleep(30);
243 | } catch (InterruptedException e) {}
244 | curTime = System.currentTimeMillis();
245 | mPhase -= MOVE_DISTACE;
246 | lastTime = curTime;
247 | mVolumeAmplitude = currentVolumeAmplitude(curTime);
248 | postInvalidate();
249 | }
250 | }
251 |
252 | public void stopRunning() {
253 | state = MOVE_STOP;
254 | }
255 |
256 | // 计算当前时间下的振幅
257 | private float currentVolumeAmplitude(long curTime) {
258 | if (lastAmplitude == nextTargetAmplitude) {
259 | return nextTargetAmplitude;
260 | }
261 |
262 | if (curTime == amplitudeSetTime) {
263 | return lastAmplitude;
264 | }
265 |
266 | // 走设置流程,改变速度快
267 | if (nextTargetAmplitude > lastAmplitude) {
268 | float target = lastAmplitude + mVerticalSpeed
269 | * (curTime - amplitudeSetTime) / 1000;
270 | if (target >= nextTargetAmplitude) {
271 | target = nextTargetAmplitude;
272 | lastAmplitude = nextTargetAmplitude;
273 | amplitudeSetTime = curTime;
274 | nextTargetAmplitude = mMinAmplitude;
275 | }
276 | return target;
277 | }
278 |
279 | // 走恢复流程,改变速度慢
280 | if (nextTargetAmplitude < lastAmplitude) {
281 | float target = lastAmplitude - mVerticalRestoreSpeed
282 | * (curTime - amplitudeSetTime) / 1000;
283 | if (target <= nextTargetAmplitude) {
284 | target = nextTargetAmplitude;
285 | lastAmplitude = nextTargetAmplitude;
286 | amplitudeSetTime = curTime;
287 | nextTargetAmplitude = mMinAmplitude;
288 | }
289 | return target;
290 | }
291 |
292 | return mMinAmplitude;
293 | }
294 |
295 | public void setAmplitude(float amplitude) {
296 | long curTime = System.currentTimeMillis();
297 | lastAmplitude = currentVolumeAmplitude(curTime);
298 | amplitudeSetTime = curTime;
299 | nextTargetAmplitude = amplitude;
300 | // interrupt();
301 | }
302 | }
303 |
304 | /*
305 | * API
306 | */
307 |
308 | // start volume animation
309 | public void start() {
310 | mPhase = 0;
311 | invalidate();
312 | if (mMoveThread != null) {
313 | mMoveThread.stopRunning();
314 | mMoveThread = null;
315 | }
316 | mMoveThread = new MoveThread();
317 | mMoveThread.start();
318 | }
319 |
320 | // stop volume animation
321 | public void stop() {
322 | if (mMoveThread != null) {
323 | mMoveThread.stopRunning();
324 | mMoveThread = null;
325 | }
326 | }
327 |
328 | // set volume value; 将音量转化成不同的振幅,其中有最大振幅和最小振幅.将音量对应成1到100
329 | public void setVolume(float volume) {
330 | if (Float.compare(volume, 0) < 0
331 | || Float.compare(volume, MAX_VALUE) > 0) {
332 | return;
333 | }
334 |
335 | float amplitude = volume / MAX_VALUE;
336 |
337 | if (amplitude < mMinAmplitude) {
338 | amplitude = mMinAmplitude;
339 | } else if (amplitude > 1.0f) {
340 | amplitude = 1.0f;
341 | }
342 | if (mMoveThread != null) {
343 | mMoveThread.setAmplitude(amplitude);
344 | }
345 | }
346 |
347 | }
348 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewDoubleMoveWave.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Paint.Style;
10 | import android.util.AttributeSet;
11 | import android.util.DisplayMetrics;
12 | import android.util.TypedValue;
13 | import android.view.View;
14 | import android.view.WindowManager;
15 |
16 | public class VolumeViewDoubleMoveWave extends View {
17 |
18 | private Paint mPaint;
19 |
20 | private Path mPath;
21 |
22 | private float mDrawHeight;
23 |
24 | private float mDrawWidth;
25 |
26 | private float mViewHeight;
27 |
28 | private float mViewWidth;
29 |
30 | private float mCenterPointX;
31 |
32 | private float mCenterPointY;
33 |
34 | private double mPhase; // 相位
35 |
36 | private static final float MOVE_DISTACE = 5f;
37 |
38 | private MoveThread mMoveThread;
39 |
40 | //small wave 是外面的小波,big wave 是里面的大波
41 |
42 | private static final int SMALL_WAVE_COLOR = 0xFF77DAFD;
43 |
44 | private static final int BIG_WAVE_COLOR = 0xFF4DA6F3;
45 |
46 | private static final float SMALL_WAVE_SPEED = 1f;
47 |
48 | private static final float BIG_WAVE_SPEED = 1.5f;
49 |
50 | private static final int SMALL_WAVE_PERIOD = 3;
51 |
52 | private static final int BIG_WAVE_PERIOD = 3;
53 |
54 | private float mMaxBigWaveAmplitude;
55 |
56 | private float mMaxSmallWaveAmplitude;
57 |
58 | public VolumeViewDoubleMoveWave(Context context, AttributeSet attrs) {
59 | super(context, attrs);
60 | init();
61 | }
62 |
63 | private void init() {
64 | mPath = new Path();
65 | mPaint = new Paint();
66 | mPaint.setAntiAlias(true);
67 | mPaint.setDither(true);
68 | mPaint.setStrokeWidth(1);
69 | mPaint.setStyle(Style.FILL);
70 | }
71 |
72 | @Override
73 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
74 | widthMeasureSpec = measureWidth(widthMeasureSpec);
75 | heightMeasureSpec = measureHeight(heightMeasureSpec);
76 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
77 |
78 | int viewWidth = getWidth();
79 | int viewHeight = getHeight();
80 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
81 | return;
82 | }
83 | mViewWidth = viewWidth;
84 | mViewHeight = viewHeight;
85 |
86 | int paddingLeft = getPaddingLeft();
87 | int paddingRight = getPaddingRight();
88 | int paddingTop = getPaddingTop();
89 | int paddingBottom = getPaddingBottom();
90 |
91 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
92 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
93 |
94 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
95 | mCenterPointY = paddingTop + mDrawHeight / 2f;
96 |
97 | mMaxSmallWaveAmplitude = mDrawHeight / 4f;
98 | mMaxBigWaveAmplitude = mDrawHeight / 2.5f;
99 | }
100 |
101 | private int measureWidth(int spec) {
102 | int mode = MeasureSpec.getMode(spec);
103 | if (mode == MeasureSpec.UNSPECIFIED) {
104 | WindowManager wm = (WindowManager) getContext().getSystemService(
105 | Context.WINDOW_SERVICE);
106 |
107 | @SuppressWarnings("deprecation")
108 | int width = wm.getDefaultDisplay().getWidth();
109 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
110 | } else if (mode == MeasureSpec.AT_MOST) {
111 | int value = MeasureSpec.getSize(spec);
112 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
113 | }
114 | return spec;
115 | }
116 |
117 | private int measureHeight(int spec) {
118 | int mode = MeasureSpec.getMode(spec);
119 | if (mode == MeasureSpec.EXACTLY) {
120 | return spec;
121 | }
122 |
123 | int height = (int) dip2px(50); // 其他模式下的最大高度
124 |
125 | if (mode == MeasureSpec.AT_MOST) {
126 | int preValue = MeasureSpec.getSize(spec);
127 | if (preValue < height) {
128 | height = preValue;
129 | }
130 | }
131 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
132 | return spec;
133 | }
134 |
135 | private float dip2px(float dp) {
136 | DisplayMetrics dm = getResources().getDisplayMetrics();
137 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
138 | }
139 |
140 | private double sine(float x, int period, float drawWidth, double phase) {
141 | return Math.sin(2 * Math.PI * period * (x + phase) / drawWidth);
142 | }
143 |
144 | private void drawSine(Canvas canvas, Path path, Paint paint, int period,
145 | float drawWidth, float amplitude, double phase) {
146 | float halfDrawWidth = drawWidth / 2f;
147 | path.reset();
148 | path.moveTo(-halfDrawWidth, 0);
149 | float y;
150 | double scaling;
151 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
152 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
153 | y = (float) (sine(x, period, drawWidth, phase) * amplitude * (1) * Math
154 | .pow(scaling, 3));
155 | path.lineTo(x, y);
156 | }
157 |
158 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
159 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
160 | y = (float) (sine(x, period, drawWidth, phase) * amplitude * (-1) * Math
161 | .pow(scaling, 3));
162 | path.lineTo(x, y);
163 | }
164 |
165 | canvas.drawPath(path, paint);
166 | canvas.save();
167 | canvas.restore();
168 | }
169 |
170 | private void drawWave(Canvas canvas, Path path, Paint paint, int period,
171 | float drawWidth, double phase) {
172 | paint.setColor(BIG_WAVE_COLOR);
173 | drawSine(canvas, mPath, mPaint, BIG_WAVE_PERIOD, mDrawWidth, mMaxBigWaveAmplitude, mPhase * BIG_WAVE_SPEED);
174 | paint.setColor(SMALL_WAVE_COLOR);
175 | drawSine(canvas, mPath, mPaint, SMALL_WAVE_PERIOD, mDrawWidth, mMaxSmallWaveAmplitude, mPhase * SMALL_WAVE_SPEED);
176 |
177 | }
178 |
179 | @Override
180 | protected void onDraw(Canvas canvas) {
181 | super.onDraw(canvas);
182 | canvas.translate(mCenterPointX, mCenterPointY);
183 | drawWave(canvas, mPath, mPaint, BIG_WAVE_PERIOD, mDrawWidth, mPhase);
184 |
185 | }
186 |
187 | private class MoveThread extends Thread {
188 | private static final int MOVE_STOP = 1;
189 |
190 | private static final int MOVE_START = 0;
191 |
192 | private int state;
193 |
194 | @Override
195 | public void run() {
196 | mPhase = 0;
197 | state = MOVE_START;
198 | while (true) {
199 | if (state == MOVE_STOP) {
200 | break;
201 | }
202 | try {
203 | sleep(30);
204 | } catch (InterruptedException e) {
205 | // ignore
206 | }
207 | mPhase -= MOVE_DISTACE;
208 | postInvalidate();
209 | }
210 | }
211 |
212 | public void stopRunning() {
213 | state = MOVE_STOP;
214 | }
215 | }
216 |
217 | /*
218 | * API
219 | */
220 |
221 | // start volume animation
222 | public void start() {
223 | mPhase = 0;
224 | invalidate();
225 | if (mMoveThread != null) {
226 | mMoveThread.stopRunning();
227 | mMoveThread = null;
228 | }
229 | mMoveThread = new MoveThread();
230 | mMoveThread.start();
231 | }
232 |
233 | // stop volume animation
234 | public void stop() {
235 | if (mMoveThread != null) {
236 | mMoveThread.stopRunning();
237 | mMoveThread = null;
238 | }
239 | postInvalidate();
240 | }
241 |
242 | }
243 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewDoubleMoveWaveOpt.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.graphics.Path;
7 | import android.graphics.Paint.Style;
8 | import android.util.AttributeSet;
9 | import android.util.DisplayMetrics;
10 | import android.util.TypedValue;
11 | import android.view.View;
12 | import android.view.WindowManager;
13 |
14 | public class VolumeViewDoubleMoveWaveOpt extends View {
15 |
16 | private Paint mPaint;
17 |
18 | private Path mPath;
19 |
20 | private float mDrawHeight;
21 |
22 | private float mDrawWidth;
23 |
24 | private float mViewHeight;
25 |
26 | private float mViewWidth;
27 |
28 | private float mCenterPointX;
29 |
30 | private float mCenterPointY;
31 |
32 | private double mPhase; // 相位
33 |
34 | private static final float MOVE_DISTACE = 5f;
35 |
36 | private MoveThread mMoveThread;
37 |
38 | // small wave 是外面的小波,big wave 是里面的大波
39 |
40 | private static final int SMALL_WAVE_COLOR = 0xFF77DAFD;
41 |
42 | private static final int BIG_WAVE_COLOR = 0xFF4DA6F3;
43 |
44 | private static final float SMALL_WAVE_SPEED = 1f;
45 |
46 | private static final float BIG_WAVE_SPEED = 1.5f;
47 |
48 | private static final int SMALL_WAVE_PERIOD = 5;
49 |
50 | private static final int BIG_WAVE_PERIOD = 6;
51 |
52 | private float mMaxBigWaveAmplitude;
53 |
54 | private float mMaxSmallWaveAmplitude;
55 |
56 | private static final float BIG_SINE_UP_AND_DOWN_MOVE = 1.8f;
57 |
58 | private static final float SMALL_SINE_UP_AND_DOWN_MOVE = 1.3f;
59 |
60 | private static final float SMALL_AMPLITUDE_SIZE_SCALE = 8f;
61 |
62 | private static final float BIG_AMPLITUDE_SIZE_SCALE = 6f;
63 |
64 | public VolumeViewDoubleMoveWaveOpt(Context context, AttributeSet attrs) {
65 | super(context, attrs);
66 | init();
67 | }
68 |
69 | private void init() {
70 | mPath = new Path();
71 | mPaint = new Paint();
72 | mPaint.setAntiAlias(true);
73 | mPaint.setDither(true);
74 | mPaint.setStrokeWidth(1);
75 | mPaint.setStyle(Style.FILL);
76 | }
77 |
78 | @Override
79 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
80 | widthMeasureSpec = measureWidth(widthMeasureSpec);
81 | heightMeasureSpec = measureHeight(heightMeasureSpec);
82 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
83 |
84 | int viewWidth = getWidth();
85 | int viewHeight = getHeight();
86 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
87 | return;
88 | }
89 | mViewWidth = viewWidth;
90 | mViewHeight = viewHeight;
91 |
92 | int paddingLeft = getPaddingLeft();
93 | int paddingRight = getPaddingRight();
94 | int paddingTop = getPaddingTop();
95 | int paddingBottom = getPaddingBottom();
96 |
97 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
98 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
99 |
100 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
101 | mCenterPointY = paddingTop + mDrawHeight / 2f;
102 |
103 | mMaxSmallWaveAmplitude = mDrawHeight / SMALL_AMPLITUDE_SIZE_SCALE;
104 | mMaxBigWaveAmplitude = mDrawHeight / BIG_AMPLITUDE_SIZE_SCALE;
105 | }
106 |
107 | private int measureWidth(int spec) {
108 | int mode = MeasureSpec.getMode(spec);
109 | if (mode == MeasureSpec.UNSPECIFIED) {
110 | WindowManager wm = (WindowManager) getContext().getSystemService(
111 | Context.WINDOW_SERVICE);
112 |
113 | @SuppressWarnings("deprecation")
114 | int width = wm.getDefaultDisplay().getWidth();
115 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
116 | } else if (mode == MeasureSpec.AT_MOST) {
117 | int value = MeasureSpec.getSize(spec);
118 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
119 | }
120 | return spec;
121 | }
122 |
123 | private int measureHeight(int spec) {
124 | int mode = MeasureSpec.getMode(spec);
125 | if (mode == MeasureSpec.EXACTLY) {
126 | return spec;
127 | }
128 |
129 | int height = (int) dip2px(50); // 其他模式下的最大高度
130 |
131 | if (mode == MeasureSpec.AT_MOST) {
132 | int preValue = MeasureSpec.getSize(spec);
133 | if (preValue < height) {
134 | height = preValue;
135 | }
136 | }
137 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
138 | return spec;
139 | }
140 |
141 | private float dip2px(float dp) {
142 | DisplayMetrics dm = getResources().getDisplayMetrics();
143 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
144 | }
145 |
146 | private double sine(float x, int period, float drawWidth, double phase) {
147 | return Math.sin(2 * Math.PI * period * (x + phase) / drawWidth);
148 | }
149 |
150 | private void drawSine(Canvas canvas, Path path, Paint paint, int period,
151 | float drawWidth, float amplitude, double phase, float upAndDownScale) {
152 | float halfDrawWidth = drawWidth / 2f;
153 | path.reset();
154 | path.moveTo(-halfDrawWidth, 0);
155 | float y;
156 | double scaling;
157 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
158 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
159 | y = (float) ((sine(x, period, drawWidth, phase) + upAndDownScale)
160 | * amplitude * (1) * Math.pow(scaling, 3));
161 | path.lineTo(x, y);
162 | }
163 |
164 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
165 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
166 | y = (float) ((sine(x, period, drawWidth, phase) + upAndDownScale)
167 | * amplitude * (-1) * Math.pow(scaling, 3));
168 | path.lineTo(x, y);
169 | }
170 |
171 | canvas.drawPath(path, paint);
172 | canvas.save();
173 | canvas.restore();
174 | }
175 |
176 | private void drawWave(Canvas canvas, Path path, Paint paint, int period,
177 | float drawWidth, double phase) {
178 | paint.setColor(BIG_WAVE_COLOR);
179 | drawSine(canvas, mPath, mPaint, BIG_WAVE_PERIOD, mDrawWidth,
180 | mMaxBigWaveAmplitude, mPhase * BIG_WAVE_SPEED,
181 | BIG_SINE_UP_AND_DOWN_MOVE);
182 | paint.setColor(SMALL_WAVE_COLOR);
183 | drawSine(canvas, mPath, mPaint, SMALL_WAVE_PERIOD, mDrawWidth,
184 | mMaxSmallWaveAmplitude, mPhase * SMALL_WAVE_SPEED,
185 | SMALL_SINE_UP_AND_DOWN_MOVE);
186 |
187 | }
188 |
189 | @Override
190 | protected void onDraw(Canvas canvas) {
191 | super.onDraw(canvas);
192 | canvas.translate(mCenterPointX, mCenterPointY);
193 | drawWave(canvas, mPath, mPaint, BIG_WAVE_PERIOD, mDrawWidth, mPhase);
194 |
195 | }
196 |
197 | private class MoveThread extends Thread {
198 | private static final int MOVE_STOP = 1;
199 |
200 | private static final int MOVE_START = 0;
201 |
202 | private int state;
203 |
204 | @Override
205 | public void run() {
206 | mPhase = 0;
207 | state = MOVE_START;
208 | while (true) {
209 | if (state == MOVE_STOP) {
210 | break;
211 | }
212 | try {
213 | sleep(30);
214 | } catch (InterruptedException e) {
215 | // ignore
216 | }
217 | mPhase -= MOVE_DISTACE;
218 | postInvalidate();
219 | }
220 | }
221 |
222 | public void stopRunning() {
223 | state = MOVE_STOP;
224 | }
225 | }
226 |
227 | /*
228 | * API
229 | */
230 |
231 | // start volume animation
232 | public void start() {
233 | mPhase = 0;
234 | invalidate();
235 | if (mMoveThread != null) {
236 | mMoveThread.stopRunning();
237 | mMoveThread = null;
238 | }
239 | mMoveThread = new MoveThread();
240 | mMoveThread.start();
241 | }
242 |
243 | // stop volume animation
244 | public void stop() {
245 | if (mMoveThread != null) {
246 | mMoveThread.stopRunning();
247 | mMoveThread = null;
248 | }
249 | postInvalidate();
250 | }
251 |
252 | }
253 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewDoubleSine.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.Paint.Style;
9 | import android.util.AttributeSet;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 |
15 | public class VolumeViewDoubleSine extends View {
16 |
17 | private Paint mPaint;
18 |
19 | private Path mPath;
20 |
21 | private float mDrawHeight;
22 |
23 | private float mDrawWidth;
24 |
25 | private float mViewHeight;
26 |
27 | private float mViewWidth;
28 |
29 | private float mCenterPointX;
30 |
31 | private float mCenterPointY;
32 |
33 | private float mAmplitude;
34 |
35 | private static final int PERIOD = 3;
36 |
37 | public VolumeViewDoubleSine(Context context, AttributeSet attrs) {
38 | super(context, attrs);
39 | init();
40 | }
41 |
42 | private void init() {
43 | mPath = new Path();
44 | mPaint = new Paint();
45 | mPaint.setAntiAlias(true);
46 | mPaint.setDither(true);
47 | mPaint.setStrokeWidth(2);
48 | mPaint.setStyle(Style.STROKE);
49 | mPaint.setColor(Color.WHITE);
50 | }
51 |
52 | @Override
53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
54 | widthMeasureSpec = measureWidth(widthMeasureSpec);
55 | heightMeasureSpec = measureHeight(heightMeasureSpec);
56 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
57 |
58 | int viewWidth = getWidth();
59 | int viewHeight = getHeight();
60 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
61 | return;
62 | }
63 | mViewWidth = viewWidth;
64 | mViewHeight = viewHeight;
65 |
66 | int paddingLeft = getPaddingLeft();
67 | int paddingRight = getPaddingRight();
68 | int paddingTop = getPaddingTop();
69 | int paddingBottom = getPaddingBottom();
70 |
71 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
72 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
73 |
74 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
75 | mCenterPointY = paddingTop + mDrawHeight / 2f;
76 |
77 | mAmplitude = mDrawHeight / 2f;
78 | }
79 |
80 | private int measureWidth(int spec) {
81 | int mode = MeasureSpec.getMode(spec);
82 | if (mode == MeasureSpec.UNSPECIFIED) {
83 | WindowManager wm = (WindowManager) getContext().getSystemService(
84 | Context.WINDOW_SERVICE);
85 |
86 | @SuppressWarnings("deprecation")
87 | int width = wm.getDefaultDisplay().getWidth();
88 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
89 | } else if (mode == MeasureSpec.AT_MOST) {
90 | int value = MeasureSpec.getSize(spec);
91 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
92 | }
93 | return spec;
94 | }
95 |
96 | private int measureHeight(int spec) {
97 | int mode = MeasureSpec.getMode(spec);
98 | if (mode == MeasureSpec.EXACTLY) {
99 | return spec;
100 | }
101 |
102 | int height = (int) dip2px(50); // 其他模式下的最大高度
103 |
104 | if (mode == MeasureSpec.AT_MOST) {
105 | int preValue = MeasureSpec.getSize(spec);
106 | if (preValue < height) {
107 | height = preValue;
108 | }
109 | }
110 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
111 | return spec;
112 | }
113 |
114 | private float dip2px(float dp) {
115 | DisplayMetrics dm = getResources().getDisplayMetrics();
116 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
117 | }
118 |
119 | private double sine(float x, int period, float drawWidth) {
120 | return Math.sin(2 * Math.PI * period * x / drawWidth);
121 | }
122 |
123 | private void drawSine(Canvas canvas, Path path, Paint paint, int period, float drawWidth, float amplitude) {
124 | float halfDrawWidth = drawWidth / 2f;
125 | path.reset();
126 | path.moveTo(-halfDrawWidth, 0);
127 | float y;
128 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
129 | y = (float) sine(x, period, drawWidth) * amplitude * (1);
130 | path.lineTo(x, y);
131 | }
132 |
133 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
134 | y = (float) sine(x, period, drawWidth) * amplitude * (-1);
135 | path.lineTo(x, y);
136 | }
137 |
138 | canvas.drawPath(path, paint);
139 | canvas.save();
140 | canvas.restore();
141 | }
142 |
143 | @Override
144 | protected void onDraw(Canvas canvas) {
145 | super.onDraw(canvas);
146 | canvas.translate(mCenterPointX, mCenterPointY);
147 | drawSine(canvas, mPath, mPaint, PERIOD, mDrawWidth, mAmplitude);
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewMoveWave.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Paint.Style;
10 | import android.util.AttributeSet;
11 | import android.util.DisplayMetrics;
12 | import android.util.TypedValue;
13 | import android.view.View;
14 | import android.view.WindowManager;
15 |
16 | public class VolumeViewMoveWave extends View {
17 |
18 | private Paint mPaint;
19 |
20 | private Path mPath;
21 |
22 | private float mDrawHeight;
23 |
24 | private float mDrawWidth;
25 |
26 | private float mViewHeight;
27 |
28 | private float mViewWidth;
29 |
30 | private float mCenterPointX;
31 |
32 | private float mCenterPointY;
33 |
34 | private float mAmplitude;
35 |
36 | private static final int PERIOD = 3;
37 |
38 | private double mPhase; // 相位
39 |
40 | private static final float MOVE_DISTACE = 5f;
41 |
42 | private MoveThread mMoveThread;
43 |
44 | public VolumeViewMoveWave(Context context, AttributeSet attrs) {
45 | super(context, attrs);
46 | init();
47 | }
48 |
49 | private void init() {
50 | mPath = new Path();
51 | mPaint = new Paint();
52 | mPaint.setAntiAlias(true);
53 | mPaint.setDither(true);
54 | mPaint.setStrokeWidth(2);
55 | mPaint.setStyle(Style.FILL);
56 | mPaint.setColor(Color.WHITE);
57 | }
58 |
59 | @Override
60 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
61 | widthMeasureSpec = measureWidth(widthMeasureSpec);
62 | heightMeasureSpec = measureHeight(heightMeasureSpec);
63 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
64 |
65 | int viewWidth = getWidth();
66 | int viewHeight = getHeight();
67 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
68 | return;
69 | }
70 | mViewWidth = viewWidth;
71 | mViewHeight = viewHeight;
72 |
73 | int paddingLeft = getPaddingLeft();
74 | int paddingRight = getPaddingRight();
75 | int paddingTop = getPaddingTop();
76 | int paddingBottom = getPaddingBottom();
77 |
78 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
79 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
80 |
81 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
82 | mCenterPointY = paddingTop + mDrawHeight / 2f;
83 |
84 | mAmplitude = mDrawHeight / 2f;
85 | }
86 |
87 | private int measureWidth(int spec) {
88 | int mode = MeasureSpec.getMode(spec);
89 | if (mode == MeasureSpec.UNSPECIFIED) {
90 | WindowManager wm = (WindowManager) getContext().getSystemService(
91 | Context.WINDOW_SERVICE);
92 |
93 | @SuppressWarnings("deprecation")
94 | int width = wm.getDefaultDisplay().getWidth();
95 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
96 | } else if (mode == MeasureSpec.AT_MOST) {
97 | int value = MeasureSpec.getSize(spec);
98 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
99 | }
100 | return spec;
101 | }
102 |
103 | private int measureHeight(int spec) {
104 | int mode = MeasureSpec.getMode(spec);
105 | if (mode == MeasureSpec.EXACTLY) {
106 | return spec;
107 | }
108 |
109 | int height = (int) dip2px(50); // 其他模式下的最大高度
110 |
111 | if (mode == MeasureSpec.AT_MOST) {
112 | int preValue = MeasureSpec.getSize(spec);
113 | if (preValue < height) {
114 | height = preValue;
115 | }
116 | }
117 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
118 | return spec;
119 | }
120 |
121 | private float dip2px(float dp) {
122 | DisplayMetrics dm = getResources().getDisplayMetrics();
123 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
124 | }
125 |
126 | private double sine(float x, int period, float drawWidth, double phase) {
127 | return Math.sin(2 * Math.PI * period * (x + phase) / drawWidth);
128 | }
129 |
130 | private void drawSine(Canvas canvas, Path path, Paint paint, int period,
131 | float drawWidth, float amplitude, double phase) {
132 | float halfDrawWidth = drawWidth / 2f;
133 | path.reset();
134 | path.moveTo(-halfDrawWidth, 0);
135 | float y;
136 | double scaling;
137 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
138 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
139 | y = (float) (sine(x, period, drawWidth, phase) * amplitude * (1) * Math
140 | .pow(scaling, 3));
141 | path.lineTo(x, y);
142 | }
143 |
144 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
145 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
146 | y = (float) (sine(x, period, drawWidth, phase) * amplitude * (-1) * Math
147 | .pow(scaling, 3));
148 | path.lineTo(x, y);
149 | }
150 |
151 | canvas.drawPath(path, paint);
152 | canvas.save();
153 | canvas.restore();
154 | }
155 |
156 | @Override
157 | protected void onDraw(Canvas canvas) {
158 | super.onDraw(canvas);
159 | canvas.translate(mCenterPointX, mCenterPointY);
160 | drawSine(canvas, mPath, mPaint, PERIOD, mDrawWidth, mAmplitude, mPhase);
161 | }
162 |
163 | private class MoveThread extends Thread {
164 | private static final int MOVE_STOP = 1;
165 |
166 | private static final int MOVE_START = 0;
167 |
168 | private int state;
169 |
170 | @Override
171 | public void run() {
172 | mPhase = 0;
173 | state = MOVE_START;
174 | while (true) {
175 | if (state == MOVE_STOP) {
176 | break;
177 | }
178 | try {
179 | sleep(30);
180 | } catch (InterruptedException e) {
181 | // ignore
182 | }
183 | mPhase -= MOVE_DISTACE;
184 | postInvalidate();
185 | }
186 | }
187 |
188 | public void stopRunning() {
189 | state = MOVE_STOP;
190 | }
191 | }
192 |
193 | /*
194 | * API
195 | */
196 |
197 | // start volume animation
198 | public void start() {
199 | mPhase = 0;
200 | invalidate();
201 | if (mMoveThread != null) {
202 | mMoveThread.stopRunning();
203 | mMoveThread = null;
204 | }
205 | mMoveThread = new MoveThread();
206 | mMoveThread.start();
207 | }
208 |
209 | // stop volume animation
210 | public void stop() {
211 | if (mMoveThread != null) {
212 | mMoveThread.stopRunning();
213 | mMoveThread = null;
214 | }
215 | postInvalidate();
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewScaleWave.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Paint.Style;
10 | import android.util.AttributeSet;
11 | import android.util.DisplayMetrics;
12 | import android.util.TypedValue;
13 | import android.view.View;
14 | import android.view.WindowManager;
15 |
16 | public class VolumeViewScaleWave extends View {
17 |
18 | private Paint mPaint;
19 |
20 | private Path mPath;
21 |
22 | private float mDrawHeight;
23 |
24 | private float mDrawWidth;
25 |
26 | private float mViewHeight;
27 |
28 | private float mViewWidth;
29 |
30 | private float mCenterPointX;
31 |
32 | private float mCenterPointY;
33 |
34 | private float mAmplitude;
35 |
36 | private static final int PERIOD = 3;
37 |
38 |
39 |
40 | public VolumeViewScaleWave(Context context, AttributeSet attrs) {
41 | super(context, attrs);
42 | init();
43 | }
44 |
45 | private void init() {
46 | mPath = new Path();
47 | mPaint = new Paint();
48 | mPaint.setAntiAlias(true);
49 | mPaint.setDither(true);
50 | mPaint.setStrokeWidth(2);
51 | mPaint.setStyle(Style.FILL);
52 | mPaint.setColor(Color.WHITE);
53 | }
54 |
55 | @Override
56 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
57 | widthMeasureSpec = measureWidth(widthMeasureSpec);
58 | heightMeasureSpec = measureHeight(heightMeasureSpec);
59 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
60 |
61 | int viewWidth = getWidth();
62 | int viewHeight = getHeight();
63 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
64 | return;
65 | }
66 | mViewWidth = viewWidth;
67 | mViewHeight = viewHeight;
68 |
69 | int paddingLeft = getPaddingLeft();
70 | int paddingRight = getPaddingRight();
71 | int paddingTop = getPaddingTop();
72 | int paddingBottom = getPaddingBottom();
73 |
74 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
75 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
76 |
77 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
78 | mCenterPointY = paddingTop + mDrawHeight / 2f;
79 |
80 | mAmplitude = mDrawHeight / 2f;
81 | }
82 |
83 | private int measureWidth(int spec) {
84 | int mode = MeasureSpec.getMode(spec);
85 | if (mode == MeasureSpec.UNSPECIFIED) {
86 | WindowManager wm = (WindowManager) getContext().getSystemService(
87 | Context.WINDOW_SERVICE);
88 |
89 | @SuppressWarnings("deprecation")
90 | int width = wm.getDefaultDisplay().getWidth();
91 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
92 | } else if (mode == MeasureSpec.AT_MOST) {
93 | int value = MeasureSpec.getSize(spec);
94 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
95 | }
96 | return spec;
97 | }
98 |
99 | private int measureHeight(int spec) {
100 | int mode = MeasureSpec.getMode(spec);
101 | if (mode == MeasureSpec.EXACTLY) {
102 | return spec;
103 | }
104 |
105 | int height = (int) dip2px(50); // 其他模式下的最大高度
106 |
107 | if (mode == MeasureSpec.AT_MOST) {
108 | int preValue = MeasureSpec.getSize(spec);
109 | if (preValue < height) {
110 | height = preValue;
111 | }
112 | }
113 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
114 | return spec;
115 | }
116 |
117 | private float dip2px(float dp) {
118 | DisplayMetrics dm = getResources().getDisplayMetrics();
119 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
120 | }
121 |
122 | private double sine(float x, int period, float drawWidth) {
123 | return Math.sin(2 * Math.PI * period * x / drawWidth);
124 | }
125 |
126 | private void drawSine(Canvas canvas, Path path, Paint paint, int period,
127 | float drawWidth, float amplitude) {
128 | float halfDrawWidth = drawWidth / 2f;
129 | path.reset();
130 | path.moveTo(-halfDrawWidth, 0);
131 | float y;
132 | double scaling;
133 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
134 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
135 | y = (float) (sine(x, period, drawWidth) * amplitude * (1) * Math
136 | .pow(scaling, 3));
137 | path.lineTo(x, y);
138 | }
139 |
140 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
141 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
142 | y = (float) (sine(x, period, drawWidth) * amplitude * (-1) * Math
143 | .pow(scaling, 3));
144 | path.lineTo(x, y);
145 | }
146 |
147 | canvas.drawPath(path, paint);
148 | canvas.save();
149 | canvas.restore();
150 | }
151 |
152 | @Override
153 | protected void onDraw(Canvas canvas) {
154 | super.onDraw(canvas);
155 | canvas.translate(mCenterPointX, mCenterPointY);
156 | drawSine(canvas, mPath, mPaint, PERIOD, mDrawWidth, mAmplitude);
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewScaleWaveOpt.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Paint.Style;
10 | import android.util.AttributeSet;
11 | import android.util.DisplayMetrics;
12 | import android.util.TypedValue;
13 | import android.view.View;
14 | import android.view.WindowManager;
15 |
16 | public class VolumeViewScaleWaveOpt extends View {
17 |
18 | private Paint mPaint;
19 |
20 | private Path mPath;
21 |
22 | private float mDrawHeight;
23 |
24 | private float mDrawWidth;
25 |
26 | private float mViewHeight;
27 |
28 | private float mViewWidth;
29 |
30 | private float mCenterPointX;
31 |
32 | private float mCenterPointY;
33 |
34 | private float mAmplitude;
35 |
36 | private static final int PERIOD = 6;
37 |
38 | private static final float SINE_UP_AND_DOWN_MOVE = 1.8f;
39 |
40 | private static final float AMPLITUDE_SIZE_SCALE = 6f;
41 |
42 | public VolumeViewScaleWaveOpt(Context context, AttributeSet attrs) {
43 | super(context, attrs);
44 | init();
45 | }
46 |
47 | private void init() {
48 | mPath = new Path();
49 | mPaint = new Paint();
50 | mPaint.setAntiAlias(true);
51 | mPaint.setDither(true);
52 | mPaint.setStrokeWidth(2);
53 | mPaint.setStyle(Style.FILL);
54 | mPaint.setColor(Color.WHITE);
55 | }
56 |
57 | @Override
58 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
59 | widthMeasureSpec = measureWidth(widthMeasureSpec);
60 | heightMeasureSpec = measureHeight(heightMeasureSpec);
61 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
62 |
63 | int viewWidth = getWidth();
64 | int viewHeight = getHeight();
65 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
66 | return;
67 | }
68 | mViewWidth = viewWidth;
69 | mViewHeight = viewHeight;
70 |
71 | int paddingLeft = getPaddingLeft();
72 | int paddingRight = getPaddingRight();
73 | int paddingTop = getPaddingTop();
74 | int paddingBottom = getPaddingBottom();
75 |
76 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
77 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
78 |
79 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
80 | mCenterPointY = paddingTop + mDrawHeight / 2f;
81 |
82 | mAmplitude = mDrawHeight / AMPLITUDE_SIZE_SCALE;
83 | }
84 |
85 | private int measureWidth(int spec) {
86 | int mode = MeasureSpec.getMode(spec);
87 | if (mode == MeasureSpec.UNSPECIFIED) {
88 | WindowManager wm = (WindowManager) getContext().getSystemService(
89 | Context.WINDOW_SERVICE);
90 |
91 | @SuppressWarnings("deprecation")
92 | int width = wm.getDefaultDisplay().getWidth();
93 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
94 | } else if (mode == MeasureSpec.AT_MOST) {
95 | int value = MeasureSpec.getSize(spec);
96 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
97 | }
98 | return spec;
99 | }
100 |
101 | private int measureHeight(int spec) {
102 | int mode = MeasureSpec.getMode(spec);
103 | if (mode == MeasureSpec.EXACTLY) {
104 | return spec;
105 | }
106 |
107 | int height = (int) dip2px(50); // 其他模式下的最大高度
108 |
109 | if (mode == MeasureSpec.AT_MOST) {
110 | int preValue = MeasureSpec.getSize(spec);
111 | if (preValue < height) {
112 | height = preValue;
113 | }
114 | }
115 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
116 | return spec;
117 | }
118 |
119 | private float dip2px(float dp) {
120 | DisplayMetrics dm = getResources().getDisplayMetrics();
121 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
122 | }
123 |
124 | private double sine(float x, int period, float drawWidth) {
125 | return Math.sin(2 * Math.PI * period * x / drawWidth);
126 | }
127 |
128 |
129 |
130 | private void drawSine(Canvas canvas, Path path, Paint paint, int period,
131 | float drawWidth, float amplitude) {
132 | float halfDrawWidth = drawWidth / 2f;
133 | path.reset();
134 | path.moveTo(-halfDrawWidth, 0);
135 | float y;
136 | double scaling;
137 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
138 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
139 | y = (float) ((sine(x, period, drawWidth) + SINE_UP_AND_DOWN_MOVE) * amplitude * (1) * Math
140 | .pow(scaling, 3));
141 | path.lineTo(x, y);
142 | }
143 |
144 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
145 | scaling = 1 - Math.pow(x / halfDrawWidth, 2);// 对y进行缩放
146 | y = (float) ((sine(x, period, drawWidth) + SINE_UP_AND_DOWN_MOVE) * amplitude * (-1) * Math
147 | .pow(scaling, 3));
148 | path.lineTo(x, y);
149 | }
150 |
151 | canvas.drawPath(path, paint);
152 | canvas.save();
153 | canvas.restore();
154 | }
155 |
156 | @Override
157 | protected void onDraw(Canvas canvas) {
158 | super.onDraw(canvas);
159 | canvas.translate(mCenterPointX, mCenterPointY);
160 | drawSine(canvas, mPath, mPaint, PERIOD, mDrawWidth, mAmplitude);
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewSine.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.Paint.Style;
9 | import android.util.AttributeSet;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 |
15 | public class VolumeViewSine extends View {
16 |
17 | private Paint mPaint;
18 |
19 | private Path mPath;
20 |
21 | private float mDrawHeight;
22 |
23 | private float mDrawWidth;
24 |
25 | private float mViewHeight;
26 |
27 | private float mViewWidth;
28 |
29 | private float mCenterPointX;
30 |
31 | private float mCenterPointY;
32 |
33 | private float mAmplitude;
34 |
35 | private static final int PERIOD = 3;
36 |
37 | public VolumeViewSine(Context context, AttributeSet attrs) {
38 | super(context, attrs);
39 | init();
40 | }
41 |
42 | private void init() {
43 | mPath = new Path();
44 | mPaint = new Paint();
45 | mPaint.setAntiAlias(true);
46 | mPaint.setDither(true);
47 | mPaint.setStrokeWidth(2);
48 | mPaint.setStyle(Style.STROKE);
49 | mPaint.setColor(Color.WHITE);
50 | }
51 |
52 | @Override
53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
54 | widthMeasureSpec = measureWidth(widthMeasureSpec);
55 | heightMeasureSpec = measureHeight(heightMeasureSpec);
56 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
57 |
58 | int viewWidth = getWidth();
59 | int viewHeight = getHeight();
60 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
61 | return;
62 | }
63 | mViewWidth = viewWidth;
64 | mViewHeight = viewHeight;
65 |
66 | int paddingLeft = getPaddingLeft();
67 | int paddingRight = getPaddingRight();
68 | int paddingTop = getPaddingTop();
69 | int paddingBottom = getPaddingBottom();
70 |
71 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
72 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
73 |
74 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
75 | mCenterPointY = paddingTop + mDrawHeight / 2f;
76 |
77 | mAmplitude = mDrawHeight / 2f;
78 | }
79 |
80 | private int measureWidth(int spec) {
81 | int mode = MeasureSpec.getMode(spec);
82 | if (mode == MeasureSpec.UNSPECIFIED) {
83 | WindowManager wm = (WindowManager) getContext().getSystemService(
84 | Context.WINDOW_SERVICE);
85 |
86 | @SuppressWarnings("deprecation")
87 | int width = wm.getDefaultDisplay().getWidth();
88 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
89 | } else if (mode == MeasureSpec.AT_MOST) {
90 | int value = MeasureSpec.getSize(spec);
91 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
92 | }
93 | return spec;
94 | }
95 |
96 | private int measureHeight(int spec) {
97 | int mode = MeasureSpec.getMode(spec);
98 | if (mode == MeasureSpec.EXACTLY) {
99 | return spec;
100 | }
101 |
102 | int height = (int) dip2px(50); // 其他模式下的最大高度
103 |
104 | if (mode == MeasureSpec.AT_MOST) {
105 | int preValue = MeasureSpec.getSize(spec);
106 | if (preValue < height) {
107 | height = preValue;
108 | }
109 | }
110 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
111 | return spec;
112 | }
113 |
114 | private float dip2px(float dp) {
115 | DisplayMetrics dm = getResources().getDisplayMetrics();
116 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
117 | }
118 |
119 | private double sine(float x, int period, float drawWidth) {
120 | return Math.sin(2 * Math.PI * period * x / drawWidth);
121 | }
122 |
123 | private void drawSine(Canvas canvas, Path path, Paint paint, int period, float drawWidth, float amplitude) {
124 | float halfDrawWidth = drawWidth / 2f;
125 | path.reset();
126 | path.moveTo(-halfDrawWidth, 0);
127 | float y;
128 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
129 | y = (float) sine(x, period, drawWidth) * amplitude;
130 | path.lineTo(x, y);
131 | }
132 | canvas.drawPath(path, paint);
133 | canvas.save();
134 | canvas.restore();
135 | }
136 |
137 | @Override
138 | protected void onDraw(Canvas canvas) {
139 | super.onDraw(canvas);
140 | canvas.translate(mCenterPointX, mCenterPointY);
141 | drawSine(canvas, mPath, mPaint, PERIOD, mDrawWidth, mAmplitude);
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/src/com/example/volumeview/VolumeViewWave.java:
--------------------------------------------------------------------------------
1 | package com.example.volumeview;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.Paint.Style;
9 | import android.util.AttributeSet;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 |
15 | public class VolumeViewWave extends View {
16 |
17 | private Paint mPaint;
18 |
19 | private Path mPath;
20 |
21 | private float mDrawHeight;
22 |
23 | private float mDrawWidth;
24 |
25 | private float mViewHeight;
26 |
27 | private float mViewWidth;
28 |
29 | private float mCenterPointX;
30 |
31 | private float mCenterPointY;
32 |
33 | private float mAmplitude;
34 |
35 | private static final int PERIOD = 3;
36 |
37 | public VolumeViewWave(Context context, AttributeSet attrs) {
38 | super(context, attrs);
39 | init();
40 | }
41 |
42 | private void init() {
43 | mPath = new Path();
44 | mPaint = new Paint();
45 | mPaint.setAntiAlias(true);
46 | mPaint.setDither(true);
47 | mPaint.setStrokeWidth(2);
48 | mPaint.setStyle(Style.FILL);
49 | mPaint.setColor(Color.WHITE);
50 | }
51 |
52 | @Override
53 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
54 | widthMeasureSpec = measureWidth(widthMeasureSpec);
55 | heightMeasureSpec = measureHeight(heightMeasureSpec);
56 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
57 |
58 | int viewWidth = getWidth();
59 | int viewHeight = getHeight();
60 | if (mViewWidth == viewWidth && mViewHeight == viewHeight) {
61 | return;
62 | }
63 | mViewWidth = viewWidth;
64 | mViewHeight = viewHeight;
65 |
66 | int paddingLeft = getPaddingLeft();
67 | int paddingRight = getPaddingRight();
68 | int paddingTop = getPaddingTop();
69 | int paddingBottom = getPaddingBottom();
70 |
71 | mDrawWidth = mViewWidth - paddingLeft - paddingRight;
72 | mDrawHeight = mViewHeight - paddingTop - paddingBottom;
73 |
74 | mCenterPointX = paddingLeft + mDrawWidth / 2f;
75 | mCenterPointY = paddingTop + mDrawHeight / 2f;
76 |
77 | mAmplitude = mDrawHeight / 2f;
78 | }
79 |
80 | private int measureWidth(int spec) {
81 | int mode = MeasureSpec.getMode(spec);
82 | if (mode == MeasureSpec.UNSPECIFIED) {
83 | WindowManager wm = (WindowManager) getContext().getSystemService(
84 | Context.WINDOW_SERVICE);
85 |
86 | @SuppressWarnings("deprecation")
87 | int width = wm.getDefaultDisplay().getWidth();
88 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
89 | } else if (mode == MeasureSpec.AT_MOST) {
90 | int value = MeasureSpec.getSize(spec);
91 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
92 | }
93 | return spec;
94 | }
95 |
96 | private int measureHeight(int spec) {
97 | int mode = MeasureSpec.getMode(spec);
98 | if (mode == MeasureSpec.EXACTLY) {
99 | return spec;
100 | }
101 |
102 | int height = (int) dip2px(50); // 其他模式下的最大高度
103 |
104 | if (mode == MeasureSpec.AT_MOST) {
105 | int preValue = MeasureSpec.getSize(spec);
106 | if (preValue < height) {
107 | height = preValue;
108 | }
109 | }
110 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
111 | return spec;
112 | }
113 |
114 | private float dip2px(float dp) {
115 | DisplayMetrics dm = getResources().getDisplayMetrics();
116 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
117 | }
118 |
119 | private double sine(float x, int period, float drawWidth) {
120 | return Math.sin(2 * Math.PI * period * x / drawWidth);
121 | }
122 |
123 | private void drawSine(Canvas canvas, Path path, Paint paint, int period, float drawWidth, float amplitude) {
124 | float halfDrawWidth = drawWidth / 2f;
125 | path.reset();
126 | path.moveTo(-halfDrawWidth, 0);
127 | float y;
128 | for (float x = -halfDrawWidth; x <= halfDrawWidth; x++) {
129 | y = (float) sine(x, period, drawWidth) * amplitude * (1);
130 | path.lineTo(x, y);
131 | }
132 |
133 | for (float x = halfDrawWidth; x >= -halfDrawWidth; x--) {
134 | y = (float) sine(x, period, drawWidth) * amplitude * (-1);
135 | path.lineTo(x, y);
136 | }
137 |
138 | canvas.drawPath(path, paint);
139 | canvas.save();
140 | canvas.restore();
141 | }
142 |
143 | @Override
144 | protected void onDraw(Canvas canvas) {
145 | super.onDraw(canvas);
146 | canvas.translate(mCenterPointX, mCenterPointY);
147 | drawSine(canvas, mPath, mPaint, PERIOD, mDrawWidth, mAmplitude);
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------