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

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

程序员文章站 2023-11-09 16:37:16
自微信出现以来取得了很好的成绩,语音对讲的实现更加方便了人与人之间的交流。今天来实践一下微信的语音对讲的录音实现,这个也比较容易实现。在此,我将该按钮封装成为一个控件,并通过策略模式的...

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

效果图:

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

实现思路:

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;
  }

}

源码下载:android仿微信语音对讲录音

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