生活随笔
收集整理的這篇文章主要介紹了
Android仿微信录音功能,自定义控件的设计技巧
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
????????歡迎各位加入我的Android開發群[257053751]
最近由于需要做一個錄音功能(/噓 悄悄透露一下,千萬別告訴紅薯,就是新版本的OSC客戶端噢),起初打算采用仿微信的錄音方式,最后又改成了QQ的錄音方式,之前的微信錄音控件也就白寫了[大哭]。之前有很多朋友在問我自定義控件應該怎么學習,遂正好拿出來講講嘍,沒來得及截效果圖,大家就自己腦補一下微信發語音時的樣子吧。
????所謂自定義控件其實就是由于系統SDK無法完成需要的功能時,通過自己擴展系統組件達到完成所需功能做出的控件。
????Android自定義控件有兩種實現方式,一種是通過繼承View類,其中的全部界面通過畫布和畫筆自己創建,這種控件一般多用于游戲開發中;另一種則是通過繼承已有控件,或采用包含關系包含一個系統控件達到目的,這也是接下來本文所要講到的方法。
????先看代碼(篇幅有限,僅保留重要方法)
/*** 錄音專用Button,可彈出自定義的錄音dialog。需要配合{@link #RecordButtonUtil}使用* @author kymjs(kymjs123@gmail.com)*/
public class RecordButton extends Button {private static final int MIN_INTERVAL_TIME = 700; // 錄音最短時間private static final int MAX_INTERVAL_TIME = 60000; // 錄音最長時間private RecordButtonUtil mAudioUtil;private Handler mVolumeHandler; // 用于更新錄音音量大小的圖片public RecordButton(Context context) {super(context);mVolumeHandler = new ShowVolumeHandler(this);mAudioUtil = new RecordButtonUtil();initSavePath();}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (mAudioFile == null) {return false;}switch (event.getAction()) {case MotionEvent.ACTION_DOWN:initlization();break;case MotionEvent.ACTION_UP:if (event.getY() < -50) {cancelRecord();} else {finishRecord();}break;case MotionEvent.ACTION_MOVE://做一些UI提示break;}return true;}/** 初始化 dialog和錄音器 */private void initlization() {mStartTime = System.currentTimeMillis();if (mRecordDialog == null) {mRecordDialog = new Dialog(getContext());mRecordDialog.setOnDismissListener(onDismiss);}mRecordDialog.show();startRecording();}/** 錄音完成(達到最長時間或用戶決定錄音完成) */private void finishRecord() {stopRecording();mRecordDialog.dismiss();long intervalTime = System.currentTimeMillis() - mStartTime;if (intervalTime < MIN_INTERVAL_TIME) {AppContext.showToastShort(R.string.record_sound_short);File file = new File(mAudioFile);file.delete();return;}if (mFinishedListerer != null) {mFinishedListerer.onFinishedRecord(mAudioFile,(int) ((System.currentTimeMillis() - mStartTime) / 1000));}}// 用戶手動取消錄音private void cancelRecord() {stopRecording();mRecordDialog.dismiss();File file = new File(mAudioFile);file.delete();if (mFinishedListerer != null) {mFinishedListerer.onCancleRecord();}}// 開始錄音private void startRecording() {mAudioUtil.setAudioPath(mAudioFile);mAudioUtil.recordAudio();mThread = new ObtainDecibelThread();mThread.start();}// 停止錄音private void stopRecording() {if (mThread != null) {mThread.exit();mThread = null;}if (mAudioUtil != null) {mAudioUtil.stopRecord();}}/******************************* inner class ****************************************/private class ObtainDecibelThread extends Thread {private volatile boolean running = true;public void exit() {running = false;}@Overridepublic void run() {while (running) {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}if (System.currentTimeMillis() - mStartTime >= MAX_INTERVAL_TIME) {// 如果超過最長錄音時間mVolumeHandler.sendEmptyMessage(-1);}if (mAudioUtil != null && running) {// 如果用戶仍在錄音int volumn = mAudioUtil.getVolumn();if (volumn != 0)mVolumeHandler.sendEmptyMessage(volumn);} else {exit();}}}}private final OnDismissListener onDismiss = new OnDismissListener() {@Overridepublic void onDismiss(DialogInterface dialog) {stopRecording();}};static class ShowVolumeHandler extends Handler {private final WeakReference<RecordButton> mOuterInstance;public ShowVolumeHandler(RecordButton outer) {mOuterInstance = new WeakReference<RecordButton>(outer);}@Overridepublic void handleMessage(Message msg) {RecordButton outerButton = mOuterInstance.get();if (msg.what != -1) {// 大于0時 表示當前錄音的音量if (outerButton.mVolumeListener != null) {outerButton.mVolumeListener.onVolumeChange(mRecordDialog,msg.what);}} else {// -1 時表示錄音超時outerButton.finishRecord();}}}/** 音量改變的監聽器 */public interface OnVolumeChangeListener {void onVolumeChange(Dialog dialog, int volume);}public interface OnFinishedRecordListener {/** 用戶手動取消 */public void onCancleRecord();/** 錄音完成 */public void onFinishedRecord(String audioPath, int recordTime);}
}
/*** {@link #RecordButton}需要的工具類* * @author kymjs(kymjs123@gmail.com)*/
public class RecordButtonUtil {public static final String AUDOI_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/oschina/audio"; // 錄音音頻保存根路徑private String mAudioPath; // 要播放的聲音的路徑private boolean mIsRecording;// 是否正在錄音private boolean mIsPlaying;// 是否正在播放private OnPlayListener listener;// 初始化 錄音器private void initRecorder() {mRecorder = new MediaRecorder();mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);mRecorder.setOutputFile(mAudioPath);mIsRecording = true;}/** 開始錄音,并保存到文件中 */public void recordAudio() {initRecorder();try {mRecorder.prepare();} catch (IOException e) {e.printStackTrace();}mRecorder.start();}/** 獲取音量值,只是針對錄音音量 */public int getVolumn() {int volumn = 0;// 錄音if (mRecorder != null && mIsRecording) {volumn = mRecorder.getMaxAmplitude();if (volumn != 0)volumn = (int) (10 * Math.log(volumn) / Math.log(10)) / 7;}return volumn;}/** 停止錄音 */public void stopRecord() {if (mRecorder != null) {mRecorder.stop();mRecorder.release();mRecorder = null;mIsRecording = false;}}public void startPlay(String audioPath) {if (!mIsPlaying) {if (!StringUtils.isEmpty(audioPath)) {mPlayer = new MediaPlayer();try {mPlayer.setDataSource(audioPath);mPlayer.prepare();mPlayer.start();if (listener != null) {listener.starPlay();}mIsPlaying = true;mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {if (listener != null) {listener.stopPlay();}mp.release();mPlayer = null;mIsPlaying = false;}});} catch (Exception e) {e.printStackTrace();}} else {AppContext.showToastShort(R.string.record_sound_notfound);}} // end playing}public interface OnPlayListener {/** 播放聲音結束時調用 */void stopPlay();/** 播放聲音開始時調用 */void starPlay();}
}
????作為控件界面控制邏輯,我們主要看一下onTouchEvent方法:當手指按下的時候,初始化錄音器。手指在屏幕上移動的時候如果滑到按鈕之上的時候,event.getY會返回一個負值(因為滑出控件了嘛)。這里我寫的是-50主要是為了多一點緩沖,防止誤操作。
public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:initlization();break;case MotionEvent.ACTION_UP:if (mIsCancel && event.getY() < -50) {cancelRecord();} else {finishRecord();}mIsCancel = false;break;case MotionEvent.ACTION_MOVE:// 當手指移動到view外面,會cancel//做一些UI提示break;}return true;}
????一些設計技巧:比如通過回調解耦,使控件變得通用。雖說自定義控件一般不需要多么的通用,但是像錄音控件這種很多應用都會用到的功能,還是做得通用一點要好。像錄音時彈出的dialog,我采用從外部獲取的方式,方便以后修改這個彈窗,也方便代碼閱讀的時候更加清晰。再比如根據話筒音量改變錄音圖標這樣的方法,設置成外部以后,就算以后更換其他圖片,更換其他顯示方式,對自定義控件本身來說,不需要改任何代碼。
????對于錄音和放音的功能實現,采用包含關系單獨寫在一個新類里面,這樣方便以后做更多擴展,比如未來采用私有的錄音編碼加密,比如播放錄音之前先放一段音樂(誰特么這么無聊)等等。。。
????再來看一下Thread與Handle的交互,這里我設計的并不是很好,其實不應該將兩種消息放在同一個msg中發出的,這里主要是考慮到消息簡單,使用一個空msg僅僅通過一個int值區分信息就行了。
????Handle中采用了一個軟引用包含外部類,這種方式在網上有很多講解,之后我也會單獨再寫一篇博客講解,這里大家知道目的是為了防止對象間的互相引用造成內存泄露就可以了。
????以上便是對仿微信錄音界面的一個講解,其實微信的錄音效果實現起來比起QQ的效果還是比較簡單的,以后我也會再講QQ錄音控件的實現方法。
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
轉載于:https://my.oschina.net/kjframe/blog/484884
總結
以上是生活随笔為你收集整理的Android仿微信录音功能,自定义控件的设计技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。