Android仿微信語(yǔ)音聊天界面設(shè)計(jì)
有段時(shí)間沒(méi)有看視頻了,昨天晚上抽了點(diǎn)空時(shí)間,又看了下鴻洋大神的視頻教程,又抽時(shí)間寫(xiě)了個(gè)學(xué)習(xí)記錄。代碼和老師講的基本一樣,網(wǎng)上也有很多相同的博客。我只是在A(yíng)ndroidStudio環(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ì)話(huà)框管理工具類(lèi)
private DialogManager mDialogManager;
//錄音管理工具類(lèi)
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ì)話(huà)框
private static final int MSG_DIALOG_DIMISS = 0x112;
/**
* @description 獲取音量大小的線(xiàn)程
* @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ì)話(huà)框
mDialogManager.showRecordingDialog();
isRecording = true;
// 開(kāi)啟一個(gè)線(xiàn)程計(jì)算錄音時(shí)間
new Thread(mGetVoiceLevelRunnable).start();
break;
case MSG_VOICE_CHANGED:
//更新聲音
mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));
break;
case MSG_DIALOG_DIMISS:
//取消對(duì)話(huà)框
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è)類(lèi)是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ì)話(huà)框
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ì)話(huà)框管理工具類(lèi)——
/**
* @description 對(duì)話(huà)框管理工具類(lèi)
* @author ldm
* @time 2016/6/25 11:53
* @param
*/
public class DialogManager {
//彈出對(duì)話(huà)框
private Dialog mDialog;
//錄音圖標(biāo)
private ImageView mIcon;
//音量顯示 圖標(biāo)
private ImageView mVoice;
//對(duì)話(huà)框上提示文字
private TextView mLable;
//上下文對(duì)象
private Context mContext;
public DialogManager(Context context) {
this.mContext = context;
}
/**
* @param
* @description 顯示對(duì)話(huà)框
* @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ì)話(huà)框
* @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ì)話(huà)框
* @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ì)話(huà)框
* @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ì)話(huà)框
*/
public void dimissDialog() {
if (mDialog != null && mDialog.isShowing()) { //顯示狀態(tài)
mDialog.dismiss();
mDialog = null;
}
}
// 顯示更新音量級(jí)別的對(duì)話(huà)框
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);
}
}
}
—-聲音播放工具類(lèi)——
/**
* @param
* @author ldm
* @description 播放聲音工具類(lèi)
* @time 2016/6/25 11:29
*/
public class MediaPlayerManager {
//播放音頻API類(lèi):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;
}
}
—–錄音操作工具類(lèi)—–
/**
* @param
* @author ldm
* @description 錄音管理工具類(lèi)
* @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 供外部類(lèi)調(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ī)生成錄音文件名稱(chēng)
* @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;
}
}
代碼中有注釋?zhuān)筒毁N圖了,和微信語(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-01
Android ExpandableListView長(zhǎng)按事件的完美解決辦法
本篇文章是對(duì)Android中ExpandableListView長(zhǎng)按事件的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android使用http協(xié)議與服務(wù)器通信的實(shí)例
本篇文章主要介紹了Android使用http協(xié)議與服務(wù)器通信,Android與服務(wù)器通信通常采用HTTP通信方式和Socket通信方式,而HTTP通信方式又分get和post兩種方式。感興趣的小伙伴們可以參考一下。2016-12-12
Android實(shí)現(xiàn)底部切換標(biāo)簽
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)底部切換標(biāo)簽,嵌套Fragment,方便自定義布局,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Android實(shí)現(xiàn)透明度可變的標(biāo)題欄效果
這篇文章主要介紹了Android實(shí)現(xiàn)透明度可變的標(biāo)題欄效果的相關(guān)資料,具有一定的參考價(jià)值,需要的朋友可以參考下2016-02-02
Android getViewById和getLayoutInflater().inflate()的詳解及比較
這篇文章主要介紹了Android getViewById和getLayoutInflater().inflate()的詳解及比較的相關(guān)資料,這里對(duì)這兩種方法進(jìn)行了詳細(xì)的對(duì)比,對(duì)于開(kāi)始學(xué)習(xí)Android的朋友使用這兩種方法是個(gè)很好的資料,需要的朋友可以參考下2016-11-11
Android實(shí)現(xiàn)可拖拽帶有坐標(biāo)尺進(jìn)度條的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Android實(shí)現(xiàn)可拖拽帶有坐標(biāo)尺進(jìn)度條的效果,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-06-06

