当前位置: 移动技术网 > IT编程>移动开发>Android > Android仿微信语音对讲录音功能

Android仿微信语音对讲录音功能

2020年03月09日  | 移动技术网IT编程  | 我要评论

开心农场任我行,中国林媛,郑元畅陈妍希吻戏

自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的方式实现录音和界面的解耦合,以方便我们在实际情况中对录音方法的不同需求(例如想要实现wav格式的编码时我们也就不能再使用mediarecorder,而只能使用audiorecord进行处理)。

效果图:

实现思路:

1.在微信中我们可以看到实现语音对讲的是通过点按按钮来完成的,因此在这里我选择重新自己的控件使其继承自button并重写ontouchevent方法,来实现对录音的判断。

2.在ontouchevent方法中,

当我们按下按钮时,首先显示录音的对话框,然后调用录音准备方法并开始录音,接着开启一个计时线程,每隔0.1秒的时间获取一次录音音量的大小,并通过handler根据音量大小更新dialog中的显示图片;

当我们移动手指时,若手指向上移动距离大于50,在dialog中显示松开手指取消录音的提示,并将iscanceled变量(表示我们最后是否取消了录音)置为true,上移动距离小于20时,我们恢复dialog的图片,并将iscanceled置为false;
当抬起手指时,我们首先关闭录音对话框,接着调用录音停止方法并关闭计时线程,然后我们判断是否取消录音,若是的话则删除录音文件,否则判断计时时间是否太短,最后调用回调接口中的recordend方法。

3.在这里为了适应不同的录音需求,我使用了策略模式来进行处理,将每一个不同的录音方法视为一种不同的策略,根据自己的需要去改写。

注意问题

1.在ontouchevent的返回值中应该返回true,这样才能屏蔽之后其他的触摸事件,否则当手指滑动离开button之后将不能在响应我们的触摸方法。
2.不要忘记为自己的app添加权限:

<uses-permission android:name="android.permission.record_audio" />
<uses-permission android:name="android.permission.write_external_storage" />
<uses-permission android:name="android.permission.read_external_storage" />

代码参考

recordbutton 类,我们的自定义控件,重新复写了ontouchevent方法

package com.example.recordtest;

import android.annotation.suppresslint;
import android.app.dialog;
import android.content.context;
import android.os.handler;
import android.os.message;
import android.util.attributeset;
import android.view.gravity;
import android.view.layoutinflater;
import android.view.motionevent;
import android.view.view;
import android.widget.button;
import android.widget.imageview;
import android.widget.textview;
import android.widget.toast;

public class recordbutton extends button {

  private static final int min_record_time = 1; // 最短录音时间,单位秒
  private static final int record_off = 0; // 不在录音
  private static final int record_on = 1; // 正在录音

  private dialog mrecorddialog;
  private recordstrategy maudiorecorder;
  private thread mrecordthread;
  private recordlistener listener;

  private int recordstate = 0; // 录音状态
  private float recodetime = 0.0f; // 录音时长,如果录音时间太短则录音失败
  private double voicevalue = 0.0; // 录音的音量值
  private boolean iscanceled = false; // 是否取消录音
  private float downy;

  private textview dialogtextview;
  private imageview dialogimg;
  private context mcontext;

  public recordbutton(context context) {
    super(context);
    // todo auto-generated constructor stub
    init(context);
  }

  public recordbutton(context context, attributeset attrs, int defstyle) {
    super(context, attrs, defstyle);
    // todo auto-generated constructor stub
    init(context);
  }

  public recordbutton(context context, attributeset attrs) {
    super(context, attrs);
    // todo auto-generated constructor stub
    init(context);
  }

  private void init(context context) {
    mcontext = context;
    this.settext("按住 说话");
  }

  public void setaudiorecord(recordstrategy record) {
    this.maudiorecorder = record;
  }

  public void setrecordlistener(recordlistener listener) {
    this.listener = listener;
  }

  // 录音时显示dialog
  private void showvoicedialog(int flag) {
    if (mrecorddialog == null) {
      mrecorddialog = new dialog(mcontext, r.style.dialogstyle);
      mrecorddialog.setcontentview(r.layout.dialog_record);
      dialogimg = (imageview) mrecorddialog
          .findviewbyid(r.id.record_dialog_img);
      dialogtextview = (textview) mrecorddialog
          .findviewbyid(r.id.record_dialog_txt);
    }
    switch (flag) {
    case 1:
      dialogimg.setimageresource(r.drawable.record_cancel);
      dialogtextview.settext("松开手指可取消录音");
      this.settext("松开手指 取消录音");
      break;

    default:
      dialogimg.setimageresource(r.drawable.record_animate_01);
      dialogtextview.settext("向上滑动可取消录音");
      this.settext("松开手指 完成录音");
      break;
    }
    dialogtextview.settextsize(14);
    mrecorddialog.show();
  }

