-
自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现wav格式的编码时我们也就不能再使用MediaRecorder,而只能使用AudioRecord进行处理)。
效果图:
实现思路
1.在微信中我们可以看到实现语音对讲的是通过点按按钮来完成的,因此在这里我选择重新自己的控件使其继承自Button并重写onTouchEvent方法,来实现对录音的判断。
2.在onTouchEvent方法中,
当我们按下按钮时,首先显示录音的对话框,然后调用录音准备方法并开始录音,接着开启一个计时线程,每隔0.1秒的时间获取一次录音音量的大小,并通过Handler根据音量大小更新Dialog中的显示图片;
当我们移动手指时,若手指向上移动距离大于50,在Dialog中显示松开手指取消录音的提示,并将isCanceled变量(表示我们最后是否取消了录音)置为true,上移动距离小于20时,我们恢复Dialog的图片,并将isCanceled置为false;
当抬起手指时,我们首先关闭录音对话框,接着调用录音停止方法并关闭计时线程,然后我们判断是否取消录音,若是的话则删除录音文件,否则判断计时时间是否太短,最后调用回调接口中的recordEnd方法。
3.在这里为了适应不同的录音需求,我使用了策略模式来进行处理,将每一个不同的录音方法视为一种不同的策略,根据自己的需要去改写。注意问题
1.在onTouchEvent的返回值中应该返回true,这样才能屏蔽之后其他的触摸事件,否则当手指滑动离开Button之后将不能在响应我们的触摸方法。
2.不要忘记为自己的App添加权限:123<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方法
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238<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布局:
12345678<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布局:
12345678<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,对话框样式
12345678<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 录音策略接口
12345678910111213141516171819202122232425262728293031323334353637383940<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>
个人写的一个录音实践策略
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<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
12345678910111213141516171819202122232425262728<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>
Android开发--仿微信语音对讲录音
最新推荐文章于 2021-05-29 09:05:44 发布