思路:
自定义Button
获取DialogManager、AudioManager
setOnLongClickListener长按事件--做好AudioManager的录音准备工作
AudioManager.setOnAudioStateListener(this)实现录音准备完毕的接口回
调方法,方法中去发送MSG_AUDIO_PREPARE消息代表录音准备工作完毕
在mHandler中接收消息,开始开启线程录音,并且计时,计时的过程中,去
发送MSG_VOICE_CHANGED消息,去updateVoiceLeve更新音量图标,另外在
MotionEvent.ACTION_UP事件中,去定义
audioFinishRecorderListener.onFinish接口方法,方法是录音完毕的回调
方法,
在MainActivity中,
mAudioRecorderButton.setAudioFinishRecorderListener实现录音完毕回
调方法去更新listViewUi界面
点击listViewItem条目播放动画, MediaManager.playSound并且播放音频
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.imooc_recorder.MainActivity" > <ListView android:id="@+id/id_listview" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ebebeb" android:divider="@null" android:dividerHeight="10dp" > </ListView> <FrameLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <View android:background="#ccc" android:layout_width="fill_parent" android:layout_height="1dp" /> <com.example.recorder_view.AudioRecorderButton android:id="@+id/id_recorder_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="7dp" android:layout_marginLeft="50dp" android:layout_marginRight="50dp" android:layout_marginTop="6dp" android:background="@drawable/button_recorder_normal" android:gravity="center" android:minHeight="0dp" android:padding="5dp" android:text="@string/str_recorder_nomal" android:textColor="#727272" > </com.example.recorder_view.AudioRecorderButton> </FrameLayout> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" 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/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/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/id_recorder_dialog_label" android:layout_marginTop="5dp" android:text="手指上滑,取消发送" android:textColor="#ffffff" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:background="#eeeeee" android:layout_height="60dp" android:layout_marginTop="5dp" > <ImageView android:id="@+id/id_icon" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="5dp" android:src="@drawable/icon" /> <FrameLayout android:id="@+id/id_recorder_length" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toLeftOf="@id/id_icon" android:background="@drawable/chatto_bg_focused" > <View android:id="@+id/id_recorder_anim" android:layout_width="25dp" android:layout_height="25dp" android:layout_gravity="center_vertical|right" android:background="@drawable/adj" /> </FrameLayout> <TextView android:id="@+id/id_recorder_time" android:layout_centerVertical="true" android:layout_toLeftOf="@id/id_recorder_length" android:layout_marginRight="3dp" android:textColor="#ff777777" android:text="" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>styles.xml
<style name="Theme_AudioDialog" parent="@android:Theme.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowIsTranslucent">true</item> <item name="android:backgroundDimEnabled">false</item> </style>
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <solid android:color="#ffffff"/> <stroke android:width="1px" android:color="#9b9b9b"/> <corners android:radius="3dp"/> </shape>
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <solid android:color="#ffffff"/> <stroke android:width="1px" android:color="#eeeeee"/> <corners android:radius="3dp"/> </shape>
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" > <item android:drawable="@drawable/v_anim1" android:duration="300"> </item> <item android:drawable="@drawable/v_anim2" android:duration="300"> </item> <item android:drawable="@drawable/v_anim3" android:duration="300"> </item> </animation-list>
package com.example.imooc_recorder; import java.util.ArrayList; import java.util.List; import com.example.recorder_view.AudioRecorderButton; import com.example.recorder_view.AudioRecorderButton.AudioFinishRecorderListener; import com.example.recorder_view.MediaManager; import android.app.Activity; import android.graphics.drawable.AnimationDrawable; import android.media.MediaPlayer; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView mListView; private ArrayAdapter<Recorder> mAdapter; private List<Recorder> mDatas = new ArrayList<MainActivity.Recorder>(); private AudioRecorderButton mAudioRecorderButton; private View animView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.id_listview); mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.id_recorder_button); //录音完毕,回调 mAudioRecorderButton.setAudioFinishRecorderListener(new AudioFinishRecorderListener() { @Override public void onFinish(float seconds, String filePath) { // TODO Auto-generated method stub Recorder recorder = new Recorder(seconds, filePath); mDatas.add(recorder); mAdapter.notifyDataSetChanged(); mListView.setSelection(mDatas.size()-1); } }); mAdapter = new RecorderAdapter(this, mDatas); mListView.setAdapter(mAdapter); mListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (animView !=null) { animView.setBackgroundResource(R.drawable.adj); animView = null; } //播放动画 animView = view.findViewById(R.id.id_recorder_anim); animView.setBackgroundResource(R.drawable.play_anim); AnimationDrawable anim = (AnimationDrawable) animView.getBackground(); anim.start(); //播放音频 MediaManager.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { // TODO Auto-generated method stub animView.setBackgroundResource(R.drawable.adj); } }); } }); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); MediaManager.pause(); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); MediaManager.resume(); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); MediaManager.release(); } /**内部类*/ 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; } } }
package com.example.imooc_recorder; import java.util.List; import com.example.imooc_recorder.MainActivity.Recorder; 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.BaseAdapter; import android.widget.LinearLayout; import android.widget.TextView; public class RecorderAdapter extends ArrayAdapter<Recorder> { // private List<Recorder> mDatas; // private Context mContext; private int mMinItemWidth; private int mMaxItemWidth; private LayoutInflater mInflater; public RecorderAdapter(Context context, List<Recorder> datas) { super(context, -1, datas); // mContext = context; // mDatas = datas; WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); mMaxItemWidth = (int) (outMetrics.widthPixels * 0.7f); mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f); mInflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.item_recorder, parent, false); holder = new ViewHolder(); holder.seconds = (TextView) convertView.findViewById(R.id.id_recorder_time); holder.length = convertView.findViewById(R.id.id_recorder_length); convertView.setTag(holder); }else { holder = (ViewHolder) convertView.getTag(); } holder.seconds.setText(Math.round(getItem(position).time)+"\""); ViewGroup.LayoutParams lp = holder.length.getLayoutParams(); lp.width = (int) (mMinItemWidth +(mMaxItemWidth/60f*getItem(position).time)); return convertView; } private class ViewHolder { private TextView seconds; private View length; } }
package com.example.recorder_view; import com.example.imooc_recorder.R; import com.example.recorder_view.AudioManager.AudioStateListener; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.telephony.SignalStrength; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.Button; public class AudioRecorderButton extends Button implements AudioStateListener { private static final int DISTANCE_Y_CANCEL = 50; /** 提示框管理工具类 */ private DialogManager mDialogManager; /** 正常、松开手指取消发送、手指上滑取消发送 */ private static final int STATE_NORMAL = 1; private static final int STATE_RECORDING = 2; private static final int STATE_WANT_TO_CANCEL = 3; /** 初始状态:正常 */ private int mCurState = STATE_NORMAL; /** 当前是否正在录音 */ private boolean isRecording = false; /** 录音工具类 */ private AudioManager mAudioManager; /** 录音总时间 */ private float mTime; /** 是否触发button */ private boolean mReady; /** *****************自定义Button************************************* */ public AudioRecorderButton(Context context) { super(context); } public AudioRecorderButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public AudioRecorderButton(Context context, AttributeSet attrs) { super(context, attrs); mDialogManager = new DialogManager(getContext()); String dir = Environment.getExternalStorageDirectory() + "/imooc_recorder_audios"; mAudioManager = AudioManager.getInstance(dir); mAudioManager.setOnAudioStateListener(this); /** 监听回调 */ setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { mReady = true; // 录音准备--准备好后,有实现接口方法 mAudioManager.prepareAndio(); return false; } }); } /****************************************************** * 完成录音准备的回调函数 */ @Override public void wellPrepared() { // 准备完毕,发送消息 mHandler.sendEmptyMessage(MSG_AUDIO_PREPARE); } private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_AUDIO_PREPARE: mDialogManager.showRecordingDailog(); isRecording = true; new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub while (isRecording) { try { Thread.sleep(100); // 录音计时 mTime += 0.1f; mHandler.sendEmptyMessage(MSG_VOICE_CHANGED); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); break; case MSG_VOICE_CHANGED: mDialogManager.updateVoiceLeve(mAudioManager.getVoiceLevel(7)); break; case MSG_DIALOG_DIMISS: mDialogManager.dimissDailog(); break; default: break; } super.handleMessage(msg); }; }; private static final int MSG_AUDIO_PREPARE = 0x110; private static final int MSG_VOICE_CHANGED = 0x111; private static final int MSG_DIALOG_DIMISS = 0x112; @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub int action = event.getAction(); int x = (int) event.getX(); int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: changeState(STATE_RECORDING); break; case MotionEvent.ACTION_MOVE: if (isRecording) { // 根据xy坐标。判断是否想要取消 if (wantToCancel(x, y)) { changeState(STATE_WANT_TO_CANCEL); } else { changeState(STATE_RECORDING); } } break; case MotionEvent.ACTION_UP: if (!mReady) { // 没有触发onclick reset(); return super.onTouchEvent(event); } if (!isRecording || mTime < 0.6f) { // prapred没有完成已经up了 mDialogManager.tooShort(); mAudioManager.cancel(); mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300); } else if (mCurState == STATE_RECORDING) { mDialogManager.dimissDailog(); mAudioManager.release(); if (audioFinishRecorderListener != null) { audioFinishRecorderListener.onFinish(mTime, mAudioManager.getCurrentFilePath()); } } else if (mCurState == STATE_WANT_TO_CANCEL) { mDialogManager.dimissDailog(); mAudioManager.cancel(); } // 恢复标志位 reset(); break; default: break; } return super.onTouchEvent(event); } private void reset() { // TODO Auto-generated method stub mReady = false; mTime = 0; isRecording = false; changeState(STATE_NORMAL); } private boolean wantToCancel(int x, int y) { // x<0代表来到按钮的范围以外 if (x < 0 || x > getWidth()) { return true; } if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) { return true; } return false; } private void changeState(int state) { // TODO Auto-generated method stub if (mCurState != state) { mCurState = state; switch (state) { case STATE_NORMAL: setBackgroundResource(R.drawable.button_recorder_normal); setText(R.string.str_recorder_nomal); break; case STATE_RECORDING: setBackgroundResource(R.drawable.button_recorder_recording); setText(R.string.str_recorder_recording); if (isRecording) { // to do mDialogManager.recording(); } break; case STATE_WANT_TO_CANCEL: setBackgroundResource(R.drawable.button_recorder_recording); setText(R.string.str_recorder_want_cancel); mDialogManager.wantToCancle(); break; default: break; } } } /** 完成录音回调 */ public interface AudioFinishRecorderListener { void onFinish(float seconds, String filePath); } private AudioFinishRecorderListener audioFinishRecorderListener; public void setAudioFinishRecorderListener( AudioFinishRecorderListener audioFinishRecorderListener) { this.audioFinishRecorderListener = audioFinishRecorderListener; } }
package com.example.recorder_view; import com.example.imooc_recorder.R; 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 DialogManager { private Dialog mDialog; private ImageView mIcon; private ImageView mVoice; private TextView mLabel; private Context mContext; public DialogManager(Context context) { this.mContext = context; } public void showRecordingDailog() { mDialog = new Dialog(mContext, R.style.Theme_AudioDialog); LayoutInflater inflater = LayoutInflater.from(mContext); View view = inflater.inflate(R.layout.dialog_recorder, null); mDialog.setContentView(view); mIcon = (ImageView) mDialog.findViewById(R.id.id_recorder_dialog_icon); mVoice = (ImageView) mDialog .findViewById(R.id.id_recorder_dialog_voice); mLabel = (TextView) mDialog.findViewById(R.id.id_recorder_dialog_label); mDialog.show(); } public void recording() { if (mDialog != null && mDialog.isShowing()) { mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.VISIBLE); mLabel.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.recorder); mLabel.setText("手指上滑,取消发送"); } } public void wantToCancle() { 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 dimissDailog() { if (mDialog != null && mDialog.isShowing()) { mDialog.dismiss(); mDialog = null; } } public void updateVoiceLeve(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); } } }
package com.example.recorder_view; import java.io.File; import java.io.IOException; import java.util.UUID; import javax.security.auth.PrivateCredentialPermission; import android.R.integer; import android.media.MediaRecorder; public class AudioManager { private MediaRecorder mMediaRecorder; private String mDir; private String mCurrentFilepath; private static AudioManager mInstance; private boolean isPrepare; /** 接口回调 */ public interface AudioStateListener { void wellPrepared(); } public AudioStateListener mAudioStateListener; public void setOnAudioStateListener(AudioStateListener audioStateListener) { mAudioStateListener = audioStateListener; } private AudioManager(String dir) { this.mDir = dir; } public static AudioManager getInstance(String dir) { if (mInstance == null) { synchronized (AudioManager.class) { if (mInstance == null) { mInstance = new AudioManager(dir); } } } return mInstance; } public void prepareAndio() { isPrepare = false; File dir = new File(mDir); if (!dir.exists()) { dir.mkdirs(); } String fileName = geneFileName(); File file = new File(dir, fileName); mCurrentFilepath = file.getAbsolutePath(); mMediaRecorder = new MediaRecorder(); // 设置输出文件 mMediaRecorder.setOutputFile(file.getAbsolutePath()); // 音频源头 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频格式 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // 设置音频编码 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { mMediaRecorder.prepare(); mMediaRecorder.start(); isPrepare = true; if (mAudioStateListener != null) { mAudioStateListener.wellPrepared(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private String geneFileName() { return UUID.randomUUID().toString() + ".amr"; } public int getVoiceLevel(int maxLevel) { if (isPrepare) { try { // mMediaRecorder.getMaxAmplitude() 1~32767 // return maxlevel*mMediaRecorder.getMaxAmplitude()/32768+1; return maxLevel * new MediaRecorder().getMaxAmplitude() / 32768 + 1; } catch (Exception e) { } } return 1; } public void release() { 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; } }
package com.example.recorder_view; import java.io.IOException; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; public class MediaManager { private static MediaPlayer mediaPlayer; private static boolean isPause; public static void playSound(String filePath, OnCompletionListener onCompletionListener) { if (mediaPlayer == null) { mediaPlayer = new MediaPlayer(); mediaPlayer.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { mediaPlayer.reset(); return false; } }); }else { mediaPlayer.reset(); } try { mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setOnCompletionListener(onCompletionListener); mediaPlayer.setDataSource(filePath); mediaPlayer.prepare(); } catch (Exception e) { e.printStackTrace(); } mediaPlayer.start(); } public static void pause(){ if (mediaPlayer!=null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); isPause = true; } } public static void resume(){ if (mediaPlayer!=null && isPause) { mediaPlayer.start(); isPause = false; } } public static void release(){ if (mediaPlayer!=null) { mediaPlayer.release(); mediaPlayer = null; } } }