Android仿微信、录制音频并发送功能
myrecorder(仿微信,录制音频并发送功能)
①布局实现(activity_main.xml)
布局采用线性布局,上面使用的一个listview,下面使用的是一个自定义的button(会在下面进行介绍)
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.yitong.myrecorder.mainactivity"> <listview android:id="@+id/main_listview" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ebebeb" android:dividerheight="10dp" android:divider="@null" /> <framelayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#fff"> <com.yitong.myrecorder.view.audiorecorderbutton android:id="@+id/main_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_margintop="6dp" android:layout_marginbottom="6dp" android:layout_marginleft="60dp" android:layout_marginright="60dp" android:minheight="0dp" android:padding="6dp" android:text="@string/str_recoder_normal" android:textsize="20sp" android:textcolor="#727272" android:background="@drawable/btn_recorder_normal" /> <view android:layout_width="match_parent" android:layout_height="1dp" android:background="#ccc"/> </framelayout> </linearlayout>
相关使用的string值(需要添加到value/string中):
<string name="str_recoder_normal">按住说话</string> <string name="str_recorder_recording">松开结束</string> <string name="str_recorder_want_cancel">松开手指,取消发送</string> <string name="str_dialog_want_cancel">手指上滑,取消发送</string> <string name="str_dialog_want_send">手指上滑,取消发送</string> <string name="str_dialog_time_short">录音时间过短</string>
②我们分析一下自定button的几种状态:
1.正常状态 (在初次显示,即没有点击的时候显示的状态,显示的文本为“按住说话”)
2.录音状态 (当手指按在button上时,即为录音状态,显示的文本为“松开结束”)
3.取消状态 (当手指上滑,此时若松开手指,便取消发送,即为取消状态,显示的文本为“松开手指,取消发送”)
③当录音状态时,在view上有一个dialog的提示,首先我们先自定义这个dialog的布局:
<?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/dialog_bg" android:orientation="vertical" android:padding="20dp"> <linearlayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="horizontal"> <imageview android:id="@+id/main_recorder_dialog_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/recorder" android:visibility="visible"/> <imageview android:id="@+id/main_recorder_dialog_voice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/v1" android:visibility="visible"/> </linearlayout> <textview android:id="@+id/main_recorder_dialog_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margintop="5dp" android:text="@string/str_dialog_want_cancel" android:textcolor="#fff" android:textsize="20sp"/> </linearlayout>
其中用到的@drawable/dialog_bg即为自定的shape
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="12dp"/> <solid android:color="#a3d7f5"/> <stroke android:width="1dp" android:color="#9b9b9b"/> </shape>
④定义dialogmanager,便于对这个自定义布局的dialog进行操作
public class dialogmanager { private static final string tag = "dialogmanager"; private dialog mdialog; private imageview micon; private imageview mvoice; private textview mlabel; private context mcontext; public dialogmanager(context mcontext) { this.mcontext = mcontext; } /** * 显示对话框 */ public void showrecordeingdialog() { mdialog = new dialog(mcontext, r.style.theme_audiodialog); layoutinflater inflater = (layoutinflater) mcontext.getsystemservice(mcontext.layout_inflater_service); view view = inflater.inflate(r.layout.dialog, null); mdialog.setcontentview(view); micon = (imageview) mdialog.findviewbyid(r.id.main_recorder_dialog_icon); mvoice = (imageview) mdialog.findviewbyid(r.id.main_recorder_dialog_voice); mlabel = (textview) mdialog.findviewbyid(r.id.main_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(r.string.str_dialog_want_send); } } /** * 取消录制对话框提示 */ public void wanttocancel() { if (mdialog != null && mdialog.isshowing()) { micon.setvisibility(view.visible); mvoice.setvisibility(view.visible); mlabel.setvisibility(view.visible); micon.setimageresource(r.mipmap.recorder); mlabel.settext(r.string.str_recorder_want_cancel); } } /** * 录音时间过短提示 */ public void tooshort() { if (mdialog != null && mdialog.isshowing()) { micon.setvisibility(view.visible); mvoice.setvisibility(view.visible); mlabel.setvisibility(view.visible); micon.setimageresource(r.mipmap.recorder); mlabel.settext(r.string.str_dialog_time_short); } } /** * 取消对话框 */ public void dismissdialog() { if (mdialog != null && mdialog.isshowing()) { mdialog.dismiss(); mdialog = null; } } /** * 显示音量大小 */ public void updatevoicelevel(int level) { if (mdialog != null && mdialog.isshowing()) { int resid = mcontext.getresources().getidentifier("v" + level, "mipmap", mcontext.getpackagename()); mvoice.setimageresource(resid); } } }
dialog的样式theme_audiodialog,需要在values/styles.xml中定义
<style name="theme_audiodialog"> <item name="android:windowbackground">@android:color/transparent</item> <item name="android:windowframe">@null</item> <item name="android:windowisfloating">true</item> <item name="android:windowistranslucent">true</item> <item name="android:backgrounddimenabled">false</item> </style>
⑤当手指按住button时,便开始录音,所以我们还需要定义一个录音的管理类audiomanager来控制录制状态。
public class audiomanager { private mediarecorder mmediarecorder; private string mdir;// 保存的目录 private string mcurrentfilepath;// 保存音频文件的全路径 private boolean isprepared = false;// 是否准备完毕 private audiomanager(string dir) { mdir = dir; } private static audiomanager minstance; public static audiomanager getminstance(string mdir) { if (minstance == null) { synchronized (audiomanager.class) { if (minstance == null) { minstance = new audiomanager(mdir); } } } return minstance; } /** * 准备完毕的回调 */ public interface audiostatelistener { void wellprepared(); } private audiostatelistener mlistener; public void setaudiostatelistener(audiostatelistener listener) { mlistener = listener; } /** 准备录制 */ public void prepareaudio() { try { isprepared = false; file dir = new file(mdir); if (!dir.exists()) { dir.mkdirs(); } string filename = generatename(); file file = new file(dir, filename); mcurrentfilepath = file.getabsolutepath(); mmediarecorder = new mediarecorder(); // 设置输出文件 mmediarecorder.setoutputfile(mcurrentfilepath); // 设置音频源为麦克风 mmediarecorder.setaudiosource(mediarecorder.audiosource.mic); // 设置音频格式 mmediarecorder.setoutputformat(mediarecorder.outputformat.raw_amr); // 设置音频编码 mmediarecorder.setaudioencoder(mediarecorder.audioencoder.amr_nb); mmediarecorder.prepare(); mmediarecorder.start(); isprepared = true; if (mlistener != null) { mlistener.wellprepared(); } } catch (exception e) { e.printstacktrace(); } } /** 获取音量大小 */ public int getvoicelevel(int maxlevel) { if (isprepared) { try { //mmediarecorder.getmaxamplitude() 1-32767 //注意此处mmediarecorder.getmaxamplitude 只能取一次,如果前面取了一次,后边再取就为0了 return ((mmediarecorder.getmaxamplitude() * maxlevel) / 32768) + 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); if(file.exists()) { file.delete(); mcurrentfilepath = null; } } } /** 获取录制音频的总路径 */ public string getmcurrentfilepath(){ return mcurrentfilepath; } /** * 生成一个随机名字 */ private string generatename() { return uuid.randomuuid().tostring() + ".mp3"; } }
⑥处理完dialogmanager和audiomanger后,接着我们回到自定义的button,即audiorecorderbutton
public class audiorecorderbutton 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_to_cancel = 3;//取消状态 private static final string tag = "audiorecorderbutton"; private int mcurstate = state_normal;//当前状态 private boolean isrecording = false;//是否正在录音 private dialogmanager mdialogmanger; private audiomanager maudiomanager; private boolean mready = false;//是否触发longclick private float mtime;//计时 public audiorecorderbutton(context context) { this(context, null); } public audiorecorderbutton(context context, attributeset attrs) { this(context, attrs, 0); } public audiorecorderbutton(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); mdialogmanger = new dialogmanager(getcontext()); string dir = environment.getexternalstoragedirectory() + "/my_recorder_audios"; maudiomanager = audiomanager.getminstance(dir); maudiomanager.setaudiostatelistener(this); setonlongclicklistener(new onlongclicklistener() { @override public boolean onlongclick(view v) { mready = true; maudiomanager.prepareaudio(); return false; } }); } @override public boolean ontouchevent(motionevent event) { int x = (int) event.getx(); int y = (int) event.gety(); switch (event.getaction()) { case motionevent.action_down: changesate(state_recording); break; case motionevent.action_move: if (isrecording) { if (iscancelrecorder(x, y)) { changesate(state_want_to_cancel); } else { changesate(state_recording); } } break; case motionevent.action_up: if (!mready) { reset(); return super.ontouchevent(event); } if (!isrecording || mtime < 0.6f) { mdialogmanger.tooshort(); maudiomanager.cancel(); mhandler.sendemptymessagedelayed(msg_loding_dismiss, 1000); } else if (mcurstate == state_recording) {//正常录制结束 mdialogmanger.dismissdialog(); maudiomanager.release(); if (mlistener != null) { mlistener.onfinish(mtime, maudiomanager.getmcurrentfilepath()); } } else if (mcurstate == state_want_to_cancel) { mdialogmanger.dismissdialog(); maudiomanager.cancel(); } reset(); break; } return super.ontouchevent(event); } /** * 根据不同状态,更改不同的文字和显示的背景 */ private void changesate(int staterecording) { if (mcurstate != staterecording) { mcurstate = staterecording; switch (mcurstate) { case state_normal: setbackgroundresource(r.drawable.btn_recorder_normal); settext(r.string.str_recoder_normal); break; case state_recording: setbackgroundresource(r.drawable.btn_recording); settext(r.string.str_recorder_recording); if (isrecording) { mdialogmanger.recording(); } break; case state_want_to_cancel: setbackgroundresource(r.drawable.btn_recording); settext(r.string.str_recorder_want_cancel); mdialogmanger.wanttocancel(); break; } } } /** * 根据移动后的位置,判断是否取消录音 */ private boolean iscancelrecorder(int x, int y) { if (x < 0 || x > getwidth() || y < 0 || y > getheight()) { return true; } return false; } /** * 重置标识位 */ private void reset() { changesate(state_normal); isrecording = false; mready = false; mtime = 0; } /** * 开始播放时回调此方法 */ @override public void wellprepared() { mhandler.sendemptymessage(msg_audio_prepared); } private static final int msg_audio_prepared = 0x110; private static final int msg_voice_chage = 0x111; private static final int msg_loding_dismiss = 0x112; private handler mhandler = new handler() { @override public void handlemessage(message msg) { super.handlemessage(msg); switch (msg.what) { case msg_audio_prepared: mdialogmanger.showrecordeingdialog(); isrecording = true; new thread(mgetvoicelevelrunnable).start(); break; case msg_voice_chage: mdialogmanger.updatevoicelevel(maudiomanager.getvoicelevel(7)); break; case msg_loding_dismiss: mdialogmanger.dismissdialog(); break; } } }; /** * 获取音量大小,并计时 */ private runnable mgetvoicelevelrunnable = new runnable() { @override public void run() { while (isrecording) { systemclock.sleep(100); mtime += 0.1f; mhandler.sendemptymessage(msg_voice_chage); } } }; /** * 完成录制后的回调接口 */ public interface audiofinishrecorderlistener { void onfinish(float time, string filepath); } private audiofinishrecorderlistener mlistener; public void setaudiofinishrecorderlistener(audiofinishrecorderlistener listener) { mlistener = listener; } }
=====================至此自定义button就定义完===================================
①接着我们回到了mainactivity,我们需要获取listview和audiorecorderbutton组件。对于listview,需要定义adapter,当点击某个条目的需要把录制的音频播放出来,需要定义一个mediamanager来控制音频的播放。
②首先我们先定义recorderadapter
/** * 音频实体类,包含音频的长度和保存的路径 */ public class recorder implements serializable { private int time; private string filepath; public recorder() { } public recorder(int time, string filepath) { this.time = time; this.filepath = filepath; } public void settime(int time) { this.time = time; } public void setfilepath(string filepath) { this.filepath = filepath; } public float gettime() { return time; } public string getfilepath() { return filepath; } } /** * 继承arrayadater,重写getview方法 */ public class recorderadapter extends arrayadapter<recorder> { private list<recorder> mdatas; private context mcontext; private layoutinflater minfalter; private int mminitemwidhth; private int mmaxitemwidhth; public recorderadapter(context context, list<recorder> datas) { super(context, -1, datas); mdatas = datas; mcontext = context; minfalter = (layoutinflater) context.getsystemservice(context.layout_inflater_service); windowmanager wm = (windowmanager) context.getsystemservice(context.window_service); displaymetrics outmetrics = new displaymetrics(); wm.getdefaultdisplay().getmetrics(outmetrics); mmaxitemwidhth = (int) (outmetrics.widthpixels * 0.7f); mminitemwidhth = (int) (outmetrics.widthpixels * 0.15f); } @override public view getview(int position, view convertview, viewgroup parent) { viewholder holder = new viewholder(); if(convertview == null) { convertview = minfalter.inflate(r.layout.item_recorder, null); } holder = holder.getholder(convertview); holder.setview(holder, mdatas.get(position)); return convertview; } private class viewholder{ textview time; view length; public viewholder getholder(view view){ viewholder holder = (viewholder) view.gettag(); if(holder == null) { holder = new viewholder(); } holder.time = (textview) view.findviewbyid(r.id.item_recorder_time); holder.length = view.findviewbyid(r.id.item_recorder_length); view.settag(holder); return holder; } public void setview(viewholder holder, recorder recorder) { holder.time.settext(recorder.gettime() + "\""); viewgroup.layoutparams layoutparams = holder.length.getlayoutparams(); layoutparams.width = (int) (mminitemwidhth + (mmaxitemwidhth / 60f * recorder.gettime())); } } }
③定义mediamanger,用于播放音频
public class mediamanager { private static mediaplayer mmediaplayer; private static boolean ispause = false;//是否是暂停 /** * 播放音频 */ 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(audiomanager.stream_music); mmediaplayer.setoncompletionlistener(oncompletionlistener); mmediaplayer.setdatasource(filepath); mmediaplayer.prepare(); mmediaplayer.start(); } catch (exception 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; } } }
④mainactivity的实现
public class mainactivity extends appcompatactivity { private static final string tag = "mainactivity"; private list<recorder> mdatas = new arraylist<recorder>(); private audiorecorderbutton maudiorecorderbutton; private listview mlistview; private recorderadapter madapter; private view manimview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); initaction(); } private void initview() { maudiorecorderbutton = (audiorecorderbutton) findviewbyid(r.id.main_btn); mlistview = (listview) findviewbyid(r.id.main_listview); } private void initaction() { maudiorecorderbutton.setaudiofinishrecorderlistener(new audiorecorderbutton.audiofinishrecorderlistener() { @override public void onfinish(float time, string filepath) { recorder recorder = new recorder((int)time, 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) { // 播放帧动画 manimview = view.findviewbyid(r.id.item_anim); manimview.setbackgroundresource(r.drawable.play_anim); 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(); } }
帧动画play_anim定义在drawable下
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@mipmap/v_anim1" android:duration="300"/> <item android:drawable="@mipmap/v_anim2" android:duration="300"/> <item android:drawable="@mipmap/v_anim3" android:duration="300"/> </animation-list>
⑤最后,不要忘了添加权限
<uses-permission android:name="android.permission.record_audio"/> <uses-permission android:name="android.permission.write_external_storage"/> <uses-permission android:name="android.permission.mount_unmount_filesystems"/>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Android多线程学习实例详解
下一篇: Vue.extend()