Android仿微信语音聊天

完整代码下载地址:
Android仿微信语音聊天


效果图:
Android仿微信语音聊天Android仿微信语音聊天

分析:
1.自定义Button中要复写onTouchEvent的DOWN,MOVE,UP三种状态,对正常按下,想要取消发送,抬起三种动作进行侦听处理。

2.Dialog共有三种状态,除上图所示的两种外,还有一个录音时间过短的提示。其中录音状态中的音量可以变化。

3.显示录音的ListView的item中有一个录音时长(TextView),一个播放动画(View)和一个头像(ImageView)。

4.录音类里有两个成员:录音长度,录音路径。

下面贴一下代码:

自定义Button

package com.zms.wechatrecorder.view;

import com.zms.wechatrecorder.MyAudioManager;
import com.zms.wechatrecorder.MyAudioManager.AudioStateChangeListener;
import com.zms.wechatrecorder.R;

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 AudioRecordButton extends Button {
    private static final int STATE_NORMAL = 1;
    private static final int STATE_RECORDING = 2;
    private static final int STATE_WANT_CANCEL = 3;

    private static final int DISTANCE_CANCEL_Y = 50;

    private int currentState = STATE_NORMAL;
    private boolean isRecording = false;
    private AudioRecordDialog dialogManager;
    private MyAudioManager audioManager;

    private float mTime;
    // 是否触发LongClick
    private boolean isReady = false;

    public AudioRecordButton(Context context) {
        this(context, null);
    }

    public AudioRecordButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        dialogManager = new AudioRecordDialog(getContext());

        String dir = Environment.getExternalStorageDirectory()
                + "/zms_chat_audios";
        audioManager = MyAudioManager.getInstance(dir);
        audioManager
                .setOnAudioStateChangeListener(new MyOnAudioStateChangeListener());

        setOnLongClickListener(new OnLongClickListener() {

            @Override
            public boolean onLongClick(View v) {
                isReady = true;
                audioManager.prepareAudio();
                return false;
            }
        });
    }

    class MyOnAudioStateChangeListener implements AudioStateChangeListener {

        @Override
        public void wellPrepared() {
            mHanlder.sendEmptyMessage(MSG_AUDIO_PREPARED);

        }
    }

    /**
     * 录音完成后的回调
     * 
     */
    public interface AudioRecordFinishListener {
        void onFinish(float second, String filePath);
    }

    private AudioRecordFinishListener audioRecordFinishListener;

    public void setAudioRecordFinishListener(AudioRecordFinishListener listener) {
        audioRecordFinishListener = listener;
    }

    private Runnable getVolumeRunnable = new Runnable() {

        @Override
        public void run() {

            while (isRecording) {
                try {
                    Thread.sleep(100);
                    mTime += 0.1f;
                    mHanlder.sendEmptyMessage(MSG_VOLUME_CHAMGED);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }

    };

    private static final int MSG_AUDIO_PREPARED = 0x110;
    private static final int MSG_VOLUME_CHAMGED = 0x111;
    private static final int MSG_DIALOG_DISMISS = 0x112;

    private Handler mHanlder = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            case MSG_AUDIO_PREPARED:
                dialogManager.showDialog();
                isRecording = true;

                // 音量
                new Thread(getVolumeRunnable).start();

                break;
            case MSG_VOLUME_CHAMGED:
                dialogManager.updateVolumeLevel(audioManager.getVoiceLevel(7));
                break;
            case MSG_DIALOG_DISMISS:
                dialogManager.dismissDialog();

                break;

            default:
                break;
            }
        };
    };

    @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 (wantCancel(x, y)) {
                    changeState(STATE_WANT_CANCEL);
                    dialogManager.stateWantCancel();
                } else {
                    changeState(STATE_RECORDING);
                    dialogManager.stateRecording();
                }
            }

            break;

        case MotionEvent.ACTION_UP:
            // 没有触发longClick
            if (!isReady) {
                resetState();
                return super.onTouchEvent(event);
            }
            // prepare未完成就up,录音时间过短
            if (!isRecording || mTime < 0.6f) {
                dialogManager.stateLengthShort();
                audioManager.cancel();
                mHanlder.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);
            } else if (currentState == STATE_RECORDING) { // 正常录制结束
                dialogManager.dismissDialog();
                audioManager.release();

                // callbackToActivity
                if (audioRecordFinishListener != null) {
                    audioRecordFinishListener.onFinish(mTime,
                            audioManager.getCurrentPath());
                }

            } else if (currentState == STATE_WANT_CANCEL) {
                dialogManager.dismissDialog();
                audioManager.cancel();

            }
            resetState();
            break;

        default:
            break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 恢复标志位
     */
    private void resetState() {

        isRecording = false;
        isReady = false;
        changeState(STATE_NORMAL);
        mTime = 0;
    }

    private boolean wantCancel(int x, int y) {
        if (x < 0 || x > getWidth()) {
            return true;
        }
        // 零点在左下角?
        if (y < -DISTANCE_CANCEL_Y || y > getHeight() + DISTANCE_CANCEL_Y) {
            return true;
        }
        return false;
    }

    private void changeState(int state) {

        if (currentState != state) {
            currentState = state;
            switch (state) {
            case STATE_NORMAL:
                setBackgroundResource(R.drawable.btn_recorder_normal);
                setText(R.string.btn_recorder_normal);

                break;
            case STATE_RECORDING:
                setBackgroundResource(R.drawable.btn_recorder_normal);
                setText(R.string.btn_recorder_recording);
                if (isRecording) {
                    dialogManager.stateRecording();
                }
                break;
            case STATE_WANT_CANCEL:
                setBackgroundResource(R.drawable.btn_recorder_normal);
                setText(R.string.btn_recorder_want_cancel);
                dialogManager.stateWantCancel();
                break;

            default:
                break;
            }
        }
    }

}


