前言
通过源码去解答面试题,查漏补缺,增强记忆!!!
今日面试题
- 问题1:给一个button同时设置onClickListener和onLongClickListener,长按这个button,长按跟单击事件是否都能够得到响应?
- 问题2:界面上有两个button,点击一个按钮,然后滑到第二个按钮抬起,如果这两个按钮都设置了点击事件,会不会触发?
解答
-
问题1:
public boolean onTouchEvent(MotionEvent event) {
// 删除无关代码
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
// 删除无关代码
// 在这判断是否已经执行过longClick事件,
// 如果已经执行过则不再执行click事件
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 执行click事件之前先移除longClick事件
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
break;
case MotionEvent.ACTION_DOWN:
// 删除无关代码
// 在down中设置pressed状态为true,
// 并发送一个延迟500毫秒的事件去执行长按事件
setPressed(true, x, y);
checkForLongClick(0, x, y);
break;
case MotionEvent.ACTION_CANCEL:
// 删除无关代码
break;
case MotionEvent.ACTION_MOVE:
// 删除无关代码
break;
}
return true;
}
private void checkForLongClick(int delayOffset, float x, float y) {
// 删除无关代码
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
// 发送延迟500毫秒的事件去执行长按事件
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
private final class CheckForLongPress implements Runnable {
@Override
public void run() {
// 删除无关代码
// OnLongClickListener的boolean onLongClick(View v)方法
// 如果返回true表示执行了长按事件,表示执行了长按事件,
// 将变量mHasPerformedLongPress设置为true
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
上面三段代码已经将这个问题做了解答,这里再做个总结:
当点击按钮的时候,执行down事件,在down里头会延迟发送一个500毫秒的事件去执行长按方法,长按事件执行成功
mHasPerformedLongPress = true
。当手指抬起,执行up事件,在up里头会去判断变量mHasPerformedLongPress
,为false则未执行长按事件,那么就会去执行点击事件,否则不执行点击事件。
最终的解答:
button同时设置onClickListener和onLongClickListener,长按这个button,最终只能长按事件得到响应。
-
问题2:
public boolean onTouchEvent(MotionEvent event) {
// 删除无关代码
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
// 删除无用代码
// 忽略prepressed,只关注前半个判断
// pressed==true则执行点击事件
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
performClick();
}
break;
case MotionEvent.ACTION_DOWN:
// 删除无用代码
// 按下按钮,设置为pressed==true
setPressed(true, x, y);
break;
case MotionEvent.ACTION_CANCEL:
// 删除无用代码
break;
case MotionEvent.ACTION_MOVE:
// 删除无用代码
// 判断当前的坐标是否在点击的按钮内部
// 如果不在按钮内部则移除长按事件,如果pressed==true则将pressed设置为false,意味着不响应点击事件
if (!pointInView(x, y, mTouchSlop)) {
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
}
break;
}
return true;
}
public void setPressed(boolean pressed) {
// 删除无用代码
if (pressed) {
mPrivateFlags |= PFLAG_PRESSED;
} else {
mPrivateFlags &= ~PFLAG_PRESSED;
}
}
代码上已经描述得很清楚,这依然做个总结:
当点击按钮的时候,执行down事件,并将设置
pressed=true
,当手指滑动的时候,在move事件中时刻监听当然手指所在的坐标点,如果当前坐标不在按钮内部,那么就移除长按事件,并且设置pressed=false
。当手指抬起,执行up事件,判断pressed==true
是否成立,不成立则不执行点击事件。
最终的解答:
点击第一个按钮,然后滑动到第二个按钮后抬起,第一个按钮会执行一个完整的事件序列(down->move->up)
,由于这move的过程中pressed设置为false了,所以不会响应点击事件,如果在500毫秒之内移出了第一个按钮长按事件也不会响应。第二个按钮由于没有获取到down事件,同一个事件序列不会交给它处理,意味着不会执行move以及up事件,所以第二个按钮不会响应点击事件。
题外话
在以上的源码中有一段值得学习的源码,日后开发中可用:
// 通过该方法去判断当前坐标是否在View内部
// slop是滑动的最小距离,默认的是8
public boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < ((mBottom - mTop) + slop);
}