如上图,是常见的仿微信的聊天程序,实现的效果如上图所示,由于项目太大,本文只讲录音部分。本项目示例代码:https://github.com/xiangzhihong/weixinAudio
主要用到4个核心类:
自定义录音按钮(RecoderButton);
弹框管理类(RecorderDialog);
录音管理类(AudioManager);
录音播放类(MediaManager)。
其中
1.AudioRecordButton状态:
1.STATE_NORMAL:普通状态
2.STATE_RECORDING:录音中
3.STATE_CANCEL:取消录音
2.DialogManager状态:
1.RECORDING:录音中
2.WANT_TO_CANCEL:取消录音
3.TOO_SHORT:录音时间太短
3.AudioManager:
1.prepare():准备状态
2.cancel():取消录音
3.release():正常结束录音
4.getVoiceLevel():获取音量
代码实现
自定义Button,重写onTouchEvent()方法,用于执行长按录音操作。
class AudioRecorderButton{onTouchEvent(){DOWN:changeButtonState(STATE_RECORDING);| DialogManager.showDialog(RECORDING)触发LongClick事件(AudioManager.prepare() --> end prepared --> | );| getVoiceLevel();//开启一个线程,更新Dialog上的音量等级 MOVE:if(wantCancel(x,y)){DialogManager.showDialog(WANT_TO_CANCEL);更新DialogchangeButtonState(STATE_WANT_TO_CANCEL);更新Button状态}else{DialogManager.showDialog(WANT_TO_CANCEL);changeButtonState(STATE_RECORDING);}UP:if(wantCancel == curState){//当前状态是想取消状态AudioManager.cancel();}if(STATE_RECORDING = curState){if(tooShort){//判断录制时长,如果录制时间过短DialogManager.showDialog(TOO_SHORT);}AudioManager.release();callbackActivity(url,time);//(当前录音文件路径,时长)}}
}
相关的逻辑请查看项目源码。
MediaManager
public class MediaManager {private static MediaPlayer mMediaPlayer;private static boolean isPause;public static void playSound(String filePath,MediaPlayer.OnCompletionListener onCompletionListener) {if(mMediaPlayer == null){mMediaPlayer = new MediaPlayer();}else {mMediaPlayer.reset();}mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);try {mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.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;}}}
RecorderDialog
录音弹窗类,主要包含录音的各种状态及弹窗。源码如下:
public class RecorderDialog {private Dialog mDialog;private ImageView mIcon;private TextView mLable;private TextView mLeftTime;private Context mContext;public RecorderDialog(Context context) {this.mContext = context;}public void showRecordingDialog() {LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.dialog_recorder, null);mDialog = new Dialog(mContext, R.style.style_dialog);mDialog.setContentView(view);mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);mLable = (TextView) mDialog.findViewById(R.id.recoder_dialog_label);mLeftTime=(TextView) mDialog.findViewById(R.id.recoder_leftTime);mDialog.show();}public void recording() {if (mDialog != null && mDialog.isShowing()) {mIcon.setImageResource(R.mipmap.recorder_icon);mLable.setText("手指上滑,取消发送");}}public void wantToCancel() {if (mDialog != null && mDialog.isShowing()) {mIcon.setImageResource(R.mipmap.cancel_recorder_icon);mLable.setText("松开手指,取消发送");}}public void tooShort() {if (mDialog != null && mDialog.isShowing()) {mIcon.setImageResource(R.mipmap.voice_to_short);mLable.setText("录音时间过短");}}//倒计时提示(10-->0)public void recoderConfirm(int time) {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.GONE);mLeftTime.setVisibility(View.VISIBLE);mLeftTime.setText(time+"");mLable.setText("松开手指,取消发送");}}public void dimissDialog() {if (mDialog != null && mDialog.isShowing()) {mDialog.dismiss();mDialog = null;}}public void setVoiceLevel(int level) {if (mDialog != null && mDialog.isShowing()) {//用switch冗余int resId = mContext.getResources().getIdentifier("v"+level,"mipmap",mContext.getPackageName());}}
}
由于需要用到权限系统,所以需要在配置文件中添加相关的权限。
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
RecoderButton
自定义录音按钮,录音的一切判断都在这个文件。
public class RecoderButton extends TextView implements AudioManager.AudioStateListener {private static final int DISANCE_Y_CANCEL = 50;private static final int STATE_RECORDER_NORMAL = 1; //正常private static final int STATE_RECORDING = 2; //正在录制private static final int STATE_CANCEL = 3; //取消private static final int MSG_AUDIO_PREPARED = 0X10;private static final int MSG_AUDIO_CHANGED = 0X11;private static final int MSG_AUDIO_DIMISS = 0X12;private static final int MSG_AUDIO_TIME_OUT = 0X13;private boolean mReady;private int mCurState = STATE_RECORDER_NORMAL;private boolean isRecording = false;//正在录音private float maxTime=10;//最大录制时长private float mTime=0;//录制时长private Timer timer = new Timer();private int leftTime=10;//录音倒计时,10开始提示private RecorderDialog dialog=null;private AudioManager audioManager=null;private FinishRecorderListener mListener;public RecoderButton(Context context) {this(context, null);}public RecoderButton(Context context, AttributeSet attrs) {super(context, attrs);initDialog();initClick();}private void initClick() {setOnLongClickListener(new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {mReady = true;audioManager.prepareAudio();return false;}});}private void initDialog() {dialog = new RecorderDialog(getContext());String dir = Environment.getExternalStorageDirectory() + "/recorder";//创建文件夹audioManager = AudioManager.getInstance(dir);audioManager.setOnAudioStateListener(this);}/*** 获取音量大小*/private Runnable mGetVoiceLeveelRunnable = new Runnable() {@Overridepublic void run() {while (isRecording) {try {Thread.sleep(100);mTime += 0.1f;mHandler.sendEmptyMessage(MSG_AUDIO_CHANGED);} catch (InterruptedException e) {e.printStackTrace();}}}};private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_AUDIO_PREPARED:dialog.showDialog();isRecording = true;new Thread(mGetVoiceLeveelRunnable).start();break;case MSG_AUDIO_CHANGED:dialog.voiceLevel(audioManager.getVoiceLevel(7));System.out.println("录音时间:"+mTime);if (mTime>maxTime){confirmTimer();}break;case MSG_AUDIO_DIMISS:dialog.dimissDialog();break;}}};//录音倒计时private Handler handler = new Handler() {public void handleMessage(Message msg) {leftTime--;if (leftTime<=0){dialog.dimissDialog();audioManager.release();if (mListener != null) {mListener.onFinish(mTime, audioManager.getCurrentFilePath());}return;}dialog.recoderConfirm(leftTime);}};@Overridepublic void wellPrepared() {mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}@Overridepublic boolean onTouchEvent(MotionEvent event) {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) {//根据x y的坐标判断是否想取消if (wantToCancel(x, y)) {changeState(STATE_CANCEL);} else {changeState(STATE_RECORDING);}}break;case MotionEvent.ACTION_UP:if (!mReady) {reset();return super.onTouchEvent(event);}if (!isRecording || mTime < 1.0f) {System.out.println("录音时间过短");dialog.recoderShort();audioManager.cancel();mHandler.sendEmptyMessageDelayed(MSG_AUDIO_DIMISS, 1300);} else if (mCurState == STATE_RECORDING) {System.out.println("正常录制");dialog.dimissDialog();audioManager.release();if (mListener != null) {mListener.onFinish(mTime, audioManager.getCurrentFilePath());}} else if (mCurState == STATE_CANCEL) {System.out.println("取消了");dialog.dimissDialog();audioManager.cancel();}reset();break;}return super.onTouchEvent(event);}//恢复状态及标志位private void reset() {isRecording = false;mReady = false;mTime = 0;changeState(STATE_RECORDER_NORMAL);}private boolean wantToCancel(int x, int y) {if (x < 0 || x > getWidth()) {//判断手指的横坐标是否超出按钮的范围return true;}//再判断Yif (y < -DISANCE_Y_CANCEL || y > getHeight() + DISANCE_Y_CANCEL) {//按钮上部或下部return true;}return false;}//随着状态的改变,文本颜色和背景改变private void changeState(int state) {if (mCurState != state) {mCurState = state;switch (state) {case STATE_RECORDER_NORMAL:setBackgroundResource(R.drawable.recentgle_gray_border);setText("按住 说话");break;case STATE_RECORDING:setBackgroundResource(R.drawable.recentgle_gray);setText("松开结束");if (isRecording) {dialog.recording();}break;case STATE_CANCEL:setBackgroundResource(R.drawable.recentgle_gray);setText("松开手指,取消发送");dialog.cancelRecorder();break;}}}//倒计时定时器private void confirmTimer() {timer.schedule(new TimerTask() {@Override public void run() {try {Thread.sleep(1000);handler.sendEmptyMessage(MSG_AUDIO_TIME_OUT);}catch (Exception e){e.printStackTrace();}}}, 0, 1000);}public interface FinishRecorderListener {void onFinish(float seconds, String filePath);}public void setRecorderListener(FinishRecorderListener listener) {mListener = listener;}
}
最后录制完成后,点击列表的语音会完成播放功能。
MediaManager
public class MediaManager {public static MediaManager instance=null;private MediaPlayer mMediaPlayer=null;private static boolean isPause=false;public static synchronized MediaManager getInstance() {if (instance == null) {instance = new MediaManager();}return instance;}public void playSound(String filePath){playSound(filePath,null);}public void playSound(String filePath,MediaPlayer.OnCompletionListener onCompletionListener) {if(mMediaPlayer == null){mMediaPlayer = new MediaPlayer();}else {mMediaPlayer.reset();}mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);try {mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.printStackTrace();}}public void pause(){if(mMediaPlayer != null && mMediaPlayer.isPlaying()){mMediaPlayer.pause();isPause = true;}}public void resume(){if(mMediaPlayer != null && isPause){mMediaPlayer.start();isPause = false;}}public void release(){if(mMediaPlayer != null){mMediaPlayer.release();mMediaPlayer = null;}}
}
对于聊天列表,是一个比较复杂的逻辑,开发的时候可以重写getItemViewType函数,然后不同的ViewType加载不同的视图,例如我的项目代码如下:
ChatItem struct = getItem(position);switch (struct.chatType) {case ChatItem.CHAT_TYPE_TIME:return initTimeView(convertView, parent, (String) struct.data);case ChatItem.CHAT_TYPE_GROUP_TIP:return initTipView(convertView, parent, (String) struct.data);case ChatItem.CHAT_TYPE_OUT_TEXT:return initOutTextView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_IN_TEXT:return initInTextView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_OUT_IMAGE:return initOutImageView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_IN_IMAGE:return initInImageView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_OUT_AUDIO:return initOutAudioView(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_BONUS_NOTICE:return initBonusNotice(convertView, parent, (Message) struct.data);case ChatItem.CHAT_TYPE_RECOMMEND:return initRecommendType(convertView, parent, (Message) struct.data);default:return new View(context);}
如果,你想了解mp3格式相关的内容,可以查看下面的链接:http://blog.csdn.net/omrapollo/article/details/50470659