自定义Dialog

package com.zms.wechatrecorder.view;

import com.zms.wechatrecorder.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 AudioRecordDialog {
    private Dialog dialog;
    private ImageView imageRecord, imageVolume;
    private TextView textHint;

    private Context context;

    public AudioRecordDialog(Context context) {
        this.context = context;
    }

    public void showDialog() {

        dialog = new Dialog(context, R.style.Theme_RecorderDialog);
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.dialog, null);
        dialog.setContentView(view);

        imageRecord = (ImageView) dialog.findViewById(R.id.imageRecord);
        imageVolume = (ImageView) dialog.findViewById(R.id.imageVolume);
        textHint = (TextView) dialog.findViewById(R.id.textHint);

        dialog.show();
    }

    public void stateRecording() {
        if (dialog != null && dialog.isShowing()) {
            imageRecord.setVisibility(View.VISIBLE);
            imageVolume.setVisibility(View.VISIBLE);
            textHint.setVisibility(View.VISIBLE);

            imageRecord.setImageResource(R.drawable.icon_dialog_recording);
            textHint.setText("手指上滑,取消发送");
        }
    }

    public void stateWantCancel() {
        if (dialog != null && dialog.isShowing()) {
            imageRecord.setVisibility(View.VISIBLE);
            imageRecord.setImageResource(R.drawable.icon_dialog_cancel);
            imageVolume.setVisibility(View.GONE);
            textHint.setVisibility(View.VISIBLE);
            textHint.setText("松开手指,取消发送");
        }
    }

    public void stateLengthShort() {
        if (dialog != null && dialog.isShowing()) {
            imageRecord.setVisibility(View.VISIBLE);
            imageRecord.setImageResource(R.drawable.icon_dialog_length_short);
            imageVolume.setVisibility(View.GONE);
            textHint.setVisibility(View.VISIBLE);
            textHint.setText("录音时间过短");
        }
    }

    public void dismissDialog() {
        if (dialog != null && dialog.isShowing()) {
            dialog.dismiss();
            dialog = null;
        }
    }

    /**
     * 更新音量
     * 
     * @param level
     */
    public void updateVolumeLevel(int level) {
        if (dialog != null && dialog.isShowing()) {
            // imageRecord.setVisibility(View.VISIBLE);
            // imageVolume.setVisibility(View.VISIBLE);
            // textHint.setVisibility(View.VISIBLE);

            int volumeResId = context.getResources().getIdentifier(
                    "icon_volume_" + level, "drawable",
                    context.getPackageName());
            imageVolume.setImageResource(volumeResId);
        }
    }
}


VoiceListAdapter:

package com.zms.wechatrecorder;

