Android利用MedioRecorder使用仿微信语音录音以及播放(总结)

今天模仿着微信的发送语音功能做了一下,现将步骤以及其中遇到的问题记录一下,以便以后查看。

实现功能:1、录制语音并显示到列表中;2、点击列表中的语音自动进行播放;3、录制语音时以及播放语音时的动画效果显示。

最终界面效果如下:

现在说一下大致的步骤:

一、布局样式

1、拉界面。很简单,上面一个ListView或者RecyclerView用来显示语音列表,下面用线性布局做一下。

2、语音录制成功后,将语音文件保存到本地并且在语音列表中以一定的样式显示出来。这里用到的是Adapter适配器相关的知识,自行处理。

3、这个对话框样式需要自定义一下。

以上三步过后,基本显示效果就有了。

二、逻辑代码

1、创建一个语音录制对话框管理类AudioDialogManage.java,用于管理弹窗上的组件的样式等,代码如下:

package com.deepreality.audiorecorderdemo;

import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * 录制语音弹窗管理类
 */
public class AudioDialogManage {
    private Dialog mDialog;
    //麦克风及删除图标
    public ImageView mIcon;
    //录音时长
    private TextView mTime;
    //录音提示文字
    private TextView mLabel;
    //音量分贝
    public ImageView ivVoice;
    private Context mContext;


    public AudioDialogManage(Context context) {
        this.mContext = context;
    }

    /**
     * 默认的对话框的显示
     */
    public void showRecorderingDialog() {
        mDialog = new Dialog(mContext, R.style.Translucent_NoTitle);

        LayoutInflater inflater = LayoutInflater.from(mContext);
        View view = inflater.inflate(
                R.layout.voicenotes_recorder_dialog, null);
        mDialog.setContentView(view);

        mIcon = mDialog.findViewById(R.id.recorder_dialog_icon);
        mTime = mDialog.findViewById(R.id.recorder_dialog_time_tv);
        mLabel = mDialog.findViewById(R.id.recorder_dialog_label);
        ivVoice = mDialog.findViewById(R.id.recorder_dialog_ivVoice);

        mDialog.show();
    }

    //下面在显示各种对话框时,mDialog已经被构造,只需要控制ImageView、TextView的显示即可
    /**
     * 正在录音时,Dialog的显示
     */
    public void recording() {
        if (mDialog != null && mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mTime.setVisibility(View.VISIBLE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.mipmap.icon_maikefeng);
            mLabel.setBackgroundColor(Color.parseColor("#00000000"));
            mLabel.setText("手指松开 开始发送");
        }
    }

    /**
     * 取消录音提示对话框
     */
    public void wantToCancel() {
        if (mDialog != null && mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mTime.setVisibility(View.GONE);
            mLabel.setVisibility(View.VISIBLE);
            ivVoice.setVisibility(View.GONE);

            mIcon.setImageResource(R.mipmap.icon_rubbish);
            mLabel.setBackgroundColor(Color.parseColor("#AF2831"));
            mLabel.setText("手指上滑 取消发送");
        }
    }

    /**
     * 录音时间过短
     */
    public void tooShort() {
        if (mDialog != null && mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mTime.setVisibility(View.GONE);
            mLabel.setVisibility(View.VISIBLE);
            ivVoice.setVisibility(View.GONE);

            mIcon.setImageResource(R.mipmap.icon_tanhao);
            mLabel.setBackgroundColor(Color.parseColor("#00000000"));
            mLabel.setText("说话时间太短");
        }
    }

    /**
     * 对话框关闭
     */
    public void dismissDialog() {
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
            mDialog = null;
        }
    }

    /**
     * 更新显示当前录音秒数
     * @param time
     */
    public void updateCurTime(String time) {
        if (mDialog != null && mDialog.isShowing()) {
            mTime.setText(time);
        }
    }
}

