欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-16 08:50:52
一、简述 效果: 实现功能: 长按button时改变button显示文字,弹出dialog(动态更新音量),动态生成录音文件,开始录音; 监听手指动作,规定区域...

一、简述

效果:

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

上一篇:

下一篇: