1、项目界面展示:


2、项目代码整体结构简析:

(1)AudioRecorderButton类(录音按钮)State:STATE_NORMAL【正常状态】、STATE_RECORDERING【正在录音的状态】、STATE_WANT_TO_CALCEL【想要放弃录音的装填】;

(2)AudioDialogManage类(录音过程中的提示对话框)Style:RECORDERING【正在录音】、WANT_TO_CANCEL【取消提示对话框】、TOO_SHAORT【录音时间过短提醒对话框】;

(3)AudioManage类(控制录音)prepare()(end prepare-—》callback)【去录音】, cancel()【取消录音】, release()【正常结束】(-—》callbackToActivity), getVoiceLevel()【获得音量大小】;

伪码展示:

class AudioRecorderButton{onTouchEvent(){DOWN: // 按下changeButtonState(STATE_RECORDERING);onLongClick -> AudioManage.prepare() -> end prepare            -> AudioDialogManage.showDialog(RECORDERING)   MOVE: // 滑动if(wantCancel(x,y)){changeButtonState(STATE_WANT_TO_CALCEL);       //更新ButtonAudioDialogManage.showDialog(WANT_TO_CANCEL);  //更新Dialog}else{changeButtonState(STATE_RECORDERING);AudioDialogManage.showDialog(RECORDERING);}UP:   // 抬起if(wantCancel == curState)          //取消{AudioManage.cancel();}if(STATE_RECORDERING = curState)    //正常结束{AudioManage.release();callbackToActivity(path,time);}......}}

1、主界面布局:

<?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" >    <!-- 语音列表 -->    <ListView        android:id="@+id/voiceNotesListView"        android:layout_width="match_parent"        android:layout_height="0dp"        android:layout_weight="1"        android:background="#ebebeb"        android:divider="@null"        android:dividerHeight="10dp" >    </ListView>    <!-- 底部按钮布局 -->    <FrameLayout        android:layout_width="match_parent"        android:layout_height="wrap_content" >        <songshi.suishou.tool.AudioRecorderButton            android:id="@+id/recorderButton"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginLeft="50dp"            android:layout_marginRight="50dp"            android:layout_marginTop="6dp"            android:layout_marginBottom="7dp"            android:gravity="center"            android:padding="5dp"            android:minHeight="0dp"            android:background="@drawable/btn_recorder_normal"            android:text="@string/str_recorder_normal"            android:textColor="#727272"            >        </songshi.suishou.tool.AudioRecorderButton>                <View android:layout_width="match_parent"            android:layout_height="1dp"            android:background="#ccc"/>    </FrameLayout></LinearLayout>

<string name="str_recorder_normal">按住 说话</string><string name="str_recorder_recording">松开 结束</string><string name="str_recorder_want_cancel">滑动手指,取消记录</string>

2、AudioRecorderButton类:

package com.example.voicenotes;import com.example.voicenotes.AudioManage.AudioStateListenter;import android.content.Context;import android.os.Environment;import android.os.Handler;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.widget.Button;public class AudioRecorderButton extends Button implements AudioStateListenter {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; // 是否触发onLongClickprivate static final int DISTANCE_Y_CANCEL = 50;private AudioDialogManage audioDialogManage;private AudioManage mAudioManage;//构造方法public AudioRecorderButton(Context context) {super(context, null);// TODO Auto-generated constructor stub}public AudioRecorderButton(Context context, AttributeSet attrs) {super(context, attrs);audioDialogManage = new AudioDialogManage(getContext());String dir = Environment.getExternalStorageDirectory()+ "/VoiceRecorder";// 此处需要判断是否有存储卡mAudioManage = AudioManage.getInstance(dir);mAudioManage.setOnAudioStateListenter(this);setOnLongClickListener(new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {// TODO Auto-generated method stubmReady = true;// 真正显示应该在audio end prepared以后mAudioManage.prepareAudio();//return true;return false;}});// TODO Auto-generated constructor stub}//录音完成后的回调public interface AudioFinishRecorderListenter{void onFinish(float seconds,String FilePath);}private AudioFinishRecorderListenter mListenter;public void setAudioFinishRecorderListenter(AudioFinishRecorderListenter listenter){this.mListenter=listenter;}//复写onTouchEvent@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();   //获取当前Actionint 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) {   //没有触发onLongClickreset();return super.onTouchEvent(event);}if (!isRecordering || mTime < 0.5f) {audioDialogManage.tooShort();mAudioManage.cancel();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 延迟,1.3秒以后关闭时间过短对话框} /*else if (mTime < 0.5f) {audioDialogManage.tooShort();isRecordering=false;mAudioManage.cancel();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 延迟}*/else if (mCurState == STATE_RECORDERING) { //正常录制结束audioDialogManage.dimissDialog();// releasemAudioManage.release();// callbackToActif(mListenter!=null){mListenter.onFinish(mTime, mAudioManage.getCurrentFilePath());}} else if (mCurState == STATE_WANT_TO_CALCEL) {// cancelaudioDialogManage.dimissDialog();mAudioManage.cancel();}reset();break;}return super.onTouchEvent(event);}// 恢复状态以及一些标志位private void reset() {isRecordering = false;mReady = false;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的背景和文本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_RECORDERING:setBackgroundResource(R.drawable.btn_recorder_recordering);setText(R.string.str_recorder_recording);if (isRecordering) {// 更新Dialog.recording()audioDialogManage.recording();}break;case STATE_WANT_TO_CALCEL:setBackgroundResource(R.drawable.btn_recorder_recordering);setText(R.string.str_recorder_want_cancel);// 更新Dialog.wantCancel()audioDialogManage.wantToCancel();break;}}}private static final int MSG_AUDIO_PREPARED = 0x110;private static final int MSG_VOICE_CHANGE = 0x111;private static final int MSG_DIALOG_DIMISS = 0x112;private float mTime;// 获取音量大小的Runnableprivate Runnable mGetVoiceLevelRunnable = new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubwhile (isRecordering) {try {Thread.sleep(100);mTime += 0.1f;mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};private Handler mHandler = new Handler() {public void handleMessage(android.os.Message msg) {switch (msg.what) {case MSG_AUDIO_PREPARED:audioDialogManage.showRecorderingDialog();isRecordering = true;new Thread(mGetVoiceLevelRunnable).start();break;case MSG_VOICE_CHANGE:audioDialogManage.updateVoiceLevel(mAudioManage.getVoiceLevel(7));break;case MSG_DIALOG_DIMISS:audioDialogManage.dimissDialog();break;}};};@Overridepublic void wellPrepared() {// TODO Auto-generated method stubmHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}}

3、AudioDialogManage类:

Dialog布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:background="@drawable/dialog_loading_bg"    android:gravity="center"    android:orientation="vertical"    android:padding="20dp" >    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:orientation="horizontal" >        <ImageView            android:id="@+id/recorder_dialog_icon"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/recorder"            android:visibility="visible" />        <ImageView            android:id="@+id/recorder_dialog_voice"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/v1"            android:visibility="visible" />    </LinearLayout>    <TextView        android:id="@+id/recorder_dialog_label"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginTop="5dp"        android:text="手指滑动,取消录音"        android:textColor="#FFFFFFFF" /></LinearLayout>
AudioDialogManage类:
package com.example.voicenotes;import android.app.Dialog;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.widget.ImageView;import android.widget.TextView;public class AudioDialogManage {private Dialog mDialog;private ImageView mIcon;private ImageView mVoice;private TextView mLabel;private Context mContext;public AudioDialogManage(Context context) {this.mContext = context;}//默认的对话框的显示public void showRecorderingDialog() {mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.tools_voice_notes_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();}//正在录音时,Dialog的显示public void recording() {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.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.drawable.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.drawable.voice_to_short);mLabel.setText("录音时间过短");}}public void dimissDialog() {if (mDialog != null && mDialog.isShowing()) {mDialog.dismiss();mDialog = null;}}//通过Level更新Voice的图片V1——V7public void updateVoiceLevel(int level) {if (mDialog != null && mDialog.isShowing()) {//mIcon.setVisibility(View.VISIBLE);//mVoice.setVisibility(View.VISIBLE);//mLabel.setVisibility(View.VISIBLE);int resId=mContext.getResources().getIdentifier("v"+level, "drawable", mContext.getPackageName());mVoice.setImageResource(resId);}}}

4、AudioManage类:

MediaRecorder:http://developer.android.com/reference/android/media/MediaRecorder.html

MediaRecorder状态转换图:


AudioManage类:

package com.example.voicenotes;import java.io.File;import java.io.IOException;import java.util.UUID;import android.media.MediaRecorder;public class AudioManage {private MediaRecorder mMediaRecorder;private String mDir; // 文件夹的名称private String mCurrentFilePath;private static AudioManage mInstance;private boolean isPrepared; // 标识MediaRecorder准备完毕private AudioManage(String dir) {mDir = dir;}// 回调准备完毕public interface AudioStateListenter {void wellPrepared(); // prepared完毕}public AudioStateListenter mListenter;public void setOnAudioStateListenter(AudioStateListenter audioStateListenter) {mListenter = audioStateListenter;}// 单例实现 AudioManage//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); // 路径+文件名字mCurrentFilePath = file.getAbsolutePath();mMediaRecorder = new MediaRecorder();mMediaRecorder.setOutputFile(file.getAbsolutePath());// 设置输出文件mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置MediaRecorder的音频源为麦克风mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);// 设置音频的格式mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 设置音频的编码为AMR_NBmMediaRecorder.prepare();mMediaRecorder.start();isPrepared = true; // 准备结束if (mListenter != null) {mListenter.wellPrepared();}} catch (IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 随机生成文件名称private String GenerateFileName() {// TODO Auto-generated method stubreturn UUID.randomUUID().toString() + ".amr"; // 音频文件格式}// 获得音量等级public int getVoiceLevel(int maxLevel) {if (isPrepared) {try {return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;} catch (Exception e) {// TODO Auto-generated catch block// e.printStackTrace();}}return 1;}// 释放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 stubreturn mCurrentFilePath;}}

5、MediaManage类:主要管理语音的播放、暂停、恢复、停止


package com.example.voicenotes;import java.io.IOException;import android.media.AudioManager;import android.media.MediaPlayer;import android.media.MediaPlayer.OnErrorListener;public class MediaManage {private static MediaPlayer mMediaPlayer;private static boolean isPause;public static void playSound(String filePath,MediaPlayer.OnCompletionListener onCompletionListenter){if(mMediaPlayer==null){mMediaPlayer=new MediaPlayer();mMediaPlayer.setOnErrorListener( new OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// TODO Auto-generated method stubmMediaPlayer.reset();return false;}});}else{mMediaPlayer.reset();}try {mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListenter);mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalStateException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.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;}}}

6、MainActivity:

ListView布局:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="60dp"    android:layout_marginTop="5dp" >    <ImageView        android:id="@+id/voice_robot"        android:layout_width="40dp"        android:layout_height="40dp"        android:layout_alignParentLeft="true"        android:layout_centerVertical="true"        android:layout_marginLeft="5dp"        android:src="@drawable/robot" />    <FrameLayout        android:id="@+id/recorder_length"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerVertical="true"        android:layout_toRightOf="@id/voice_robot"        android:background="@drawable/popup_inline_error" >                <View android:id="@+id/recorder_anim"            android:layout_width="25dp"            android:layout_height="25dp"            android:layout_gravity="center_vertical|left"            android:background="@drawable/presence_audio_online"/>            </FrameLayout>    <TextView android:id="@+id/recorder_time"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerVertical="true"        android:layout_toRightOf="@id/recorder_length"        android:text=""        android:layout_marginLeft="3dp"        android:textColor="#ff777777"/></RelativeLayout>
AudioRecorderAdapter:

package com.example.voicenotes;import java.util.List;import android.content.Context;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.example.voicenotes.MainActivity.Recorder;public class AudioRecorderAdapter extends ArrayAdapter<Recorder> {        private List<Recorder> mDatas;<span style="white-space:pre"></span>private Context mContext;private int mMinItemWidth;private int mMaxItemWidth;private LayoutInflater mInflater;public AudioRecorderAdapter(Context context, List<Recorder> datas) {super(context, -1, datas);<span style="white-space:pre"></span>mContext=context;<span style="white-space:pre"></span>mDatas=datas;<span style="white-space:pre"></span><span style="white-space:pre"></span>mInflater=LayoutInflater.from(context);WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();windowManager.getDefaultDisplay().getMetrics(outMetrics);mMaxItemWidth = (int) (outMetrics.widthPixels * 0.7f);mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f);}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubViewHolder holder = null;if (convertView == null) {convertView = mInflater.inflate(R.layout.tools_voice_notes_recorder_item, parent, false);holder=new ViewHolder();holder.seconds=(TextView) convertView.findViewById(R.id.recorder_time);holder.mLength=convertView.findViewById(R.id.recorder_length);convertView.setTag(holder);}else{holder=(ViewHolder) convertView.getTag();}holder.seconds.setText(Math.round(getItem(position).getTime())+"\"");ViewGroup.LayoutParams lp=holder.mLength.getLayoutParams();lp.width=(int) (mMinItemWidth+(mMaxItemWidth/60f * getItem(position).getTime()));return convertView;}private class ViewHolder {TextView seconds;View mLength;}}
MainActivity:

package com.example.voicenotes;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.graphics.drawable.AnimationDrawable;import android.media.MediaPlayer;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ArrayAdapter;import android.widget.ListView;import com.example.voicenotes.AudioRecorderButton.AudioFinishRecorderListenter;public class MainActivity extends Activity {private ListView mListView;private ArrayAdapter<Recorder> mAdapter;private List<Recorder> mDatas = new ArrayList<Recorder>();private AudioRecorderButton mAudioRecorderButton;private View mAnimView;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mListView = (ListView) findViewById(R.id.voiceNotesListView);mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.recorderButton);mAudioRecorderButton.setAudioFinishRecorderListenter(new AudioFinishRecorderListenter() {@Overridepublic void onFinish(float seconds, String FilePath) {// TODO Auto-generated method stubRecorder recorder = new Recorder(seconds, FilePath);mDatas.add(recorder);mAdapter.notifyDataSetChanged();mListView.setSelection(mDatas.size() - 1);}});mAdapter = new AudioRecorderAdapter(this, mDatas);mListView.setAdapter(mAdapter);mListView.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id) {if (mAnimView != null) {mAnimView.setBackgroundResource(R.drawable.adj);mAnimView = null;}// 播放动画mAnimView = view.findViewById(R.id.recorder_anim);mAnimView.setBackgroundResource(R.drawable.audio_play_anim);AnimationDrawable anim = (AnimationDrawable) mAnimView.getBackground();anim.start();// 播放音频MediaManage.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mAnimView.setBackgroundResource(R.drawable.adj);}});}});}public class Recorder {float time;String filePath;public Recorder(float time, String filePath) {super();this.time = time;this.filePath = filePath;}public float getTime() {return time;}public void setTime(float time) {this.time = time;}public String getFilePath() {return filePath;}public void setFilePath(String filePath) {this.filePath = filePath;}}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();MediaManage.pause();}@Overrideprotected void onResume() {// TODO Auto-generated method stubsuper.onResume();MediaManage.resume();}@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();MediaManage.release();}}
全部代码:https://github.com/songshimvp/VoiceNotes

更多相关文章

  1. Android(安卓)Fragment 生命周期图
  2. Android学习之路——Activity(1)
  3. android学习笔记NO.3
  4. Android(安卓)第三天(下午)
  5. Android(安卓)Audio简述
  6. 修改状态栏(StatusBar)图标(icon)(定制自己的状态栏)
  7. android状态栏一体化(改变状态栏的背景颜色)
  8. 关于Android主题splash全屏和主页面的沉浸式状态栏
  9. TextInputEditText样式设置

随机推荐

  1. Android经常使用开源组件汇总
  2. 【Android】判断某个AP是否在系统中存在(P
  3. android post请求接口demo
  4. Android(安卓)Audio代码分析23 - attachA
  5. Android文件保存和读取
  6. android的Android(安卓)中Intent和Pendin
  7. 修改 android 手机 hosts 文件的方法
  8. 图片加载库Glide的使用
  9. Android(安卓)system/bin 命令
  10. Android方法过多MutiDex使用说明