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

Android仿微信、录制音频并发送功能

程序员文章站 2024-03-05 21:17:13
myrecorder(仿微信,录制音频并发送功能) ①布局实现(activity_main.xml) 布局采用线性布局,上面使用的一个listview,下面使用的...

myrecorder(仿微信,录制音频并发送功能)

Android仿微信、录制音频并发送功能

①布局实现(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的布局:

Android仿微信、录制音频并发送功能

<?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"/>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。