Android開發--仿微信語音對講錄音

原文地址:http://www.2cto.com/kf/201502/378704.htmlcss

自微信出現以來取得了很好的成績,語音對講的實現更加方便了人與人之間的交流。今天來實踐一下微信的語音對講的錄音實現,這個也比較容易實現。在 此,我將該按鈕封裝成爲一個控件,並經過策略模式的方式實現錄音和界面的解耦合,以方便咱們在實際狀況中對錄音方法的不一樣需求(例如想要實現wav格式的 編碼時咱們也就不能再使用MediaRecorder,而只能使用AudioRecord進行處理)。
效果圖:
title=html

實現思路

1.在微信中咱們能夠看到實現語音對講的是經過點按按鈕來完成的,所以在這裏我選擇從新本身的控件使其繼承自Button並重寫onTouchEvent方法,來實現對錄音的判斷。
2.在onTouchEvent方法中,
當咱們按下按鈕時,首先顯示錄音的對話框,而後調用錄音準備方法並開始錄音,接着開啓一個計時線程,每隔0.1秒的時間獲取一次錄音音量的大小,並經過Handler根據音量大小更新Dialog中的顯示圖片;
當咱們移動手指時,若手指向上移動距離大於50,在Dialog中顯示鬆開手指取消錄音的提示,並將isCanceled變量(表示咱們最後是否取消了錄音)置爲true,上移動距離小於20時,咱們恢復Dialog的圖片,並將isCanceled置爲false;
當擡起手指時,咱們首先關閉錄音對話框,接着調用錄音中止方法並關閉計時線程,而後咱們判斷是否取消錄音,如果的話則刪除錄音文件,不然判斷計時時間是否過短,最後調用回調接口中的recordEnd方法。
3.在這裏爲了適應不一樣的錄音需求,我使用了策略模式來進行處理,將每個不一樣的錄音方法視爲一種不一樣的策略,根據本身的須要去改寫。java

注意問題

1.在onTouchEvent的返回值中應該返回true,這樣才能屏蔽以後其餘的觸摸事件,不然當手指滑動離開Button以後將不能在響應咱們的觸摸方法。
2.不要忘記爲本身的App添加權限:android

?
1
2
3
<code class = "hljs" xml= "" >    <uses-permission android:name= "android.permission.RECORD_AUDIO" >
     <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE" >
     <uses-permission android:name= "android.permission.READ_EXTERNAL_STORAGE" ></uses-permission></uses-permission></uses-permission></code>

代碼參考

RecordButton 類,咱們的自定義控件,從新複寫了onTouchEvent方法微信

?
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
<code class = "hljs" java= "" > package com.example.recordtest;
 
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
 
public class RecordButton extends Button {
 
     private static final int MIN_RECORD_TIME = 1 ; // 最短錄音時間,單位秒
     private static final int RECORD_OFF = 0 ; // 不在錄音
     private static final int RECORD_ON = 1 ; // 正在錄音
 
     private Dialog mRecordDialog;
     private RecordStrategy mAudioRecorder;
     private Thread mRecordThread;
     private RecordListener listener;
 
     private int recordState = 0 ; // 錄音狀態
     private float recodeTime = 0 .0f; // 錄音時長,若是錄音時間過短則錄音失敗
     private double voiceValue = 0.0 ; // 錄音的音量值
     private boolean isCanceled = false ; // 是否取消錄音
     private float downY;
 
     private TextView dialogTextView;
     private ImageView dialogImg;
     private Context mContext;
 
     public RecordButton(Context context) {
         super (context);
         // TODO Auto-generated constructor stub
         init(context);
     }
 
     public RecordButton(Context context, AttributeSet attrs, int defStyle) {
         super (context, attrs, defStyle);
         // TODO Auto-generated constructor stub
         init(context);
     }
 
     public RecordButton(Context context, AttributeSet attrs) {
         super (context, attrs);
         // TODO Auto-generated constructor stub
         init(context);
     }
 
     private void init(Context context) {
         mContext = context;
         this .setText(按住 說話);
     }
 
     public void setAudioRecord(RecordStrategy record) {
         this .mAudioRecorder = record;
     }
 
     public void setRecordListener(RecordListener listener) {
         this .listener = listener;
     }
 
     // 錄音時顯示Dialog
     private void showVoiceDialog( int flag) {
         if (mRecordDialog == null ) {
             mRecordDialog = new Dialog(mContext, R.style.Dialogstyle);
             mRecordDialog.setContentView(R.layout.dialog_record);
             dialogImg = (ImageView) mRecordDialog
                     .findViewById(R.id.record_dialog_img);
             dialogTextView = (TextView) mRecordDialog
                     .findViewById(R.id.record_dialog_txt);
         }
         switch (flag) {
         case 1 :
             dialogImg.setImageResource(R.drawable.record_cancel);
             dialogTextView.setText(鬆開手指可取消錄音);
             this .setText(鬆開手指 取消錄音);
             break ;
 
         default :
             dialogImg.setImageResource(R.drawable.record_animate_01);
             dialogTextView.setText(向上滑動可取消錄音);
             this .setText(鬆開手指 完成錄音);
             break ;
         }
         dialogTextView.setTextSize( 14 );
         mRecordDialog.show();
     }
 
