最近做一个项目,有类似于微信和按住录音,松开停止录音的效果,这些类似于录音的功能很容易做,但是由于自己的大意,那个触摸事件的触摸点的采集总是有问题,烦躁了好几天,今天总算将bug抓了出来,特以此文,祭奠那死去的bug.
一:录音功能的实现:
这个功能比较简单,直接贴出Code:
/**
* 录音功能
*/
private void recordSound() {
try {
recorder = new MediaRecorder(); // 创建记录器对象
recorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源, 麦克风
recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_WB); // 设置输出格式, 3gp
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_WB); // 设置编码
filePath = Environment.getExternalStorageDirectory() + "/"
+ System.currentTimeMillis() + ".amr";
recorder.setOutputFile(filePath); // 设置文件路径
recorder.prepare(); // 准备
Log.i(TAG, "开始录音。。。。。。。");
//TODO:检测声音的大小
recorder.start(); // 开始
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 停止录音的功能
*/
private void stopRecord() {
if (recorder != null) {
endTime = System.currentTimeMillis();
Log.i(TAG, "停止录音endTime:" + endTime);
recorder.stop();// 停止
recorder.release();// 释放
recorder = null;// 回收
}
}
二:音量动态变化的效果:
这里用到了PopupWindow及关键方法getMaxAmplitude的调用
PopupWindow的使用:
1.初始化popupWindow:
Code:
/**
* 创建popupWindow
*/
private void createPopupWindow() {
if(popupWindow != null)
return;
View view = View.inflate(getApplicationContext(), R.layout.popupwindow_record_sound, null);
popupWindow = new PopupWindow(view);
iv_rcd_hint_anim = (ImageView) view.findViewById(R.id.iv_rcd_hint_anim);
popupWindow.setWidth(DensityUtil.dip2px(RecordSoundActivity.this, 150));
popupWindow.setHeight(DensityUtil.dip2px(RecordSoundActivity.this, 200));
}
2.显示popupWindow:
Code:
/**
* 展示popupWindow
*/
private void showPopupWindow(){
if(popupWindow.isShowing())
return;
int xoff = (rl_recordSound.getWidth()-popupWindow.getWidth())/2;
int yoff = rl_recordSound.getHeight()+popupWindow.getHeight()-100;
popupWindow.showAsDropDown(rl_recordSound, xoff , -yoff);
}
3.隐藏popupWindow:
Code:
if (popupWindow.isShowing())
popupWindow.dismiss();
效果图:
由于上面的图不是美丽的美工小美女做的,是从微信里面反编译出来的,有点丑,不过不打紧,目前功能第一,以后才是界面的事。
getMaxAmplitude的调用:
1.由于音量是在不断变化中的,所以getMaxAmplitude不是只执行一次,应该在一个线程中执行,要每隔一段时间采样一次,为了简单起见,用void java.util.Timer.schedule(TimerTask task, long delay, long period)
每隔200ms采样一次,然后将采集到的数据handler出去交给UI Thread处理以便让UI界面有动态变化的效果,这里需要注意的问题是:①当界面被另外一个界面覆盖后②:停止录音时 这两个情况都需要将录音那个对象回收掉:
recorder.stop();// 停止
recorder.release();// 释放
recorder = null;// 回收
但在getMaxAmplitude是在线程中执行的,很可能就在recorder刚被回收后的那一刻,线程执行到了recorder.getMaxAmplitude();这个方法,会报空指针异常:NullPointerException,还有其他什么的乱七八糟的破玩意,所以捕获了一下异常的老子。
Code:
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
if(recorder != null){//下面要加锁!!!!
synchronized (this) {
int maxAmplitude = recorder.getMaxAmplitude();
Log.i(TAG,"当前声音---音量:" + maxAmplitude);
int index = maxAmplitude/200;
if(index>6)
index=6;
Message msg = new Message();
msg.what = GET_BG_INDEX;
msg.obj = index;
mHandler.sendMessage(msg);
}
}
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG,"没有获取到音量");
}
}
}, 0, 200);
改变主界面的handler的具体处理:
private Handler mHandler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case GET_BG_INDEX:
int index = (Integer) msg.obj;
Log.i(TAG,"index:"+index);
iv_rcd_hint_anim.setBackgroundResource(rcd_amin_bg[index]);
break;
}
}
};
三:Touch事件的处理:
这是关键的一步,老子就是在这一步被卡了几天,搞得欲仙欲死。其实一切代码逻辑都正常,关键老子为了屏幕适配加在布局文件中加了一个ScrollView,然后在自定义了一个RelativeLayout,然后写回调来处理触摸事件,结果Action_Move只能采样一小会,根本不能在你手指不规则移动时一直采样,这就令人非常蛋疼了,搞得我一直以为我Touch事件哪里出了问题,后来将ScrollView直接去掉,哗,整个世界清静了...
Code:
rl_recordSound.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
createPopupWindow();
iv_recordSound
.setBackgroundResource(R.drawable.voice_after);
showPopupWindow();
recordSound();
break;
case MotionEvent.ACTION_MOVE:
float getX = event.getX();
float getY = event.getY();
int left = rl_recordSound.getLeft();
int right = rl_recordSound.getRight();
int top = rl_recordSound.getTop();
int bottom = rl_recordSound.getBottom();
float r=(right-left)/2;//圆半径
float ox=r+left;//圆心x坐标
float oy=r+top;//圆心y坐标
Double L=Math.pow(Math.abs((getX-150)), 2)+Math.pow(Math.abs((getY -150)), 2);
L = Math.sqrt(L);
if(L>r){
if (popupWindow.isShowing())
popupWindow.dismiss();
iv_recordSound.setBackgroundResource(R.drawable.voice_before);
stopRecord();
toNextActivity();
break;
}
break;
case MotionEvent.ACTION_UP:
iv_recordSound
.setBackgroundResource(R.drawable.voice_before);
if (popupWindow.isShowing())
popupWindow.dismiss();
stopRecord();
toNextActivity();
break;
}
return true;
}
});
布局没其他要讲的,只要没有被ScrollView等类似的玩意嵌套住就ok了,
遗留的问题:
①:这个界面的屏幕的适配<问题不严重>
②:ScrollView与其他View的嵌套使用问题
③:ScrollView的监听机制。