Android仿微信语音消息的录制和播放功能
一、简述
效果:
实现功能:
长按button时改变button显示文字,弹出dialog(动态更新音量),动态生成录音文件,开始录音;
监听手指动作,规定区域。录音状态下手指划出规定区域取消录音,删除生成的录音文件;
监听手指动作。当手指抬起时,判断是否开始录音,录音时长是否过短,符合条件则提示录音时长过短;正常结束时通过回调返回该次录音的文件路径和时长。
4.点击录音列表的item时,播放动画,播放对应的音频文件。
主要用到4个核心类:
自定义录音按钮(audiorecordbutton);
弹框管理类(dialogmanager);
录音管理类(audiomanager)。
1.audiorecordbutton状态:
1.state_normal:普通状态
2.state_recording:录音中
3.state_cancel:取消录音
2.dialogmanager状态:
1.recording:录音中
2.want_to_cancel:取消录音
3.too_short:录音时间太短
3.audiomanager:
1.prepare():准备状态
2.cancel():取消录音
3.release():正常结束录音
4.getvoicelevel():获取音量
核心逻辑:
自定义button,重写ontouchevent()方法。
伪代码:
class audiorecorderbutton{ ontouchevent(){ down: changebuttonstate(state_recording); | dialogmanager.showdialog(recording) 触发longclick事件(audiomanager.prepare() --> end prepared --> | ); | getvoicelevel();//开启一个线程,更新dialog上的音量等级 move: if(wantcancel(x,y)){ dialogmanager.showdialog(want_to_cancel);更新dialog changebuttonstate(state_want_to_cancel);更新button状态 }else{ dialogmanager.showdialog(want_to_cancel); changebuttonstate(state_recording); } up: if(wantcancel == curstate){//当前状态是想取消状态 audiomanager.cancel(); } if(state_recording = curstate){ if(tooshort){//判断录制时长,如果录制时间过短 dialogmanager.showdialog(too_short); } audiomanager.release(); callbackactivity(url,time);//(当前录音文件路径,时长) } } }
二、mediamanager封装
简述:使用mediaplayer播放录制好的音频文件,要注意mediaplayer资源的释放。
代码:
import android.media.*; import java.io.ioexception; /** * 播放管理类 */ public class mediamanager { private static mediaplayer mmediaplayer; private static boolean ispause; public static void playsound(string filepath, mediaplayer.oncompletionlistener oncompletionlistener) { if (mmediaplayer == null) { mmediaplayer = new mediaplayer(); mmediaplayer.setonerrorlistener(new mediaplayer.onerrorlistener() { @override public boolean onerror(mediaplayer mp, int what, int extra) { mmediaplayer.reset(); return false; } }); } else { mmediaplayer.reset(); } try { mmediaplayer.setaudiostreamtype(android.media.audiomanager.stream_music); mmediaplayer.setoncompletionlistener(oncompletionlistener); mmediaplayer.setdatasource(filepath); mmediaplayer.prepare(); mmediaplayer.start(); } catch (ioexception e) { e.printstacktrace(); } } public static void pause(){ if(mmediaplayer != null && mmediaplayer.isplaying()){ mmediaplayer.pause(); ispause = true; } } public static void resume(){ if(mmediaplayer != null && ispause){ mmediaplayer.start(); ispause = false; } } public static void release(){ if(mmediaplayer != null){ mmediaplayer.release(); mmediaplayer = null; } } }
三、dialogmanager封装
封装了6个方法:
1. showrecordingdialog():用来设置diaog布局,拿到控件的引用,显示dialog。
2. recording():更改dialog状态为录音中状态。
3. wanttocancel():更改dialog状态为想要取消状态。
4. tooshort():更改dialog状态为录音时长过短状态。
5. dismissdialog():移除dialog。
6. updatevoicelevel():用来更新音量图片。
代码:
import android.app.dialog; import android.content.context; import android.view.layoutinflater; import android.view.view; import android.widget.imageview; import android.widget.textview; import com.tiddlerliu.wxrecorder.r; /** * dialog管理类 */ public class dialogmanager { private dialog mdialog; private imageview micon; private imageview mvoice; private textview mlabel; private context mcontext; public dialogmanager(context context) { mcontext = context; } /** * 显示dialog */ public void showrecordingdialog(){ //将布局应用于dialog mdialog = new dialog(mcontext, r.style.theme_audiodialog); layoutinflater inflater = layoutinflater.from(mcontext); view view = inflater.inflate(r.layout.dialog_recorder,null); mdialog.setcontentview(view); //成员控件赋值 micon = (imageview) mdialog.findviewbyid(r.id.recorder_dialog_icon); mvoice = (imageview) mdialog.findviewbyid(r.id.recorder_dialog_voice); mlabel = (textview) mdialog.findviewbyid(r.id.recorder_dialog_label); mdialog.show(); } public void recording(){ if(mdialog != null && mdialog.isshowing()){ micon.setvisibility(view.visible); mvoice.setvisibility(view.visible); mlabel.setvisibility(view.visible); micon.setimageresource(r.mipmap.recorder); mlabel.settext("手指上滑,取消发送"); } } public void wanttocancel(){ if(mdialog != null && mdialog.isshowing()){ micon.setvisibility(view.visible); mvoice.setvisibility(view.gone); mlabel.setvisibility(view.visible); micon.setimageresource(r.mipmap.cancel); mlabel.settext("松开手指,取消发送"); } } public void tooshort(){ if(mdialog != null && mdialog.isshowing()){ micon.setvisibility(view.visible); mvoice.setvisibility(view.gone); mlabel.setvisibility(view.visible); micon.setimageresource(r.mipmap.voice_to_short); mlabel.settext("录音时间过短"); } } public void dismissdialog(){ if(mdialog != null && mdialog.isshowing()){ mdialog.dismiss(); mdialog = null; } } /** * 通过level更新音量资源图片 * @param level */ public void updatevoicelevel(int level){ if(mdialog != null && mdialog.isshowing()){ int resid = mcontext.getresources().getidentifier("v"+level,"mipmap",mcontext.getpackagename()); mvoice.setimageresource(resid); } } }
四、audiomanager封装
4.1 添加必要权限
<uses-permission android:name="android.permission.record_audio"/> <uses-permission android:name="android.permission.write_external_storage"/>
4.2 代码
import android.media.mediarecorder; import java.io.file; import java.io.ioexception; import java.util.uuid; /** * 录音管理类 */ public class audiomanager { private string mdir;//文件夹名称 private mediarecorder mmediarecorder; private string mcurrentfilepath;//文件储存路径 private static audiomanager minstance; //表明mediarecorder是否进入prepare状态(状态为true才能调用stop和release方法) private boolean isprepared; public audiomanager(string dir) { mdir = dir; } public string getcurrentfilepath() { return mcurrentfilepath; } /** * 准备完毕接口 */ public interface audiostatelistener{ void wellprepared(); } public audiostatelistener mlistener; public void setonaudiostatelistener(audiostatelistener listener){ mlistener = listener; } /** * 单例 * @return audiomanager */ public static audiomanager getinstance(string dir){ if (minstance == null){ synchronized (audiomanager.class){ if(minstance == null){ minstance = new audiomanager(dir); } } } return minstance; } /** * 准备 */ public void prepareaudio() { try { isprepared = false; file dir = new file(mdir);//创建文件夹 if (!dir.exists()) { dir.mkdirs(); } string filename = generatefilename();//随机生成文件名 file file = new file(dir, filename);//创建文件 mcurrentfilepath = file.getabsolutepath(); mmediarecorder = new mediarecorder(); mmediarecorder.setoutputfile(file.getabsolutepath());//设置输出文件 mmediarecorder.setaudiosource(mediarecorder.audiosource.mic);//设置麦克风为音频源 mmediarecorder.setoutputformat(mediarecorder.outputformat.amr_nb);//设置音频格式 mmediarecorder.setaudioencoder(mediarecorder.audioencoder.amr_nb);//设置音频编码 mmediarecorder.prepare(); mmediarecorder.start(); //准备结束 isprepared = true; if (mlistener != null){ mlistener.wellprepared(); } } catch (ioexception e) { e.printstacktrace(); } } /** * 随机生成文件的名称 * @return */ private string generatefilename() { return uuid.randomuuid().tostring()+".amr"; } /** * 获取音量等级 */ public int getvoicelevel(int maxlevel) { if (isprepared) { try { //mmediarecorder.getmaxamplitude() 范围:1-32767 return maxlevel * mmediarecorder.getmaxamplitude() / 32768 + 1;//最大值 * [0,1)+ 1 } catch (exception e) { } } return 1; } /** * 重置 */ public void release(){ if(mmediarecorder != null){ mmediarecorder.stop(); mmediarecorder.release(); mmediarecorder = null; } } /** * 取消 */ public void cancel(){ release(); //删除产生的文件 if(mcurrentfilepath != null){ file file = new file(mcurrentfilepath); file.delete(); mcurrentfilepath = null; } } }
五、audiorecordbutton封装
import android.annotation.suppresslint; import android.content.context; import android.os.environment; import android.os.handler; import android.os.message; import android.util.attributeset; import android.view.motionevent; import android.view.view; import android.widget.button; import com.tiddlerliu.wxrecorder.r; /** * 自定义button */ @suppresslint("appcompatcustomview") public class audiorecordbutton extends button implements audiomanager.audiostatelistener{ private static final int state_normal = 1;//默认状态 private static final int state_recording = 2;//录音状态 private static final int state_want_cancel = 3;//想取消状态 private static final int distance_y_cancel = 50;//定义上滑取消距离 private int mcurstate = state_normal;//记录当前状态 private boolean isrecording = false;//是否在录音状态 private dialogmanager mdialogmanager; private audiomanager maudiomanager; private float mtime;//记录录音时长 private boolean mready;//是否触发onlongclick事件 private boolean iscomplete = true;//是否已经完成 public audiorecordbutton(context context) { this(context,null); } public audiorecordbutton(context context, attributeset attrs) { super(context, attrs); mdialogmanager = new dialogmanager(getcontext()); string dir = environment.getexternalstoragedirectory()+"/tiddlerliu/recorder/audios";//最好判断sd卡是否存在可读 maudiomanager = audiomanager.getinstance(dir); maudiomanager.setonaudiostatelistener(this); setonlongclicklistener(new onlongclicklistener() { @override public boolean onlongclick(view v) { mready = true; maudiomanager.prepareaudio(); return false; } }); } /** * 录音完成后的回调 */ public interface audiofinishrecorderlistener { void onfinish(float seconds,string filepath); } private audiofinishrecorderlistener maudiofinishrecorderlistener; public void setaudiofinishrecorderlistener(audiofinishrecorderlistener listener){ maudiofinishrecorderlistener = listener; } private static final int msg_audio_prepared = 0x110; private static final int msg_voice_changed = 0x111; private static final int msg_dialog_dismiss = 0x112; private static final int msg_audio_complete = 0x113;//达到最大时长,自动完成 /** * 获取音量大小 */ private runnable mgetvoicelevelrunnable = new runnable() { @override public void run() { while (isrecording){ try { thread.sleep(100); mtime += 0.1f; if(mtime >= 60f){//60s自动触发完成录制 mhandler.sendemptymessage(msg_audio_complete); } mhandler.sendemptymessage(msg_voice_changed); } catch (interruptedexception e) { e.printstacktrace(); } } } }; private handler mhandler = new handler(){ @override public void handlemessage(message msg) { switch (msg.what){ case msg_audio_prepared: //显示应该在audio end prepared以后 mdialogmanager.showrecordingdialog(); isrecording = true; iscomplete = false; new thread(mgetvoicelevelrunnable).start(); break; case msg_voice_changed: mdialogmanager.updatevoicelevel(maudiomanager.getvoicelevel(7)); break; case msg_dialog_dismiss: mdialogmanager.dismissdialog(); break; case msg_audio_complete: complete(); reset(); break; default: break; } } }; @override public void wellprepared() { mhandler.sendemptymessage(msg_audio_prepared); } @override public boolean ontouchevent(motionevent event) { int action = event.getaction(); int x = (int) event.getx(); int y = (int) event.gety(); switch (action){ case motionevent.action_down: changestate(state_recording); break; case motionevent.action_move: if(isrecording){ //根据(x,y)坐标,判断是否想要取消 if (wanttocancel(x,y)){ changestate(state_want_cancel); }else{ changestate(state_recording); } } break; case motionevent.action_up: if(!iscomplete){//没有执行超时自动完成逻辑 if (!mready) {//还未触发onlongclick事件 reset(); return super.ontouchevent(event); } if (!isrecording || mtime < 0.6f) {//还未开始录音 或者 录制时长过短 mdialogmanager.tooshort(); maudiomanager.cancel(); mhandler.sendemptymessagedelayed(msg_dialog_dismiss, 1300);//1.3秒后关闭对话框 } else if (mcurstate == state_recording) {//正常录制结束 complete(); } else if (mcurstate == state_want_cancel) {//想要取消状态 mdialogmanager.dismissdialog(); maudiomanager.cancel(); } reset(); } break; } return super.ontouchevent(event); } /** * 正常录制结束 */ private void complete() { mdialogmanager.dismissdialog(); maudiomanager.release(); if(maudiofinishrecorderlistener != null && !iscomplete){ maudiofinishrecorderlistener.onfinish(mtime,maudiomanager.getcurrentfilepath()); } } /** * 恢复状态和标志位 */ private void reset() { isrecording = false; mready = false; mtime = 0; iscomplete = true; changestate(state_normal); } /** * 根据(x,y)坐标,判断是否想要取消 * @param x * @param y * @return */ private boolean wanttocancel(int x, int y) { if(x < 0 || x > getwidth()){//手指移出button范围 return true; } if(y < - distance_y_cancel || y > getheight() + distance_y_cancel){//手指移出y轴设定范围 return true; } return false; } /** * 改变状态 * @param state */ private void changestate(int state) { if(mcurstate != state){ mcurstate = state; switch (state){ case state_normal: setbackgroundresource(r.drawable.btn_recorder_normal); settext(r.string.str_recorder_normal); break; case state_recording: setbackgroundresource(r.drawable.btn_recorder_recording); settext(r.string.str_recorder_recording); if(isrecording){ mdialogmanager.recording(); } break; case state_want_cancel: setbackgroundresource(r.drawable.btn_recorder_recording); settext(r.string.str_recorder_want_cancel); mdialogmanager.wanttocancel(); break; default: break; } } } }
六、 主界面实现
6.1 adapter
import android.content.context; import android.support.annotation.nonnull; import android.support.annotation.nullable; import android.util.displaymetrics; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.view.windowmanager; import android.widget.arrayadapter; import android.widget.textview; import com.tiddlerliu.wxrecorder.r; import com.tiddlerliu.wxrecorder.model.recorder; import java.util.list; public class recorderadapter extends arrayadapter<recorder>{ private int mminitemwidth; private int mmaxitemwidth; private layoutinflater minflater; public recorderadapter(@nonnull context context, list<recorder> datas) { super(context, -1 ,datas); minflater = layoutinflater.from(context); //获取屏幕参数 windowmanager wm = (windowmanager) context.getsystemservice(context.window_service); displaymetrics outmetrics = new displaymetrics(); wm.getdefaultdisplay().getmetrics(outmetrics); //设置最小宽度和最大宽度 mminitemwidth = (int) (outmetrics.widthpixels * 0.16f); mmaxitemwidth = (int) (outmetrics.widthpixels * 0.64f); } @nonnull @override public view getview(int position, @nullable view convertview, @nonnull viewgroup parent) { viewholder holder = null; if(convertview == null){ convertview = minflater.inflate(r.layout.item_recorder,parent,false); holder = new viewholder(); holder.seconds = (textview) convertview.findviewbyid(r.id.item_recorder_time); holder.length = convertview.findviewbyid(r.id.item_recorder_length); convertview.settag(holder); }else { holder = (viewholder) convertview.gettag(); } //设置时长 holder.seconds.settext(math.round(getitem(position).gettime())+ "\""); //根据时长按比例设置时长 viewgroup.layoutparams lp = holder.length.getlayoutparams(); lp.width = (int) (mminitemwidth + (mmaxitemwidth/60f * getitem(position).gettime())); return convertview; } private class viewholder{ textview seconds; view length; } }
6.2 activity
import android.graphics.drawable.animationdrawable; import android.media.mediaplayer; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.view.view; import android.widget.adapterview; import android.widget.arrayadapter; import android.widget.listview; import com.tiddlerliu.wxrecorder.customview.audiorecordbutton; import com.tiddlerliu.wxrecorder.customview.mediamanager; import com.tiddlerliu.wxrecorder.adapter.recorderadapter; import com.tiddlerliu.wxrecorder.model.recorder; import java.util.arraylist; import java.util.list; public class mainactivity extends appcompatactivity { private listview mlistview; private audiorecordbutton maudiorecordbutton; private arrayadapter<recorder> madapter ; private list<recorder> mdatas = new arraylist<>(); private view manimview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mlistview = (listview) findviewbyid(r.id.recorder_list); maudiorecordbutton = (audiorecordbutton) findviewbyid(r.id.recorder_button); maudiorecordbutton.setaudiofinishrecorderlistener(new audiorecordbutton.audiofinishrecorderlistener() { @override public void onfinish(float seconds, string filepath) { recorder recorder = new recorder(seconds,filepath); mdatas.add(recorder); madapter.notifydatasetchanged(); mlistview.setselection(mdatas.size()-1); } }); madapter = new recorderadapter(this,mdatas); mlistview.setadapter(madapter); mlistview.setonitemclicklistener(new adapterview.onitemclicklistener() { @override public void onitemclick(adapterview<?> parent, view view, int position, long id) { if(manimview != null){ manimview.setbackgroundresource(r.mipmap.adj); manimview = null; } //播放动画 manimview = view.findviewbyid(r.id.item_recorder_anim); manimview.setbackgroundresource(r.drawable.play_ainm); animationdrawable anim = (animationdrawable) manimview.getbackground(); anim.start(); //播放音频 mediamanager.playsound(mdatas.get(position).getfilepath(), new mediaplayer.oncompletionlistener() { @override public void oncompletion(mediaplayer mp) { manimview.setbackgroundresource(r.mipmap.adj); } }); } }); } @override protected void onpause() { super.onpause(); mediamanager.pause(); } @override protected void onresume() { super.onresume(); mediamanager.resume(); } @override protected void ondestroy() { super.ondestroy(); mediamanager.release(); } }
总结
以上所述是小编给大家介绍的android仿微信语音消息的录制和播放功能,希望对大家有所帮助