当前位置: 移动技术网 > IT编程>移动开发>Android > Android仿微信语音消息的录制和播放功能

Android仿微信语音消息的录制和播放功能

2019年07月24日  | 移动技术网IT编程  | 我要评论

金庸武侠里的小神仙,札幌天气,力帆kp150

一、简述

效果:

实现功能:

长按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仿微信语音消息的录制和播放功能,希望对大家有所帮助

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网