-
自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现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<codeclass="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<codeclass="hljs"java="">packagecom.example.recordtest;importandroid.annotation.SuppressLint;importandroid.app.Dialog;importandroid.content.Context;importandroid.os.Handler;importandroid.os.Message;importandroid.util.AttributeSet;importandroid.view.Gravity;importandroid.view.LayoutInflater;importandroid.view.MotionEvent;importandroid.view.View;importandroid.widget.Button;importandroid.widget.ImageView;importandroid.widget.TextView;importandroid.widget.Toast;publicclassRecordButtonextendsButton {privatestaticfinalintMIN_RECORD_TIME =1;// 最短录音时间,单位秒privatestaticfinalintRECORD_OFF =0;// 不在录音privatestaticfinalintRECORD_ON =1;// 正在录音privateDialog mRecordDialog;privateRecordStrategy mAudioRecorder;privateThread mRecordThread;privateRecordListener listener;privateintrecordState =0;// 录音状态privatefloatrecodeTime =0.0f;// 录音时长,如果录音时间太短则录音失败privatedoublevoiceValue =0.0;// 录音的音量值privatebooleanisCanceled =false;// 是否取消录音privatefloatdownY;privateTextView dialogTextView;privateImageView dialogImg;privateContext mContext;publicRecordButton(Context context) {super(context);// TODO Auto-generated constructor stubinit(context);}publicRecordButton(Context context, AttributeSet attrs,intdefStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stubinit(context);}publicRecordButton(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinit(context);}privatevoidinit(Context context) {mContext = context;this.setText(按住 说话);}publicvoidsetAudioRecord(RecordStrategy record) {this.mAudioRecorder = record;}publicvoidsetRecordListener(RecordListener listener) {this.listener = listener;}// 录音时显示DialogprivatevoidshowVoiceDialog(intflag) {if(mRecordDialog ==null) {mRecordDialog =newDialog(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) {case1: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显示privatevoidshowWarnToast(String toastText) {Toast toast =newToast(mContext);View warnView = LayoutInflater.from(mContext).inflate(R.layout.toast_warn,null);toast.setView(warnView);toast.setGravity(Gravity.CENTER,0,0);// 起点位置为中间toast.show();}// 开启录音计时线程privatevoidcallRecordTimeThread() {mRecordThread =newThread(recordThread);mRecordThread.start();}// 录音Dialog图片随录音音量大小切换privatevoidsetDialogImage() {if(voiceValue <600.0) {dialogImg.setImageResource(R.drawable.record_animate_01);}elseif(voiceValue >600.0&& voiceValue <1000.0) {dialogImg.setImageResource(R.drawable.record_animate_02);}elseif(voiceValue >1000.0&& voiceValue <1200.0) {dialogImg.setImageResource(R.drawable.record_animate_03);}elseif(voiceValue >1200.0&& voiceValue <1400.0) {dialogImg.setImageResource(R.drawable.record_animate_04);}elseif(voiceValue >1400.0&& voiceValue <1600.0) {dialogImg.setImageResource(R.drawable.record_animate_05);}elseif(voiceValue >1600.0&& voiceValue <1800.0) {dialogImg.setImageResource(R.drawable.record_animate_06);}elseif(voiceValue >1800.0&& voiceValue <2000.0) {dialogImg.setImageResource(R.drawable.record_animate_07);}elseif(voiceValue >2000.0&& voiceValue <3000.0) {dialogImg.setImageResource(R.drawable.record_animate_08);}elseif(voiceValue >3000.0&& voiceValue <4000.0) {dialogImg.setImageResource(R.drawable.record_animate_09);}elseif(voiceValue >4000.0&& voiceValue <6000.0) {dialogImg.setImageResource(R.drawable.record_animate_10);}elseif(voiceValue >6000.0&& voiceValue <8000.0) {dialogImg.setImageResource(R.drawable.record_animate_11);}elseif(voiceValue >8000.0&& voiceValue <10000.0) {dialogImg.setImageResource(R.drawable.record_animate_12);}elseif(voiceValue >10000.0&& voiceValue <12000.0) {dialogImg.setImageResource(R.drawable.record_animate_13);}elseif(voiceValue >12000.0) {dialogImg.setImageResource(R.drawable.record_animate_14);}}// 录音线程privateRunnable recordThread =newRunnable() {@Overridepublicvoidrun() {recodeTime =0.0f;while(recordState == RECORD_ON) {{try{Thread.sleep(100);recodeTime +=0.1;// 获取音量,更新dialogif(!isCanceled) {voiceValue = mAudioRecorder.getAmplitude();recordHandler.sendEmptyMessage(1);}}catch(InterruptedException e) {e.printStackTrace();}}}}};@SuppressLint(HandlerLeak)privateHandler recordHandler =newHandler() {@OverridepublicvoidhandleMessage(Message msg) {setDialogImage();}};@OverridepublicbooleanonTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch(event.getAction()) {caseMotionEvent.ACTION_DOWN:// 按下按钮if(recordState != RECORD_ON) {showVoiceDialog(0);downY = event.getY();if(mAudioRecorder !=null) {mAudioRecorder.ready();recordState = RECORD_ON;mAudioRecorder.start();callRecordTimeThread();}}break;caseMotionEvent.ACTION_MOVE:// 滑动手指floatmoveY = event.getY();if(downY - moveY >50) {isCanceled =true;showVoiceDialog(1);}if(downY - moveY <20) {isCanceled =false;showVoiceDialog(0);}break;caseMotionEvent.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;}returntrue;}publicinterfaceRecordListener {publicvoidrecordEnd(String filePath);}}</code>Dialog布局:
12345678<codeclass="hljs"xml=""><!--?xml version=1.0encoding=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<codeclass="hljs"xml=""><!--?xml version=1.0encoding=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<codeclass="hljs"java="">packagecom.example.recordtest;/*** RecordStrategy 录音策略接口* @author acer*/publicinterfaceRecordStrategy {/*** 在这里进行录音准备工作,重置录音文件名等*/publicvoidready();/*** 开始录音*/publicvoidstart();/*** 录音结束*/publicvoidstop();/*** 录音失败时删除原来的旧文件*/publicvoiddeleteOldFile();/*** 获取录音音量的大小* @return*/publicdoublegetAmplitude();/*** 返回录音文件完整路径* @return*/publicString getFilePath();}</code>个人写的一个录音实践策略
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697<codeclass="hljs"java="">packagecom.example.recordtest;importjava.io.File;importjava.io.IOException;importjava.text.SimpleDateFormat;importjava.util.Date;importandroid.media.MediaRecorder;importandroid.os.Environment;publicclassAudioRecorderimplementsRecordStrategy {privateMediaRecorder recorder;privateString fileName;privateString fileFolder = Environment.getExternalStorageDirectory().getPath() + /TestRecord;privatebooleanisRecording =false;@Overridepublicvoidready() {// TODO Auto-generated method stubFile file =newFile(fileFolder);if(!file.exists()) {file.mkdir();}fileName = getCurrentDate();recorder =newMediaRecorder();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}// 以当前时间作为文件名privateString getCurrentDate() {SimpleDateFormat formatter =newSimpleDateFormat(yyyy_MM_dd_HHmmss);Date curDate =newDate(System.currentTimeMillis());// 获取当前时间String str = formatter.format(curDate);returnstr;}@Overridepublicvoidstart() {// TODO Auto-generated method stubif(!isRecording) {try{recorder.prepare();recorder.start();}catch(IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();}catch(IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}isRecording =true;}}@Overridepublicvoidstop() {// TODO Auto-generated method stubif(isRecording) {recorder.stop();recorder.release();isRecording =false;}}@OverridepublicvoiddeleteOldFile() {// TODO Auto-generated method stubFile file =newFile(fileFolder + / + fileName + .amr);file.deleteOnExit();}@OverridepublicdoublegetAmplitude() {// TODO Auto-generated method stubif(!isRecording) {return0;}returnrecorder.getMaxAmplitude();}@OverridepublicString getFilePath() {// TODO Auto-generated method stubreturnfileFolder + / + fileName + .amr;}}</code>MainActivity
12345678910111213141516171819202122232425262728<codeclass="hljs"java="">packagecom.example.recordtest;importandroid.os.Bundle;importandroid.app.Activity;importandroid.view.Menu;publicclassMainActivityextendsActivity {RecordButton button;@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (RecordButton) findViewById(R.id.btn_record);button.setAudioRecord(newAudioRecorder());}@OverridepublicbooleanonCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);returntrue;}}</code>
Android开发--仿微信语音对讲录音
最新推荐文章于 2024-04-13 21:44:36 发布
281

被折叠的 条评论
为什么被折叠?