  // 录音时间太短时toast显示
  private void showwarntoast(string toasttext) {
    toast toast = new toast(mcontext);
    view warnview = layoutinflater.from(mcontext).inflate(
        r.layout.toast_warn, null);
    toast.setview(warnview);
    toast.setgravity(gravity.center, 0, 0);// 起点位置为中间
    toast.show();
  }

  // 开启录音计时线程
  private void callrecordtimethread() {
    mrecordthread = new thread(recordthread);
    mrecordthread.start();
  }

  // 录音dialog图片随录音音量大小切换
  private void setdialogimage() {
    if (voicevalue < 600.0) {
      dialogimg.setimageresource(r.drawable.record_animate_01);
    } else if (voicevalue > 600.0 && voicevalue < 1000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_02);
    } else if (voicevalue > 1000.0 && voicevalue < 1200.0) {
      dialogimg.setimageresource(r.drawable.record_animate_03);
    } else if (voicevalue > 1200.0 && voicevalue < 1400.0) {
      dialogimg.setimageresource(r.drawable.record_animate_04);
    } else if (voicevalue > 1400.0 && voicevalue < 1600.0) {
      dialogimg.setimageresource(r.drawable.record_animate_05);
    } else if (voicevalue > 1600.0 && voicevalue < 1800.0) {
      dialogimg.setimageresource(r.drawable.record_animate_06);
    } else if (voicevalue > 1800.0 && voicevalue < 2000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_07);
    } else if (voicevalue > 2000.0 && voicevalue < 3000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_08);
    } else if (voicevalue > 3000.0 && voicevalue < 4000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_09);
    } else if (voicevalue > 4000.0 && voicevalue < 6000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_10);
    } else if (voicevalue > 6000.0 && voicevalue < 8000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_11);
    } else if (voicevalue > 8000.0 && voicevalue < 10000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_12);
    } else if (voicevalue > 10000.0 && voicevalue < 12000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_13);
    } else if (voicevalue > 12000.0) {
      dialogimg.setimageresource(r.drawable.record_animate_14);
    }
  }

  // 录音线程
  private runnable recordthread = new runnable() {

    @override
    public void run() {
      recodetime = 0.0f;
      while (recordstate == record_on) {
        {
          try {
            thread.sleep(100);
            recodetime += 0.1;
            // 获取音量,更新dialog
            if (!iscanceled) {
              voicevalue = maudiorecorder.getamplitude();
              recordhandler.sendemptymessage(1);
            }
          } catch (interruptedexception e) {
            e.printstacktrace();
          }
        }
      }
    }
  };

  @suppresslint("handlerleak")
  private handler recordhandler = new handler() {
    @override
    public void handlemessage(message msg) {
      setdialogimage();
    }
  };

  @override
  public boolean ontouchevent(motionevent event) {
    // todo auto-generated method stub
    switch (event.getaction()) {
    case motionevent.action_down: // 按下按钮
      if (recordstate != record_on) {
        showvoicedialog(0);
        downy = event.gety();
        if (maudiorecorder != null) {
          maudiorecorder.ready();
          recordstate = record_on;
          maudiorecorder.start();
          callrecordtimethread();
        }
      }
      break;
    case motionevent.action_move: // 滑动手指
      float movey = event.gety();
      if (downy - movey > 50) {
        iscanceled = true;
        showvoicedialog(1);
      }
      if (downy - movey < 20) {
        iscanceled = false;
        showvoicedialog(0);
      }
      break;
    case motionevent.action_up: // 松开手指
      if (recordstate == record_on) {
        recordstate = record_off;
        if (mrecorddialog.isshowing()) {
          mrecorddialog.dismiss();
        }
        maudiorecorder.stop();
        mrecordthread.interrupt();
        voicevalue = 0.0;
        if (iscanceled) {
          maudiorecorder.deleteoldfile();
        } else {
          if (recodetime < min_record_time) {
            showwarntoast("时间太短 录音失败");
            maudiorecorder.deleteoldfile();
          } else {
            if (listener != null) {
              listener.recordend(maudiorecorder.getfilepath());
            }
          }
        }
        iscanceled = false;
        this.settext("按住 说话");
      }
      break;
    }
    return true;
  }

  public interface recordlistener {
    public void recordend(string filepath);
  }
}

dialog布局:

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center"
  android:gravity="center"
  android:background="@drawable/record_bg"  
  android:padding="20dp" >

  <imageview
    android:id="@+id/record_dialog_img"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

  <textview
    android:id="@+id/record_dialog_txt"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textcolor="@android:color/white"
    android:layout_margintop="5dp" />

