虚位以待(AD)
虚位以待(AD)
首页 > 软件编程 > Android编程 > Android仿微信语音消息的录制和播放功能

Android仿微信语音消息的录制和播放功能
类别:Android编程   作者:码皇   来源:互联网   点击:

这篇文章主要介绍了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仿微信语音消息的录制和播放功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关热词搜索: android仿微信语音录制 微信语音播放