事件的分发流程
Activity
->window
->ViewGroup(decor View)
dispathTouchEvent(属于Activity)
决定是否Activity
要自己消费事件,首先将事件交由依附Activity
的Window
进行分发,如果View
消费了事件,返回true,那么分发过程结束,如果所有的View都没有消费事件,那么Avtivity
的onTouchEvent()
会被调用.
decorView 分发过程
顶级View(ViewGroup)对View的分发包括几个过程:
点击事件到达-> 调用dispatchTouchEvent()
->如果interecptTouchEvent()
返回true,消费事件->a 否则 ->b
a.如果有onTouchListener()
,则 onTouch()
会被触发,否则onTouchEvent()
会被调用,如果在onTouchEvent
中设置了onClickListener
,onCLick
会被调用.
b.事件会传递给事件链上的子View,子View接收到事件调用dispatchTouchEvent()
.
事件链
是一开始ViewGroup就会遍历所有的子View来判断哪些View可以接受到事件,主要根据 子View是否在播动画 和 点击坐标是否位于子View上.
interecptTouchEvent
并非每次都会调用,只有当 事件是ACTION_DOWN 且 mFirstTouchTarget != null(交由子View处理) 且 FLAG_DISALLOW_INTERCEPT 没被设置 时调用 onInterecptTouchEvent
.
因为ACTION_DOWN
基本上都是一个新的事件,这个时候肯定没有交由子View来处理,所以mFirstTouchTarget == null
,再加上前面就有代码对FLAG_DISALLOW_INTERCEPT
进行初始化,所以如果是ACTION_DOWN
,onInterecptTouchEvent
一定会被调用,但是decorView(Viewgroup)
默认都不会拦截事件.因此子View不能影响decorView
对ACTION_DOWN
的处理.
如果 事件是ACTION_DOWN 且 mFirstTouchTarget != null(交由当前View处理) 都不成立,即是收到ACTION_MOVE
,ACTION_UP
且已经标记不拦截事件,交由子View处理了就直接交由子View,不再拦截.
当消费事件返回true,不消费返回false.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | public boolean dispatchTouchEvent(MotionEvent ev) { ... /** 见 上一节 `interecptTouchEvent` */ // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } ... /** 分发之核心,遍历所有的子View来判断哪些View可以接受到事件,主要根据 子View是否在播动画 和 点击坐标是否位于子View上. */ for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); ... resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } /** 如果所有的View都没有消费事件,那么ViewGroup将会自己消费事件; 否则继续处理后续事件(ACTION_UP,ACTION_MOVE)的分发,还有可能出现的取消点击(在屏幕上滑动离开目标View) 不再判断,直接将点击事件传给目标View */ // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } ... } |
View对点击事件的处理
以上都是点击事件怎么从Activity->Window->decor View
但是大多数情况下,处理点击事件的是View.
具体过程已经写了注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); /** 这里是View处于不可DISABLE的状态,但是仍然会消费事件. 前提是view是CLICKABLE 或者 LONG_CLICKABLE */ if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == 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) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } /** 如果View有设置代理,会执行mTouchDelegate.onTouchEvent(event) */ if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } /** 如果View是CLICKABLE或者LONG_CLICKABLE,就消费事件. */ if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { /** 如果`ACTION_UP`,说明一次点击事件结束,`performClick()`方法被调用,它调用它内部的`click()`方法. */ case MotionEvent.ACTION_UP: ... if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } ... break; /** 如果是`ACTION_DOWN`的情况,考虑是不是滑动操作.如果是就进行滑动操作的初始化; 否则调用`setPressed()`来设置当前的按压状态->因为: 事件分发到这里就以及确定了哪个`View`消费了事件,这个时候就要把这个消息往回传,告诉高等级那些大人物们. 回传的起点自然就是当前所在的`view`的`onTouchEvent`->`dispatchSetPressed(pressed)`, 让整个view都知道事件消费情况,并且返回`result`告诉viewGroup */ //ViewGroup#dispatchTransformedTouchEvent() if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } /** 而ViewGroup#dispatchTouchEvent()收到返回值是这样的 */ //ViewGroup#dispatchTouchEvent() if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } /** 这是一个循环的一部分,这个循环遍历child(子View),用`dispatchTransformedTouchEvent()`进行分发; 如果分发未果,没有消费事件,就遍历下一个子View; 如果分发成功执行其内部操作,可以看到,已经分发成功,并获取到目标`View`. */ case MotionEvent.ACTION_DOWN: ... // 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(); } ... } else { // Not inside a scrolling container, so show the feedback right away /** * Sets the pressed state for this view and provides a touch coordinate for * animation hinting. * * @param pressed Pass true to set the View's internal state to "pressed", * or false to reverts the View's internal state from a * previously set "pressed" state. * @param x The x coordinate of the touch that caused the press * @param y The y coordinate of the touch that caused the press */ setPressed(true, x, y); checkForLongClick(0); } break; /** 撤销动作,算作没有消费事件 */ case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); ... break; /** 移动 */ case MotionEvent.ACTION_MOVE: ... break; } return true; } return false; } |
事件分发-恶魔法则11条
1.同一序列事件是指从手指接触屏幕开始,到手指离开屏幕结束,中间产生的一系列以ACTION_DOWN
开始,中间N个ACTION_MOVE
,最后以ACTION_UP
结束的一系列事件.
2.某个View一旦决定拦截事件(拦截了ACTION_DOWN
),那么剩下的ACTION_MOVE & ACTION_UP
都只能交由它处理,并且它的onInterceptTouchEvent()
不再会被调用(其实如果是子View的话,decor View的也不会再被调用).
3.一个事件只能被一个View拦截消耗,因为后续不会再有拦截,也就没机会交给其它View处理.
4.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN
事件(onTouchEvent
返回false)那么同一事件序列中的其它事件都不会再交由它处理, 会交由他们的父元素处理(Activity),既父元素的onTouchEvent
会被调用.
5.如果View不消耗ACTION_DOWN
以外的事件,这个点击事件会消失,父元素的onTouchEvent
不会被调用,最后这些消失的点击事件会传给Activity处理.
6.ViewaGroup默认不拦截任何事件.
7.View没有onInterceptTouchEvent
,事件传递给它后它的onTouchEvent
就会被调用.
8.View的onTouchEvent
默认消耗事件,除非它的CLICKABLE
和LONG_CLICKABLE
都为false.
9.View的ENABLE
不影响事件的消费,只有CLICKABLE
影响.
10.onClick
会发生的前提是View是可点击的,并且收到了一系列事件(ACTION_DOWN
->ACTION_UP
),并且设置了监听(listener).
11.事件总是先传给父元素再分发给子View,可以通过requestDissallowInterceptTouchEvent
来干预父元素除了ACTION_DOWN
事件的分发.\