其中,对话框的布局代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:background="@drawable/record_microphone_bj"
        android:layout_width="140dp"
        android:layout_height="140dp"
        android:gravity="center"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <LinearLayout
                android:id="@+id/recorder_dialog_lLayout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_centerInParent="true">

                <ImageView
                    android:id="@+id/recorder_dialog_icon"
                    android:layout_width="55dp"
                    android:layout_height="65dp"
                    android:src="@mipmap/icon_maikefeng"
                    android:visibility="visible" />

                <ImageView
                    android:id="@+id/recorder_dialog_ivVoice"
                    android:layout_width="40dp"
                    android:layout_height="65dp"
                    android:src="@mipmap/icon_voice_1"/>

            </LinearLayout>

            <TextView
                android:id="@+id/recorder_dialog_time_tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="15sp"
                android:layout_alignBottom="@id/recorder_dialog_lLayout"
                android:layout_toRightOf="@id/recorder_dialog_lLayout"
                android:textColor="@color/colorWhite"
                android:text="60''"/>

        </RelativeLayout>

        <TextView
            android:id="@+id/recorder_dialog_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:textSize="14sp"
            android:gravity="center"
            android:paddingLeft="5dp"
            android:paddingRight="5dp"
            android:text="手指松开 开始发送"
            android:textColor="@color/colorWhite" />

    </LinearLayout>

</LinearLayout>

2、创建一个Audio的管理类,用以管理音频录制,录制音频保存等。代码如下:

package com.deepreality.audiorecorderdemo;

import android.media.MediaRecorder;
import android.os.Handler;
import android.util.TimeUtils;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Audio管理类
 */

public class AudioManage {
    private MediaRecorder mMediaRecorder;  //MediaRecorder可以实现录音和录像。需要严格遵守API说明中的函数调用先后顺序.
    private String mDir;             // 文件夹的名称
    private String mCurrentFilePath;

    private static AudioManage mInstance;

    private boolean isPrepared; // 标识MediaRecorder准备完毕

    private AudioManage(String dir) {
        mDir = dir;
    }

    private OnAudioStatusUpdateListener audioStatusUpdateListener;

    private long startTime;

    /**
     * 回调“准备完毕”
     *
     */
    public interface AudioStateListener {
        void wellPrepared();    // prepared完毕
    }

    public AudioStateListener mListener;

    public void setOnAudioStateListener(AudioStateListener audioStateListener) {
        mListener = audioStateListener;
    }


