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.****.net/download/lpcrazyboy/10698275