这次的工作在上次的基础上完成录音发送功能。
自定义AudioRecordButton类继承自Button,用于实现聊天时的录音和发送语音及其他一些相关功能。
构造方法,设置按钮样式以及提示用户可以按住按钮说话
public AudioRecordButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化按钮样式
setBackgroundResource(R.drawable.record_button_normal);
setText(getResources().getString(R.string.press_record));//按住 说话
}
初始化按钮,并设置长按按钮的事件监听器。
public void init(String audioSaveDir) {
mAudioSaveDir = audioSaveDir;
// 初始化 dialog 管理器
mDialogManager = new RecordDialogManager(getContext());
// 获取音频管理,以申请音频焦点
mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
// 初始化录音管理器
mAudioRecordManager = AudioRecordManager.getInstance(mAudioSaveDir);
mAudioRecordManager.setAudioStateListener(this);
// 设置按钮长按事件监听,只有触发长按才开始准备录音
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// 获取焦点
int focus = mAudioManager.requestAudioFocus(null,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
if (focus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED){
isReady = true;
mAudioRecordManager.prepareAudio();
}else if (focus == AudioManager.AUDIOFOCUS_REQUEST_FAILED){
if (mRecordingListener != null) {
mRecordingListener.recordError("AUDIO_FOCUS_REQUEST_FAILED");
}
}
return true;
}
});
hasInit = true;
}
子线程runnable,每间隔0.1秒获取音量大小,并记录录音时间
private Runnable mGetVoiceLevelRunnable = new Runnable() {
@Override
public void run() {
while (isRecording) {
try {
Thread.sleep(100);
mRecordTime += 100;
mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
通过Handler关联子线程,通过Handler将Message和Runnable对象发送到该Handler所关联线程的MessageQueue(消息队列)中,然后该消息队列一直在循环拿出一个Message,对其进行处理,处理完之后拿出下一个Message,继续进行处理,周而复始。
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_AUDIO_PREPARED:
// 录音管理器 prepare 成功,开始录音并显示dialog
// 启动线程记录时间并获取音量变化
isRecording = true;
mDialogManager.showDialogRecord();
// 启动线程,每隔0.1秒获取音量大小
new Thread(mGetVoiceLevelRunnable).start();
break;
case MSG_VOICE_CHANGE:
mDialogManager.updateVoiceLevel(mAudioRecordManager.getVoiceLevel(7));
break;
case MSG_DIALOG_DISMISS:
mDialogManager.dismissDialog();
break;
}
}
};
录音准备完成和录音结束时的回调函数
// 录音准备出错时回调
@Override
public void prepareError(String message) {
if (mRecordingListener != null) {
mRecordingListener.recordError(message);
}
}
// 录音准备完成后回调
@Override
public void prepareFinish(String audioFilePath) {
mAudioFilePath = audioFilePath;
mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
}
根据用户动作(移动、松开、长按)判断对应的操作(取消、发送等)
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!hasInit) {
return true;
}
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeState(STATE_RECORDING);
break;
case MotionEvent.ACTION_MOVE:
if (isRecording) {
if (isWantToCancel(x, y)) {
changeState(STATE_WANT_CANCEL);
} else {
changeState(STATE_RECORDING);
}
}
break;
case MotionEvent.ACTION_UP:
// 未触发 longClick,直接重置
if (!isReady) {
reset();
return super.onTouchEvent(event);
}
// 触发了longClick,开始初始化录音,但是为初始化完成,或者录音时间太短
if (!isRecording || mRecordTime < 0.8f) {
mDialogManager.showDialogToShort();
mAudioRecordManager.cancelAudio();
mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1000);
} else if (mCurrentState == STATE_RECORDING) {
mDialogManager.dismissDialog();
mAudioRecordManager.releaseAudio();
// 将录音文件路径和录音时长回调
if (mRecordingListener != null) {
mRecordingListener.recordFinish(mAudioFilePath, mRecordTime);
}
} else if (mCurrentState == STATE_WANT_CANCEL) {
mDialogManager.dismissDialog();
mAudioRecordManager.cancelAudio();
}
reset();
break;
}
return super.onTouchEvent(event);
}
改变录音提示状态,包括“按住 说话”、“松开 发送”、“松开手指 取消发送”
private void changeState(int state) {
if (mCurrentState != state) {
mCurrentState = state;
if (state == STATE_NORMAL) {
setText(getResources().getString(R.string.press_record));
setBackgroundResource(R.drawable.record_button_normal);
} else if (state == STATE_RECORDING) {
setText(getResources().getString(R.string.release_end));
setBackgroundResource(R.drawable.record_button_recoding);
if (isRecording) {
mDialogManager.showRecording();
}
} else if (state == STATE_WANT_CANCEL) {
setText(getResources().getString(R.string.release_cancel));
setBackgroundResource(R.drawable.record_button_recoding);
if (isRecording) {
mDialogManager.showDialogWantCancel();
}
}
}
}
判断是否要取消语音发送,通过判断触控点的x、y坐标变化来判断用户行为。
private boolean isWantToCancel(int x, int y) {
return x < 0 || x > getWidth()
|| y < -CANCEL_HEIGHT || y > getHeight() + CANCEL_HEIGHT;
}
释放资源,释放音频焦点
private void reset() {
isReady = false;
isRecording = false;
mRecordTime = 0;
changeState(STATE_NORMAL);
// 释放焦点
if (mAudioManager != null){
mAudioManager.abandonAudioFocus(null);
}
}
然后在上周的文本输入框中添加发送录音的功能
// 录音按钮初始化和录音监听
mBtnAudioRecord.init(Constant.APP_CACHE_AUDIO);
mBtnAudioRecord.setRecordingListener(new AudioRecordButton.OnRecordingListener() {
@Override
public void recordFinish(String audioFilePath, long recordTime) {
if (mLayoutListener != null) {
mLayoutListener.audioRecordFinish(audioFilePath, recordTime);
}
}
@Override
public void recordError(String message) {
if (mLayoutListener != null) {
mLayoutListener.audioRecordError(message);
}
}
});