前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >仿微信语音聊天

仿微信语音聊天

作者头像
xiangzhihong
发布2018-02-06 19:49:22
9.1K2
发布2018-02-06 19:49:22
举报
文章被收录于专栏:向治洪

如上图,是常见的仿微信的聊天程序,实现的效果如上图所示,由于项目太大,本文只讲录音部分。本项目示例代码: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()方法,用于执行长按录音操作。

代码语言:javascript
复制
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

代码语言:javascript
复制
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

录音弹窗类,主要包含录音的各种状态及弹窗。源码如下:

代码语言:javascript
复制
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());
        }
    }
}

由于需要用到权限系统,所以需要在配置文件中添加相关的权限。

代码语言:javascript
复制
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

RecoderButton

自定义录音按钮,录音的一切判断都在这个文件。

代码语言:javascript
复制
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

代码语言:javascript
复制
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加载不同的视图,例如我的项目代码如下:

代码语言:javascript
复制
 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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-12-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 代码实现
    • MediaManager
      • RecorderDialog
        • RecoderButton
          • MediaManager
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档