目录
2、onInterceptTouchEvent 和 requestDisallowInterceptTouchEvent
3、onTouch 、onTouchEvent、onClick
4、dispatchTouchEvent和onTouchEvent 的 DOWN、MOVE、UP事件拦截,以及onInterceptTouchEvent 的Down、MOVE、UP事件拦截 的区别。
(1)、dispatchTouchEvent和onTouchEvent对事件是否消费是在DOWN事件中来决定的。
(2)、onInterceptTouchEvent 中所有事件都可以去进行拦截,是否消费不会收到DOWN事件的消费影响。
一、官方流程图
话不多说,先来张系统触发事件分发流程图
二、过程讲解
1、执行 Activity的 dispatchTouchEvent方法,有三种返回值,super、false、true,如果返回super,执行过程2,如果false则代表交给上层 onTouchEvent,Activity为顶层,则自己本身处理,如果为 true代表自身消费。
2、执行ViewGroup的dispatchTouchEvent方法,有三种返回值,super、false、true,如果返回super,执行过程3,如果false则代表交给上层Activity onTouchEvent,如果为true代表自身消费。
3、执行ViewGroup的onInterceptTouchEvent方法,有三种返回值,super、false、true,如果返回 false和super(默认是false,所以super和false返回值一样),执行过程4,如果执行true,则代表拦截,调用自身 ViewGroup onTouchEvent。
4、执行View的dispatchTouchEvent方法,有三种返回值,super、false、true,如果返回super,执行过程5,如果返回false,则叫给上层ViewGroup onTouchEvent,如果为true代表自身消费
5、执行View的onTouchEvent方法,有三种返回值,super、false、true,返回false和super,执行过程6,如果返回true,则代表自身消费
6、执行ViewGroup的onTouchEvent方法,有三种返回值,super、false、true,返回false和super,执行过程7,如果返回true,则代表自身消费
7、执行Activity的onTouchEvent方法
总结:如果没有被消费和被拦截,默认的顺序是:从Activity->ViewGroup->View 进行分发,然后从View->ViewGroup->Activity进行处理
三、相关方法
1、dispatchTouchEvent
事件分发处理,返回true代表自我消费,默认super代表传递下一级,false代表调用上一级onTouchEvent
2、onInterceptTouchEvent 和 requestDisallowInterceptTouchEvent
onInterceptTouchEvent 事件拦截,只在ViewGroup中有,代表是否拦截这个事件,为true,则执行自身的 onTouchEvent,false或者super,不拦截则交给下层。不像onTouchEvent是否拦截取决于down事件,该方法每个事件都可以做拦截。一经拦截,后续move、up事件都直接交给onTouchEvent,不会重新询问是否拦截。
requestDisallowInterceptTouchEvent 不允许事件拦截,如果子view中来调用,调用super.requestDisallowInterceptTouchEvent ,不允许父view拦截事件
3、onTouch 、onTouchEvent、onClick
通过源码可以看到 onTouch 最先触发,然后再触发onTouchEvent,最后触发onClick。
View的dispatchTouchEvent 代码如下:
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
主要看这部分代码:
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
先判断OnTouchListener是否为空,不为空,触发mOnTouchListener.onTouch,onTouch是在事件传递到onTouchEvent之前被调用的。如果onTouch返回值为true,则不再会触发后续事件。
再来看看onClick和onTouchEvent 顺序,看下onTouchEvent源码
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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)) {
performClickInternal();
}
}
}
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();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
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();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
final float ambiguousMultiplier =
ViewConfiguration.getAmbiguousGestureMultiplier();
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* ambiguousMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= ambiguousMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
主要看MotionEvent.ACTION_UP中如下代码
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)) {
performClickInternal();
}
}
明显可以看到 onClick的触发是在手势抬起的情况,所以onClick是最后触发的
4、dispatchTouchEvent和onTouchEvent 的 DOWN、MOVE、UP事件拦截,以及onInterceptTouchEvent 的Down、MOVE、UP事件拦截 的区别。
(1)、dispatchTouchEvent和onTouchEvent对事件是否消费是在DOWN事件中来决定的。
如果某个View 中DOWN事件返回false,则该View中的move和up不会再执行,而是由这个View上面某个最终消费DOWN事件来消费move和up事件。如果某个View中Down事件为false,则该View中的dispatchTouchEvent和onTouchEvent 中move和up也会走到。
(2)、onInterceptTouchEvent 中所有事件都可以去进行拦截,是否消费不会收到DOWN事件的消费影响。
down和move都可以去执行onterceptTouchEvent拦截,如果down中执行了onterceptTouchEvent拦截为true,则后续的move和up都会直接走消费down事件view的onTouchEvent方法。如果down没去拦截,在move也可以执行onterceptTouchEvent拦截则后续走的是消费move事件view的onTouchEvent