import java.util.List;

import com.zms.wechatrecorder.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.TextView;

public class VoiceListAdapter extends ArrayAdapter<Recorder> {

    private List<Recorder> mDatas;
    private Context context;

    private int minItemWidth;
    private int maxItemWidth;

    private LayoutInflater inflater;

    public VoiceListAdapter(Context context, List<Recorder> datas) {
        super(context, -1, datas);
        this.context = context;
        mDatas = datas;

        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        maxItemWidth = (int) (outMetrics.widthPixels * 0.8);
        maxItemWidth = (int) (outMetrics.widthPixels * 0.2);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            inflater = LayoutInflater.from(getContext());
            convertView = inflater.inflate(R.layout.list_item_voice, parent,
                    false);
            holder = new ViewHolder();
            holder.seconds = (TextView) convertView
                    .findViewById(R.id.textLength);
            holder.length = convertView.findViewById(R.id.voiceAnim);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.seconds.setText(Math.round(getItem(position).audioLength) + "\"");
//      ViewGroup.LayoutParams params = holder.length.getLayoutParams();
//      params.width = (int) (minItemWidth + maxItemWidth / 60f
//              * getItem(position).audioLength);
//      holder.length.setLayoutParams(params);

        return convertView;
    }

    private class ViewHolder {
        TextView seconds;
        View length;
    }
}

MyAudioManager:

package com.zms.wechatrecorder;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

import android.media.MediaRecorder;

public class MyAudioManager {

    private MediaRecorder mediaRecorder;
    private String dir;
    private String currentFilePath;

    private static MyAudioManager audioInstance; // 单例

    public boolean isPrepared = false;

    private MyAudioManager(String dir) {
        this.dir = dir;
    }

    public interface AudioStateChangeListener {
        void wellPrepared();
    }

    public AudioStateChangeListener audioStateChangeListener;

    public void setOnAudioStateChangeListener(AudioStateChangeListener listener) {
        audioStateChangeListener = listener;
    }

    public static MyAudioManager getInstance(String dir) {
        if (audioInstance == null) {
            synchronized (MyAudioManager.class) {
                if (audioInstance == null) {
                    audioInstance = new MyAudioManager(dir);
                }
            }
        }
        return audioInstance;
    }

    public void prepareAudio() {
        try {
            isPrepared = false;
            File fileDir = new File(dir);
            if (!fileDir.exists())
                fileDir.mkdirs();
            String fileName = generateFileName();
            File file = new File(fileDir, fileName);

            currentFilePath = file.getAbsolutePath();
            mediaRecorder = new MediaRecorder();
            // 设置输出文件
            mediaRecorder.setOutputFile(file.getAbsolutePath());
            // 设置音频源
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            // 设置音频格式
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
            // 设置音频编码
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

            mediaRecorder.prepare();
            mediaRecorder.start();
            // 准备结束
            isPrepared = true;
            //
            if (audioStateChangeListener != null) {
                audioStateChangeListener.wellPrepared();
            }
        } catch (IllegalStateException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 随机生成文件名称
     * 
     * @return
     */
    private String generateFileName() {
        return UUID.randomUUID().toString() + ".amr";
    }

    public int getVoiceLevel(int maxLevel) {
        if (isPrepared) {
            try {
                // 振幅范围mediaRecorder.getMaxAmplitude():1-32767
                return maxLevel * mediaRecorder.getMaxAmplitude() / 32768 + 1;
            } catch (Exception e) {
            }
        }
        return 1;
    }

    public void release() {
        mediaRecorder.stop();
        mediaRecorder.release();
        mediaRecorder = null;

    }

    public void cancel() {
        release();
        if (currentFilePath != null) {
            File file = new File(currentFilePath);
            file.delete();
            currentFilePath = null;
        }
    }

    public String getCurrentPath() {
        return currentFilePath;
    }
}


MediaManager

package com.zms.wechatrecorder;

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();
            mediaPlayer.start();
        } catch (IllegalArgumentException | SecurityException
                | IllegalStateException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

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

}

感谢hyman在慕课网的教程:

Android仿微信语音聊天

Android仿微信语音聊天

上一篇:微信开发 LBS位置定位的存在问题的改进 基于java语言和mysql数据库


下一篇:android 应用实现微信好友或朋友圈分享