Android仿微信語音聊天功能
本文實(shí)例講述了Android仿微信語音聊天功能代碼。分享給大家供大家參考。具體如下:
項(xiàng)目效果如下:
具體代碼如下:
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; 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; } /** * 使用接口 用于回調(diào) */ public interface AudioStateListener { void wellPrepared(); } public AudioStateListener mAudioStateListener; /** * 回調(diào)方法 */ public void setOnAudioStateListener(AudioStateListener listener) { mAudioStateListener = listener; } // 去準(zhǔn)備 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(); // 設(shè)置輸出文件 mMediaRecorder.setOutputFile(dir.getAbsolutePath()); // 設(shè)置MediaRecorder的音頻源為麥克風(fēng) mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 設(shè)置音頻格式 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // 設(shè)置音頻編碼 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 準(zhǔn)備錄音 mMediaRecorder.prepare(); // 開始 mMediaRecorder.start(); // 準(zhǔn)備結(jié)束 isPrepare = true; if (mAudioStateListener != null) { mAudioStateListener.wellPrepared(); } } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 隨機(jī)生成文件的名稱 */ private String generateFileName() { return UUID.randomUUID().toString() + ".amr"; } public int getVoiceLevel(int maxlevel) { if (isPrepare) { try { // mMediaRecorder.getMaxAmplitude() 1~32767 return maxlevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1; } catch (Exception e) { } } return 1; } /** * 釋放資源 */ public void release() { //mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder = null; } /** * 取消錄音 */ public void cancel() { release(); if (mCurrentFilePath != null) { File file = new File(mCurrentFilePath); file.delete(); mCurrentFilePath = null; } } public String getCurrentFilePath() { return mCurrentFilePath; } }
AudioRecorderButton.java
package com.xuliugen.weichat; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import com.xuliugen.weichat.R; import com.xuliugen.weichat.AudioManager.AudioStateListener; public class AudioRecorderButton extends Button { private static final int STATE_NORMAL = 1;// 默認(rèn)的狀態(tài) private static final int STATE_RECORDING = 2;// 正在錄音 private static final int STATE_WANT_TO_CANCEL = 3;// 希望取消 private int mCurrentState = STATE_NORMAL; // 當(dāng)前的狀態(tài) private boolean isRecording = false;// 已經(jīng)開始錄音 private static final int DISTANCE_Y_CANCEL = 50; private DialogManager mDialogManager; private AudioManager mAudioManager; private float mTime; // 是否觸發(fā)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; /* * 獲取音量大小的線程 */ private Runnable mGetVoiceLevelRunnable = new Runnable() { public void run() { while (isRecording) { try { Thread.sleep(100); mTime += 0.1f; mHandler.sendEmptyMessage(MSG_VOICE_CHANGED); } 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; // 開啟一個(gè)線程 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); } }; /** * 以下2個(gè)方法是構(gòu)造方法 */ public AudioRecorderButton(Context context, AttributeSet attrs) { super(context, attrs); mDialogManager = new DialogManager(context); String dir = "/storage/sdcard0/my_weixin"; //String dir = Environment.getExternalStorageDirectory()+"/my_weixin"; mAudioManager = AudioManager.getInstance(dir); mAudioManager.setOnAudioStateListener(new AudioStateListener() { public void wellPrepared() { mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED); } }); // 由于這個(gè)類是button所以在構(gòu)造方法中添加監(jiān)聽事件 setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { mReady = true; mAudioManager.prepareAudio(); return false; } }); } public AudioRecorderButton(Context context) { this(context, null); } /** * 錄音完成后的回調(diào) */ public interface AudioFinishRecorderListener { void onFinish(float seconds, String filePath); } private AudioFinishRecorderListener audioFinishRecorderListener; public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener) { audioFinishRecorderListener = listener; } /** * 屏幕的觸摸事件 */ @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); int x = (int) event.getX();// 獲得x軸坐標(biāo) int y = (int) event.getY();// 獲得y軸坐標(biāo) switch (action) { case MotionEvent.ACTION_DOWN: changeState(STATE_RECORDING); break; case MotionEvent.ACTION_MOVE: if (isRecording) { // 如果想要取消,根據(jù)x,y的坐標(biāo)看是否需要取消 if (wantToCancle(x, y)) { changeState(STATE_WANT_TO_CANCEL); } else { changeState(STATE_RECORDING); } } break; case MotionEvent.ACTION_UP: if (!mReady) { reset(); return super.onTouchEvent(event); } if (!isRecording || mTime < 0.6f) { mDialogManager.tooShort(); mAudioManager.cancel(); mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1000);// 延遲顯示對(duì)話框 } else if (mCurrentState == STATE_RECORDING) { // 正在錄音的時(shí)候,結(jié)束 mDialogManager.dimissDialog(); mAudioManager.release(); if (audioFinishRecorderListener != null) { audioFinishRecorderListener.onFinish(mTime,mAudioManager.getCurrentFilePath()); } } else if (mCurrentState == STATE_WANT_TO_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) { if (x < 0 || x > getWidth()) { // 超過按鈕的寬度 return true; } // 超過按鈕的高度 if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) { return true; } return false; } /** * 改變 */ 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_WANT_TO_CANCEL: setBackgroundResource(R.drawable.btn_recorder_recording); setText(R.string.str_recorder_want_cancel); mDialogManager.wantToCancel(); break; } } } }
DialogManager.java
package com.xuliugen.weichat; import android.app.AlertDialog; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.xuliugen.weichat.R; /** * 用于管理Dialog * * @author xuliugen * */ public class DialogManager { private AlertDialog.Builder builder; private ImageView mIcon; private ImageView mVoice; private TextView mLable; private Context mContext; private AlertDialog dialog;//用于取消AlertDialog.Builder /** * 構(gòu)造方法 傳入上下文 */ public DialogManager(Context context) { this.mContext = context; } // 顯示錄音的對(duì)話框 public void showRecordingDialog() { builder = new AlertDialog.Builder(mContext, R.style.AudioDialog); LayoutInflater inflater = LayoutInflater.from(mContext); View view = inflater.inflate(R.layout.dialog_recorder,null); 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); builder.setView(view); builder.create(); dialog = builder.show(); } public void recording(){ if(dialog != null && dialog.isShowing()){ //顯示狀態(tài) mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.VISIBLE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.recorder); mLable.setText("手指上滑,取消發(fā)送"); } } // 顯示想取消的對(duì)話框 public void wantToCancel() { if(dialog != null && dialog.isShowing()){ //顯示狀態(tài) mIcon.setVisibility(View.VISIBLE); mVoice.setVisibility(View.GONE); mLable.setVisibility(View.VISIBLE); mIcon.setImageResource(R.drawable.cancel); mLable.setText("松開手指,取消發(fā)送"); } } // 顯示時(shí)間過短的對(duì)話框 public void tooShort() { if(dialog != null && dialog.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í)間過短"); } } // 顯示取消的對(duì)話框 public void dimissDialog() { if(dialog != null && dialog.isShowing()){ //顯示狀態(tài) dialog.dismiss(); dialog = null; } } // 顯示更新音量級(jí)別的對(duì)話框 public void updateVoiceLevel(int level) { if(dialog != null && dialog.isShowing()){ //顯示狀態(tài) // mIcon.setVisibility(View.VISIBLE); // mVoice.setVisibility(View.VISIBLE); // mLable.setVisibility(View.VISIBLE); //設(shè)置圖片的id int resId = mContext.getResources().getIdentifier("v"+level, "drawable", mContext.getPackageName()); mVoice.setImageResource(resId); } } }
MainActivity.java
package com.xuliugen.weichat; import java.util.ArrayList; import java.util.List; import com.xuliugen.weichat.AudioRecorderButton.AudioFinishRecorderListener; import android.app.Activity; import android.graphics.drawable.AnimationDrawable; import android.media.MediaPlayer; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.AdapterView.OnItemClickListener; public class MainActivity extends Activity { private ListView mListView; private ArrayAdapter<Recorder> mAdapter; private List<Recorder> mDatas = new ArrayList<MainActivity.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.setAudioFinishRecorderListener(new AudioFinishRecorderListener() { public void onFinish(float seconds, String filePath) { Recorder recorder = new Recorder(seconds, filePath); mDatas.add(recorder); mAdapter.notifyDataSetChanged(); //通知更新的內(nèi)容 mListView.setSelection(mDatas.size() - 1); //將lisview設(shè)置為最后一個(gè) } }); 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)畫(幀動(dòng)畫) 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(); // 播放錄音 MediaManager.playSound(mDatas.get(position).filePath,new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer mp) { animView.setBackgroundResource(R.drawable.adj); } }); } }); } @Override protected void onPause() { super.onPause(); MediaManager.pause(); } @Override protected void onResume() { super.onResume(); MediaManager.resume(); } @Override protected void onDestroy() { super.onDestroy(); MediaManager.release(); } class Recorder { float time; String filePath; public Recorder(float time, String filePath) { super(); this.time = time; this.filePath = filePath; } public float getTime() { return time; } public void setTime(float time) { this.time = time; } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } } }
MediaManager.java
package com.xuliugen.weichat; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; public class MediaManager { private static MediaPlayer mMediaPlayer; private static boolean isPause; /** * 播放音樂 * @param filePath * @param onCompletionListener */ public static void playSound(String filePath,OnCompletionListener onCompletionListener) { if (mMediaPlayer == null) { mMediaPlayer = new MediaPlayer(); //設(shè)置一個(gè)error監(jiān)聽器 mMediaPlayer.setOnErrorListener(new OnErrorListener() { public boolean onError(MediaPlayer arg0, int arg1, int arg2) { mMediaPlayer.reset(); return false; } }); } else { mMediaPlayer.reset(); } try { mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setOnCompletionListener(onCompletionListener); mMediaPlayer.setDataSource(filePath); mMediaPlayer.prepare(); mMediaPlayer.start(); } catch (Exception e) { } } /** * 暫停播放 */ public static void pause() { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { //正在播放的時(shí)候 mMediaPlayer.pause(); isPause = true; } } /** * 當(dāng)前是isPause狀態(tài) */ public static void resume() { if (mMediaPlayer != null && isPause) { mMediaPlayer.start(); isPause = false; } } /** * 釋放資源 */ public static void release() { if (mMediaPlayer != null) { mMediaPlayer.release(); mMediaPlayer = null; } } }
RecoderAdapter.java
package com.xuliugen.weichat; import java.util.List; 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; import com.xuliugen.weichat.MainActivity.Recorder; public class RecoderAdapter extends ArrayAdapter<Recorder> { private Context mContext; private List<Recorder> mDatas; private int mMinItemWidth; //最小的item寬度 private int mMaxItemWidth; //最大的item寬度 private LayoutInflater mInflater; public RecoderAdapter(Context context, List<Recorder> datas) { super(context, -1, datas); mContext = context; mDatas = datas; //獲取屏幕的寬度 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); mMaxItemWidth = (int) (outMetrics.widthPixels * 0.7f); mMinItemWidth = (int) (outMetrics.widthPixels * 0.15f); mInflater = LayoutInflater.from(context); } /** * 定義一個(gè)ViewHolder */ private class ViewHolder { TextView seconds; // 時(shí)間 View length; // 長度 } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.item_recoder, parent,false); holder = new ViewHolder(); holder.seconds = (TextView) convertView.findViewById(R.id.id_recoder_time); holder.length = convertView.findViewById(R.id.id_recoder_lenght); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.seconds.setText(Math.round(getItem(position).time) + "\""); ViewGroup.LayoutParams lp = holder.length.getLayoutParams(); lp.width = (int) (mMinItemWidth + (mMaxItemWidth / 60f)* getItem(position).time); return convertView; } }
本文已被整理到了《Android微信開發(fā)教程匯總》,歡迎大家學(xué)習(xí)閱讀。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
- Android高仿微信聊天界面代碼分享
- android 仿微信聊天氣泡效果實(shí)現(xiàn)思路
- Android如何獲取QQ與微信的聊天記錄并保存到數(shù)據(jù)庫詳解
- 詳解Android 獲取手機(jī)中微信聊天記錄方法
- Android仿QQ、微信聊天界面長按提示框效果
- Android RichText 讓Textview輕松的支持富文本(圖像ImageSpan、點(diǎn)擊效果等等類似QQ微信聊天)
- Android破解微信獲取聊天記錄和通訊錄信息(靜態(tài)方式)
- Android仿微信語音聊天界面設(shè)計(jì)
- android仿微信聊天界面 語音錄制功能
- Android ListView仿微信聊天界面
相關(guān)文章
Navigation?Bundle實(shí)現(xiàn)兩個(gè)Fragment參數(shù)傳遞
這篇文章主要為大家介紹了Navigation?Bundle實(shí)現(xiàn)兩個(gè)Fragment參數(shù)傳遞,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Android Kotlin開發(fā)實(shí)例(Hello World!)及語法詳解
這篇文章主要介紹了Android Kotlin開發(fā)實(shí)例及語法詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05Android開發(fā)準(zhǔn)確獲取手機(jī)IP地址的兩種方式
這篇文章主要介紹了Android開發(fā)準(zhǔn)確獲取手機(jī)IP地址的兩種方式,需要的朋友可以參考下2020-03-03Android中在GridView網(wǎng)格視圖上實(shí)現(xiàn)item拖拽交換的方法
這篇文章主要介紹了Android中在GridView上實(shí)現(xiàn)item拖拽交換效果的方法,比起ListView的列表?xiàng)l目交換稍顯復(fù)雜,文中先介紹了關(guān)于創(chuàng)建GridView的一些基礎(chǔ)知識(shí),需要的朋友可以參考下2016-04-04Kotlin高階函數(shù)reduce與fold使用實(shí)例
Kotlin的高階函數(shù)reduce和fold可以用來對(duì)集合進(jìn)行聚合操作。reduce函數(shù)將集合元素逐個(gè)累加,而fold函數(shù)則可以指定一個(gè)初始值進(jìn)行累加。這兩個(gè)函數(shù)在處理大數(shù)據(jù)集時(shí)非常有用2023-04-04Android手機(jī)開發(fā)設(shè)計(jì)之記事本功能
這篇文章主要為大家詳細(xì)介紹了Android手機(jī)開發(fā)設(shè)計(jì)之記事本功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05