    /**
     * 使用单例实现 AudioManage
     * @param dir
     * @return
     */
    //DialogManage主要管理Dialog,Dialog主要依赖Context,而且此Context必须是Activity的Context,
    //如果DialogManage写成单例实现,将是Application级别的,将无法释放,容易造成内存泄露,甚至导致错误
    public static AudioManage getInstance(String dir) {
        if (mInstance == null) {
            synchronized (AudioManage.class) {   // 同步
                if (mInstance == null) {
                    mInstance = new AudioManage(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);  // 路径+文件名字

            //MediaRecorder可以实现录音和录像。需要严格遵守API说明中的函数调用先后顺序.
            mMediaRecorder = new MediaRecorder();
            mCurrentFilePath = file.getAbsolutePath();
            mMediaRecorder.setOutputFile(file.getAbsolutePath());    // 设置输出文件
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);    // 设置MediaRecorder的音频源为麦克风
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);    // 设置音频的格式
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);    // 设置音频的编码为AMR_NB

            mMediaRecorder.prepare();

            mMediaRecorder.start();
            startTime = System.currentTimeMillis();
            updateMicStatus();

            isPrepared = true; // 准备结束

            if (mListener != null) {
                mListener.wellPrepared();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 生成文件名称
     * @return
     */
    private String GenerateFileName() {
        // TODO Auto-generated method stub
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// HH:mm:ss
        Date date = new Date(System.currentTimeMillis());
        return simpleDateFormat.format(date) + ".amr";     // 生成带有时间的名字

    }

    /**
     * 释放资源
     */
    public void release() {
        mMediaRecorder.stop();
        mMediaRecorder.release();
        mMediaRecorder = null;
    }

    /**
     * 取消(释放资源+删除文件)
     */
    public void cancel() {

        release();

        if (mCurrentFilePath != null) {
            File file = new File(mCurrentFilePath);
            file.delete();    //删除录音文件
            mCurrentFilePath = null;
        }
    }

    public String getCurrentFilePath() {
        // TODO Auto-generated method stub
        return mCurrentFilePath;
    }

    private int BASE = 1;
    private int SPACE = 100;// 间隔取样时间

    public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
        this.audioStatusUpdateListener = audioStatusUpdateListener;
    }

    private final Handler mHandler = new Handler();
    private Runnable mUpdateMicStatusTimer = new Runnable() {
        public void run() {
            updateMicStatus();
        }
    };

    /**
     * 更新麦克状态
     */
    private void updateMicStatus() {

        if (mMediaRecorder != null) {
            double ratio = (double)mMediaRecorder.getMaxAmplitude() / BASE;
            double db;// 分贝
            if (ratio > 1) {
                db = 20 * Math.log10(ratio);
                if(null != audioStatusUpdateListener) {
                    audioStatusUpdateListener.onUpdate(db,System.currentTimeMillis() - startTime);
                }
            }
            mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
        }
    }

    public interface OnAudioStatusUpdateListener {
        /**
         * 录音中...
         * @param db 当前声音分贝
         * @param time 录音时长
         */
        public void onUpdate(double db, long time);

    }
}

3、自定义控制录制语音的按钮AudioRecorderButton,并将上面的管理器AudioManage和AudioDialogManage进行整合。代码如下:

package com.deepreality.audiorecorderdemo;

import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * 控制录音Button
 * 1、重写onTouchEvent;(changeState方法、wantToCancel方法、reset方法);
 * 2、编写AudioDialogManage、并与该类AudioRecorderButton进行整合;
 * 3、编写AudioManage、并与该类AudioRecorderButton进行整合;
 */

public class AudioRecorderButton extends android.support.v7.widget.AppCompatButton implements AudioManage.AudioStateListener {

    /**
     * AudioRecorderButton的三个状态
     */
    private static final int STATE_NORMAL = 1;           //默认状态
    private static final int STATE_RECORDERING = 2;      //录音状态
    private static final int STATE_WANT_TO_CALCEL = 3;   //取消状态

    private int mCurState = STATE_NORMAL;    // 当前录音状态
    private boolean isRecordering = false;   // 是否已经开始录音
    private boolean mReady;    // 是否触发onLongClick

    private static final int DISTANCE_Y_CANCEL = 50;

    private AudioDialogManage audioDialogManage;

    private AudioManage mAudioManage;

    private int[] arrayImageId = new int[] {R.mipmap.icon_voice_1, R.mipmap.icon_voice_2, R.mipmap.icon_voice_3
            , R.mipmap.icon_voice_4, R.mipmap.icon_voice_5, R.mipmap.icon_voice_6};

    /**
     * 正常录音完成后的回调接口
     */
    public interface AudioFinishRecorderListener{
        void onFinish(int seconds, String FilePath);
    }

    private AudioFinishRecorderListener mListener;

    /**
     * 添加监听完成后回调接口的方法
     * @param listener
     */
    public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener){
        this.mListener = listener;
    }

