讲解一个比较通用的录音控件实现方法与设计技巧
最近由于需要做一个录音功能(/嘘 悄悄透露一下,千万别告诉红薯,就是新版本的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();
}
@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:
//做一些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;
}
@Override
public 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() {
@Override
public void onDismiss(DialogInterface dialog) {
stopRecording();
}
};
static class ShowVolumeHandler extends Handler {
private final WeakReference<RecordButton> mOuterInstance;
public ShowVolumeHandler(RecordButton outer) {
mOuterInstance = new WeakReference<RecordButton>(outer);
}
@Override
public 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);
}
}