Android仿微信语音对讲录音功能
自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现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添加权限:
<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" />
代码参考
recordbutton 类,我们的自定义控件,重新复写了ontouchevent方法
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); } }
dialog布局:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:background="@drawable/record_bg" android:padding="20dp" > <imageview android:id="@+id/record_dialog_img" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <textview android:id="@+id/record_dialog_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textcolor="@android:color/white" android:layout_margintop="5dp" /> </linearlayout>
录音时间太短的toast布局:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/record_bg" android:padding="20dp" android:gravity="center" android:orientation="vertical" > <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/voice_to_short" /> <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:textcolor="@android:color/white" android:textsize="15sp" android:text="时间太短 录音失败" /> </linearlayout>
自定义的dialogstyle,对话框样式
<style name="dialogstyle"> <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>
recordstrategy 录音策略接口
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(); }
个人写的一个录音实践策略
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"; } }
mainactivity
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; } }
源码下载:android仿微信语音对讲录音
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。