Android仿微信语音聊天界面
有段时间没有看视频了,昨天晚上抽了点空时间,又看了下鸿洋大神的视频教程,又抽时间写了个学习记录。代码和老师讲的基本一样,网上也有很多相同的博客。我只是在AndroidStudio环境下写的。
—-主界面代码——
public class MainActivity extends Activity {private ListView mListView;private ArrayAdapter<Recorder> mAdapter;private List<Recorder> mDatas = new ArrayList<Recorder>();private AudioRecorderButton mAudioRecorderButton;private View animView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mListView = (ListView) findViewById(R.id.id_listview);mAudioRecorderButton = (AudioRecorderButton) findViewById(R.id.id_recorder_button);mAudioRecorderButton.setFinishRecorderCallBack(new AudioRecorderButton.AudioFinishRecorderCallBack() {public void onFinish(float seconds, String filePath) {Recorder recorder = new Recorder(seconds, filePath);mDatas.add(recorder);//更新数据mAdapter.notifyDataSetChanged();//设置位置mListView.setSelection(mDatas.size() - 1);}});mAdapter = new RecoderAdapter(this, mDatas);mListView.setAdapter(mAdapter);//listView的item点击事件mListView.setOnItemClickListener(new OnItemClickListener() {public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {// 声音播放动画if (animView != null) {animView.setBackgroundResource(R.drawable.adj);animView = null;}animView = view.findViewById(R.id.id_recoder_anim);animView.setBackgroundResource(R.drawable.play_anim);AnimationDrawable animation = (AnimationDrawable) animView.getBackground();animation.start();// 播放录音MediaPlayerManager.playSound(mDatas.get(position).filePath, new MediaPlayer.OnCompletionListener() {public void onCompletion(MediaPlayer mp) {//播放完成后修改图片animView.setBackgroundResource(R.drawable.adj);}});}});}@Overrideprotected void onPause() {super.onPause();MediaPlayerManager.pause();}@Overrideprotected void onResume() {super.onResume();MediaPlayerManager.resume();}@Overrideprotected void onDestroy() {super.onDestroy();MediaPlayerManager.release();}
—自定义Button——-
/*** @param* @author ldm* @description 自定义Button* @time 2016/6/25 9:26*/
public class AudioRecorderButton extends Button {// 按钮正常状态(默认状态)private static final int STATE_NORMAL = 1;//正在录音状态private static final int STATE_RECORDING = 2;//录音取消状态private static final int STATE_CANCEL = 3;//记录当前状态private int mCurrentState = STATE_NORMAL;//是否开始录音标志private boolean isRecording = false;//判断在Button上滑动距离,以判断 是否取消private static final int DISTANCE_Y_CANCEL = 50;//对话框管理工具类private DialogManager mDialogManager;//录音管理工具类private AudioManager mAudioManager;//记录录音时间private float mTime;// 是否触发longClickprivate boolean mReady;//录音准备private static final int MSG_AUDIO_PREPARED = 0x110;//音量发生改变private static final int MSG_VOICE_CHANGED = 0x111;//取消提示对话框private static final int MSG_DIALOG_DIMISS = 0x112;/*** @description 获取音量大小的线程* @author ldm* @time 2016/6/25 9:30* @param*/private Runnable mGetVoiceLevelRunnable = new Runnable() {public void run() {while (isRecording) {//判断正在录音try {Thread.sleep(100);mTime += 0.1f;//录音时间计算mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);//每0.1秒发送消息} catch (InterruptedException e) {e.printStackTrace();}}}};private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_AUDIO_PREPARED://显示对话框mDialogManager.showRecordingDialog();isRecording = true;// 开启一个线程计算录音时间new Thread(mGetVoiceLevelRunnable).start();break;case MSG_VOICE_CHANGED://更新声音mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));break;case MSG_DIALOG_DIMISS://取消对话框mDialogManager.dimissDialog();break;}super.handleMessage(msg);}};public AudioRecorderButton(Context context, AttributeSet attrs) {super(context, attrs);mDialogManager = new DialogManager(context);//录音文件存放地址String dir = Environment.getExternalStorageDirectory() + "/ldm_voice";mAudioManager = AudioManager.getInstance(dir);mAudioManager.setOnAudioStateListener(new AudioManager.AudioStateListener() {public void wellPrepared() {mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}});// 由于这个类是button所以在构造方法中添加监听事件setOnLongClickListener(new OnLongClickListener() {public boolean onLongClick(View v) {mReady = true;mAudioManager.prepareAudio();return false;}});}public AudioRecorderButton(Context context) {this(context, null);}/*** @description 录音完成后的回调* @author ldm* @time 2016/6/25 11:18* @param*/public interface AudioFinishRecorderCallBack {void onFinish(float seconds, String filePath);}private AudioFinishRecorderCallBack finishRecorderCallBack;public void setFinishRecorderCallBack(AudioFinishRecorderCallBack listener) {finishRecorderCallBack = listener;}/*** @param* @description 处理Button的OnTouchEvent事件* @author ldm* @time 2016/6/25 9:35*/@Overridepublic boolean onTouchEvent(MotionEvent event) {//获取TouchEvent状态int action = event.getAction();// 获得x轴坐标int x = (int) event.getX();// 获得y轴坐标int y = (int) event.getY();switch (action) {case MotionEvent.ACTION_DOWN://手指按下changeState(STATE_RECORDING);break;case MotionEvent.ACTION_MOVE://手指移动if (isRecording) {//根据x,y的坐标判断是否需要取消if (wantToCancle(x, y)) {changeState(STATE_CANCEL);} else {changeState(STATE_RECORDING);}}break;case MotionEvent.ACTION_UP://手指放开if (!mReady) {reset();return super.onTouchEvent(event);}if (!isRecording || mTime < 0.6f) {//如果时间少于0.6s,则提示录音过短mDialogManager.tooShort();mAudioManager.cancel();// 延迟显示对话框mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1000);} else if (mCurrentState == STATE_RECORDING) {//如果状态为正在录音,则结束录制mDialogManager.dimissDialog();mAudioManager.release();if (finishRecorderCallBack != null) {finishRecorderCallBack.onFinish(mTime, mAudioManager.getCurrentFilePath());}} else if (mCurrentState == STATE_CANCEL) { // 想要取消mDialogManager.dimissDialog();mAudioManager.cancel();}reset();break;}return super.onTouchEvent(event);}/*** 恢复状态及标志位*/private void reset() {isRecording = false;mTime = 0;mReady = false;changeState(STATE_NORMAL);}private boolean wantToCancle(int x, int y) {// 超过按钮的宽度if (x < 0 || x > getWidth()) {return true;}// 超过按钮的高度if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {return true;}return false;}/*** @param* @description 根据状态改变Button显示* @author ldm* @time 2016/6/25 9:36*/private void changeState(int state) {if (mCurrentState != state) {mCurrentState = state;switch (state) {case STATE_NORMAL:setBackgroundResource(R.drawable.btn_recorder_normal);setText(R.string.str_recorder_normal);break;case STATE_RECORDING:setBackgroundResource(R.drawable.btn_recorder_recording);setText(R.string.str_recorder_recording);if (isRecording) {mDialogManager.recording();}break;case STATE_CANCEL:setBackgroundResource(R.drawable.btn_recorder_recording);mDialogManager.wantToCancel();setText(R.string.str_recorder_want_cancel);break;}}}
}
—-对话框管理工具类——
/*** @description 对话框管理工具类* @author ldm* @time 2016/6/25 11:53* @param */
public class DialogManager {//弹出对话框private Dialog mDialog;//录音图标private ImageView mIcon;//音量显示 图标private ImageView mVoice;//对话框上提示文字private TextView mLable;//上下文对象private Context mContext;public DialogManager(Context context) {this.mContext = context;}/*** @param* @description 显示对话框* @author ldm* @time 2016/6/25 9:56*/public void showRecordingDialog() {//根据指定sytle实例化DialogmDialog = new Dialog(mContext, R.style.AudioDialog);LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.dialog_recorder, null);mDialog.setContentView(view);mIcon = (ImageView) view.findViewById(R.id.id_recorder_dialog_icon);mVoice = (ImageView) view.findViewById(R.id.id_recorder_dialog_voice);mLable = (TextView) view.findViewById(R.id.id_recorder_dialog_label);mDialog.show();}/*** @param* @description 正在录音状态的对话框* @author ldm* @time 2016/6/25 10:08*/public void recording() {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLable.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.recorder);mLable.setText("手指上滑,取消发送");}}/*** @param* @description 取消录音状态对话框* @author ldm* @time 2016/6/25 10:08*/public void wantToCancel() {if (mDialog != null && mDialog.isShowing()) {mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLable.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.cancel);mLable.setText("松开手指,取消发送");}}/*** @param* @description时间过短提示的对话框* @author ldm* @time 2016/6/25 10:09*/public void tooShort() {if (mDialog != null && mDialog.isShowing()) { //显示状态mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLable.setVisibility(View.VISIBLE);mIcon.setImageResource(R.drawable.voice_to_short);mLable.setText("录音时间过短");}}/*** @param* @description* @author ldm* @time 2016/6/25 取消(关闭)对话框*/public void dimissDialog() {if (mDialog != null && mDialog.isShowing()) { //显示状态mDialog.dismiss();mDialog = null;}}// 显示更新音量级别的对话框public void updateVoiceLevel(int level) {if (mDialog != null && mDialog.isShowing()) { //显示状态mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLable.setVisibility(View.VISIBLE);//设置图片的id,我们放在drawable中的声音图片是以v+数字格式的int resId = mContext.getResources().getIdentifier("v" + level, "drawable", mContext.getPackageName());mVoice.setImageResource(resId);}}}
—-声音播放工具类——
/*** @param* @author ldm* @description 播放声音工具类* @time 2016/6/25 11:29*/
public class MediaPlayerManager {//播放音频API类:MediaPlayerprivate static MediaPlayer mMediaPlayer;//是否暂停private static boolean isPause;/*** @param* filePath:文件路径* onCompletionListener:播放完成监听* @description 播放声音* @author ldm* @time 2016/6/25 11:30*/public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener) {if (mMediaPlayer == null) {mMediaPlayer = new MediaPlayer();//设置一个error监听器mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {public boolean onError(MediaPlayer arg0, int arg1, int arg2) {mMediaPlayer.reset();return false;}});} else {mMediaPlayer.reset();}try {mMediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (Exception e) {}}/*** @param* @description 暂停播放* @author ldm* @time 2016/6/25 11:31*/public static void pause() {if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { //正在播放的时候mMediaPlayer.pause();isPause = true;}}/*** @param* @description 重新播放* @author ldm* @time 2016/6/25 11:31*/public static void resume() {if (mMediaPlayer != null && isPause) {mMediaPlayer.start();isPause = false;}}/*** @param* @description 释放操作* @author ldm* @time 2016/6/25 11:32*/public static void release() {if (mMediaPlayer != null) {mMediaPlayer.release();mMediaPlayer = null;}}
—–录音操作工具类—–
/*** @param* @author ldm* @description 录音管理工具类* @time 2016/6/25 9:39*/
public class AudioManager {//AudioRecord: 主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理。// 优点:可以语音实时处理,可以实现各种音频的封装private MediaRecorder mMediaRecorder;//录音文件private String mDir;//当前录音文件目录private String mCurrentFilePath;//单例模式private static AudioManager mInstance;//是否准备好private boolean isPrepare;//私有构造方法private AudioManager(String dir) {mDir = dir;}//对外公布获取实例的方法public static AudioManager getInstance(String dir) {if (mInstance == null) {synchronized (AudioManager.class) {if (mInstance == null) {mInstance = new AudioManager(dir);}}}return mInstance;}/*** @param* @author ldm* @description 录音准备工作完成回调接口* @time 2016/6/25 11:14*/public interface AudioStateListener {void wellPrepared();}public AudioStateListener mAudioStateListener;/*** @param* @description 供外部类调用的设置回调方法* @author ldm* @time 2016/6/25 11:14*/public void setOnAudioStateListener(AudioStateListener listener) {mAudioStateListener = listener;}/*** @param* @description 录音准备工作* @author ldm* @time 2016/6/25 11:15*/public void prepareAudio() {try {isPrepare = false;File dir = new File(mDir);if (!dir.exists()) {dir.mkdirs();//文件不存在,则创建文件}String fileName = generateFileName();File file = new File(dir, fileName);mCurrentFilePath = file.getAbsolutePath();mMediaRecorder = new MediaRecorder();// 设置输出文件路径mMediaRecorder.setOutputFile(file.getAbsolutePath());// 设置MediaRecorder的音频源为麦克风mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置音频格式为RAW_AMRmMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);// 设置音频编码为AMR_NBmMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);// 准备录音mMediaRecorder.prepare();// 开始,必需在prepare()后调用mMediaRecorder.start();// 准备完成isPrepare = true;if (mAudioStateListener != null) {mAudioStateListener.wellPrepared();}} catch (Exception e) {e.printStackTrace();}}/*** @param* @description 随机生成录音文件名称* @author ldm* @time 2016/6/25 、*/private String generateFileName() {//随机生成不同的UUIDreturn UUID.randomUUID().toString() + ".amr";}/*** @param* @description 获取音量值* @author ldm* @time 2016/6/25 9:49*/public int getVoiceLevel(int maxlevel) {if (isPrepare) {try {// getMaxAmplitude返回的数值最大是32767return maxlevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;//返回结果1-7之间} catch (Exception e) {e.printStackTrace();}}return 1;}/*** @param* @description 释放资源* @author ldm* @time 2016/6/25 9:50*/public void release() {mMediaRecorder.stop();mMediaRecorder.reset();mMediaRecorder = null;}/*** @param* @description 录音取消* @author ldm* @time 2016/6/25 9:51*/public void cancel() {release();if (mCurrentFilePath != null) {//取消录音后删除对应文件File file = new File(mCurrentFilePath);file.delete();mCurrentFilePath = null;}}/*** @param* @description 获取当前文件路径* @author ldm* @time 2016/6/25 9:51*/public String getCurrentFilePath() {return mCurrentFilePath;}
}
代码中有注释,就不贴图了,和微信语音聊天界面一样的,所以叫仿微信 嘛,呵呵。运行了也可以看到效果。所有代码可以从这里下载:
http://download.csdn.net/detail/true100/9559210
Android仿微信语音聊天界面相关推荐
- Android仿微信语音聊天界面设计
这篇文章主要为大家详细介绍了Android仿微信语音聊天界面设计代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 有段时间没有看视频了,昨天晚上抽了点空时间,又看了下鸿洋大神的视频教程,又抽时间 ...
- Android仿微信气泡聊天界面设计
Android仿微信气泡聊天界面设计 微信的气泡聊天是仿iPhone自带短信而设计出来的,不过感觉还不错可以尝试一下仿着微信的气泡聊天做一个Demo,给大家分享一下!效果图如下: 气泡聊天最终要的是素 ...
- android 仿微信语音聊天
android 仿微信语音聊天 跟着imooc老师学习 代码地址: https://github.com/tingsky9985/Weixin_Recorder
- android仿微信语音聊天功能,Android仿微信发送语音消息的功能及示例代码
微信的发送语音是有一个向上取消的,我们使用ontouchlistener来监听手势,然后做出相应的操作就行了. 直接上代码: //语音操作对象 private mediaplayer mplayer ...
- Android 仿微信语音聊天音量大小显示控件
某日用微信语音功能聊天,发现当我使用语音功能时,会弹出一个窗口,窗口中间有一个控件会实时的显示我说话声音的大小(即分贝).当时觉得挺好玩,决定也仿制一个,效果如下 分析控件显示效果,可判断左边是一个i ...
- Android仿微信语音聊天
1.项目界面展示: 2.项目代码整体结构简析: (1)AudioRecorderButton类(录音按钮):State:STATE_NORMAL[正常状态].STATE_RECORDERING[正在录 ...
- 转-Android仿微信气泡聊天界面设计
微信的气泡聊天是仿iPhone自带短信而设计出来的,不过感觉还不错可以尝试一下仿着微信的气泡聊天做一个Demo,给大家分享一下!效果图如下: 气泡聊天最终要的是素材,要用到9.png文件的素材,这样气 ...
- Android仿微信语音聊天demo
其实我接触android时间也不是很久,但是发现android远远比我们想象的要有趣并且复杂很多,所以还是要多花点时间来写一写这些demo例子,这个程序是我从慕课网上学来的,因为毕竟要自己手写,才能体 ...
- Android 仿微信语音聊天,正式加入字节跳动
/** 恢复标志位 */ private void resetState() { isRecording = false; isReady = false; changeState(STATE_NOR ...
- Android 仿微信语音聊天,flutter项目结构
4.录音类里有两个成员:录音长度,录音路径. 下面贴一下代码: 自定义Button package com.zms.wechatrecorder.view; import com.zms.wechat ...
最新文章
- 【Linux】一步一步学Linux——pmap命令(145)
- 文本数据可视化_如何使用TextHero快速预处理和可视化文本数据
- 编辑器推荐:Visual Studio Code(VSCode/VSC)
- 大学计算机应用基础考试题库,大学计算机应用基础考试题库
- php+使用go编译,golang如何编译
- 长生不死、名人复活?疯狂的AI时代,人类竟要靠IA实现“永生”
- java并发编程LockSupport讲解
- Facebook 开源首个全卷积语音识别工具包 wav2letter++
- 计算机专业盲打,在win7系统电脑练习盲打的方法
- Jquery中$(document).ready(function(){ })函数
- 三星android+l,高配原生安卓!移动定制版三星I9008L评测
- 一个比Profiler和Netch更好用的软件代理加速工具
- jsp内置对象request
- EasyUI 中combobox利用拼音进行检索
- HttpClient Java 常用写法总结
- 网页加速器1.0.5.6 免费版
- 最稳定的工作,是你认可的那份
- 本地项目及依赖上传私服nexus
- 人体三维重建SMPL、SMPLX、SMPLifyX学习笔记
- 世界读书日丨程序员经典语录值得读一读
热门文章
- 浅谈工业互联网与产业互联网区别
- 神农班2019年总结
- 新安装Visio2013每次打开都提示正在配置,解决办法
- 苹果手机(IOS)蓝牙相关知识【配对后蓝牙设置界面i标识】【连接参数的限制】[只以地址作为识别依据]
- ROS入门(十)——两只小乌龟(乌龟跟随C++实现)
- 数学建模学习笔记——预测类型1
- 郑捷《机器学习算法原理与编程实践》学习笔记(第三章 决策树的发展)(一 )_ID3...
- 玩转树莓派二、树莓派配置工具 raspi-config 使用指南
- 无缝衔接的人会遭报应吗_怎么看待分手后无缝衔接的人?
- 视频物体检测(VID) FGFA:Flow-Guided Feature Aggregation for Video Object Detection