前面我们介绍了android-----事件分发机制对其分发过程有了一个大致的了解,但是还有一个重要的知识没有解释,就是在android中,touch事件是在层级传递的,如果我们给一个控件注册了touch事件的话会触发一系列诸如ACTION_DOWN,ACTION_MOVE,ACTION_UP的事件,但是这些事件的执行会出现个问题就是只有当前一个事件执行结束返回true之后才会执行下一个事件,也就是说只有ACTION_DOWN执行结束返回true才会执行ACTION_MOVE,只有ACTION_MOVE执行结束返回true才会执行ACTION_UP事件,只要前一个事件执行之后返回false的话,这个事件后面的所有事件都将不再会执行;
但是如果我们有下面的例子:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.out.println("执行了Button的onClick方法");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
System.out.println("执行了Button的onTouch方法的action: "+arg1.getAction());
return false;
}
});
在我们点击button触发其上面的绑定事件的时候发现输出结果是:
04-30 05:31:31.761: I/System.out(1687): 执行了Button的onTouch方法的action: 0
04-30 05:31:31.832: I/System.out(1687): 执行了Button的onTouch方法的action: 1
04-30 05:31:31.902: I/System.out(1687): 执行了Button的onClick方法
也就是说即使我们将在onTouch事件执行结束之后返回了false还是照样会执行ACTION_DOWN和ACTION_UP,这一点不是和之前刚刚说的矛盾了吗?因为只有返回true事件才能传递下去呀,要想搞懂这个需要看看View的dispatchTouchEvent源码:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
这里onTouch返回的是false,那么一定会执行onTouchEvent方法:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true);
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
可以看到在第20行if进行判断的时候,只要当前View是可点击的话,最后一句都是会执行第122行的return true,那么也就意味着即使你在前面onTouch中将返回值设置成了false,最后在调用onTouchEvent方法的时候还是会设置为true,那也就意味着后面的ACTION_UP也会执行到的,为了更加充分的了解这一点,我们可以换一个控件,并且为他绑定touch事件,比如ImageView控件:
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
System.out.println("执行了ImageView的onTouch方法的action: "+arg1.getAction());
return false;
}
});
同样,我们将其onTouch事件的返回值设置为false,程序运行结果为:
04-30 09:14:10.845: I/System.out(1756): 执行了ImageView的onTouch方法的action: 0
这个和Button控件最大的区别在于Button控件默认是可以点击的,因此在onTouchEvent方法的第20行执行if条件判断的时候是会进入if语句块中的,所以最后会执行return true操作,进而允许执行随后的ACTION_UP操作,但是ImageView默认情况下是不能点击的,因此在执行onTouchEvent方法的第20行if语句判断的时候,结果为假,不会执行if语句块中的内容,而直接执行第125行语句返回false,这样的话就阻止了后续的ACTION_UP操作啦,因此只输出上面的内容啦!
如果这还不够明了的话,我们可以将ImageView设置成为可点击的,方法就是在配置ImageView控件的地方加入:
android:clickable="true"
然后重新运行程序,点击ImageView控件,查看输出结果为:
04-30 09:20:53.611: I/System.out(1800): 执行了ImageView的onTouch方法的action: 0
04-30 09:20:53.685: I/System.out(1800): 执行了ImageView的onTouch方法的action: 1
看到了和Button控件一样的效果,因为我们没有绑定click事件,所以没有输出任何有关click的内容
至此我们算是真正知道了touch事件只有当ACTION_DOWN返回true的时候才会执行后续的ACTION_MOVE和ACTION_UP操作,即使我们在程序中表面上看到是输出false,但是归根结底还是会返回true的,只要该控件是可点击的;