</linearlayout>

录音时间太短的toast布局:

<?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/record_bg"
  android:padding="20dp"
  android:gravity="center"
  android:orientation="vertical" >

  <imageview
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/voice_to_short" />

  <textview
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textcolor="@android:color/white"
    android:textsize="15sp"
    android:text="时间太短 录音失败" />

</linearlayout>

自定义的dialogstyle,对话框样式

<style name="dialogstyle">
    <item name="android:windowbackground">@android:color/transparent</item>
    <item name="android:windowframe">@null</item>
    <item name="android:windownotitle">true</item>
    <item name="android:windowisfloating">true</item>
    <item name="android:windowistranslucent">true</item>
    <item name="android:windowanimationstyle">@android:style/animation.dialog</item>
    <!-- 显示对话框时当前的屏幕是否变暗 -->
    <item name="android:backgrounddimenabled">false</item>
</style>

recordstrategy 录音策略接口

package com.example.recordtest;

/**
 * recordstrategy 录音策略接口
 * @author acer
 */
public interface recordstrategy {

  /**
   * 在这里进行录音准备工作,重置录音文件名等
   */
  public void ready();
  /**
   * 开始录音
   */
  public void start();
  /**
   * 录音结束
   */
  public void stop();

  /**
   * 录音失败时删除原来的旧文件
   */
  public void deleteoldfile();

  /**
   * 获取录音音量的大小
   * @return 
   */
  public double getamplitude();

  /**
   * 返回录音文件完整路径
   * @return
   */
  public string getfilepath();

}

个人写的一个录音实践策略

package com.example.recordtest;

import java.io.file;
import java.io.ioexception;
import java.text.simpledateformat;
import java.util.date;

import android.media.mediarecorder;
import android.os.environment;

public class audiorecorder implements recordstrategy {

  private mediarecorder recorder;
  private string filename;
  private string filefolder = environment.getexternalstoragedirectory()
      .getpath() + "/testrecord";

  private boolean isrecording = false;

  @override
  public void ready() {
    // todo auto-generated method stub
    file file = new file(filefolder);
    if (!file.exists()) {
      file.mkdir();
    }
    filename = getcurrentdate();
    recorder = new mediarecorder();
    recorder.setoutputfile(filefolder + "/" + filename + ".amr");
    recorder.setaudiosource(mediarecorder.audiosource.mic);// 设置mediarecorder的音频源为麦克风
    recorder.setoutputformat(mediarecorder.outputformat.raw_amr);// 设置mediarecorder录制的音频格式
    recorder.setaudioencoder(mediarecorder.audioencoder.amr_nb);// 设置mediarecorder录制音频的编码为amr
  }

  // 以当前时间作为文件名
  private string getcurrentdate() {
    simpledateformat formatter = new simpledateformat("yyyy_mm_dd_hhmmss");
    date curdate = new date(system.currenttimemillis());// 获取当前时间
    string str = formatter.format(curdate);
    return str;
  }

  @override
  public void start() {
    // todo auto-generated method stub
    if (!isrecording) {
      try {
        recorder.prepare();
        recorder.start();
      } catch (illegalstateexception e) {
        // todo auto-generated catch block
        e.printstacktrace();
      } catch (ioexception e) {
        // todo auto-generated catch block
        e.printstacktrace();
      }

      isrecording = true;
    }

  }

  @override
  public void stop() {
    // todo auto-generated method stub
    if (isrecording) {
      recorder.stop();
      recorder.release();
      isrecording = false;
    }

  }

  @override
  public void deleteoldfile() {
    // todo auto-generated method stub
    file file = new file(filefolder + "/" + filename + ".amr");
    file.deleteonexit();
  }

  @override
  public double getamplitude() {
    // todo auto-generated method stub
    if (!isrecording) {
      return 0;
    }
    return recorder.getmaxamplitude();
  }

  @override
  public string getfilepath() {
    // todo auto-generated method stub
    return filefolder + "/" + filename + ".amr";
  }

}

mainactivity

package com.example.recordtest;

import android.os.bundle;
import android.app.activity;
import android.view.menu;

public class mainactivity extends activity {

  recordbutton button;

  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
    button = (recordbutton) findviewbyid(r.id.btn_record);
    button.setaudiorecord(new audiorecorder());
  }


  @override
  public boolean oncreateoptionsmenu(menu menu) {
    // inflate the menu; this adds items to the action bar if it is present.
    getmenuinflater().inflate(r.menu.main, menu);
    return true;
  }

}

源码下载:

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

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

相关文章:

验证码:
移动技术网