     // 錄音時間過短時Toast顯示
     private void showWarnToast(String toastText) {
         Toast toast = new Toast(mContext);
         View warnView = LayoutInflater.from(mContext).inflate(
                 R.layout.toast_warn, null );
         toast.setView(warnView);
         toast.setGravity(Gravity.CENTER, 0 , 0 ); // 起點位置爲中間
         toast.show();
     }
 
     // 開啓錄音計時線程
     private void callRecordTimeThread() {
         mRecordThread = new Thread(recordThread);
         mRecordThread.start();
     }
 
     // 錄音Dialog圖片隨錄音音量大小切換
     private void setDialogImage() {
         if (voiceValue < 600.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_01);
         } else if (voiceValue > 600.0 && voiceValue < 1000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_02);
         } else if (voiceValue > 1000.0 && voiceValue < 1200.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_03);
         } else if (voiceValue > 1200.0 && voiceValue < 1400.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_04);
         } else if (voiceValue > 1400.0 && voiceValue < 1600.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_05);
         } else if (voiceValue > 1600.0 && voiceValue < 1800.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_06);
         } else if (voiceValue > 1800.0 && voiceValue < 2000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_07);
         } else if (voiceValue > 2000.0 && voiceValue < 3000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_08);
         } else if (voiceValue > 3000.0 && voiceValue < 4000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_09);
         } else if (voiceValue > 4000.0 && voiceValue < 6000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_10);
         } else if (voiceValue > 6000.0 && voiceValue < 8000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_11);
         } else if (voiceValue > 8000.0 && voiceValue < 10000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_12);
         } else if (voiceValue > 10000.0 && voiceValue < 12000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_13);
         } else if (voiceValue > 12000.0 ) {
             dialogImg.setImageResource(R.drawable.record_animate_14);
         }
     }
 
     // 錄音線程
     private Runnable recordThread = new Runnable() {
 
         @Override
         public void run() {
             recodeTime = 0 .0f;
             while (recordState == RECORD_ON) {
                 {
                     try {
                         Thread.sleep( 100 );
                         recodeTime += 0.1 ;
                         // 獲取音量,更新dialog
                         if (!isCanceled) {
                             voiceValue = mAudioRecorder.getAmplitude();
                             recordHandler.sendEmptyMessage( 1 );
                         }
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         }
     };
 
     @SuppressLint (HandlerLeak)
     private Handler recordHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             setDialogImage();
         }
     };
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         // TODO Auto-generated method stub
         switch (event.getAction()) {
         case MotionEvent.ACTION_DOWN: // 按下按鈕
             if (recordState != RECORD_ON) {
                 showVoiceDialog( 0 );
                 downY = event.getY();
                 if (mAudioRecorder != null ) {
                     mAudioRecorder.ready();
                     recordState = RECORD_ON;
                     mAudioRecorder.start();
                     callRecordTimeThread();
                 }
             }
             break ;
         case MotionEvent.ACTION_MOVE: // 滑動手指
             float moveY = event.getY();
             if (downY - moveY > 50 ) {
                 isCanceled = true ;
                 showVoiceDialog( 1 );
             }
             if (downY - moveY < 20 ) {
                 isCanceled = false ;
                 showVoiceDialog( 0 );
             }
             break ;
         case MotionEvent.ACTION_UP: // 鬆開手指
             if (recordState == RECORD_ON) {
                 recordState = RECORD_OFF;
                 if (mRecordDialog.isShowing()) {
                     mRecordDialog.dismiss();
                 }
                 mAudioRecorder.stop();
                 mRecordThread.interrupt();
                 voiceValue = 0.0 ;
                 if (isCanceled) {
                     mAudioRecorder.deleteOldFile();
                 } else {
                     if (recodeTime < MIN_RECORD_TIME) {
                         showWarnToast(時間過短  錄音失敗);
                         mAudioRecorder.deleteOldFile();
                     } else {
                         if (listener != null ) {
                             listener.recordEnd(mAudioRecorder.getFilePath());
                         }
                     }
                 }
                 isCanceled = false ;
                 this .setText(按住 說話);
             }
             break ;
         }
         return true ;
     }
 
     public interface RecordListener {
         public void recordEnd(String filePath);
     }
}
</code>

Dialog佈局:app

?
1
2
3
4
5
6
7
8
<code class = "hljs" xml= "" ><!--?xml version= 1.0 encoding=utf- 8 ?-->
<linearlayout android:background= "@drawable/record_bg" android:gravity= "center" android:layout_gravity= "center" android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:orientation= "vertical" android:padding= "20dp" xmlns:android= "http://schemas.android.com/apk/res/android" >
 
     <imageview android:id= "@+id/record_dialog_img" android:layout_height= "wrap_content" android:layout_width= "wrap_content" >
 
     <textview android:id= "@+id/record_dialog_txt" android:layout_height= "wrap_content" android:layout_margintop= "5dp" android:layout_width= "wrap_content" android:textcolor= "@android:color/white" >
 
