Android仿微信語(yǔ)音聊天界面設(shè)計(jì)
有段時(shí)間沒(méi)有看視頻了,昨天晚上抽了點(diǎn)空時(shí)間,又看了下鴻洋大神的視頻教程,又抽時(shí)間寫(xiě)了個(gè)學(xué)習(xí)記錄。代碼和老師講的基本一樣,網(wǎng)上也有很多相同的博客。我只是在AndroidStudio環(huán)境下寫(xiě)的。
—-主界面代碼——
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); //更新數(shù)據(jù) mAdapter.notifyDataSetChanged(); //設(shè)置位置 mListView.setSelection(mDatas.size() - 1); } }); mAdapter = new RecoderAdapter(this, mDatas); mListView.setAdapter(mAdapter); //listView的item點(diǎn)擊事件 mListView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> arg0, View view, int position, long id) { // 聲音播放動(dòng)畫(huà) 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 { // 按鈕正常狀態(tài)(默認(rèn)狀態(tài)) private static final int STATE_NORMAL = 1; //正在錄音狀態(tài) private static final int STATE_RECORDING = 2; //錄音取消狀態(tài) private static final int STATE_CANCEL = 3; //記錄當(dāng)前狀態(tài) private int mCurrentState = STATE_NORMAL; //是否開(kāi)始錄音標(biāo)志 private boolean isRecording = false; //判斷在Button上滑動(dòng)距離,以判斷 是否取消 private static final int DISTANCE_Y_CANCEL = 50; //對(duì)話框管理工具類 private DialogManager mDialogManager; //錄音管理工具類 private AudioManager mAudioManager; //記錄錄音時(shí)間 private float mTime; // 是否觸發(fā)longClick private boolean mReady; //錄音準(zhǔn)備 private static final int MSG_AUDIO_PREPARED = 0x110; //音量發(fā)生改變 private static final int MSG_VOICE_CHANGED = 0x111; //取消提示對(duì)話框 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;//錄音時(shí)間計(jì)算 mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);//每0.1秒發(fā)送消息 } catch (InterruptedException e) { e.printStackTrace(); } } } }; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_AUDIO_PREPARED: //顯示對(duì)話框 mDialogManager.showRecordingDialog(); isRecording = true; // 開(kāi)啟一個(gè)線程計(jì)算錄音時(shí)間 new Thread(mGetVoiceLevelRunnable).start(); break; case MSG_VOICE_CHANGED: //更新聲音 mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7)); break; case MSG_DIALOG_DIMISS: //取消對(duì)話框 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); } }); // 由于這個(gè)類是button所以在構(gòu)造方法中添加監(jiān)聽(tīng)事件 setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { mReady = true; mAudioManager.prepareAudio(); return false; } }); } public AudioRecorderButton(Context context) { this(context, null); } /** * @description 錄音完成后的回調(diào) * @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狀態(tài) int action = event.getAction(); // 獲得x軸坐標(biāo) int x = (int) event.getX(); // 獲得y軸坐標(biāo) int y = (int) event.getY(); switch (action) { case MotionEvent.ACTION_DOWN://手指按下 changeState(STATE_RECORDING); break; case MotionEvent.ACTION_MOVE://手指移動(dòng) if (isRecording) { //根據(jù)x,y的坐標(biāo)判斷是否需要取消 if (wantToCancle(x, y)) { changeState(STATE_CANCEL); } else { changeState(STATE_RECORDING); } } break; case MotionEvent.ACTION_UP://手指放開(kāi) if (!mReady) { reset(); return super.onTouchEvent(event); } if (!isRecording || mTime < 0.6f) {//如果時(shí)間少于0.6s,則提示錄音過(guò)短 mDialogManager.tooShort(); mAudioManager.cancel(); // 延遲顯示對(duì)話框 mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1000); } else if (mCurrentState == STATE_RECORDING) { //如果狀態(tài)為正在錄音,則結(jié)束錄制 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); } /** * 恢復(fù)狀態(tài)及標(biāo)志位 */ private void reset() { isRecording = false; mTime = 0; mReady = false; changeState(STATE_NORMAL); } private boolean wantToCancle(int x, int y) { // 超過(guò)按鈕的寬度 if (x < 0 || x > getWidth()) { return true; } // 超過(guò)按鈕的高度 if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) { return true; } return false; } /** * @param * @description 根據(jù)狀態(tài)改變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; } } } }
—-對(duì)話框管理工具類——
/** * @description 對(duì)話框管理工具類 * @author ldm * @time 2016/6/25 11:53 * @param */ public class DialogManager { //彈出對(duì)話框 private Dialog mDialog; //錄音圖標(biāo) private ImageView mIcon; //音量顯示 圖標(biāo) private ImageView mVoice; //對(duì)話框上提示文字 private TextView mLable; //上下文對(duì)象 private Context mContext; public DialogManager(Context context) { this.mContext = context; } /** * @param * @description 顯示對(duì)話框 * @author ldm * @time 2016/6/25 9:56 */ public void showRecordingDialog() { //根據(jù)指定sytle實(shí)例化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 正在錄音狀態(tài)的對(duì)話框 * @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("手指上滑,取消發(fā)送"); } } /** * @param * @description 取消錄音狀態(tài)對(duì)話框 * @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("松開(kāi)手指,取消發(fā)送"); } } /** * @param * @description時(shí)間過(guò)短提示的對(duì)話框 * @author ldm * @time 2016/6/25 10:09 */ public void tooShort() { if (mDialog != null && mDialog.isShowing()) { //顯示狀態(tài) mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.GONE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.voice_to_short); mLable.setText("錄音時(shí)間過(guò)短"); } } /** * @param * @description * @author ldm * @time 2016/6/25 取消(關(guān)閉)對(duì)話框 */ public void dimissDialog() { if (mDialog != null && mDialog.isShowing()) { //顯示狀態(tài) mDialog.dismiss(); mDialog = null; } } // 顯示更新音量級(jí)別的對(duì)話框 public void updateVoiceLevel(int level) { if (mDialog != null && mDialog.isShowing()) { //顯示狀態(tài) mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.VISIBLE); mLable.setVisibility(View.VISIBLE); //設(shè)置圖片的id,我們放在drawable中的聲音圖片是以v+數(shù)字格式的 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:播放完成監(jiān)聽(tīng) * @description 播放聲音 * @author ldm * @time 2016/6/25 11:30 */ public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener) { if (mMediaPlayer == null) { mMediaPlayer = new MediaPlayer(); //設(shè)置一個(gè)error監(jiān)聽(tīng)器 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()) { //正在播放的時(shí)候 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: 主要是實(shí)現(xiàn)邊錄邊播(AudioRecord+AudioTrack)以及對(duì)音頻的實(shí)時(shí)處理。 // 優(yōu)點(diǎn):可以語(yǔ)音實(shí)時(shí)處理,可以實(shí)現(xiàn)各種音頻的封裝 private MediaRecorder mMediaRecorder; //錄音文件 private String mDir; //當(dāng)前錄音文件目錄 private String mCurrentFilePath; //單例模式 private static AudioManager mInstance; //是否準(zhǔn)備好 private boolean isPrepare; //私有構(gòu)造方法 private AudioManager(String dir) { mDir = dir; } //對(duì)外公布獲取實(shí)例的方法 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 錄音準(zhǔn)備工作完成回調(diào)接口 * @time 2016/6/25 11:14 */ public interface AudioStateListener { void wellPrepared(); } public AudioStateListener mAudioStateListener; /** * @param * @description 供外部類調(diào)用的設(shè)置回調(diào)方法 * @author ldm * @time 2016/6/25 11:14 */ public void setOnAudioStateListener(AudioStateListener listener) { mAudioStateListener = listener; } /** * @param * @description 錄音準(zhǔn)備工作 * @author ldm * @time 2016/6/25 11:15 */ public void prepareAudio() { try { isPrepare = false; File dir = new File(mDir); if (!dir.exists()) { dir.mkdirs();//文件不存在,則創(chuàng)建文件 } String fileName = generateFileName(); File file = new File(dir, fileName); mCurrentFilePath = file.getAbsolutePath(); mMediaRecorder = new MediaRecorder(); // 設(shè)置輸出文件路徑 mMediaRecorder.setOutputFile(file.getAbsolutePath()); // 設(shè)置MediaRecorder的音頻源為麥克風(fēng) mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設(shè)置音頻格式為RAW_AMR mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // 設(shè)置音頻編碼為AMR_NB mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 準(zhǔn)備錄音 mMediaRecorder.prepare(); // 開(kāi)始,必需在prepare()后調(diào)用 mMediaRecorder.start(); // 準(zhǔn)備完成 isPrepare = true; if (mAudioStateListener != null) { mAudioStateListener.wellPrepared(); } } catch (Exception e) { e.printStackTrace(); } } /** * @param * @description 隨機(jī)生成錄音文件名稱 * @author ldm * @time 2016/6/25 、 */ private String generateFileName() { //隨機(jī)生成不同的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返回的數(shù)值最大是32767 return maxlevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;//返回結(jié)果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) { //取消錄音后刪除對(duì)應(yīng)文件 File file = new File(mCurrentFilePath); file.delete(); mCurrentFilePath = null; } } /** * @param * @description 獲取當(dāng)前文件路徑 * @author ldm * @time 2016/6/25 9:51 */ public String getCurrentFilePath() { return mCurrentFilePath; } }
代碼中有注釋,就不貼圖了,和微信語(yǔ)音聊天界面一樣的,所以叫仿微信嘛,呵呵。運(yùn)行了也可以看到效果。所有代碼可以從這里下載:http://xiazai.jb51.net/201611/yuanma/AndroidWXchat(jb51.net).rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android解析服務(wù)器端發(fā)來(lái)的xml數(shù)據(jù)示例
Android跟服務(wù)器交互數(shù)據(jù),有時(shí)數(shù)據(jù)量大時(shí),就需要以xml形式的交互數(shù)據(jù),下面與大家分享下使用XmlPullParser來(lái)解析xml數(shù)據(jù),感興趣的朋友可以參考下哈2013-06-06詳解Android Scroller與computeScroll的調(diào)用機(jī)制關(guān)系
這篇文章主要介紹了詳解Android Scroller與computeScroll的調(diào)用機(jī)制關(guān)系的相關(guān)資料,需要的朋友可以參考下2016-01-01Android ExpandableListView長(zhǎng)按事件的完美解決辦法
本篇文章是對(duì)Android中ExpandableListView長(zhǎng)按事件的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06Android使用http協(xié)議與服務(wù)器通信的實(shí)例
本篇文章主要介紹了Android使用http協(xié)議與服務(wù)器通信,Android與服務(wù)器通信通常采用HTTP通信方式和Socket通信方式,而HTTP通信方式又分get和post兩種方式。感興趣的小伙伴們可以參考一下。2016-12-12Android實(shí)現(xiàn)底部切換標(biāo)簽
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)底部切換標(biāo)簽,嵌套Fragment,方便自定義布局,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Android實(shí)現(xiàn)透明度可變的標(biāo)題欄效果
這篇文章主要介紹了Android實(shí)現(xiàn)透明度可變的標(biāo)題欄效果的相關(guān)資料,具有一定的參考價(jià)值,需要的朋友可以參考下2016-02-02Android getViewById和getLayoutInflater().inflate()的詳解及比較
這篇文章主要介紹了Android getViewById和getLayoutInflater().inflate()的詳解及比較的相關(guān)資料,這里對(duì)這兩種方法進(jìn)行了詳細(xì)的對(duì)比,對(duì)于開(kāi)始學(xué)習(xí)Android的朋友使用這兩種方法是個(gè)很好的資料,需要的朋友可以參考下2016-11-11Android實(shí)現(xiàn)可拖拽帶有坐標(biāo)尺進(jìn)度條的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Android實(shí)現(xiàn)可拖拽帶有坐標(biāo)尺進(jìn)度條的效果,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-06-06