    //构造方法
    public AudioRecorderButton(Context context) {
        super(context, null);
        // TODO Auto-generated constructor stub
    }
    public AudioRecorderButton(final Context context, AttributeSet attrs) {
        super(context, attrs);
        //实例化对话框管理器
        audioDialogManage = new AudioDialogManage(getContext());

        //音频文件保存路径
        String dir = Environment.getExternalStorageDirectory()
                + "/deepreality/VoiceCache";
        //获取音频管理器
        mAudioManage = AudioManage.getInstance(dir);
        //监听准备完成接口
        mAudioManage.setOnAudioStateListener(this);

        setOnLongClickListener(new OnLongClickListener() {

            @Override
            public boolean onLongClick(View v) {
                mReady = true;
                // 真正显示应该在audio end prepared以后
                //开始录音
                mAudioManage.prepareAudio();
                return false;
            }
        });

        mAudioManage.setOnAudioStatusUpdateListener(new AudioManage.OnAudioStatusUpdateListener() {

            //录音中....db为声音分贝,time为录音时长
            @Override
            public void onUpdate(double db, long time) {
                //根据分贝值来设置录音时音量图标的上下波动
                //audioDialogManage.mIcon.getDrawable().setLevel((int) (3000 + 6000 * db / 100));
                int imageIndex = 0;
                if (db > 50) {
                    imageIndex = ((int)db - 50) / 10;
                }
                if (imageIndex >= 5) {
                    imageIndex = 5;
                }
                audioDialogManage.ivVoice.setImageResource(arrayImageId[imageIndex]);
            }

        });


        // TODO Auto-generated constructor stub
    }

    /*
     * 复写onTouchEvent
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action = event.getAction();   //获取当前Action
        int x = (int) event.getX();       //获取当前的坐标
        int y = (int) event.getY();

        switch (action) {

            case MotionEvent.ACTION_DOWN:
                changeState(STATE_RECORDERING);
                break;

            case MotionEvent.ACTION_MOVE:

                // 已经开始录音状态时,根据X、Y的坐标,判断是否想要取消
                if (isRecordering) {
                    if (wantToCancel(x, y)) {
                        changeState(STATE_WANT_TO_CALCEL);
                    } else {
                        changeState(STATE_RECORDERING);
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                if (!mReady) {   //没有触发onLongClick
                    reset();
                    return super.onTouchEvent(event);
                }

                if (!isRecordering || mTime < 900) {  //录音时间过短
                    audioDialogManage.tooShort();
                    mAudioManage.cancel();
                    mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);// 延迟,1.3秒以后关闭“时间过短对话框”
                }

                else if (mCurState == STATE_RECORDERING) { //正常录制结束
                    audioDialogManage.dismissDialog();
                    // release
                    mAudioManage.release();
                    // callbackToAct
                    // 正常录制结束,回调录音时间和录音文件完整路径——在播放的时候需要使用
                    if(mListener!=null){
                        mListener.onFinish(mTime /1000, mAudioManage.getCurrentFilePath());
                    }

                } else if (mCurState == STATE_WANT_TO_CALCEL) {
                    // cancel
                    audioDialogManage.dismissDialog();
                    mAudioManage.cancel();
                }
                reset();
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 恢复状态以及一些标志位
     */
    private void reset() {
        isRecordering = false;
        mReady = false;                 //是否触发onLongClick
        mTime = 0;
        changeState(STATE_NORMAL);
    }

