├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── nan
│ │ └── recordbutton
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── nan
│ │ │ └── recordbutton
│ │ │ ├── Constants.java
│ │ │ ├── MainActivity.java
│ │ │ ├── utils
│ │ │ ├── AudioRecordManager.java
│ │ │ └── VibratorUtils.java
│ │ │ └── widget
│ │ │ ├── HorVoiceView.java
│ │ │ └── RecorderButton.java
│ └── res
│ │ ├── drawable-xhdpi
│ │ ├── bg_record.png
│ │ ├── bg_recording.png
│ │ ├── iv_bg.jpg
│ │ └── wave.png
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── raw
│ │ ├── fx.mp3
│ │ ├── fy.mp3
│ │ └── gj.mp3
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── nan
│ └── recordbutton
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.8
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ###一个绚丽的录音按钮,带有水波纹效果,这是从真实项目里面挖下来的。
2 | 由于时间匆促,代码还有待重构。
3 |
4 | ###效果:
5 |
6 | 
7 |
8 | 
9 |
10 | 
11 |
12 |
13 | ###使用方法:
14 |
15 | 1. 在布局文件中添加
16 |
17 |
33 |
34 | 2. 在Activity中找到RecorderButton以后记得设置监听器RecorderButton.AudioStateRecorderListener,从而实现自己的业务逻辑。具体可以参考项目中的代码。
35 |
36 | private RecorderButton btn_record;
37 |
38 | btn_record = (RecorderButton) findViewById(R.id.btn_record);
39 | btn_record.setAudioStateRecorderListener(new MyRecordListener());
40 |
41 | class MyRecordListener implements RecorderButton.AudioStateRecorderListener {
42 |
43 | @Override
44 | public void onStart(float time) {
45 | }
46 |
47 | @Override
48 | public void onUpdateTime(float currentTime, float minTime, float maxTime){
49 | }
50 |
51 | @Override
52 | public void onReturnToRecord() {
53 | }
54 |
55 | @Override
56 | public void onWantToCancel() {
57 | }
58 |
59 | @Override
60 | public void onFinish(float seconds, String filePath) {
61 | }
62 |
63 | @Override
64 | public void onCancel(boolean isTooShort) {
65 | }
66 |
67 | @Override
68 | public void onVoiceChange(int voiceLevel) {
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "25.0.0"
6 | defaultConfig {
7 | applicationId "com.nan.recordbutton"
8 | minSdkVersion 14
9 | targetSdkVersion 24
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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:24.2.1'
28 | testCompile 'junit:junit:4.12'
29 | }
30 |
--------------------------------------------------------------------------------
/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 D:\Users\Administrator\AppData\Local\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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/nan/recordbutton/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.nan.recordbutton", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nan/recordbutton/Constants.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton;
2 |
3 | import android.os.Environment;
4 |
5 | import java.io.File;
6 |
7 | public class Constants {
8 |
9 | public static final class FilePath {
10 | public static final String RECORD_TEMP_PATH = Environment.getExternalStorageDirectory() + File.separator + "com.sxbb" + File.separator + "recordTemp" + File.separator;
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nan/recordbutton/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton;
2 |
3 | import android.Manifest;
4 | import android.annotation.SuppressLint;
5 | import android.content.Context;
6 | import android.content.pm.PackageManager;
7 | import android.media.MediaRecorder;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.os.Handler;
11 | import android.os.Message;
12 | import android.support.annotation.NonNull;
13 | import android.support.v4.app.ActivityCompat;
14 | import android.support.v4.content.ContextCompat;
15 | import android.support.v7.app.AppCompatActivity;
16 | import android.view.View;
17 | import android.view.animation.AlphaAnimation;
18 | import android.view.animation.Animation;
19 | import android.view.animation.AnimationSet;
20 | import android.view.animation.ScaleAnimation;
21 | import android.widget.ImageView;
22 | import android.widget.TextView;
23 | import android.widget.Toast;
24 |
25 | import com.nan.recordbutton.widget.RecorderButton;
26 | import com.nan.recordbutton.widget.HorVoiceView;
27 |
28 | import java.io.File;
29 |
30 | public class MainActivity extends AppCompatActivity {
31 |
32 | private RecorderButton btn_record;
33 | private TextView tv_state;
34 | private TextView tv_time;
35 | private HorVoiceView hv_voice;
36 | private String str_record1;
37 | private String str_record2;
38 |
39 | private static final int MAX_RECORD_TIME = 15;
40 |
41 | private ImageView mWave1;
42 | private ImageView mWave2;
43 | private ImageView mWave3;
44 | private AnimationSet mAnimationSet1;
45 | private AnimationSet mAnimationSet2;
46 | private AnimationSet mAnimationSet3;
47 | private static final int OFFSET = 600; //每个动画的播放时间间隔
48 | private static final int MSG_WAVE2_ANIMATION = 2;
49 | private static final int MSG_WAVE3_ANIMATION = 3;
50 | // 这个标志用于防止
51 | private boolean isShowingWave = false;
52 |
53 | private boolean isRecordEnable = false;
54 |
55 | @SuppressLint("HandlerLeak")
56 | private Handler mHandler = new Handler() {
57 | @Override
58 | public void handleMessage(Message msg) {
59 | switch (msg.what) {
60 | case MSG_WAVE2_ANIMATION:
61 | if (isShowingWave) {
62 | mWave2.startAnimation(mAnimationSet2);
63 | } else {
64 | mWave2.clearAnimation();
65 | }
66 | break;
67 | case MSG_WAVE3_ANIMATION:
68 | if (isShowingWave) {
69 | mWave3.startAnimation(mAnimationSet3);
70 | } else {
71 | mWave3.clearAnimation();
72 | }
73 | break;
74 | }
75 | }
76 | };
77 | private ImageView iv_bg;
78 | private Context mCtx;
79 | private MediaRecorder mMediaRecorder;
80 | private TextView tv_txt0;
81 |
82 | @Override
83 | protected void onCreate(Bundle savedInstanceState) {
84 | super.onCreate(savedInstanceState);
85 | setContentView(R.layout.activity_main);
86 |
87 | findView();
88 | init();
89 |
90 | }
91 |
92 | public static final int CODE_REQUEST_RECORD = 100;
93 |
94 | /**
95 | * android6.0权限管理
96 | */
97 | private void checkRecordPermission() {
98 |
99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
100 | if (ContextCompat.checkSelfPermission(this,
101 | Manifest.permission.CALL_PHONE)
102 | != PackageManager.PERMISSION_GRANTED) {
103 |
104 | ActivityCompat.requestPermissions(this,
105 | new String[]{Manifest.permission.CALL_PHONE},
106 | CODE_REQUEST_RECORD);
107 | }
108 | }
109 | }
110 |
111 | @Override
112 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
113 |
114 | if (requestCode == CODE_REQUEST_RECORD) {
115 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
116 |
117 | Toast.makeText(MainActivity.this, "成功授权录音权限", Toast.LENGTH_SHORT).show();
118 |
119 | } else {
120 | // Permission Denied
121 | Toast.makeText(MainActivity.this, "没有录音权限", Toast.LENGTH_SHORT).show();
122 | }
123 | return;
124 | }
125 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
126 | }
127 |
128 |
129 | @Override
130 | protected void onDestroy() {
131 | super.onDestroy();
132 |
133 | if (mMediaRecorder != null) {
134 | mMediaRecorder.release();
135 | }
136 | }
137 |
138 | private void findView() {
139 | iv_bg = (ImageView) findViewById(R.id.iv_bg);
140 | mWave1 = (ImageView) findViewById(R.id.wave1);
141 | mWave2 = (ImageView) findViewById(R.id.wave2);
142 | mWave3 = (ImageView) findViewById(R.id.wave3);
143 | hv_voice = (HorVoiceView) findViewById(R.id.hv_voice);
144 | btn_record = (RecorderButton) findViewById(R.id.btn_record);
145 | tv_state = (TextView) findViewById(R.id.tv_state);
146 | tv_time = (TextView) findViewById(R.id.tv_time);
147 | tv_txt0 = (TextView) findViewById(R.id.tv_txt0);
148 | }
149 |
150 | private void init() {
151 |
152 | mCtx = this;
153 |
154 | checkRecordPermission();
155 | // TintBarUtils.setStatusBarTextStyle(this, true);
156 | // immerseLayout();
157 |
158 | mAnimationSet1 = initAnimationSet();
159 | mAnimationSet2 = initAnimationSet();
160 | mAnimationSet3 = initAnimationSet();
161 |
162 | str_record1 = getResources().getString(R.string.record_txt4);
163 | str_record2 = getResources().getString(R.string.record_txt5);
164 | updateTime(MAX_RECORD_TIME);
165 |
166 | btn_record.setAudioStateRecorderListener(new MyRecordListener());
167 | }
168 |
169 | /**
170 | * 上传录音
171 | * 需要修改URL
172 | *
173 | * @param seconds 语音的时间长度
174 | * @param recordFile 录音文件
175 | */
176 | private void uploadRecord(final int seconds, final File recordFile) {
177 |
178 | String ts = System.currentTimeMillis() + "";
179 |
180 | //......
181 | }
182 |
183 |
184 | /**
185 | * 更新时间
186 | *
187 | * @param time 更新显示的时间
188 | */
189 | private void updateTime(int time) {
190 | tv_time.setText(str_record1 + time + str_record2);
191 | }
192 |
193 | /**
194 | * 文案的还原
195 | */
196 | private void resetTextAndTime() {
197 |
198 | tv_state.setText(R.string.record_normal);
199 | updateTime(MAX_RECORD_TIME);
200 |
201 | }
202 |
203 | private AnimationSet initAnimationSet() {
204 | AnimationSet as = new AnimationSet(true);
205 | ScaleAnimation sa = new ScaleAnimation(1f, 3.5f, 1f, 3.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
206 | sa.setDuration(OFFSET * 3);
207 | sa.setRepeatCount(Animation.INFINITE);// 设置循环
208 | AlphaAnimation aa = new AlphaAnimation(1, 0.1f);
209 | aa.setDuration(OFFSET * 3);
210 | aa.setRepeatCount(Animation.INFINITE);//设置循环
211 | as.addAnimation(sa);
212 | as.addAnimation(aa);
213 | return as;
214 | }
215 |
216 | private void startWaveAnimation() {
217 | mWave1.startAnimation(mAnimationSet1);
218 | mHandler.sendEmptyMessageDelayed(MSG_WAVE2_ANIMATION, OFFSET);
219 | mHandler.sendEmptyMessageDelayed(MSG_WAVE3_ANIMATION, OFFSET * 2);
220 | isShowingWave = true;
221 | }
222 |
223 | private void stopWaveAnimation() {
224 | mWave1.clearAnimation();
225 | mWave2.clearAnimation();
226 | mWave3.clearAnimation();
227 | isShowingWave = false;
228 | }
229 |
230 | class MyRecordListener implements RecorderButton.AudioStateRecorderListener {
231 |
232 | @Override
233 | public void onStart(float time) {
234 |
235 | tv_txt0.setVisibility(View.GONE);
236 | startWaveAnimation();
237 | tv_time.setVisibility(View.VISIBLE);
238 | tv_state.setText(R.string.record_ing);
239 | // Toast.makeText(AudioRecordActivity.this, "开始录制", Toast.LENGTH_SHORT).show();
240 | }
241 |
242 | @Override
243 | public void onUpdateTime(float currentTime, float minTime, float maxTime) {
244 |
245 | //保留一位小数
246 | int max = (int) maxTime;
247 | int time = (int) currentTime;
248 | hv_voice.setText(" " + (max - time) + " ");
249 | updateTime(max - time);
250 |
251 | if (time >= 10) {
252 | tv_txt0.setText(R.string.record_enougth);
253 | tv_txt0.setVisibility(View.VISIBLE);
254 | }
255 | }
256 |
257 | @Override
258 | public void onReturnToRecord() {
259 | tv_state.setText(R.string.record_ing);
260 | }
261 |
262 | @Override
263 | public void onWantToCancel() {
264 | tv_state.setText(R.string.record_want_to_cancel);
265 | }
266 |
267 | @Override
268 | public void onFinish(float seconds, String filePath) {
269 |
270 | btn_record.setEnabled(false);
271 | stopWaveAnimation();
272 | tv_state.setText(R.string.record_success);
273 | tv_time.setVisibility(View.GONE);
274 |
275 | uploadRecord((int) seconds, new File(filePath));
276 | // Toast.makeText(AudioRecordActivity.this, (int) seconds + "\n" + filePath, Toast.LENGTH_SHORT).show();
277 | }
278 |
279 | @Override
280 | public void onCancel(boolean isTooShort) {
281 | stopWaveAnimation();
282 | tv_state.setText(R.string.record_normal);
283 | if (isTooShort) {
284 | tv_time.setText(R.string.record_too_short);
285 | }
286 |
287 | }
288 |
289 | @Override
290 | public void onVoiceChange(int voiceLevel) {
291 | hv_voice.setVoice(voiceLevel);
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/app/src/main/java/com/nan/recordbutton/utils/AudioRecordManager.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton.utils;
2 |
3 | import android.media.MediaRecorder;
4 | import android.util.Log;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.util.UUID;
9 |
10 | /**
11 | * Created by 焕楠 2016-6-27
12 | */
13 | public class AudioRecordManager {
14 |
15 | private MediaRecorder mMediaRecorder;
16 | private String mDir;
17 | private String mCurrentFilePath;
18 |
19 | private static AudioRecordManager mInstance;
20 |
21 | private boolean isPrepared = false;
22 |
23 | private AudioRecordManager(String dir) {
24 | mDir = dir;
25 | }
26 |
27 | public String getCurrentFilePath() {
28 | return mCurrentFilePath;
29 | }
30 |
31 | /**
32 | * 回调准备完毕
33 | */
34 | public interface AudioStateListener {
35 | void wellPrepared();
36 | }
37 |
38 | public AudioStateListener mListener;
39 |
40 | public void setOnAudioStateListner(AudioStateListener listner) {
41 | mListener = listner;
42 | }
43 |
44 | public static AudioRecordManager getInstance(String dir) {
45 | if (null == mInstance) {
46 | synchronized (AudioRecordManager.class) {
47 | if (null == mInstance) {
48 | mInstance = new AudioRecordManager(dir);
49 | }
50 | }
51 | }
52 | return mInstance;
53 | }
54 |
55 | public void prepareAudio() {
56 | Log.d("LONG", "preparedAudio");
57 | try {
58 | isPrepared = false;
59 | File dir = new File(mDir);
60 | if (!dir.exists()) {
61 | dir.mkdirs();
62 | }
63 |
64 | String fileName = generateFileName();
65 | File file = new File(dir, fileName);
66 |
67 | Log.d("LONG", "the file name is " + fileName);
68 |
69 | mCurrentFilePath = file.getAbsolutePath();
70 | mMediaRecorder = new MediaRecorder();
71 | // 设置输出文件
72 | mMediaRecorder.setOutputFile(file.getAbsolutePath());
73 | Log.d("LONG", "1");
74 | // 设置MediaRecorder的音频源为麦克风
75 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
76 | Log.d("LONG", "2");
77 | // 设置音频格式 AMR_NB
78 | // mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
79 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
80 | Log.d("LONG", "3");
81 | // 设置音频的编码为AMR
82 | // mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
83 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
84 | Log.d("LONG", "4");
85 | mMediaRecorder.prepare();
86 | Log.d("LONG", "5");
87 | mMediaRecorder.start();
88 | Log.d("LONG", "6");
89 | // 准备结束
90 | isPrepared = true;
91 | Log.d("LONG", "7");
92 |
93 | if (mListener != null) {
94 | Log.d("LONG", "AudioStateListener is not null");
95 | mListener.wellPrepared();
96 | } else {
97 | Log.d("LONG", "lisetner null");
98 | }
99 | } catch (IOException e) {
100 | e.printStackTrace();
101 | }
102 |
103 | }
104 |
105 | /**
106 | * 随机生成文件的名称
107 | *
108 | * @return
109 | */
110 | private String generateFileName() {
111 | // return UUID.randomUUID().toString() + ".amr";
112 | return UUID.randomUUID().toString() + ".aac";
113 | }
114 |
115 | public int getVoiceLevel(int maxLevel) {
116 | if (isPrepared) {
117 | // mMediaRecorder.getMaxAmplitude() 1-32767
118 | try {
119 | return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;
120 | } catch (Exception e) {
121 | // 忽略产生的异常
122 | }
123 | }
124 | return 1;
125 | }
126 |
127 | public void release() {
128 | if (mMediaRecorder != null) {
129 | mMediaRecorder.stop();
130 | mMediaRecorder.release();
131 | mMediaRecorder = null;
132 | }
133 | }
134 |
135 | /**
136 | * 释放资源 同时删除音频文件
137 | */
138 | public void cancel() {
139 | release();
140 | if (mCurrentFilePath != null) {
141 | File file = new File(mCurrentFilePath);
142 | file.delete();
143 | mCurrentFilePath = null;
144 | }
145 | }
146 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nan/recordbutton/utils/VibratorUtils.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton.utils;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.os.Vibrator;
6 |
7 | /**
8 | * Created by huannan on 2016/7/25.
9 | * 震动的工具类
10 | */
11 | public class VibratorUtils {
12 |
13 | /**
14 | * final Activity activity :调用该方法的Activity实例
15 | * long milliseconds :震动的时长,单位是毫秒
16 | * long[] pattern :自定义震动模式 。数组中数字的含义依次是[静止时长,震动时长,静止时长,震动时长。。。]时长的单位是毫秒
17 | * boolean isRepeat : 是否反复震动,如果是true,反复震动,如果是false,只震动一次
18 | */
19 |
20 | public static void vibrate(final Context context, long milliseconds) {
21 | Vibrator vib = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);
22 | vib.vibrate(milliseconds);
23 | }
24 |
25 | public static void vibrate(final Context context, long[] pattern, boolean isRepeat) {
26 | Vibrator vib = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);
27 | vib.vibrate(pattern, isRepeat ? 1 : -1);
28 | }
29 |
30 |
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nan/recordbutton/widget/HorVoiceView.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton.widget;
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.RectF;
9 | import android.graphics.Typeface;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.View;
13 |
14 | import com.nan.recordbutton.R;
15 |
16 | import java.util.LinkedList;
17 |
18 | public class HorVoiceView extends View {
19 |
20 | private Paint paint;
21 | private int color;
22 | private float lineHeight = 8;
23 | private float maxLineheight;
24 | private float lineWidth;
25 | private float textSize;
26 | private String text = " 15 ";
27 | private int textColor;
28 | private Thread mThread;
29 | private int milliSeconds;
30 | private boolean isStart = false;
31 | private Runnable mRunable;
32 |
33 | LinkedList list = new LinkedList<>();
34 |
35 | public HorVoiceView(Context context) {
36 | super(context);
37 | }
38 |
39 | public HorVoiceView(Context context, AttributeSet attrs) {
40 | this(context, attrs, 0);
41 | }
42 |
43 | public HorVoiceView(Context context, AttributeSet attrs, int defStyleAttr) {
44 | super(context, attrs, defStyleAttr);
45 | for (int i = 0; i < 10; i++) {
46 | list.add(1);
47 | }
48 | paint = new Paint();
49 | mRunable = new Runnable() {
50 | @Override
51 | public void run() {
52 | while (isStart) {
53 | milliSeconds += 200;
54 | text = milliSeconds / 1000 < 10 ? "0" + milliSeconds / 1000 : milliSeconds / 1000 + "";
55 | Log.e("horvoiceview", "text " + text);
56 | setVoice(1);
57 | try {
58 | Thread.sleep(200);
59 | } catch (InterruptedException e) {
60 | e.printStackTrace();
61 | }
62 | // postInvalidate();
63 | }
64 | }
65 | };
66 | TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.HorVoiceView);
67 | color = mTypedArray.getColor(R.styleable.HorVoiceView_voiceLineColor, Color.BLACK);
68 | lineWidth = mTypedArray.getDimension(R.styleable.HorVoiceView_voiceLineWidth, 35);
69 | lineHeight = mTypedArray.getDimension(R.styleable.HorVoiceView_voiceLineHeight, 8);
70 | maxLineheight = mTypedArray.getDimension(R.styleable.HorVoiceView_voiceLineHeight, 32);
71 | textSize = mTypedArray.getDimension(R.styleable.HorVoiceView_voiceTextSize, 45);
72 | textColor = mTypedArray.getColor(R.styleable.HorVoiceView_voiceTextColor, Color.BLACK);
73 | mTypedArray.recycle();
74 | }
75 |
76 | @Override
77 | protected void onDraw(Canvas canvas) {
78 | super.onDraw(canvas);
79 | int widthcentre = getWidth() / 2;
80 | int heightcentre = getHeight() / 2;
81 |
82 | paint.setStrokeWidth(0);
83 | paint.setColor(textColor);
84 | paint.setTextSize(textSize);
85 | paint.setTypeface(Typeface.DEFAULT_BOLD);
86 | float textWidth = paint.measureText(text);
87 | canvas.drawText(text, widthcentre - textWidth / 2, heightcentre - (paint.ascent() + paint.descent()) / 2, paint);
88 |
89 | // Log.e("Voice", text);
90 |
91 | paint.setColor(color);
92 | paint.setStyle(Paint.Style.FILL);
93 | paint.setStrokeWidth(lineWidth);
94 | paint.setAntiAlias(true);
95 | for (int i = 0; i < 10; i++) {
96 | RectF rect = new RectF(widthcentre + 2 * i * lineHeight + textWidth / 2 + lineHeight, heightcentre - list.get(i) * lineHeight / 2, widthcentre + 2 * i * lineHeight + 2 * lineHeight + textWidth / 2, heightcentre + list.get(i) * lineHeight / 2);
97 | RectF rect2 = new RectF(widthcentre - (2 * i * lineHeight + 2 * lineHeight + textWidth / 2), heightcentre - list.get(i) * lineHeight / 2, widthcentre - (2 * i * lineHeight + textWidth / 2 + lineHeight), heightcentre + list.get(i) * lineHeight / 2);
98 | canvas.drawRect(rect, paint);
99 | canvas.drawRect(rect2, paint);
100 | }
101 | }
102 |
103 | public synchronized void setVoice(Integer height) {
104 | for (int i = 0; i <= height / 30; i++) {
105 | list.remove(9 - i);
106 | list.add(i, (height / 20 - i) < 1 ? 1 : height / 20 - i);
107 | }
108 | // Log.e("波峰", "height" + height);
109 | postInvalidate();
110 | }
111 |
112 | public synchronized void setText(String text) {
113 | this.text = text;
114 | postInvalidate();
115 | }
116 |
117 | public synchronized void startRecording() {
118 | milliSeconds = 0;
119 | isStart = true;
120 | new Thread(mRunable).start();
121 | }
122 |
123 | public synchronized void stopRecord() {
124 | isStart = false;
125 | list.clear();
126 | for (int i = 0; i < 10; i++) {
127 | list.add(1);
128 | }
129 | text = "00";
130 | postInvalidate();
131 | }
132 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/nan/recordbutton/widget/RecorderButton.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.media.MediaPlayer;
6 | import android.os.Environment;
7 | import android.os.Handler;
8 | import android.os.Message;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 | import android.view.MotionEvent;
12 | import android.view.View;
13 | import android.widget.Button;
14 |
15 | import com.nan.recordbutton.R;
16 | import com.nan.recordbutton.utils.AudioRecordManager;
17 | import com.nan.recordbutton.utils.VibratorUtils;
18 |
19 | import java.io.File;
20 |
21 |
22 | /**
23 | * Created by 焕楠 2016-6-27
24 | */
25 | public class RecorderButton extends Button implements AudioRecordManager.AudioStateListener {
26 |
27 | private static final String TAG = "AudioRecorderButton";
28 |
29 | private static final int DISTANCE_CANCEL = 50;
30 | private static final int STATE_NORMAL = 1;
31 | private static final int STATE_RECORDING = 2;
32 | private static final int STATE_WANT_TO_CANCEL = 3;
33 |
34 | private int mCurState = STATE_NORMAL;
35 | private boolean isRecording = false; // 已经开始录音
36 |
37 | private AudioRecordManager mAudioRecordManager;
38 |
39 | private float mTime;
40 | // 是否触发longclick
41 | private boolean mReady;
42 | private String str_recorder_normal;
43 | private String str_recorder_recording;
44 | private String str_recorder_want_cancel;
45 | private int bg_recorder_normal;
46 | private int bg_recorder_recording;
47 | private int bg_recorder_cancel;
48 | private float max_record_time;
49 | private float min_record_time;
50 | private int max_voice_level;
51 | private MediaPlayer mMediaPlayer;
52 | private Context mCtx;
53 |
54 | public RecorderButton(Context context) {
55 | this(context, null, 0);
56 | }
57 |
58 | public RecorderButton(Context context, AttributeSet attrs) {
59 | this(context, attrs, 0);
60 | }
61 |
62 | public RecorderButton(Context context, AttributeSet attrs, int defStyleAttr) {
63 | super(context, attrs, defStyleAttr);
64 |
65 | final TypedArray a = context.obtainStyledAttributes(attrs,
66 | R.styleable.RecorderButton);
67 |
68 | mCtx = context;
69 |
70 | str_recorder_normal = a.getString(R.styleable.RecorderButton_txt_normal);
71 | str_recorder_recording = a.getString(R.styleable.RecorderButton_txt_recording);
72 | str_recorder_want_cancel = a.getString(R.styleable.RecorderButton_txt_want_cancel);
73 |
74 | bg_recorder_normal = a.getResourceId(R.styleable.RecorderButton_bg_normal, 0);
75 | bg_recorder_recording = a.getResourceId(R.styleable.RecorderButton_bg_recording, 0);
76 | bg_recorder_cancel = a.getResourceId(R.styleable.RecorderButton_bg_want_cancel, 0);
77 |
78 | //最大录音时间,默认为15秒
79 | //最小录音时间,默认为10秒
80 | max_record_time = a.getFloat(R.styleable.RecorderButton_max_record_time, 15);
81 | min_record_time = a.getFloat(R.styleable.RecorderButton_min_record_time, 10);
82 |
83 | max_voice_level = a.getInt(R.styleable.RecorderButton_max_voice_level, 5);
84 |
85 | a.recycle();
86 |
87 | String dir = Environment.getExternalStorageDirectory() + File.separator + "sxbb" + File.separator + "record";
88 | mAudioRecordManager = AudioRecordManager.getInstance(dir);
89 | mAudioRecordManager.setOnAudioStateListner(this);
90 |
91 | setText(str_recorder_normal);
92 | setBackgroundResource(bg_recorder_normal);
93 |
94 | // setOnClickListener(new OnClickListener() {
95 | // @Override
96 | // public void onClick(View view) {
97 | // mHandler.postDelayed(new Runnable() {
98 | // @Override
99 | // public void run() {
100 | // mReady = true;
101 | // Log.e(TAG, "OnLongClick");
102 | //
103 | // //播放提示音
104 | // MediaPlayer.create(mCtx, R.raw.fx).start();
105 | // mAudioRecordManager.prepareAudio();
106 | // }
107 | // }, 1000);
108 | // }
109 | // });
110 |
111 | setOnLongClickListener(new OnLongClickListener() {
112 | @Override
113 | public boolean onLongClick(View v) {
114 |
115 | mReady = true;
116 | Log.e(TAG, "OnLongClick");
117 |
118 | VibratorUtils.vibrate(mCtx, 60);
119 |
120 | //播放提示音
121 | MediaPlayer.create(mCtx, R.raw.fx).start();
122 | mAudioRecordManager.prepareAudio();
123 |
124 | return false;
125 | }
126 | });
127 |
128 | // setOnClickListener(new OnClickListener() {
129 | // @Override
130 | // public void onClick(View view) {
131 | // if (mListener != null) {
132 | // mListener.onCancel(true);
133 | // }
134 | // }
135 | // });
136 | }
137 |
138 | /**
139 | * 录音完成后的回调
140 | */
141 | public interface AudioStateRecorderListener {
142 | void onFinish(float seconds, String filePath);
143 |
144 | void onCancel(boolean isTooShort);
145 |
146 | void onVoiceChange(int voiceLevel);
147 |
148 | void onStart(float time);
149 |
150 | void onUpdateTime(float currentTime, float minTime, float maxTime);
151 |
152 | void onReturnToRecord();
153 |
154 | void onWantToCancel();
155 | }
156 |
157 | private AudioStateRecorderListener mListener;
158 |
159 | public void setAudioStateRecorderListener(AudioStateRecorderListener listener) {
160 | mListener = listener;
161 | }
162 |
163 | /**
164 | * 获取音量大小的Runnable
165 | */
166 | private Runnable mGetVoiceLevelRunnable = new Runnable() {
167 |
168 | @Override
169 | public void run() {
170 | while (isRecording) {
171 | try {
172 | Thread.sleep(100);
173 | mTime += 0.1f;
174 | mHandler.sendEmptyMessage(MSG_UPDATE_TIME);
175 | mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);
176 | if (mTime >= max_record_time) {
177 | //如果时间到了最大的录音时间
178 | mAudioRecordManager.release();
179 | mHandler.sendEmptyMessage(MSG_TIME_LIMIT);
180 | }
181 | } catch (InterruptedException e) {
182 | e.printStackTrace();
183 | }
184 | }
185 | }
186 | };
187 |
188 | private static final int MSG_AUDIO_PREPARED = 0x110;
189 | private static final int MSG_VOICE_CHANGE = 0x111;
190 | private static final int MSG_UPDATE_TIME = 0x113;
191 | //最大的录音时间到了
192 | private static final int MSG_TIME_LIMIT = 0x114;
193 | private Handler mHandler = new Handler() {
194 | @Override
195 | public void handleMessage(Message msg) {
196 | switch (msg.what) {
197 | case MSG_AUDIO_PREPARED:
198 |
199 | // audio end prepared以后开始录音
200 | isRecording = true;
201 |
202 | if (mListener != null) {
203 | mListener.onStart(mTime);
204 | }
205 |
206 | // 开启线程,监听音量变化
207 | new Thread(mGetVoiceLevelRunnable).start();
208 | break;
209 | case MSG_VOICE_CHANGE:
210 | if (mListener != null) {
211 | //根据用户设置的最大值去获取音量级别
212 | mListener.onVoiceChange(mAudioRecordManager.getVoiceLevel(max_voice_level));
213 | }
214 | break;
215 | case MSG_UPDATE_TIME:
216 | if (mListener != null) {
217 | mListener.onUpdateTime(mTime, min_record_time, max_record_time);
218 | }
219 | break;
220 | case MSG_TIME_LIMIT:
221 | if (mListener != null) {
222 | //到达时间限制了
223 | MediaPlayer.create(mCtx, R.raw.gj).start();
224 | mListener.onFinish(mTime, mAudioRecordManager.getCurrentFilePath());
225 | }
226 | changeState(STATE_NORMAL);
227 | reset();
228 | break;
229 | }
230 | super.handleMessage(msg);
231 | }
232 | };
233 |
234 | @Override
235 | public void wellPrepared() {
236 | Log.d("LONG", "wellPrepared");
237 | mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
238 | }
239 |
240 | @Override
241 | public boolean onTouchEvent(MotionEvent event) {
242 | int action = event.getAction();
243 | int x = (int) event.getX();
244 | int y = (int) event.getY();
245 |
246 | switch (action) {
247 | case MotionEvent.ACTION_DOWN:
248 | if (mReady) {
249 | changeState(STATE_RECORDING);
250 | }
251 | break;
252 | case MotionEvent.ACTION_MOVE:
253 | // 根据x, y的坐标,判断是否想要取消
254 | if (mReady) {
255 | if (wantToCancel(x, y)) {
256 | changeState(STATE_WANT_TO_CANCEL);
257 | if (mListener != null) {
258 | mListener.onWantToCancel();
259 | }
260 | } else {
261 | changeState(STATE_RECORDING);
262 | if (mListener != null) {
263 | mListener.onReturnToRecord();
264 | }
265 | }
266 | }
267 | break;
268 | case MotionEvent.ACTION_UP:
269 | /**
270 | * 1. 未触发onLongClick
271 | * 2. prepared没有完毕已经up
272 | * 3. 录音时间小于预定的值,这个值我们设置为在onLongClick之前
273 | */
274 | if (!mReady) { // 未触发onLongClick
275 | changeState(STATE_NORMAL);
276 | reset();
277 | return super.onTouchEvent(event);
278 | }
279 |
280 | if (!isRecording || mTime < min_record_time) { // prepared没有完毕 或 录音时间过短
281 | isRecording = false;
282 | mAudioRecordManager.cancel();
283 | // 用户录音时间太短,取消
284 | if (mListener != null) {
285 | MediaPlayer.create(mCtx, R.raw.fy).start();
286 | mListener.onCancel(true);
287 | }
288 | } else if (STATE_RECORDING == mCurState) { // 正常录制结束
289 | mAudioRecordManager.release();
290 | if (mListener != null) {
291 | MediaPlayer.create(mCtx, R.raw.gj).start();
292 | mListener.onFinish(mTime, mAudioRecordManager.getCurrentFilePath());
293 | }
294 | } else if (STATE_WANT_TO_CANCEL == mCurState) {
295 | mAudioRecordManager.cancel();
296 | if (mListener != null) {
297 | MediaPlayer.create(mCtx, R.raw.fy).start();
298 | mListener.onCancel(false);
299 | }
300 | }
301 | changeState(STATE_NORMAL);
302 | reset();
303 | break;
304 | }
305 |
306 | return super.onTouchEvent(event);
307 | }
308 |
309 | /**
310 | * 恢复状态及标志位
311 | */
312 | private void reset() {
313 | isRecording = false;
314 | mReady = false;
315 | mTime = 0;
316 | mCurState = STATE_NORMAL;
317 | }
318 |
319 | /**
320 | * 根据坐标去判断是否应该取消
321 | *
322 | * @param x
323 | * @param y
324 | * @return
325 | */
326 | private boolean wantToCancel(int x, int y) {
327 | if (x < 0 || x > getWidth()) {
328 | return true;
329 | }
330 | if (y < -DISTANCE_CANCEL || y > getHeight() + DISTANCE_CANCEL) {
331 | return true;
332 | }
333 | return false;
334 | }
335 |
336 | /**
337 | * 按钮状态改变
338 | *
339 | * @param state
340 | */
341 | private void changeState(int state) {
342 | if (mCurState != state) {
343 | mCurState = state;
344 | switch (mCurState) {
345 | case STATE_NORMAL:
346 | setBackgroundResource(bg_recorder_normal);
347 | setText(str_recorder_normal);
348 | break;
349 | case STATE_RECORDING:
350 | setBackgroundResource(bg_recorder_recording);
351 | setText(str_recorder_recording);
352 | break;
353 | case STATE_WANT_TO_CANCEL:
354 | setBackgroundResource(bg_recorder_cancel);
355 | setText(str_recorder_want_cancel);
356 | break;
357 | }
358 | }
359 | }
360 |
361 |
362 | }
363 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/bg_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/drawable-xhdpi/bg_record.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/bg_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/drawable-xhdpi/bg_recording.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/iv_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/drawable-xhdpi/iv_bg.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/wave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/drawable-xhdpi/wave.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
27 |
28 |
34 |
35 |
51 |
52 |
60 |
61 |
71 |
72 |
82 |
83 |
94 |
95 |
105 |
106 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/fx.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/raw/fx.mp3
--------------------------------------------------------------------------------
/app/src/main/res/raw/fy.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/raw/fy.mp3
--------------------------------------------------------------------------------
/app/src/main/res/raw/gj.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/app/src/main/res/raw/gj.mp3
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #ef4133
7 | #707070
8 | #A4A3A3
9 | #737373
10 | #FE7308
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 100dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RecordButton
3 |
4 | 确认后你们可以自由沟通
5 | 对方将通过你的描述决定是否派单
6 | 按住开始说话
7 | 语音将在满
8 | s后自动发送哦
9 | 录音完毕,正在发送...
10 | 按住开始说话
11 | 正在录音
12 | 松开取消
13 | 录音时间过短
14 | 录音发送失败,请重试
15 |
16 | 可以松手了
17 | 没有录音权限
18 | 没有录音权限,请在安全中心打开录音权限
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/test/java/com/nan/recordbutton/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.nan.recordbutton;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/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 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/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/huannan/RecorderButton/3a472feacbb8680cbbe30c240e64c09a902bf1c8/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
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-2.14.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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------