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; @Override protected 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); } }); } }); } @Override protected void onPause() { super.onPause(); MediaPlayerManager.pause(); } @Override protected void onResume() { super.onResume(); MediaPlayerManager.resume(); } @Override protected 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; // 是否触发longClick private 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() { @Override public 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 */ @Override public 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实例化Dialog mDialog = 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类:MediaPlayer private 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_AMR mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // 设置音频编码为AMR_NB mMediaRecorder.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() { //随机生成不同的UUID return UUID.randomUUID().toString() + ".amr"; } /** * @param * @description 获取音量值 * @author ldm * @time 2016/6/25 9:49 */ public int getVoiceLevel(int maxlevel) { if (isPrepare) { try { // getMaxAmplitude返回的数值最大是32767 return 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://xiazai.jb51.net/201611/yuanma/AndroidWXchat(jb51.net).rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
相关文章
Android的Fragment的生命周期各状态和回调函数使用
这篇文章主要介绍了Android的Fragments的生命周期各状态和回调函数使用,Fragments的生命周期与Activity息息相关,需要的朋友可以参考下2016-02-02Android自定义View中Paint、Rect、Canvas介绍(一)
这篇文章主要为大家详细介绍了Android自定义View中Paint、Rect、Canvas的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2017-03-03浅析Android中getWidth()和getMeasuredWidth()的区别
这篇文章主要介绍了浅析Android中getWidth()和getMeasuredWidth()的区别 ,getMeasuredWidth()获取的是view原始的大小,getWidth()获取的是这个view最终显示的大小,具体区别介绍大家参考下本文2018-04-04
最新评论