    private boolean wantToCancel(int x, int y) {
        // 判断手指的滑动是否超出范围
        if (x < 0 || x > getWidth()) {
            return true;
        }
        if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
            return true;
        }
        return false;
    }

    /**
     * 改变Button的背景和文本、展示不同状态的录音提示对话框
     * @param state
     */
    private void changeState(int state) {
        if (mCurState != state) {
            mCurState = state;
            switch (state) {
                case STATE_NORMAL:
                    setBackgroundResource(R.drawable.send_speech_btn_normal_style);
                    setText("按住说话");
                    break;

                case STATE_RECORDERING:
                    setBackgroundResource(R.drawable.send_speech_btn_pres_style);
                    setText("松开发送");
                    if (isRecordering) {
                        // 更新Dialog.recording()
                        audioDialogManage.recording();
                    }
                    break;

                case STATE_WANT_TO_CALCEL:
                    setBackgroundResource(R.drawable.send_speech_btn_pres_style);
                    setText("取消发送");
                    // 更新Dialog.wantCancel()
                    audioDialogManage.wantToCancel();
                    break;
            }
        }
    }

    /*
     * 实现“准备完毕”接口
     */
    @Override
    public void wellPrepared() {
        // TODO Auto-generated method stub
        mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
    }

    private static final int MSG_AUDIO_PREPARED = 0x110;   //准备完全
    private static final int MSG_CURRENT_TIME = 0x111;     //当前语音时长
    private static final int MSG_DIALOG_DISMISS = 0x112;    //销毁对话框
    private static final int MSG_COUNT_DOWN_DONE = 0x113;    //录音倒计时结束

    /**
     * 接收子线程数据,并用此数据配合主线程更新UI
     * Handler运行在主线程(UI线程)中,它与子线程通过Message对象传递数据。
     * Handler接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,把这些消息放入主线程队列中,配合主线程进行更新UI。
     */
    private Handler mHandler = new Handler() {

        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case MSG_AUDIO_PREPARED:        //216:mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
                    audioDialogManage.showRecorderingDialog();
                    isRecordering = true;
                    //已经在录制,同时开启一个获取音量、并且计时的线程
                    new Thread(mUpdateCurTimeRunnable).start();
                    break;

                case MSG_CURRENT_TIME:          //265:mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);
                    audioDialogManage.updateCurTime(String.valueOf(mTime / 1000));
                    break;

                //这里在Handler里面处理DIALOG_DIMISS,是因为想让该对话框显示一段时间,延迟关闭,——详见125行
                case MSG_DIALOG_DISMISS:         //125:mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);
                    audioDialogManage.dismissDialog();
                    break;
                //处理录音时间结束
                case MSG_COUNT_DOWN_DONE:
                    mAudioManage.release();
                    // callbackToAct
                    // 正常录制结束,回调录音时间和录音文件完整路径——在播放的时候需要使用
                    if(mListener!=null){
                        mListener.onFinish(mTime /1000, mAudioManage.getCurrentFilePath());
                    }
                    audioDialogManage.dismissDialog();
                    reset();
                    break;
            }
        }
    };

    private int mTime;  //开始录音计时,计时;(在reset()中置空) 单位为毫秒
    /**
     * 更新当前录音时长的runnable
     */
    private Runnable mUpdateCurTimeRunnable = new Runnable() {

        @Override
        public void run() {

            while (isRecordering) {
                try {
                    Thread.sleep(100);
                    mTime += 100;
                    mHandler.sendEmptyMessage(MSG_CURRENT_TIME);

                    if(mTime == 60 * 1000){
                        mHandler.sendEmptyMessage(MSG_COUNT_DOWN_DONE);
                    }
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }
    };
}

4、那么,如何使用自定义的音频录制按钮呢,MainActivity.java的代码如下:

package com.deepreality.audiorecorderdemo;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.AnimationDrawable;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import com.deepreality.audiorecorderdemo.Adapters.AudioListAdapter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener
        , MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener {

    private Context mContext;

    private ListView lvAudioRecorderList;
    private AudioRecorderButton btnRecord;

    private List<Tb_AudioRecorder> tbAudioRecorderList;
    private List<Boolean> booleanList;
    private AudioListAdapter audioListAdapter;
    private Tb_AudioRecorder tb_audioRecorder;

    private MediaPlayer mMediaPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //判断android版本号,弹出申请权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            showConfirmAppPermissions();
        }

        DeviceBaseInfo.getActivityWidthAndHeight(getWindowManager());

        baseDataInit();
        bindViews();
        viewsAddListener();
        viewsDataInit();

    }

    private void baseDataInit() {
        mContext = this;
        tbAudioRecorderList = new ArrayList<>();
        booleanList = new ArrayList<>();

        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    }

    private void bindViews() {
        btnRecord = findViewById(R.id.Main_btnRecord);
        lvAudioRecorderList = findViewById(R.id.Main_lvAudioRecorderList);
    }

    private void viewsAddListener() {
        btnRecord.setAudioFinishRecorderListener(new AudioRecorderButton.AudioFinishRecorderListener() {
            @Override
            public void onFinish(int seconds, String FilePath) {
                Toast.makeText(mContext, "发送成功!", Toast.LENGTH_SHORT).show();
                tb_audioRecorder = new Tb_AudioRecorder(FilePath, seconds);
                tbAudioRecorderList.add(tb_audioRecorder);
                booleanList.add(false);
                audioListAdapter.notifyDataSetChanged();
            }
        });
        lvAudioRecorderList.setOnItemClickListener(this);
        mMediaPlayer.setOnPreparedListener(this);
        mMediaPlayer.setOnCompletionListener(this);
    }

    private void viewsDataInit() {
        audioListAdapter = new AudioListAdapter(mContext, tbAudioRecorderList, booleanList);
        lvAudioRecorderList.setAdapter(audioListAdapter);
    }

    // 7.0动态申请权限
    public void showConfirmAppPermissions() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
                PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                                Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, 1);
            }
        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        for (int i = 0; i < booleanList.size(); i ++) {
            booleanList.set(i, false);
        }
        booleanList.set(position, true);
        audioListAdapter.notifyDataSetChanged();
        //播放录音
        mMediaPlayer.reset();
        try {
            //String path = "/storage/emulated/0/kairui/VoiceCache/2018-09-30 10:20:26.amr";
            File file = new File(tbAudioRecorderList.get(position).getAudioFilePath());
            FileInputStream fis = new FileInputStream(file);
            mMediaPlayer.setDataSource(fis.getFD());
            mMediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        Log.e("播放完成", "成功");
        Toast.makeText(mContext, "播放完成!", Toast.LENGTH_SHORT).show();
        for (int i = 0; i < booleanList.size(); i ++) {
            booleanList.set(i, false);
        }
        audioListAdapter.notifyDataSetChanged();
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        Log.e("准备完成", "成功");
        Toast.makeText(mContext, "开始播放", Toast.LENGTH_SHORT).show();
        mMediaPlayer.start();
    }
}

