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
    评论
众所周知,人工智能是当前最热门的话题之一, 计算机技术与互联网技术的快速发展更是将对人工智能的研究推向一个新的高潮。 人工智能是研究模拟和扩展人类智能的理论与方法及其应用的一门新兴技术科学。 作为人工智能核心研究领域之一的机器学习, 其研究动机是为了使计算机系统具有人的学习能力以实现人工智能。 那么, 什么是机器学习呢? 机器学习 (Machine Learning) 是对研究问题进行模型假设,利用计算机从训练数据中学习得到模型参数,并最终对数据进行预测和分析的一门学科。 机器学习的用途 机器学习是一种通用的数据处理技术,其包含了大量的学习算法。不同的学习算法在不同的行业及应用中能够表现出不同的性能和优势。目前,机器学习已成功地应用于下列领域: 互联网领域----语音识别、搜索引擎、语言翻译、垃圾邮件过滤、自然语言处理等 生物领域----基因序列分析、DNA 序列预测、蛋白质结构预测等 自动化领域----人脸识别、无人驾驶技术、图像处理、信号处理等 金融领域----证券市场分析、信用卡欺诈检测等 医学领域----疾病鉴别/诊断、流行病爆发预测等 刑侦领域----潜在犯罪识别与预测、模拟人工智能侦探等 新闻领域----新闻推荐系统等 游戏领域----游戏战略规划等 从上述所列举的应用可知,机器学习正在成为各行各业都会经常使用到的分析工具,尤其是在各领域数据量爆炸的今天,各行业都希望通过数据处理与分析手段,得到数据中有价值的信息,以便明确客户的需求和指引企业的发展。
众所周知,人工智能是当前最热门的话题之一, 计算机技术与互联网技术的快速发展更是将对人工智能的研究推向一个新的高潮。 人工智能是研究模拟和扩展人类智能的理论与方法及其应用的一门新兴技术科学。 作为人工智能核心研究领域之一的机器学习, 其研究动机是为了使计算机系统具有人的学习能力以实现人工智能。 那么, 什么是机器学习呢? 机器学习 (Machine Learning) 是对研究问题进行模型假设,利用计算机从训练数据中学习得到模型参数,并最终对数据进行预测和分析的一门学科。 机器学习的用途 机器学习是一种通用的数据处理技术,其包含了大量的学习算法。不同的学习算法在不同的行业及应用中能够表现出不同的性能和优势。目前,机器学习已成功地应用于下列领域: 互联网领域----语音识别、搜索引擎、语言翻译、垃圾邮件过滤、自然语言处理等 生物领域----基因序列分析、DNA 序列预测、蛋白质结构预测等 自动化领域----人脸识别、无人驾驶技术、图像处理、信号处理等 金融领域----证券市场分析、信用卡欺诈检测等 医学领域----疾病鉴别/诊断、流行病爆发预测等 刑侦领域----潜在犯罪识别与预测、模拟人工智能侦探等 新闻领域----新闻推荐系统等 游戏领域----游戏战略规划等 从上述所列举的应用可知,机器学习正在成为各行各业都会经常使用到的分析工具,尤其是在各领域数据量爆炸的今天,各行业都希望通过数据处理与分析手段,得到数据中有价值的信息,以便明确客户的需求和指引企业的发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值