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仿微信语音聊天

时间: 2024-11-16 07:21:09

Android仿微信语音聊天的相关文章

Android仿微信语音聊天界面

有段时间没有看视频了,昨天晚上抽了点空时间,又看了下鸿洋大神的视频教程,又抽时间写了个学习记录.代码和老师讲的基本一样,网上也有很多相同的博客.我只是在AndroidStudio环境下写的. --主界面代码-- public class MainActivity extends Activity { private ListView mListView; private ArrayAdapter<Recorder> mAdapter; private List<Recorder>

Android 仿微信QQ聊天界面

一些IM聊天软件的展现形式是左右分开的形式.比如说,别人给你发的信息全部靠左显示,你自己发给别人的信息全部靠右显示. 而我们的ListView很多时候是显示同一个布局,其实BaseAdapter中有2个重要的方法在大多数情况下我们并未使用到,一个是public int getViewTypeCount(),显示ListView中有多少种布局(默认是显示是1),像微信那样聊天界面,是有2种布局方式:另外一个getItemViewType(),可以让不同item条目加载不同的布局,下面就简单的模拟下

转-Android仿微信气泡聊天界面设计

微信的气泡聊天是仿iPhone自带短信而设计出来的,不过感觉还不错可以尝试一下仿着微信的气泡聊天做一个Demo,给大家分享一下!效果图如下: 气泡聊天最终要的是素材,要用到9.png文件的素材,这样气泡会随着聊天内容的多少而改变气泡的大小且不失真.为了方便,我就直接在微信里面提取出来啦. 聊天的内容是用ListView来显示的,将聊天的内容封装成一个ChatMsgEntity类的对象里面,然后交给自定义的ListView适配器将聊天内容显示出来. ChatMsgEntity.java比较简单,只

Android仿微信SlideView聊天列表滑动删除效果

package com.ryg.slideview; import com.ryg.slideview.MainActivity.MessageItem; //Download by http://www.okbase.net import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import andro

Android模仿微信语音聊天功能

项目效果如下: 项目目录结构如下: 代码如下: AudioManager.java package com.xuliugen.weichat; import java.io.File; import java.io.IOException; import java.util.UUID; import android.media.MediaRecorder; public class AudioManager { private MediaRecorder mMediaRecorder; priv

基于百度云推送的高仿微信实时聊天Android源码

基于百度云推送的高仿微信实时聊天Android源码 使用服务:百度云推送    功能分类:社交     支持平台:Android 运行环境:Android       开发语言:Java     开发工具:Eclipse 下载地址:http://sina.lt/z84 源码简介 基于百度云推送的一款Android高仿微信的实时聊天app 运行动态图

html5聊天案例|趣聊h5|仿微信界面聊天|红包|语音聊天|地图

之前有开发过一个h5微直播项目,当时里面也用到过聊天模块部分,今天就在之前聊天部分的基础上重新抽离模块,开发了这个h5趣聊项目,功能效果比较类似微信聊天界面.采用html5+css3+Zepto+swiper+wcPop+flex等技术融合开发,实现了发送消息.表情(动图),图片.视频预览,添加好友/群聊,右键长按菜单.另外新增了语音模块.地图定位模块.整体功能界面效果比较接近微信聊天. 项目运行效果图: // ripple波纹效果 wcRipple({ elem: '.effect__ripp

Android仿微信UI布局视图(圆角布局的实现)

圆角按钮,或布局可以在xml文件中实现,但也可以使用图片直接达到所需的效果,以前版本的微信就使用了这种方法. 实现效果图:    不得不说,这种做法还是比较方便的. 源代码: MainActivity(没写任何代码,效果全在布局文件中实现): package com.android_settings; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity

Android仿微信下拉列表实现

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 本文要实现微信6.1中点击顶部菜单栏的"+"号按钮时,会弹出一个列表框.这里用的了Activity实现,其实最好的方法可以用ActionBar,不过这货好像只支持3.0以后的版本.本文的接上文Android仿微信底部菜单栏+顶部菜单栏(附源码) 效果: 一.仿微信下拉列表布局pop_dialog.xml <?xml version="1.0" encodi