至此,主要布局以及逻辑代码已完成。其中:

录音时的动画是用handler来修改ImageView的显示图片。

播放音频时的动画是用帧动画,通过Adapter适配器来控制AnimationDrawable对象的播放和停止来实现,其中比较重要的是播放完成后要将动画恢复到第一帧,代码如下:

//恢复到第一帧
animationDrawable.selectDrawable(0);

另外,不要忘了在清单文件里添加用户权限!!!(同组的权限添加一个即可,其他的系统会自动添加)

<!--录音权限-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--读写权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

完整Demo下载地址:https://download.csdn.net/download/lpcrazyboy/10698275

 

 

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现Android仿微信语音通话的效果,你可以按照以下步骤进行操作: 1. 首先,在styles.xml文件中定义一个自定义的对话框主题,可以参考引用\[1\]中的代码。这个主题可以设置对话框的背景透明、无边框、浮现在activity之上以及背景模糊显示等效果。 2. 在strings.xml文件中定义三个字符串资源,分别表示正常录音状态、录音中状态和取消发送状态。可以参考引用\[2\]中的代码。 3. 在布局文件中创建一个用于显示语音通话的界面,可以使用一个ImageView显示语音图标,一个TextView显示录音状态,以及一个Button用于开始和结束录音。 4. 在Activity中,根据用户的操作来改变TextView的文本内容,以及根据录音状态来改变ImageView的图标。 5. 使用MediaRecorder类来实现录音功能,可以在Button的点击事件中开始和结束录音。 6. 使用AudioManager类来控制音频播放和停止,可以在Activity的生命周期方法中进行相应的操作。 通过以上步骤,你可以实现一个仿微信语音通话的效果。记得根据你的具体需求进行适当的修改和调整。 #### 引用[.reference_title] - *1* *2* [Android 仿微信实现语音聊天功能](https://blog.csdn.net/lhk147852369/article/details/78658055)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值