├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── jaygoo
│ │ └── wavelineview
│ │ ├── App.java
│ │ ├── LeakTestActivity.java
│ │ ├── MainActivity.java
│ │ └── WaveMp3Recorder.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── blog.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── jaygoo
│ │ └── widget
│ │ └── wlv
│ │ ├── RenderView.java
│ │ └── WaveLineView.java
│ └── res
│ └── values
│ └── attrs.xml
├── pictures
├── logo.jpg
├── 优化前效果图.jpg
├── 优化后效果图.png
├── 内存泄漏图.png
├── 效果.gif
├── 曲线函数图.png
├── 衰减函数图.png
└── 静态效果图.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 | androidTest/
22 | test/
23 | # Local configuration file (sdk path, etc)
24 | local.properties
25 |
26 | # Proguard folder generated by Eclipse
27 | proguard/
28 |
29 | # Log Files
30 | *.log
31 |
32 | # Android Studio Navigation editor temp files
33 | .navigation/
34 |
35 | # Android Studio captures folder
36 | captures/
37 |
38 | # Intellij
39 | *.iml
40 | .idea/workspace.xml
41 | .idea/tasks.xml
42 | .idea/gradle.xml
43 | .idea/dictionaries
44 | .idea/libraries
45 |
46 | # Keystore files
47 | *.jks
48 |
49 | # External native build folder generated in Android Studio 2.2 and later
50 | .externalNativeBuild
51 |
52 | # Google Services (e.g. APIs or Firebase)
53 | google-services.json
54 |
55 | # Freeline
56 | freeline.py
57 | freeline/
58 | freeline_project_description.json
59 |
60 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WaveLineView
2 | ## 一款内存友好的录音漂亮的波浪动画
3 |
4 | # 效果图(实际效果更好)
5 |
6 | 
7 |
8 | ----------
9 |
10 | # Usage
11 | ## Step1
12 | ```
13 | allprojects {
14 | repositories {
15 | ...
16 | maven { url 'https://jitpack.io' }
17 | }
18 | }
19 |
20 | dependencies {
21 | compile 'com.github.Jay-Goo:WaveLineView:v1.0.4'
22 | }
23 | ```
24 | ## Step2
25 |
26 | ```
27 |
34 | ```
35 | ## Step3
36 |
37 | ```
38 | waveLineView.startAnim();
39 |
40 | waveLineView.stopAnim();
41 | ```
42 |
43 | ```
44 | @Override
45 | protected void onResume() {
46 | super.onResume();
47 | waveLineView.onResume();
48 | }
49 |
50 | @Override
51 | protected void onPause() {
52 | super.onPause();
53 | waveLineView.onPause();
54 | }
55 |
56 | @Override
57 | protected void onDestroy() {
58 | super.onDestroy();
59 | waveLineView.release();
60 | }
61 | ```
62 |
63 | ----------
64 | # Attributes
65 | attr | format | description
66 | -------- | ---|---
67 | backgroundColor|color|背景色
68 | wlvLineColor|color|波浪线的颜色
69 | wlvThickLineWidth|dimension|中间粗波浪曲线的宽度
70 | wlvFineLineWidth|dimension|三条细波浪曲线的宽度
71 | wlvMoveSpeed|float|波浪线移动的速度,默认值为290F,方向从左向右,你可以使用负数改变移动方向
72 | wlvSamplingSize|integer|采样率,动画效果越大越精细,默认64
73 | wlvSensibility|integer|灵敏度,范围[1,10],越大越灵敏,默认值为5
74 |
75 | ## [原理讲解传送门](https://github.com/Jay-Goo/WaveLineView/blob/master/blog.md)
76 |
77 | ## 联系我
78 |
79 | - Email: 1015121748@qq.com
80 | - QQ Group: 573830030 有时候工作很忙没空看邮件和Issue,大家可以通过QQ群联系我
81 |
82 |
83 |
84 |
85 | ## 一杯咖啡
86 |
87 | 大家都知道开源是件很辛苦的事情,这个项目也是我工作之余完成的,平时工作很忙,但大家提的需求基本上我都尽量满足,如果这个项目帮助你节省了大量时间,你很喜欢,你可以给我一杯咖啡的鼓励,不在于钱多钱少,关键是你的这份鼓励所带给我的力量~
88 |
89 |
90 |
91 |
92 | # 致谢
93 | [Bugly—以Tencent OS录音机波形动画为实例](https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653577211&idx=1&sn=2619c7df79f675e45e87891b7eb17669&scene=4#wechat_redirect)
94 |
95 | [DrkCore—以Tencent OS录音机波形为例](http://blog.csdn.net/drkcore/article/details/51822818)
96 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "jaygoo.wavelineview"
8 | minSdkVersion 16
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 |
25 | compile 'com.android.support:appcompat-v7:25.3.1'
26 | compile project(path: ':library')
27 | compile 'com.github.Jay-Goo:AndroidMP3Recorder:v1.0.7'
28 | //内存泄漏检测
29 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
30 | releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
31 | testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
32 | }
33 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mac/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/jaygoo/wavelineview/App.java:
--------------------------------------------------------------------------------
1 | package jaygoo.wavelineview;
2 |
3 | import android.app.Application;
4 |
5 | import com.squareup.leakcanary.LeakCanary;
6 |
7 |
8 | /**
9 | * ================================================
10 | * 作 者:JayGoo
11 | * 版 本:
12 | * 创建日期:2017/7/28
13 | * 描 述:
14 | * ================================================
15 | */
16 | public class App extends Application {
17 | @Override
18 | public void onCreate() {
19 | super.onCreate();
20 | if (LeakCanary.isInAnalyzerProcess(this)) {
21 | return;
22 | }
23 | LeakCanary.install(this);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/jaygoo/wavelineview/LeakTestActivity.java:
--------------------------------------------------------------------------------
1 | package jaygoo.wavelineview;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 |
8 | import jaygoo.widget.wlv.WaveLineView;
9 |
10 | /**
11 | * ================================================
12 | * 作 者:JayGoo
13 | * 版 本:
14 | * 创建日期:2017/7/31
15 | * 描 述:
16 | * ================================================
17 | */
18 | public class LeakTestActivity extends Activity{
19 | private WaveLineView waveLineView;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 | waveLineView = (WaveLineView) findViewById(R.id.waveLineView);
26 |
27 | findViewById(R.id.startBtn).setOnClickListener(new View.OnClickListener() {
28 | @Override
29 | public void onClick(View v) {
30 | waveLineView.startAnim();
31 | }
32 | });
33 |
34 | findViewById(R.id.stopBtn).setOnClickListener(new View.OnClickListener() {
35 | @Override
36 | public void onClick(View v) {
37 | waveLineView.stopAnim();
38 |
39 | }
40 | });
41 |
42 | findViewById(R.id.leakTestBtn).setOnClickListener(new View.OnClickListener() {
43 | @Override
44 | public void onClick(View v) {
45 | finish();
46 | startActivity(new Intent(LeakTestActivity.this,MainActivity.class));
47 | }
48 | });
49 | }
50 |
51 |
52 | @Override
53 | protected void onResume() {
54 | super.onResume();
55 | waveLineView.onResume();
56 | }
57 |
58 | @Override
59 | protected void onPause() {
60 | super.onPause();
61 | waveLineView.onPause();
62 | }
63 |
64 | @Override
65 | protected void onDestroy() {
66 | super.onDestroy();
67 | waveLineView.release();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/jaygoo/wavelineview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package jaygoo.wavelineview;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.util.Log;
7 | import android.view.View;
8 |
9 | import jaygoo.widget.wlv.WaveLineView;
10 |
11 | public class MainActivity extends AppCompatActivity {
12 |
13 | private WaveLineView waveLineView;
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 | waveLineView = (WaveLineView) findViewById(R.id.waveLineView);
20 |
21 | findViewById(R.id.startBtn).setOnClickListener(new View.OnClickListener() {
22 | @Override
23 | public void onClick(View v) {
24 | waveLineView.startAnim();
25 | }
26 | });
27 |
28 | findViewById(R.id.stopBtn).setOnClickListener(new View.OnClickListener() {
29 | @Override
30 | public void onClick(View v) {
31 | waveLineView.stopAnim();
32 |
33 | }
34 | });
35 |
36 | findViewById(R.id.leakTestBtn).setOnClickListener(new View.OnClickListener() {
37 | @Override
38 | public void onClick(View v) {
39 | finish();
40 | startActivity(new Intent(MainActivity.this,LeakTestActivity.class));
41 | }
42 | });
43 | }
44 |
45 | @Override
46 | public void onWindowFocusChanged(boolean hasFocus) {
47 | super.onWindowFocusChanged(hasFocus);
48 | if (hasFocus){
49 | // waveLineView.justDrawBackground();
50 | }
51 | }
52 |
53 | @Override
54 | protected void onResume() {
55 | super.onResume();
56 | waveLineView.onResume();
57 |
58 | }
59 |
60 | @Override
61 | protected void onPause() {
62 | super.onPause();
63 | waveLineView.onPause();
64 | }
65 |
66 | @Override
67 | protected void onDestroy() {
68 | super.onDestroy();
69 | waveLineView.release();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/jaygoo/wavelineview/WaveMp3Recorder.java:
--------------------------------------------------------------------------------
1 | package jaygoo.wavelineview;
2 |
3 | import android.content.Context;
4 | import android.os.Environment;
5 | import android.os.Handler;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 |
9 | import com.czt.mp3recorder.MP3Recorder;
10 |
11 | import java.io.File;
12 |
13 | import jaygoo.widget.wlv.WaveLineView;
14 |
15 | /**
16 | * ================================================
17 | * 作 者:JayGoo
18 | * 版 本:
19 | * 创建日期:2017/7/18
20 | * 描 述:
21 | * ================================================
22 | */
23 | public class WaveMp3Recorder extends WaveLineView {
24 | private String RECORD_FILE_DIR = Environment.getExternalStorageDirectory()+"/";
25 | private String recordFileName = "WaveLineViewTest.mp3";
26 | private MP3Recorder mp3Recorder;
27 | private long maxRecordTime = 1000 * 60 * 60 * 24;
28 | private long recordTime = 0;
29 | private int UPDATE_TIME = 200;
30 | private OnRecordStateChangeListener mOnRecordStateChangeListener;
31 | private Handler mHandler = new Handler();
32 |
33 | public WaveMp3Recorder(Context context) {
34 | this(context,null);
35 | }
36 |
37 | public WaveMp3Recorder(Context context, AttributeSet attrs) {
38 | this(context, attrs,0);
39 | }
40 |
41 | public WaveMp3Recorder(Context context, AttributeSet attrs, int defStyleAttr) {
42 | super(context, attrs, defStyleAttr);
43 | initRecorder();
44 | }
45 |
46 | private void updateRecordingUI(){
47 | if (mp3Recorder != null) {
48 | setVolume(100 * mp3Recorder.getVolume() / mp3Recorder.getMaxVolume());
49 | }
50 | }
51 |
52 | private void updateStopRecordUI(){
53 | stopAnim();
54 | }
55 |
56 | private void updateStartRecordUI(){
57 | startAnim();
58 | }
59 |
60 | /**
61 | * 录音更新进度条
62 | */
63 | private Runnable mRecordProgressTask = new Runnable() {
64 | public void run() {
65 |
66 | //录音时间超出最大时间,自动停止
67 | if (recordTime > maxRecordTime){
68 | stopRecord(true);
69 | }else {
70 | updateRecordingUI();
71 | if (mHandler != null) {
72 | mHandler.postDelayed(mRecordProgressTask, UPDATE_TIME);
73 | }
74 | }
75 | }
76 | };
77 |
78 | public void initRecorder(){
79 | File recordFile = new File(RECORD_FILE_DIR, recordFileName);
80 | mp3Recorder = new MP3Recorder(recordFile);
81 | mp3Recorder.setDefaultLameMp3BitRate(96);
82 | }
83 |
84 | public void startRecording(){
85 | try {
86 | mp3Recorder.start();
87 | if (mHandler != null) {
88 | mHandler.post(mRecordProgressTask);
89 | }
90 | } catch (Exception e) {
91 | e.printStackTrace();
92 | }
93 |
94 | }
95 |
96 |
97 | //停止录音按钮状态
98 | public void stopRecord(boolean isFromUser){
99 | if (mHandler != null) {
100 | mHandler.removeCallbacksAndMessages(null);
101 | }
102 | try {
103 | recordTime = 0;
104 | updateStopRecordUI();
105 | if (mp3Recorder != null) {
106 | mp3Recorder.stop();
107 | }
108 | }catch (Exception e){
109 | e.printStackTrace();
110 | }
111 |
112 |
113 | if (mOnRecordStateChangeListener != null){
114 | mOnRecordStateChangeListener.onStopRecord(getRecordFile(),isFromUser);
115 | }
116 | }
117 |
118 | public void stopRecord(){
119 | stopRecord(false);
120 | }
121 |
122 | //开始录音
123 | public void startRecord(){
124 | updateStartRecordUI();
125 | try {
126 | mp3Recorder.start();
127 | if (mHandler != null) {
128 | mHandler.post(mRecordProgressTask);
129 | }
130 | } catch (Exception e) {
131 | e.printStackTrace();
132 | }
133 |
134 | if (mOnRecordStateChangeListener != null){
135 | mOnRecordStateChangeListener.onStartRecord();
136 | }
137 | }
138 |
139 | //是否在录音
140 | public boolean isRecording(){
141 | if (mp3Recorder != null){
142 | return mp3Recorder.isRecording();
143 | }
144 | return false;
145 | }
146 |
147 | //设置录音的最大时间
148 | public void setMaxRecordTime(long millis){
149 | maxRecordTime = millis;
150 | }
151 |
152 | //获取录音文件
153 | public File getRecordFile(){
154 | return new File(RECORD_FILE_DIR, recordFileName);
155 | }
156 |
157 | public String getRecordFilePath(){
158 | return RECORD_FILE_DIR + "/"+recordFileName;
159 | }
160 |
161 | public MP3Recorder getMp3Recorder(){
162 | return mp3Recorder;
163 | }
164 |
165 |
166 | //录音状态监听
167 | public interface OnRecordStateChangeListener{
168 | void onStartRecord();
169 | void onStopRecord(File recordFile, boolean isFromUser);
170 | }
171 |
172 | public void setOnRecordStateChangeListener(OnRecordStateChangeListener listener){
173 | mOnRecordStateChangeListener = listener;
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
18 |
19 |
20 |
28 |
29 |
37 |
38 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WaveLineView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/blog.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 | # 前言
5 | 本文实战性较强,主要目的是通过一个自定义控件的开发,引出我对自定义控件性能优化的一些思考和实践,欢迎各位喜欢移动开发的小伙伴来拍砖~
6 |
7 | 本文由于篇幅有限,只讲解思路,并没有放出大量源代码,如果对本项目感兴趣,文末会放出Demo,可以自行去Github上fork和star。
8 |
9 |
10 | ----------
11 |
12 |
13 | # 动画效果
14 | 这是最近正在开发功能里的一个录音控件,我们的UI设计说做成某软件的效果,于是仿照它做了一个,相似度还是很高的:
15 |
16 | 
17 |
18 | ----------
19 |
20 |
21 | # 知识储备
22 | 众所周知,一般自绘动画我们都是在View中实现的,一般会重写onMeasure(测量)、onLayout(布局)、onDraw(绘制)三个方法。
23 |
24 | Android系统为了简化线程开发,将这三个过程都放在主线程中执行,以保证绘制系统的线程安全。
25 |
26 | 整个绘制过程通过一个Choreographer定时器驱动调用更新,每16ms会刷新一次,通过树状结构存储的 ViewGroup,依次递归的调用到每个 View 的 onMeasure、onLayout、onDraw 方法,从而最后将每个 View 都绘制出来(为了保证绘制效率,并不是每个View的这些方法每个绘制周期都会调用,那些没有变化的不会被重绘)。
27 |
28 | 但是由于普通的 View 都处于主线程中,Android 除了绘制之外,在主线程中还需要处理用户的各种点击事件。很多情况,在主线程中还需要运行额外的用户处理逻辑、轮询消息事件等。 如果主线程过于繁忙,不能及时的处理和响应用户的输入,会让用户的体验急剧降低。如果更严重的情况,当主线程延迟时间达到5s的时候,还会触发 ANR(Application Not Responding)。 如果界面的绘制和动画比较复杂,计算量比较大的情况,就不再适合使用 View 这种方式来绘制了。
29 |
30 | Android考虑到这种场景,提出了SurfaceView机制,它可以在非主线程进行图形绘制,释放了主线程的压力,所以我们可以把View的绘制放到SurfaceView中完成。如果对SurfaceView不太熟悉,可以自行百度,或参看demo中封装好的RenderView,这里由于篇幅限制,这里就不作详细介绍了。
31 |
32 |
33 | ----------
34 |
35 |
36 | # 动画实现
37 | 看了一下要实现的效果,感觉是由四条振幅不等的正弦曲线组成,这些曲线振幅中间比较高,两边比较低,应该有一个对称的衰减系数,然后这些曲线根据声音的大小上下波动,保持一个速率向右运动。
38 |
39 | 这里再取回还给高中老师的数学知识,下面是正弦曲线的公式:y=Asin(ωx+φ)+k,其中A 代表的是振幅,对应的波峰和波谷的高度,即 y 轴上的距离;ω 是角速度,换成频率是 2πf,能够控制波形的宽度;φ 是初始相位,能够决定正弦曲线的初始 x 轴位置;k 是偏距,能够控制在 y 轴上的偏移量。
40 |
41 | 那么我们只需根据时间改变φ,那么曲线就可以实现移动,通过一个对称衰减函数乘以A,就可实现曲线衰减变化,通过改变A的值可以实现上下波动。恩,完美,开干!
42 |
43 | 这里我要推荐大家一个绘图网站 [https://www.desmos.com/calculator](https://www.desmos.com/calculator) ,它可以帮你将函数转换成相应的图形,十分方便。
44 | 函数很简单,正弦就行,主要是衰减函数的选取,这里要找一个对称的衰减函数,如图所示:
45 | 
46 | 我们只要将每个点的x分别映射到衰减函数的一个对称区间,根据函数计算出相应的衰减系数,就可以实现振幅不同的波动曲线了。
47 | 最后通过一些调整,我们可以大致可以得出和目标效果图相似的曲线:
48 | 
49 | 接下来,我们只需要在 SurfaceView 中使用 Path,通过上面的公式计算出一个个的点,然后画直线连接起来就行啦!效果如图所示:
50 | 
51 |
52 | 然后就是让它动起来了,前面也说了,可以根据时间改变曲线的相位值φ来实现移动,我在封装好的RenderView中实现了一个叫做onRender的方法,它主要是代替onDraw工作的,我们传入时间millisPassed,定义位移系数offsetSpeed,那么相位值φ = π * millisPassed / offsetSpeed,每次渲染周期都将φ代入函数就可以让曲线实现位移效果了。最后再给一个volume变量,volume乘以一个初始振幅amplitude和根据横坐标算出的衰减系数,作为纵坐标,便实现了曲线形状和根据声音大小波动的效果。
53 |
54 | 这里再总结下大致实现步骤:
55 | - 计算出函数曲线和对称衰减函数
56 | - 根据函数计算出需要绘制的点,通过Path连线
57 | - 根据时间改变曲线的φ,实现曲线位移效果
58 | - 根据volume和衰减函数改变振幅,实现曲线上下波动
59 |
60 | # 动画优化
61 | 你以为效果实现就完事那就大错特错了!当我把动画运行到一个性能较差的低端机时,看到CPU的占有率达到30%多,有时候还会一卡一卡的时候,惊呆了……和想象的不一样啊!到底是哪里出了问题呢?
62 | 我们可以通过Android Studio的Monitor工具的CPU method trace查看到底是哪些方法占用了我的CPU:
63 |
64 | 
65 |
66 | 从CPU trace中可以看出calcValue和path的lineTo方法很耗时,占了一半的CPU时间,那么有什么方法可以降低呢?
67 |
68 | ## 降低绘制密度
69 | Ok,让我们review下绘制过程,第二步的时候我们需要计算曲线的点,然后通过Path连线这些点,而现在的手机屏幕1960x1080已经成为了标配,如果我们把宽度的像素点叫做采样点,每次我们要把每个采样点的x代入函数求出y,然后调用lineTo连线,那么我们每16ms都需要做出大量的计算。
70 |
71 | 但是事实上人的肉眼是有一定容忍度的,特别是快速运动的动画,一些失真的地方,肉眼很难分辨,所以我们没必要把1080个点每个都算出来,经过试验发现我们只要在60个以上的采样点,效果还是十分的平滑,粗略计算,这样做可以将计算量减少到原来的1/16,于是可以释放大量的CPU时间(由于采样点的减少,图形会出现锯齿,我们可以通过Paint的抗锯齿属性优化)。
72 |
73 | 总结:通过动态调节自定义的绘制密度,在绘制密度与最终实现效果中找到一个平衡点(即不影响最后的视觉效果,同时还能最大限度的减少计算量),这个是最直接,也最简单的优化方法。
74 |
75 |
76 |
77 | ## 减少重复实时计算量
78 | 虽然现在的设备的CPU已经足够强大,但是由于每16ms中,系统要做大量工作,为了保证动画流畅稳定,我们还是要尽量的减少一些重复的计算。
79 |
80 | 最常用的方法就是使用查表法,利用空间换时间(注意把握空间和时间的关系,莫要一味追求时间而牺牲大量内存空间,那么就得不偿失了)。
81 |
82 | 学过计算机的都知道,CPU在计算加减乘是非常快的,但是除法是比较慢的,特别是浮点数除法,我们可以将这些浮点运算转换成整数除法,除数、被除数乘以一个统一的精确度,用到时再除以精确度,这个方法在大量浮点计算时是很有效的,但是注意处理整形溢出。另外还要避免一些乘方、开平方根等运算的重复计算。
83 |
84 | 就本例来讲,calcValue方法是为了计算每个点代表的衰减系数,但其实我们计算衰减函数的时候对于每次固定的x,我们算出的衰减系数都是一样,这就会产生大量重复的计算。我们可以把这些计算好的值直接放入表中,然后通过查表的方式,下次就不需要重复计算这些复杂运算了。关于存储,如果数量不是很多建议使用SparseArray,它可以避免自动装箱,节约不少时间。理论上是这样的,但其实由于本例的衰减函数不是很复杂,这种做法的优化空间并不是很大,而且由于前面已经降低了绘制密度,已经减少了大量的计算,统计了下,耗时节约了几ms左右,但这确实也是一个优化的好方向,特别是一些复杂的运算,还是很有意义的。
85 |
86 | 总结:尽量减少重复运算,对重复复杂的计算,可以适当使用空间换时间。尽量减少浮点数除法运算。
87 |
88 | 经过前两步的优化,再看一下目前的CPU trace,发现已经降低了很多,动画也流畅了起来。
89 |
90 | 
91 |
92 | ## 内存泄漏
93 | CPU占有率降下去了,动画也流畅了,不过还有问题需要特别注意,那就是内存泄漏。
94 |
95 | Android 在内存分配和释放方面,采用了 JAVA 的垃圾回收 GC 模式。 当分配的内存不再使用的时候,系统会定时帮我们自动清理。这给我们应用开发带来了极大的便利,我们从此不再需要过多的关注内存的分配与回收,也因此减少很多内存使用的风险。但是由于一些不正确的操作,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了,内存泄漏时导致程序OOM的原因之一,而OOM就意味着Crash。
96 |
97 | 常见的内存泄漏检测方法是通过手动GC以及监听Java heap的情况,通过查看Reference Tree的层级确定是否内存泄漏,通过MAT工具,分析具体泄漏原因,但是,不得不说,这个方法确实很复杂,如果不是很熟练,很难发现隐藏的一些内存泄漏,这里推荐使用LeakCanary,通过代码接入的方式,监听内存泄漏,它会以插件的形式伴随程序一起,如果发生内存泄漏,LeakCanary会给出泄漏的层级,十分清晰。
98 |
99 | 就本例,我通过多次创建销毁Activity,检测程序是否发生了内存泄漏。
100 | 结果LeakCanary提示程序发生了内存泄漏,如图所示
101 | 
102 |
103 | RenderThread持有了Activity的隐式context,导致Activity不能释放资源,
104 | 追踪到代码,我们发现这样一段代码:
105 |
106 | ```
107 | public RenderThread(SurfaceHolder holder) {
108 | super("RenderThread");
109 | surfaceHolder = holder
110 | }
111 | ```
112 | 在 Java 中,非静态匿名内部类会持有其外部类的隐式引用,所以RenderThread所持有的context就是holder和它的Runable方法持有的引用,而Activity销毁时,因为Thread的持有强引用导致无法及时的释放掉内存,从而导致内存泄漏。
113 |
114 | 解决方案就是,将RenderThread改为私有的静态内部类,这样它便不会持有其外部类的引用,另外可以对surfaceHolder使用弱引用,确保GC可以及时释放掉holder。
115 |
116 | ```
117 | surfaceHolder = new WeakReference<>(holder);
118 |
119 | ```
120 | 获取surfaceHolder时可以使用(注意判空)`surfaceHolder.get()`
121 |
122 | 总结:当然内存泄露不只这一类情况,情况还有很多,百度也有一大堆,就不再累述。如果想了解更多的内存优化方面的,可以关注[胡凯的博客](http://hukai.me/blog/archives/),他从各个方面讲解性能优化,干货很多,目前我也只是消化了一部分,并应用到项目,任重而道远,有兴趣的可以关注一下。
123 |
124 | ## 优化内存
125 | 尽量减少内存的分配次数,因为每次GC都是会耗一定时间的,如果放到平时倒无所谓,但如果放到一个16ms的定时器中,如果GC的频率过高也会引起动画有卡顿感,合理的减少内存的分配次数还可以有效的避免产生内存抖动问题,优化动画体验。
126 | 这里其实已经做得不错,主要是总结下一些常用的方案:
127 |
128 | 1. 减少对象的重复创建,例如Paint,Path,Rect等
129 | 2. 减少大量临时对象的创建,对于那些无法避免,每次又必须分配的,我们可以采用对象池模型的方式来分配对象。对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。
130 | 3. 减少一些资源操作,例如getColor,这个方法中会创建多个 StringBuilder 的变量
131 |
132 | # 总结
133 | 通过一系列的优化,动画在中端手机上CPU稳定在2~3%左右,内存在2MB左右,在一些低端手机CPU占有率在控制在10%左右,内存在15MB左右(为什么内存这么高?我还没有研究),不过欣慰的是两者动画都十分流畅。
134 |
135 | 本文介绍了从需求开始,如何一步步开发一个自定义控件,并通过降低绘制密度、减少重复实时计算量、避免和解决内存泄漏、如何优化内存等四方面对控件的性能进行了优化,希望能给大家平时开发工作带来一些启发和帮助,也希望大家可以提出更多更好的优化方案~
136 |
137 | 限于笔者的水平和经验有限,本文如果有纰漏和错误的地方,欢迎大家指出。如果大家有更多更好的建议,欢迎一起分享讨论,共同进步。
138 |
139 | # Github
140 | [https://github.com/Jay-Goo/WaveLineView](https://github.com/Jay-Goo/WaveLineView)
141 | 欢迎各位Star,Mark
142 |
143 | # 致谢
144 | [Bugly—以Tencent OS录音机波形动画为实例](https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653577211&idx=1&sn=2619c7df79f675e45e87891b7eb17669&scene=4#wechat_redirect)
145 |
146 | [DrkCore—以Tencent OS录音机波形为例](http://blog.csdn.net/drkcore/article/details/51822818)
147 |
148 |
149 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.4.2'
10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | maven { url 'https://jitpack.io' }
20 | google()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jul 21 11:25:55 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | group='com.github.Jay-Goo'
4 |
5 | android {
6 | compileSdkVersion 25
7 | buildToolsVersion "25.0.2"
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 25
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 |
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | }
25 |
26 | dependencies {
27 | compile fileTree(dir: 'libs', include: ['*.jar'])
28 | compile 'com.android.support:appcompat-v7:25.3.1'
29 | }
30 |
31 | // 指定编码
32 | tasks.withType(JavaCompile) {
33 | options.encoding = "UTF-8"
34 | }
35 |
36 | // 打包源码
37 | task sourcesJar(type: Jar) {
38 | from android.sourceSets.main.java.srcDirs
39 | classifier = 'sources'
40 | }
41 |
42 | task javadoc(type: Javadoc) {
43 | failOnError false
44 | source = android.sourceSets.main.java.sourceFiles
45 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
46 | classpath += configurations.compile
47 | }
48 |
49 | // 制作文档(Javadoc)
50 | task javadocJar(type: Jar, dependsOn: javadoc) {
51 | classifier = 'javadoc'
52 | from javadoc.destinationDir
53 | }
54 |
55 | artifacts {
56 | archives sourcesJar
57 | archives javadocJar
58 | }
59 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mac/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/java/jaygoo/widget/wlv/RenderView.java:
--------------------------------------------------------------------------------
1 | package jaygoo.widget.wlv;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.util.AttributeSet;
6 | import android.view.SurfaceHolder;
7 | import android.view.SurfaceView;
8 |
9 | import java.lang.ref.WeakReference;
10 | import java.util.List;
11 |
12 | /**
13 | * ================================================
14 | * 作 者:JayGoo
15 | * 版 本:1.0.0
16 | * 创建日期:2017/7/21
17 | * 描 述: 封装的SurfaceView
18 | * ================================================
19 | */
20 | public abstract class RenderView extends SurfaceView implements SurfaceHolder.Callback {
21 |
22 | //是否正在绘制动画
23 | private boolean isStartAnim = false;
24 | private final static Object surfaceLock = new Object();
25 | private RenderThread renderThread;
26 |
27 | /**
28 | * 绘制背景,防止开始时黑屏
29 | * 子View可以执行此方法
30 | *
31 | * @param canvas
32 | */
33 | protected abstract void doDrawBackground(Canvas canvas);
34 |
35 | /**
36 | * 渲染surfaceView的回调方法。
37 | *
38 | * @param canvas 画布
39 | */
40 | protected abstract void onRender(Canvas canvas, long millisPassed);
41 |
42 | public RenderView(Context context) {
43 | this(context, null);
44 | }
45 |
46 | public RenderView(Context context, AttributeSet attrs) {
47 | this(context, attrs, 0);
48 | }
49 |
50 | public RenderView(Context context, AttributeSet attrs, int defStyleAttr) {
51 | super(context, attrs, defStyleAttr);
52 | getHolder().addCallback(this);
53 | }
54 |
55 |
56 | /*回调/线程*/
57 | private static class RenderThread extends Thread {
58 |
59 | private static final long SLEEP_TIME = 16;
60 | private WeakReference renderView;
61 | private boolean running = false;
62 | private boolean destoryed = false;
63 | private boolean isPause = false;
64 |
65 | public RenderThread(RenderView renderView) {
66 | super("RenderThread");
67 | this.renderView = new WeakReference<>(renderView);
68 | }
69 |
70 | private SurfaceHolder getSurfaceHolder() {
71 | if (getRenderView() != null) {
72 | return getRenderView().getHolder();
73 | }
74 | return null;
75 | }
76 |
77 | private RenderView getRenderView() {
78 | return renderView.get();
79 | }
80 |
81 | @Override
82 | public void run() {
83 | long startAt = System.currentTimeMillis();
84 | while (!destoryed) {
85 | synchronized (surfaceLock) {
86 |
87 | //这里并没有真正的结束Thread,防止部分手机连续调用同一Thread出错
88 | while (isPause) {
89 | try {
90 | surfaceLock.wait();
91 | } catch (InterruptedException e) {
92 | e.printStackTrace();
93 | }
94 | }
95 |
96 | if (running) {
97 | if (getSurfaceHolder() != null && getRenderView() != null) {
98 | Canvas canvas = getSurfaceHolder().lockCanvas();
99 | if (canvas != null) {
100 | getRenderView().doDrawBackground(canvas);
101 | if (getRenderView().isStartAnim) {
102 | getRenderView().render(canvas, System.currentTimeMillis() - startAt); //这里做真正绘制的事情
103 | }
104 | getSurfaceHolder().unlockCanvasAndPost(canvas);
105 | }
106 | } else {
107 | running = false;
108 | }
109 |
110 | }
111 |
112 | }
113 | try {
114 | Thread.sleep(SLEEP_TIME);
115 | } catch (InterruptedException e) {
116 | e.printStackTrace();
117 | }
118 |
119 | }
120 |
121 | }
122 |
123 |
124 | public void setRun(boolean isRun) {
125 | this.running = isRun;
126 | }
127 |
128 | }
129 |
130 |
131 | @Override
132 | public void surfaceCreated(SurfaceHolder holder) {
133 | renderer = onCreateRenderer();
134 | if (renderer != null && renderer.isEmpty()) {
135 | throw new IllegalStateException();
136 | }
137 |
138 | renderThread = new RenderThread(this);
139 | }
140 |
141 | /**
142 | * 解锁暂停,继续执行绘制任务
143 | * 默认当Resume时不自动启动动画
144 | */
145 | public void onResume() {
146 | synchronized (surfaceLock) {
147 | if (renderThread != null) {
148 | renderThread.isPause = false;
149 | surfaceLock.notifyAll();
150 | }
151 | }
152 | }
153 |
154 |
155 | //假暂停,并没有结束Thread
156 | public void onPause() {
157 | synchronized (surfaceLock) {
158 | if (renderThread != null) {
159 | renderThread.isPause = true;
160 | }
161 | }
162 | }
163 |
164 |
165 | @Override
166 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
167 | //这里可以获取SurfaceView的宽高等信息
168 | }
169 |
170 | @Override
171 | public void surfaceDestroyed(SurfaceHolder holder) {
172 | synchronized (surfaceLock) { //这里需要加锁,否则doDraw中有可能会crash
173 | renderThread.setRun(false);
174 | renderThread.destoryed = true;
175 | }
176 | }
177 |
178 | public void onWindowFocusChanged(boolean hasFocus) {
179 | if (hasFocus && isStartAnim) {
180 | startAnim();
181 | } else {
182 | startThread();
183 | }
184 | }
185 |
186 | /*绘图*/
187 | public interface IRenderer {
188 | void onRender(Canvas canvas, long millisPassed);
189 | }
190 |
191 | private List renderer;
192 |
193 | protected List onCreateRenderer() {
194 | return null;
195 | }
196 |
197 | private void render(Canvas canvas, long millisPassed) {
198 | if (renderer != null) {
199 | for (int i = 0, size = renderer.size(); i < size; i++) {
200 | renderer.get(i).onRender(canvas, millisPassed);
201 | }
202 | } else {
203 | onRender(canvas, millisPassed);
204 | }
205 | }
206 |
207 | public void startAnim() {
208 | isStartAnim = true;
209 | startThread();
210 | }
211 |
212 | private void startThread() {
213 |
214 | if (renderThread != null && !renderThread.running) {
215 | renderThread.setRun(true);
216 | try {
217 | if (renderThread.getState() == Thread.State.NEW) {
218 | renderThread.start();
219 | }
220 |
221 | } catch (Exception e) {
222 | e.printStackTrace();
223 | }
224 |
225 | }
226 | }
227 |
228 | public void stopAnim() {
229 | isStartAnim = false;
230 | if (renderThread != null && renderThread.running) {
231 | renderThread.setRun(false);
232 | renderThread.interrupt();
233 | }
234 | }
235 |
236 | public boolean isRunning() {
237 | if (renderThread != null) {
238 | return renderThread.running;
239 | }
240 | return false;
241 | }
242 |
243 | //释放相关资源,防止内存泄漏
244 | public void release() {
245 | if (getHolder() != null && getHolder().getSurface() != null) {
246 | getHolder().getSurface().release();
247 | getHolder().removeCallback(this);
248 | }
249 | }
250 |
251 | }
252 |
--------------------------------------------------------------------------------
/library/src/main/java/jaygoo/widget/wlv/WaveLineView.java:
--------------------------------------------------------------------------------
1 | package jaygoo.widget.wlv;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PixelFormat;
10 | import android.graphics.PorterDuff;
11 | import android.util.AttributeSet;
12 | import android.util.Log;
13 | import android.util.SparseArray;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | /**
19 | * ================================================
20 | * 作 者:JayGoo
21 | * 版 本:1.0.0
22 | * 创建日期:2017/7/21
23 | * 描 述: 绘制波浪曲线
24 | * ================================================
25 | */
26 | public class WaveLineView extends RenderView {
27 |
28 | private final static int DEFAULT_SAMPLING_SIZE = 64;
29 | private final static float DEFAULT_OFFSET_SPEED = 250F;
30 | private final static int DEFAULT_SENSIBILITY = 5;
31 |
32 | //采样点的数量,越高越精细,但是高于一定限度肉眼很难分辨,越高绘制效率越低
33 | private int samplingSize;
34 |
35 | //控制向右偏移速度,越小偏移速度越快
36 | private float offsetSpeed;
37 | //平滑改变的音量值
38 | private float volume = 0;
39 |
40 | //用户设置的音量,[0,100]
41 | private int targetVolume = 50;
42 |
43 | //每次平滑改变的音量单元
44 | private float perVolume;
45 |
46 | //灵敏度,越大越灵敏[1,10]
47 | private int sensibility;
48 |
49 | //背景色
50 | private int backGroundColor = Color.WHITE;
51 |
52 | //波浪线颜色
53 | private int lineColor;
54 | //粗线宽度
55 | private int thickLineWidth;
56 | //细线宽度
57 | private int fineLineWidth;
58 |
59 | private final Paint paint = new Paint();
60 |
61 | {
62 | //防抖动
63 | paint.setDither(true);
64 | //抗锯齿,降低分辨率,提高绘制效率
65 | paint.setAntiAlias(true);
66 | }
67 |
68 | private List paths = new ArrayList<>();
69 |
70 | {
71 | for (int i = 0; i < 4; i++) {
72 | paths.add(new Path());
73 | }
74 | }
75 |
76 | //不同函数曲线系数
77 | private float[] pathFuncs = {
78 | 0.6f, 0.35f, 0.1f, -0.1f
79 | };
80 |
81 | //采样点X坐标
82 | private float[] samplingX;
83 | //采样点位置映射到[-2,2]之间
84 | private float[] mapX;
85 | //画布宽高
86 | private int width, height;
87 | //画布中心的高度
88 | private int centerHeight;
89 | //振幅
90 | private float amplitude;
91 | //存储衰变系数
92 | private SparseArray recessionFuncs = new SparseArray<>();
93 | //连线动画结束标记
94 | private boolean isPrepareLineAnimEnd = false;
95 | //连线动画位移
96 | private int lineAnimX = 0;
97 | //渐入动画结束标记
98 | private boolean isPrepareAlphaAnimEnd = false;
99 | //渐入动画百分比值[0,1f]
100 | private float prepareAlpha = 0f;
101 | //是否开启准备动画
102 | private boolean isOpenPrepareAnim = false;
103 |
104 | private boolean isTransparentMode = false;
105 |
106 | public WaveLineView(Context context) {
107 | this(context, null);
108 | }
109 |
110 | public WaveLineView(Context context, AttributeSet attrs) {
111 | this(context, attrs, 0);
112 | }
113 |
114 | public WaveLineView(Context context, AttributeSet attrs, int defStyleAttr) {
115 | super(context, attrs, defStyleAttr);
116 | initAttr(attrs);
117 | }
118 |
119 | private void initAttr(AttributeSet attrs) {
120 | TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.WaveLineView);
121 | backGroundColor = t.getColor(R.styleable.WaveLineView_wlvBackgroundColor, Color.WHITE);
122 | samplingSize = t.getInt(R.styleable.WaveLineView_wlvSamplingSize, DEFAULT_SAMPLING_SIZE);
123 | lineColor = t.getColor(R.styleable.WaveLineView_wlvLineColor, Color.parseColor("#2ED184"));
124 | thickLineWidth = (int) t.getDimension(R.styleable.WaveLineView_wlvThickLineWidth, 6);
125 | fineLineWidth = (int) t.getDimension(R.styleable.WaveLineView_wlvFineLineWidth, 2);
126 | offsetSpeed = t.getFloat(R.styleable.WaveLineView_wlvMoveSpeed, DEFAULT_OFFSET_SPEED);
127 | sensibility = t.getInt(R.styleable.WaveLineView_wlvSensibility, DEFAULT_SENSIBILITY);
128 | isTransparentMode = backGroundColor == Color.TRANSPARENT;
129 | t.recycle();
130 | checkVolumeValue();
131 | checkSensibilityValue();
132 | //将RenderView放到最顶层
133 | setZOrderOnTop(true);
134 | if (getHolder() != null) {
135 | //使窗口支持透明度
136 | getHolder().setFormat(PixelFormat.TRANSLUCENT);
137 | }
138 | }
139 |
140 | @Override
141 | protected void doDrawBackground(Canvas canvas) {
142 | //绘制背景
143 | if (isTransparentMode) {
144 | //启用CLEAR模式,所绘制内容不会提交到画布上。
145 | canvas.drawColor(backGroundColor, PorterDuff.Mode.CLEAR);
146 | } else {
147 | canvas.drawColor(backGroundColor);
148 | }
149 | }
150 |
151 | private boolean isParametersNull() {
152 | if (null == samplingX || null == mapX || null == pathFuncs) {
153 | return true;
154 | }
155 | return false;
156 | }
157 |
158 | @Override
159 | protected void onRender(Canvas canvas, long millisPassed) {
160 | float offset = millisPassed / offsetSpeed;
161 |
162 | if (isParametersNull()) {
163 | initDraw(canvas);
164 | }
165 |
166 | if (lineAnim(canvas)) {
167 | resetPaths();
168 | softerChangeVolume();
169 |
170 | //波形函数的值
171 | float curY;
172 | for (int i = 0; i <= samplingSize; i++) {
173 | //双重判断确保必要参数正常
174 | if (isParametersNull()) {
175 | initDraw(canvas);
176 | if (isParametersNull()) {
177 | return;
178 | }
179 | }
180 | float x = samplingX[i];
181 | curY = (float) (amplitude * calcValue(mapX[i], offset));
182 | for (int n = 0; n < paths.size(); n++) {
183 | //四条线分别乘以不同的函数系数
184 | float realY = curY * pathFuncs[n] * volume * 0.01f;
185 | paths.get(n).lineTo(x, centerHeight + realY);
186 | }
187 | }
188 |
189 | //连线至终点
190 | for (int i = 0; i < paths.size(); i++) {
191 | paths.get(i).moveTo(width, centerHeight);
192 | }
193 |
194 | //绘制曲线
195 | for (int n = 0; n < paths.size(); n++) {
196 | if (n == 0) {
197 | paint.setStrokeWidth(thickLineWidth);
198 | paint.setAlpha((int) (255 * alphaInAnim()));
199 | } else {
200 | paint.setStrokeWidth(fineLineWidth);
201 | paint.setAlpha((int) (100 * alphaInAnim()));
202 | }
203 | canvas.drawPath(paths.get(n), paint);
204 | }
205 | }
206 |
207 | }
208 |
209 | //检查音量是否合法
210 | private void checkVolumeValue() {
211 | if (targetVolume > 100) targetVolume = 100;
212 | }
213 |
214 | //检查灵敏度值是否合法
215 | private void checkSensibilityValue() {
216 | if (sensibility > 10) sensibility = 10;
217 | if (sensibility < 1) sensibility = 1;
218 | }
219 |
220 | /**
221 | * 使曲线振幅有较大改变时动画过渡自然
222 | */
223 | private void softerChangeVolume() {
224 | //这里减去perVolume是为了防止volume频繁在targetVolume上下抖动
225 | if (volume < targetVolume - perVolume) {
226 | volume += perVolume;
227 | } else if (volume > targetVolume + perVolume) {
228 | if (volume < perVolume * 2) {
229 | volume = perVolume * 2;
230 | } else {
231 | volume -= perVolume;
232 | }
233 | } else {
234 | volume = targetVolume;
235 | }
236 |
237 | }
238 |
239 | /**
240 | * 渐入动画
241 | *
242 | * @return progress of animation
243 | */
244 | private float alphaInAnim() {
245 | if (!isOpenPrepareAnim) return 1;
246 | if (prepareAlpha < 1f) {
247 | prepareAlpha += 0.02f;
248 | } else {
249 | prepareAlpha = 1;
250 | }
251 | return prepareAlpha;
252 | }
253 |
254 | /**
255 | * 连线动画
256 | *
257 | * @param canvas
258 | * @return whether animation is end
259 | */
260 | private boolean lineAnim(Canvas canvas) {
261 | if (isPrepareLineAnimEnd || !isOpenPrepareAnim) return true;
262 | paths.get(0).moveTo(0, centerHeight);
263 | paths.get(1).moveTo(width, centerHeight);
264 |
265 | for (int i = 1; i <= samplingSize; i++) {
266 | float x = 1f * i * lineAnimX / samplingSize;
267 | paths.get(0).lineTo(x, centerHeight);
268 | paths.get(1).lineTo(width - x, centerHeight);
269 | }
270 |
271 | paths.get(0).moveTo(width / 2f, centerHeight);
272 | paths.get(1).moveTo(width / 2f, centerHeight);
273 |
274 | lineAnimX += width / 60;
275 | canvas.drawPath(paths.get(0), paint);
276 | canvas.drawPath(paths.get(1), paint);
277 |
278 | if (lineAnimX > width / 2) {
279 | isPrepareLineAnimEnd = true;
280 | return true;
281 | }
282 | return false;
283 | }
284 |
285 | /**
286 | * 重置path
287 | */
288 | private void resetPaths() {
289 | for (int i = 0; i < paths.size(); i++) {
290 | paths.get(i).rewind();
291 | paths.get(i).moveTo(0, centerHeight);
292 | }
293 | }
294 |
295 | //初始化参数
296 | private void initParameters() {
297 | lineAnimX = 0;
298 | prepareAlpha = 0f;
299 | isPrepareLineAnimEnd = false;
300 | isPrepareAlphaAnimEnd = false;
301 | samplingX = null;
302 | }
303 |
304 | @Override
305 | public void startAnim() {
306 | initParameters();
307 | super.startAnim();
308 | }
309 |
310 | @Override
311 | public void stopAnim() {
312 | super.stopAnim();
313 | clearDraw();
314 | }
315 |
316 | //清空画布所有内容
317 | public void clearDraw() {
318 | Canvas canvas = null;
319 | try {
320 | canvas = getHolder().lockCanvas(null);
321 | canvas.drawColor(backGroundColor);
322 | resetPaths();
323 | for (int i = 0; i < paths.size(); i++) {
324 | canvas.drawPath(paths.get(i), paint);
325 | }
326 | } catch (Exception e) {
327 | } finally {
328 | if (canvas != null) {
329 | getHolder().unlockCanvasAndPost(canvas);
330 | }
331 | }
332 | }
333 |
334 | //初始化绘制参数
335 | private void initDraw(Canvas canvas) {
336 | width = canvas.getWidth();
337 | height = canvas.getHeight();
338 |
339 | if (width == 0 || height == 0 || samplingSize == 0) return;
340 |
341 | centerHeight = height >> 1;
342 | //振幅为高度的1/4
343 | amplitude = height / 3.0f;
344 |
345 | //适合View的理论最大音量值,和音量不属于同一概念
346 | perVolume = sensibility * 0.35f;
347 |
348 | //初始化采样点及映射
349 | //这里因为包括起点和终点,所以需要+1
350 | samplingX = new float[samplingSize + 1];
351 | mapX = new float[samplingSize + 1];
352 | //确定采样点之间的间距
353 | float gap = width / (float) samplingSize;
354 | //采样点的位置
355 | float x;
356 | for (int i = 0; i <= samplingSize; i++) {
357 | x = i * gap;
358 | samplingX[i] = x;
359 | //将采样点映射到[-2,2]
360 | mapX[i] = (x / (float) width) * 4 - 2;
361 | }
362 |
363 | paint.setStyle(Paint.Style.STROKE);
364 | paint.setColor(lineColor);
365 | paint.setStrokeWidth(thickLineWidth);
366 | }
367 |
368 | /**
369 | * 计算波形函数中x对应的y值
370 | *
371 | * 使用稀疏矩阵进行暂存计算好的衰减系数值,下次使用时直接查找,减少计算量
372 | *
373 | * @param mapX 换算到[-2,2]之间的x值
374 | * @param offset 偏移量
375 | * @return [-1, 1]
376 | */
377 | private double calcValue(float mapX, float offset) {
378 | int keyX = (int) (mapX * 1000);
379 | offset %= 2;
380 | double sinFunc = Math.sin(Math.PI * mapX - offset * Math.PI);
381 | double recessionFunc;
382 | if (recessionFuncs.indexOfKey(keyX) >= 0) {
383 | recessionFunc = recessionFuncs.get(keyX);
384 | } else {
385 | recessionFunc = 4 / (4 + Math.pow(mapX, 4));
386 | recessionFuncs.put(keyX, recessionFunc);
387 | }
388 | return sinFunc * recessionFunc;
389 | }
390 |
391 | /**
392 | * the wave line animation move speed from left to right
393 | * you can use negative number to make the animation from right to left
394 | * the default value is 290F,the smaller, the faster
395 | *
396 | * @param moveSpeed
397 | */
398 | public void setMoveSpeed(float moveSpeed) {
399 | this.offsetSpeed = moveSpeed;
400 | }
401 |
402 |
403 | /**
404 | * User set volume, [0,100]
405 | *
406 | * @param volume
407 | */
408 | public void setVolume(int volume) {
409 | if (Math.abs(targetVolume - volume) > perVolume) {
410 | this.targetVolume = volume;
411 | checkVolumeValue();
412 | }
413 | }
414 |
415 | public void setBackGroundColor(int backGroundColor) {
416 | this.backGroundColor = backGroundColor;
417 | this.isTransparentMode = (backGroundColor == Color.TRANSPARENT);
418 | }
419 |
420 | public void setLineColor(int lineColor) {
421 | this.lineColor = lineColor;
422 | }
423 |
424 | /**
425 | * Sensitivity, the bigger the more sensitive [1,10]
426 | * the default value is 5
427 | *
428 | * @param sensibility
429 | */
430 | public void setSensibility(int sensibility) {
431 | this.sensibility = sensibility;
432 | checkSensibilityValue();
433 | }
434 | }
435 |
--------------------------------------------------------------------------------
/library/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
21 |
22 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/pictures/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/logo.jpg
--------------------------------------------------------------------------------
/pictures/优化前效果图.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/优化前效果图.jpg
--------------------------------------------------------------------------------
/pictures/优化后效果图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/优化后效果图.png
--------------------------------------------------------------------------------
/pictures/内存泄漏图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/内存泄漏图.png
--------------------------------------------------------------------------------
/pictures/效果.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/效果.gif
--------------------------------------------------------------------------------
/pictures/曲线函数图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/曲线函数图.png
--------------------------------------------------------------------------------
/pictures/衰减函数图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/衰减函数图.png
--------------------------------------------------------------------------------
/pictures/静态效果图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jay-Goo/WaveLineView/340f06861ef4164a46df0692af34a1755f9af290/pictures/静态效果图.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------