</textview></imageview></linearlayout></code>

錄音時間過短的Toast佈局:ide

?
1
2
3
4
5
6
7
8
<code class = "hljs" xml= "" ><!--?xml version= 1.0 encoding=utf- 8 ?-->
<linearlayout android:background= "@drawable/record_bg" android:gravity= "center" android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:orientation= "vertical" android:padding= "20dp" xmlns:android= "http://schemas.android.com/apk/res/android" >
 
     <imageview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:src= "@drawable/voice_to_short" >
 
     <textview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "時間過短" android:textcolor= "@android:color/white" android:textsize= "15sp" >
 
</textview></imageview></linearlayout></code>

自定義的Dialogstyle,對話框樣式佈局

?
1
2
3
4
5
6
7
8
<code applescript= "" class = "hljs" ><style name= "Dialogstyle" type= "text/css" ><item name=android:windowBackground> @android :color/transparent</item>
         <item name=android:windowFrame> @null </item>
         <item name=android:windowNoTitle> true </item>
         <item name=android:windowIsFloating> true </item>
         <item name=android:windowIsTranslucent> true </item>
         <item name=android:windowAnimationStyle> @android :style/Animation.Dialog</item>
         <!-- 顯示對話框時當前的屏幕是否變暗 -->
         <item name=android:backgroundDimEnabled> false </item></style></code>

RecordStrategy 錄音策略接口this

?
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
<code class = "hljs" java= "" > package com.example.recordtest;
 
/**
  * RecordStrategy 錄音策略接口
  * @author acer
  */
public interface RecordStrategy {
 
     /**
      * 在這裏進行錄音準備工做,重置錄音文件名等
      */
     public void ready();
     /**
      * 開始錄音
      */
     public void start();
     /**
      * 錄音結束
      */
     public void stop();
 
     /**
      * 錄音失敗時刪除原來的舊文件
      */
     public void deleteOldFile();
 
     /**
      * 獲取錄音音量的大小
      * @return
      */
     public double getAmplitude();
 
     /**
      * 返回錄音文件完整路徑
      * @return
      */
     public String getFilePath();
 
}
</code>

我的寫的一個錄音實踐策略編碼

?
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<code class = "hljs" java= "" > package com.example.recordtest;
 
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
 
import android.media.MediaRecorder;
import android.os.Environment;
 
public class AudioRecorder implements RecordStrategy {
 
     private MediaRecorder recorder;
     private String fileName;
     private String fileFolder = Environment.getExternalStorageDirectory()
             .getPath() + /TestRecord;
 
     private boolean isRecording = false ;
 
     @Override
     public void ready() {
         // TODO Auto-generated method stub
         File file = new File(fileFolder);
         if (!file.exists()) {
             file.mkdir();
         }
         fileName = getCurrentDate();
         recorder = new MediaRecorder();
         recorder.setOutputFile(fileFolder + / + fileName + .amr);
         recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設置MediaRecorder的音頻源爲麥克風
         recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // 設置MediaRecorder錄製的音頻格式
         recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 設置MediaRecorder錄製音頻的編碼爲amr
     }
 
     // 以當前時間做爲文件名
     private String getCurrentDate() {
         SimpleDateFormat formatter = new SimpleDateFormat(yyyy_MM_dd_HHmmss);
         Date curDate = new Date(System.currentTimeMillis()); // 獲取當前時間
         String str = formatter.format(curDate);
         return str;
     }
 
     @Override
     public void start() {
         // TODO Auto-generated method stub
         if (!isRecording) {
             try {
                 recorder.prepare();
                 recorder.start();
             } catch (IllegalStateException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
 
             isRecording = true ;
         }
 
     }
 
     @Override
     public void stop() {
         // TODO Auto-generated method stub
         if (isRecording) {
             recorder.stop();
             recorder.release();
             isRecording = false ;
         }
 
     }
 
     @Override
     public void deleteOldFile() {
         // TODO Auto-generated method stub
         File file = new File(fileFolder + / + fileName + .amr);
         file.deleteOnExit();
     }
 
     @Override
     public double getAmplitude() {
         // TODO Auto-generated method stub
         if (!isRecording) {
             return 0 ;
         }
         return recorder.getMaxAmplitude();
     }
 
     @Override
     public String getFilePath() {
         // TODO Auto-generated method stub
         return fileFolder + / + fileName + .amr;
     }
 
}
</code>

MainActivity

?

 

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
<code class = "hljs" java= "" > package com.example.recordtest;
 
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
 
public class MainActivity extends Activity {
 
     RecordButton button;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         button = (RecordButton) findViewById(R.id.btn_record);
         button.setAudioRecord( new AudioRecorder());
     }
 
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // Inflate the menu; this adds items to the action bar if it is present.
         getMenuInflater().inflate(R.menu.main, menu);
         return true ;
     }
 
}
</code>
相關文章
相關標籤/搜索