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