Android imitation recording function and custom control design skills
Welcome to my Android Development Group [257053751]
Recently, as a recording function is required (/James quietly revealed that don't tell Sweet Potato, that is, the new version of the OSC Client Oh), at first we plan to adopt the imitation recording method, finally, the QQ recording method was changed, and the previous recording control was also white-coded [crying]. Many of my friends have asked me how to learn custom controls. I just want to talk about it. If you don't have time to cut it off, let's just make up your own voice.
The so-called custom control is because the system SDK cannot complete the required functions, you can complete the required functions by extending the system components.
Android custom controls can be implemented in two ways. One is to inherit the View class, And all interfaces are created by canvas and paint brush. These controls are generally used in game development; another method is to inherit an existing control, or to include a system control using the inclusion relationship. This is also the method described in this article.
First look at the code (limited space, retain only important methods)
/*** Specifies a special Button for recording. A custom recording dashboard is displayed. Need to work with {@ link # RecordButtonUtil} using * @ author kymjs (kymjs123@gmail.com) */public class RecordButton extends Button {private static final int MIN_INTERVAL_TIME = 700; // recording shortest time private static final int MAX_INTERVAL_TIME = 60000; // recording shortest time private RecordButtonUtil mAudioUtil; private Handler mVolumeHandler; // public RecordButton (Context context) {super (context); mVolumeHandler = new Show VolumeHandler (this); mAudioUtil = new RecordButtonUtil (); initSavePath () ;}@ Override public 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: // do some UI Prompt break;} return true;}/** initialize dialog and recorder */private void initlization () {mStartTime = System. currentTimeMillis (); if (mRecordDialog = null) {mRecordDialog = new Dialog (getContext (); mRecordDialog. setOnDismissListener (onDismiss);} mrecorddiener. show (); startRecording ();}/** recording completed (maximum time reached or the user decides to complete the recording) */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);} // manually cancel the recording private void cancelRecord () {stopRecording (); mRecordDialog. dismiss (); File file = new File (mAudioFile); file. delete (); if (mFinishedListerer! = Null) {mFinishedListerer. onCancleRecord () ;}}// start recording private void startRecording () {mAudioUtil. setAudioPath (mAudioFile); mAudioUtil. recordAudio (); mThread = new ObtainDecibelThread (); mThread. start () ;}// stop recording 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;} @ Override public void run () {while (running) {try {Thread. sleep (300);} catch (InterruptedException e) {e. printStackTrace ();} if (System. c UrrentTimeMillis ()-mStartTime> = MAX_INTERVAL_TIME) {// if the maximum recording time is exceeded mVolumeHandler. sendEmptyMessage (-1);} if (mAudioUtil! = Null & running) {// if the user is still recording int volumn = mAudioUtil. getVolumn (); if (volumn! = 0) mVolumeHandler. sendEmptyMessage (volumn);} else {exit () ;}}} private final OnDismissListener onDismiss = new OnDismissListener () {@ Override public void onDismiss (DialogInterface dialog) {stopRecording () ;}}; static class ShowVolumeHandler extends Handler {private final WeakReference
MOuterInstance; public ShowVolumeHandler (RecordButton outer) {mOuterInstance = new WeakReference
(Outer) ;}@ Override public void handleMessage (Message msg) {RecordButton outerButton = mOuterInstance. get (); if (msg. what! =-1) {// if (outerButton. mVolumeListener! = Null) {outerButton. mVolumeListener. onVolumeChange (mRecordDialog, msg. what) ;}} else {//-1 indicates the recording times out outerButton. finishRecord () ;}}/ ** listener for volume change */public interface OnVolumeChangeListener {void onVolumeChange (Dialog dialog, int volume );} public interface OnFinishedRecordListener {/** manually cancel */public void onCancleRecord ();/** recording completed */public void onFinishedRecord (String audioPath, int recordTime );}}
/*** {@ Link # RecordButton} required tool class ** @ author kymjs (kymjs123@gmail.com) */public class RecordButtonUtil {public static final String AUDOI_DIR = Environment. getExternalStorageDirectory (). getAbsolutePath () + "/oschina/audio"; // The root path of the audio recording file private String mAudioPath; // the path of the audio to be played private boolean mIsRecording; // whether the recording is private boolean mIsPlaying; // whether the private OnPlayListener listener is being played; // initialize the recorder pr Ivate 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;}/** start the recording and save it to the file */public void recordAudio () {initRecorder (); try {mRecorder. prepare ();} catch (IOException e) {e. printStackTrace ();} mRecorder. start ();}/** get the volume value, only for the recording volume */public int getVolumn () {int volumn = 0; // The recording if (mRecorder! = Null & mIsRecording) {volumn = mRecorder. getmaxamplding (); if (volumn! = 0) volumn = (int) (10 * Math. log (volumn)/Math. log (10)/7;} return volumn;}/** stop recording */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 () {@ Override public 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 {/** call/void stopPlay () when the playing sound ends (); /** call */void starPlay () ;}} when the playing sound starts ();}}
As the control interface control logic, we mainly look at the onTouchEvent method: When the finger is pressed, the recorder is initialized. When the finger moves on the screen, if it slides onto the button, event. getY returns a negative value (because it slides out of the control ). Here I am writing-50 mainly for a little more buffer to prevent misoperation.
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: // when the finger moves out of the view, the cancel // UI prompts break;} return true ;}
Some design skills: for example, through callback decoupling, the control becomes universal. Although custom controls do not need to be universally used, many applications, such as recording controls, still need to be more universal. The dialog that appears during the recording. I use an external method to modify the pop-up window in the future and make the code clearer. Another example is to change the recording icon according to the microphone volume. After it is set to external, even if other images are changed in the future and other display modes are changed, for the custom control itself, you do not need to change any code.
For the functions of recording and playing, use the inclusion relationship to write it in a new category separately, which facilitates further expansion in the future. For example, private audio encoding encryption will be used in the future, for example, before playing a recording, play a piece of music (who is so boring...
Let's take a look at the interaction between Thread and Handle. Here I designed not very well. In fact, we should not put the two types of messages in the same msg for sending. Here we mainly consider that the message is simple, only one int value can be used to distinguish information with an empty msg.
Handle uses a soft reference containing external classes. This method has many explanations on the internet, and I will write a blog to explain it separately, the purpose is to prevent memory leakage caused by mutual reference between objects.
The above is an explanation of the imitation recording interface. In fact, the effect of the recording is easier than that of QQ. In the future, I will talk about the implementation of QQ recording controls.