仿微信语音聊天

coding

如上图,是常见的仿微信的聊天程序,实现的效果如上图所示,由于项目太大,本文只讲录音部分。本项目示例代码: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);更新Dialog

changeButtonState(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() {

@Override

public 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() {

@Override

public 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() {

@Override

public 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);

}

};

@Override

public void wellPrepared() {

mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);

}

@Override

public 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;

}

//再判断Y

if (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

本文同步分享在 博客“xiangzhihong8”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

以上是 仿微信语音聊天 的全部内容, 来源链接: utcz.com/z/510123.html

回到顶部