Android源码解读——事件分发原理
首先我们需要了解,当屏幕被点击之后,事件会经理哪些流程?
那么ViewGroup的dispatchTouchEvent与View的dispatchTouchEvent有什么区别呢?
- ViewGroup---》dispatchTouchEvent---》事件分发
- View ---》dispatchTouchEvent---》事件处理
- ViewGroup继承自View,重写了View的dispatchTouchEvent方法实现事件分发功能,因为ViewGroup是容器而View不是容器,所以view里面的dispatchTouchEvent是不会进行事件分发的,只做事件处理。
有事件就会有冲突,那么什么是事件冲突呢?
通俗来说,事件只有一个,而多个人想要处理该事件,如果处理该事件的对象不是我们希望的对象,那么久可以说发生了事件冲突。
先了解view的事件处理
按照图1的时序图,我们先了解view的事件处理,之后再看事件分发的时候,事件处理我们就可以不用再管了,先看如下代码:
tv_test = findViewById(R.id.tv_test); tv_test.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("test", "onClick"); } }); tv_test.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e("test", "onTouch"); return false;//注释111 } });
在“注释111”处,如果return false;则onclick和onTouch都会打印,如果return true;那么只会打印onTouch不会打印onClick,这点我们都知道,但是是什么原因导致这种情况的发生呢?
进入到源码,看view的dispatchTouchEvent是如何处理的,我们会发现在dispatchTouchEvent方法里面有这样一段代码
if (onFilterTouchEventForSecurity(event)) {
//if 1 if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo;
// if 2 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //if 3 if (!result && onTouchEvent(event)) { result = true; } }
可以看到,if2里面执行了li.mOnTouchListener.onTouch,这个onTouch就是执行了我们代码里面onTouchListener里面的onTouch方法
在这个if判断里面用的是三个&&判断,我们都知道&&判断是有短路的,当前面的某个条件等于false,后面的判断就不会执行了,所以看onTouch是否执行首先要看前面的三个条件是否都返回true
在view的setOnTouchListener方法里面执行了这样一行代码
public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }
我们的tv_text在.setOnTouchListener的时候就会直接进入view的这个方法,该方法里面的getListenerInfo()方法会直接初始化mListenerInfo
因此li != null和li.mOnTouchListener != null返回的都是true
第三个条件(mViewFlags & ENABLED_MASK) == ENABLED意思是使能,那么我们点击按钮的时候使能肯定是为true的
因此正常情况下,只要我们控件实现了setOnTouchListener监听,那么onTouch是必然会执行的。
终上所述可得,当我们onTouch返回为true的时候,源码里面的result=true,当我们onTouch返回为false的时候,源码里面的result=false,所以我们的onTouch返回值直接会影响到result。
当我们代码里面的onTouch返回为false的时候,我们紧跟着看if3的判断。
if (!result && onTouchEvent(event)) { result = true; }
这也是一个短路语,如果result为false则会执行onTouchEvent(event)条件。进入该方法,可以看到源码里面有对若干action的switch判断
我们都知道onClick执行是在ACTION_UP的时候,那么在这个case里面有一段非常关键的代码
if (!post(mPerformClick)) { performClickInternal(); }
我们进入performClickInternal()方法
private boolean performClickInternal() { // Must notify autofill manager before performing the click actions to avoid scenarios where // the app has a click listener that changes the state of views the autofill service might // be interested on. notifyAutofillManagerOnClick(); return performClick(); }
再进入它返回的performClick()方法
public boolean performClick() { // We still need to call this method to handle the cases where performClick() was called // externally, instead of through performClickInternal() notifyAutofillManagerOnClick(); final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }
里面有一个if判断,这个if判断是否感觉非常熟悉呢?这个if在我们onTouch的时候就已经得知li != null和li.mOnTouchListener != null返回的都是true,因此会执行if里面的语句
在if里面执行完onClick之后,result会直接返回true,这也是为什么onClick事件没有返回值而onTouch事件有返回值的原因。在我们执行完onClick之后,系统会默认直接返回true,表示该事件已经处理完毕。
playSoundEffect(SoundEffectConstants.CLICK);表示我们点击的时候所发出的声音,感兴趣的可以去看一下。
至此,我们的view事件处理流程就分析完毕了。也知道了为什么onTouch返回true时会影响到onClick。
源码解读ViewGroup的事件分发流程
对照上图来说能够更加直观的了解,下面的部分我们全部都是站在“总经理”的角度来分析,也就是DecorView的角度,贴上ViewGroup的dispatchTouchEvent方法代码,一步一步分析
1 @Override 2 public boolean dispatchTouchEvent(MotionEvent ev) { 3 if (mInputEventConsistencyVerifier != null) { 4 mInputEventConsistencyVerifier.onTouchEvent(ev, 1); 5 } 6 7 // If the event targets the accessibility focused view and this is it, start 8 // normal event dispatch. Maybe a descendant is what will handle the click. 9 if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { 10 ev.setTargetAccessibilityFocus(false); 11 } 12 13 boolean handled = false; 14 if (onFilterTouchEventForSecurity(ev)) { 15 final int action = ev.getAction(); 16 final int actionMasked = action & MotionEvent.ACTION_MASK; 17 18 // Handle an initial down. 19 if (actionMasked == MotionEvent.ACTION_DOWN) { 20 // Throw away all previous state when starting a new touch gesture. 21 // The framework may have dropped the up or cancel event for the previous gesture 22 // due to an app switch, ANR, or some other state change. 23 cancelAndClearTouchTargets(ev); 24 resetTouchState(); 25 } 26 27 // Check for interception. 28 final boolean intercepted; 29 if (actionMasked == MotionEvent.ACTION_DOWN 30 || mFirstTouchTarget != null) { 31 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 32 if (!disallowIntercept) { 33 intercepted = onInterceptTouchEvent(ev); 34 ev.setAction(action); // restore action in case it was changed 35 } else { 36 intercepted = false; 37 } 38 } else { 39 // There are no touch targets and this action is not an initial down 40 // so this view group continues to intercept touches. 41 intercepted = true; 42 } 43 44 // If intercepted, start normal event dispatch. Also if there is already 45 // a view that is handling the gesture, do normal event dispatch. 46 if (intercepted || mFirstTouchTarget != null) { 47 ev.setTargetAccessibilityFocus(false); 48 } 49 50 // Check for cancelation. 51 final boolean canceled = resetCancelNextUpFlag(this) 52 || actionMasked == MotionEvent.ACTION_CANCEL; 53 54 // Update list of touch targets for pointer down, if needed. 55 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 56 TouchTarget newTouchTarget = null; 57 boolean alreadyDispatchedToNewTouchTarget = false; 58 if (!canceled && !intercepted) { 59 60 // If the event is targeting accessibility focus we give it to the 61 // view that has accessibility focus and if it does not handle it 62 // we clear the flag and dispatch the event to all children as usual. 63 // We are looking up the accessibility focused host to avoid keeping 64 // state since these events are very rare. 65 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() 66 ? findChildWithAccessibilityFocus() : null; 67 68 if (actionMasked == MotionEvent.ACTION_DOWN 69 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 70 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 71 final int actionIndex = ev.getActionIndex(); // always 0 for down 72 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) 73 : TouchTarget.ALL_POINTER_IDS; 74 75 // Clean up earlier touch targets for this pointer id in case they 76 // have become out of sync. 77 removePointersFromTouchTargets(idBitsToAssign); 78 79 final int childrenCount = mChildrenCount; 80 if (newTouchTarget == null && childrenCount != 0) { 81 final float x = ev.getX(actionIndex); 82 final float y = ev.getY(actionIndex); 83 // Find a child that can receive the event. 84 // Scan children from front to back. 85 final ArrayList<View> preorderedList = buildTouchDispatchChildList(); 86 final boolean customOrder = preorderedList == null 87 && isChildrenDrawingOrderEnabled(); 88 final View[] children = mChildren; 89 for (int i = childrenCount - 1; i >= 0; i--) { 90 final int childIndex = getAndVerifyPreorderedIndex( 91 childrenCount, i, customOrder); 92 final View child = getAndVerifyPreorderedView( 93 preorderedList, children, childIndex); 94 95 // If there is a view that has accessibility focus we want it 96 // to get the event first and if not handled we will perform a 97 // normal dispatch. We may do a double iteration but this is 98 // safer given the timeframe. 99 if (childWithAccessibilityFocus != null) { 100 if (childWithAccessibilityFocus != child) { 101 continue; 102 } 103 childWithAccessibilityFocus = null; 104 i = childrenCount - 1; 105 } 106 107 if (!child.canReceivePointerEvents() 108 || !isTransformedTouchPointInView(x, y, child, null)) { 109 ev.setTargetAccessibilityFocus(false); 110 continue; 111 } 112 113 newTouchTarget = getTouchTarget(child); 114 if (newTouchTarget != null) { 115 // Child is already receiving touch within its bounds. 116 // Give it the new pointer in addition to the ones it is handling. 117 newTouchTarget.pointerIdBits |= idBitsToAssign; 118 break; 119 } 120 121 resetCancelNextUpFlag(child); 122 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 123 // Child wants to receive touch within its bounds. 124 mLastTouchDownTime = ev.getDownTime(); 125 if (preorderedList != null) { 126 // childIndex points into presorted list, find original index 127 for (int j = 0; j < childrenCount; j++) { 128 if (children[childIndex] == mChildren[j]) { 129 mLastTouchDownIndex = j; 130 break; 131 } 132 } 133 } else { 134 mLastTouchDownIndex = childIndex; 135 } 136 mLastTouchDownX = ev.getX(); 137 mLastTouchDownY = ev.getY(); 138 newTouchTarget = addTouchTarget(child, idBitsToAssign); 139 alreadyDispatchedToNewTouchTarget = true; 140 break; 141 } 142 143 // The accessibility focus didn't handle the event, so clear 144 // the flag and do a normal dispatch to all children. 145 ev.setTargetAccessibilityFocus(false); 146 } 147 if (preorderedList != null) preorderedList.clear(); 148 } 149 150 if (newTouchTarget == null && mFirstTouchTarget != null) { 151 // Did not find a child to receive the event. 152 // Assign the pointer to the least recently added target. 153 newTouchTarget = mFirstTouchTarget; 154 while (newTouchTarget.next != null) { 155 newTouchTarget = newTouchTarget.next; 156 } 157 newTouchTarget.pointerIdBits |= idBitsToAssign; 158 } 159 } 160 } 161 162 // Dispatch to touch targets. 163 if (mFirstTouchTarget == null) { 164 // No touch targets so treat this as an ordinary view. 165 handled = dispatchTransformedTouchEvent(ev, canceled, null, 166 TouchTarget.ALL_POINTER_IDS); 167 } else { 168 // Dispatch to touch targets, excluding the new touch target if we already 169 // dispatched to it. Cancel touch targets if necessary. 170 TouchTarget predecessor = null; 171 TouchTarget target = mFirstTouchTarget; 172 while (target != null) { 173 final TouchTarget next = target.next; 174 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 175 handled = true; 176 } else { 177 final boolean cancelChild = resetCancelNextUpFlag(target.child) 178 || intercepted; 179 if (dispatchTransformedTouchEvent(ev, cancelChild, 180 target.child, target.pointerIdBits)) { 181 handled = true; 182 } 183 if (cancelChild) { 184 if (predecessor == null) { 185 mFirstTouchTarget = next; 186 } else { 187 predecessor.next = next; 188 } 189 target.recycle(); 190 target = next; 191 continue; 192 } 193 } 194 predecessor = target; 195 target = next; 196 } 197 } 198 199 // Update list of touch targets for pointer up or cancel, if needed. 200 if (canceled 201 || actionMasked == MotionEvent.ACTION_UP 202 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 203 resetTouchState(); 204 } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { 205 final int actionIndex = ev.getActionIndex(); 206 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); 207 removePointersFromTouchTargets(idBitsToRemove); 208 } 209 } 210 211 if (!handled && mInputEventConsistencyVerifier != null) { 212 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); 213 } 214 return handled; 215 }
前两个if不用管,首先正常流程我们会进入第14行代码的if语句块,我们只按照正常流程来分析,其他异常流程不管。
接下来到19行代码,如果是down事件,那么会执行cancelAndClearTouchTargets(ev);、resetTouchState();这是把之前的状态清零
if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }
接下来到29行代码,如果为down事件或者mFirstTouchTarget != null那么就会进入if,这是或判断,显然我们是down事件,那么我们进入if,整个if就是用来判断事件是否被拦截。
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; }
这个if里面首先会对disallowIntercept进行赋值,这个值初始为false,所以在为down事件的时候,32行if语句是会进入。也就是进入
if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed }
onInterceptTouchEvent(ev)为true就表示拦截,为false表示不拦截
先分析拦截
我们以重写的viewpager为例
onInterceptTouchEvent(ev)==true 则 intercepted==true,此时上面的代码58行的 if (!canceled && !intercepted) 就进不去了
直接跑到163行代码,这里的if和else表示分发或者拦截(拦截:相当于最后一个,事件到底处不处理)
那么此时,到底是进行分发还是直接处理事件呢?
我们知道刚开始的时候 mFirstTouchTarget 肯定是为空的,他的初始化和赋值,都是后面才会执行的,因此进入该if语句。执行代码
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
也就是如果拦截了,就会执行上面的代码。
注意,dispatchTransformedTouchEvent这个方法的第三个参数为null,进入dispatchTransformedTouchEvent方法看下,这个方法非常关键,这个方法意思就是:是否处理
1 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 2 View child, int desiredPointerIdBits) { 3 final boolean handled; 4 5 // Canceling motions is a special case. We don't need to perform any transformations 6 // or filtering. The important part is the action, not the contents. 7 final int oldAction = event.getAction(); 8 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { 9 event.setAction(MotionEvent.ACTION_CANCEL); 10 if (child == null) { 11 handled = super.dispatchTouchEvent(event); 12 } else { 13 handled = child.dispatchTouchEvent(event); 14 } 15 event.setAction(oldAction); 16 return handled; 17 } 18 19 // Calculate the number of pointers to deliver. 20 final int oldPointerIdBits = event.getPointerIdBits(); 21 final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; 22 23 // If for some reason we ended up in an inconsistent state where it looks like we 24 // might produce a motion event with no pointers in it, then drop the event. 25 if (newPointerIdBits == 0) { 26 return false; 27 } 28 29 // If the number of pointers is the same and we don't need to perform any fancy 30 // irreversible transformations, then we can reuse the motion event for this 31 // dispatch as long as we are careful to revert any changes we make. 32 // Otherwise we need to make a copy. 33 final MotionEvent transformedEvent; 34 if (newPointerIdBits == oldPointerIdBits) { 35 if (child == null || child.hasIdentityMatrix()) { 36 if (child == null) { 37 handled = super.dispatchTouchEvent(event); 38 } else { 39 final float offsetX = mScrollX - child.mLeft; 40 final float offsetY = mScrollY - child.mTop; 41 event.offsetLocation(offsetX, offsetY); 42 43 handled = child.dispatchTouchEvent(event); 44 45 event.offsetLocation(-offsetX, -offsetY); 46 } 47 return handled; 48 } 49 transformedEvent = MotionEvent.obtain(event); 50 } else { 51 transformedEvent = event.split(newPointerIdBits); 52 } 53 54 // Perform any necessary transformations and dispatch. 55 if (child == null) { 56 handled = super.dispatchTouchEvent(transformedEvent); 57 } else { 58 final float offsetX = mScrollX - child.mLeft; 59 final float offsetY = mScrollY - child.mTop; 60 transformedEvent.offsetLocation(offsetX, offsetY); 61 if (! child.hasIdentityMatrix()) { 62 transformedEvent.transform(child.getInverseMatrix()); 63 } 64 65 handled = child.dispatchTouchEvent(transformedEvent); 66 } 67 68 // Done. 69 transformedEvent.recycle(); 70 return handled; 71 }
当进入dispatchTransformedTouchEvent方法,第三个参数是View child,而我们传进来的是null,这表示该方法并不是给他的child处理,而是给自己处理,因为自己已经拦截了。
因为child==null ,那么到55行代码进入if 执行 handled = super.dispatchTouchEvent(transformedEvent);这里的super指向的就是view,因为viewgroup继承自view,所以执行view的dispatchTouchEvent方法,就到了前面分析的view的事件处理了。
整个onInterceptTouchEvent(ev)为true的拦截分析到此结束。
******注意:我们前面分析的view的dispatchTouchEvent是有返回值result的,该返回值实际上就是返回给ViewGroup的handled的,也就是在执行代码handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);时对handled进行的赋值。
那么这个handled的值为false或者true对接下来代码有什么影响呢?
接下来我们分析不拦截(分发)
onInterceptTouchEvent(ev)为false则 intercepted == false
那么就会进入viewgroup的dispatchTouchEvent方法的58行代码 if语句块
1 if (!canceled && !intercepted) { 2 3 // If the event is targeting accessibility focus we give it to the 4 // view that has accessibility focus and if it does not handle it 5 // we clear the flag and dispatch the event to all children as usual. 6 // We are looking up the accessibility focused host to avoid keeping 7 // state since these events are very rare. 8 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() 9 ? findChildWithAccessibilityFocus() : null; 10 11 if (actionMasked == MotionEvent.ACTION_DOWN 12 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 13 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 14 final int actionIndex = ev.getActionIndex(); // always 0 for down 15 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) 16 : TouchTarget.ALL_POINTER_IDS; 17 18 // Clean up earlier touch targets for this pointer id in case they 19 // have become out of sync. 20 removePointersFromTouchTargets(idBitsToAssign); 21 22 final int childrenCount = mChildrenCount; 23 if (newTouchTarget == null && childrenCount != 0) { 24 final float x = ev.getX(actionIndex); 25 final float y = ev.getY(actionIndex); 26 // Find a child that can receive the event. 27 // Scan children from front to back. 28 final ArrayList<View> preorderedList = buildTouchDispatchChildList(); 29 final boolean customOrder = preorderedList == null 30 && isChildrenDrawingOrderEnabled(); 31 final View[] children = mChildren; 32 for (int i = childrenCount - 1; i >= 0; i--) { 33 final int childIndex = getAndVerifyPreorderedIndex( 34 childrenCount, i, customOrder); 35 final View child = getAndVerifyPreorderedView( 36 preorderedList, children, childIndex); 37 38 // If there is a view that has accessibility focus we want it 39 // to get the event first and if not handled we will perform a 40 // normal dispatch. We may do a double iteration but this is 41 // safer given the timeframe. 42 if (childWithAccessibilityFocus != null) { 43 if (childWithAccessibilityFocus != child) { 44 continue; 45 } 46 childWithAccessibilityFocus = null; 47 i = childrenCount - 1; 48 } 49 50 if (!child.canReceivePointerEvents() 51 || !isTransformedTouchPointInView(x, y, child, null)) { 52 ev.setTargetAccessibilityFocus(false); 53 continue; 54 } 55 56 newTouchTarget = getTouchTarget(child); 57 if (newTouchTarget != null) { 58 // Child is already receiving touch within its bounds. 59 // Give it the new pointer in addition to the ones it is handling. 60 newTouchTarget.pointerIdBits |= idBitsToAssign; 61 break; 62 } 63 64 resetCancelNextUpFlag(child); 65 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 66 // Child wants to receive touch within its bounds. 67 mLastTouchDownTime = ev.getDownTime(); 68 if (preorderedList != null) { 69 // childIndex points into presorted list, find original index 70 for (int j = 0; j < childrenCount; j++) { 71 if (children[childIndex] == mChildren[j]) { 72 mLastTouchDownIndex = j; 73 break; 74 } 75 } 76 } else { 77 mLastTouchDownIndex = childIndex; 78 } 79 mLastTouchDownX = ev.getX(); 80 mLastTouchDownY = ev.getY(); 81 newTouchTarget = addTouchTarget(child, idBitsToAssign); 82 alreadyDispatchedToNewTouchTarget = true; 83 break; 84 } 85 86 // The accessibility focus didn't handle the event, so clear 87 // the flag and do a normal dispatch to all children. 88 ev.setTargetAccessibilityFocus(false); 89 } 90 if (preorderedList != null) preorderedList.clear(); 91 } 92 93 if (newTouchTarget == null && mFirstTouchTarget != null) { 94 // Did not find a child to receive the event. 95 // Assign the pointer to the least recently added target. 96 newTouchTarget = mFirstTouchTarget; 97 while (newTouchTarget.next != null) { 98 newTouchTarget = newTouchTarget.next; 99 } 100 newTouchTarget.pointerIdBits |= idBitsToAssign; 101 } 102 } 103 }
这个if语句里面,就是对事件进行分发。
首先我们进入的时候,会判断如果为down事件的时候才会分发
我们开发时记得一个结论:如果down事件没有处理权,那么move事件也处理不了。那么这个结论是针对叶节点的。也就是红色框框里面的view。
我们继续分析分发流程,这里为什么是针对叶节点的,后面会有分析。
OK,进入该代码块之后会执行到第23行代码 if (newTouchTarget == null && childrenCount != 0)
首先newTouchTarget 是等于null的,因为在if代码块之前设置了,TouchTarget newTouchTarget = null; 那么childrenCount 是什么玩意呢?
childrenCount 就表示该ViewGroup下面的子view的数量。如果这个ViewGroup下面有子view,那么就可以进行事件分发。
在分发的过程中,子view会依次放进一个数组进行排序,排序的规则是按照Z-index的方式
例如:FrameLayout里面有三个TextView,分别为text1、text2、text3,那么FrameLayout的布局会让三个textview全部重叠在页面的左上角,我们在进行将view插入数组的时候,那么顺序就是text3、text2、text1。
了解了排序规则之后,接下来进入32行的for循环代码,这个for循环进行的是倒序取出。也就是先判断text3,再判断text2,再判断text1,是这样的顺序进行判断的,这是他的一种设计模式。我们主要看for循环里面做了什么。
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
这两行代码就是将view取出来,取出child之后,那么这个child表示什么呢?
分析之前就说了,我们是站在DecorView的角度进行分析的,所以这个child的实际上就是DecorView下面的各个ViewGroup。
在拿到child之后首先会进行判断该viewgroup是否有能力处理此事件
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }
进入canReceivePointerEvents() 方法看下代码是什么样的!
protected boolean canReceivePointerEvents() { return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null; }
该方法会进行两个判断
- 判断该view是否是显示状态,这个view至少是要visible状态
- 判断该view是否是有Animation属性。(animation这个动画,当我们将view进行平移之后,平移之前的位置是没有view了的。但是点击平移之前的位置依然有用)
即首先这个view我要看得到,如果看不到,是不是因为你有动画。
接下来进入if语句的第二个条件isTransformedTouchPointInView
protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) { final float[] point = getTempPoint(); point[0] = x; point[1] = y; transformPointToViewLocal(point, child); final boolean isInView = child.pointInView(point[0], point[1]); if (isInView && outLocalPoint != null) { outLocalPoint.set(point[0], point[1]); } return isInView; }
这个方法主要就是判断我们点击的位置是否在该view的point 范围内。比如该控件在屏幕右下角,那我点击左上角,肯定是无效的,主要就是这个判断。
那么这个child如果有能力处理的话,我们会执行continue。然后接着往下执行代码:
newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; }
这段代码其实我们可以不用管的,因为getTouchTarget方法里面返回的其实就是 mFirstTouchTarget 而此时的mFirstTouchTarget 依旧为空(前面提到了为什么是空,忘记了可以上去看一下。)
因此这个if直接跳过,接下来会执行关键代码,需要记住的是下面的分析是基于dispatchTransformedTouchEvent返回的是true所执行的代码
*************关键代码**********************
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; }
*************关键代码**********************
这个if判断 dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign) 这个代码已经非常熟悉了,因为又到了前面所说到的,分发处理事件
前面我们说过如果ViewGroup对事件进行拦截,那么会执行
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
但是拦截时进入dispatchTransformedTouchEvent方法第三个参数为null,而这里第三个参数是有值的,我们再次回到dispatchTransformedTouchEvent方法代码:
1 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 2 View child, int desiredPointerIdBits) { 3 final boolean handled; 4 5 // Canceling motions is a special case. We don't need to perform any transformations 6 // or filtering. The important part is the action, not the contents. 7 final int oldAction = event.getAction(); 8 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { 9 event.setAction(MotionEvent.ACTION_CANCEL); 10 if (child == null) { 11 handled = super.dispatchTouchEvent(event); 12 } else { 13 handled = child.dispatchTouchEvent(event); 14 } 15 event.setAction(oldAction); 16 return handled; 17 } 18 19 // Calculate the number of pointers to deliver. 20 final int oldPointerIdBits = event.getPointerIdBits(); 21 final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; 22 23 // If for some reason we ended up in an inconsistent state where it looks like we 24 // might produce a motion event with no pointers in it, then drop the event. 25 if (newPointerIdBits == 0) { 26 return false; 27 } 28 29 // If the number of pointers is the same and we don't need to perform any fancy 30 // irreversible transformations, then we can reuse the motion event for this 31 // dispatch as long as we are careful to revert any changes we make. 32 // Otherwise we need to make a copy. 33 final MotionEvent transformedEvent; 34 if (newPointerIdBits == oldPointerIdBits) { 35 if (child == null || child.hasIdentityMatrix()) { 36 if (child == null) { 37 handled = super.dispatchTouchEvent(event); 38 } else { 39 final float offsetX = mScrollX - child.mLeft; 40 final float offsetY = mScrollY - child.mTop; 41 event.offsetLocation(offsetX, offsetY); 42 43 handled = child.dispatchTouchEvent(event); 44 45 event.offsetLocation(-offsetX, -offsetY); 46 } 47 return handled; 48 } 49 transformedEvent = MotionEvent.obtain(event); 50 } else { 51 transformedEvent = event.split(newPointerIdBits); 52 } 53 54 // Perform any necessary transformations and dispatch. 55 if (child == null) { 56 handled = super.dispatchTouchEvent(transformedEvent); 57 } else { 58 final float offsetX = mScrollX - child.mLeft; 59 final float offsetY = mScrollY - child.mTop; 60 transformedEvent.offsetLocation(offsetX, offsetY); 61 if (! child.hasIdentityMatrix()) { 62 transformedEvent.transform(child.getInverseMatrix()); 63 } 64 65 handled = child.dispatchTouchEvent(transformedEvent); 66 } 67 68 // Done. 69 transformedEvent.recycle(); 70 return handled; 71 }
当child不为null的时候,会执行57行代码,进入else代码块,那么在else方法里面能看到调用了child.dispatchTouchEvent方法
那么这个dispatchTouchEvent是指向谁呢?我们一直在强调,我们是站在DecorView的角度来分析的,那么此时的child就是DecorView下面的各个ViewGroup,在董事长那个图里面,这里的child指的就是市场总监、工程总监等
因此这个child是ViewGroup,所以此时的child.dispatchTouchEvent指向的就是ViewGroup的child.dispatchTouchEvent方法。所以基于上面的分析会再执行一遍。如此反复,直到事件被消费。这相当于就是一个递归了。
那么ViewGroup又会进行分发处理,那么当child.dispatchTouchEvent的child指向的是view的时候,就会直接进入view的dispatchTouchEvent进行事件处理了,而不是继续分发。
需要注意的是上面的分析是基于dispatchTransformedTouchEvent返回的是true所执行的代码,那么如果是false会怎么样呢?
当是false的时候,也就是ViewGroup不处理该事件的时候,这个取child的for循环就会继续循环下去,去询问下一个子view是否符合条件,是否处理该事件,也就是市场总监不处理该事件,那么总经理会询问工程总监是否处理
直到循环结束如果找不到执行者,就会执行for循环下面的语句,也就是直接到了下面的拦截处理的代码块。
// 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); }
所以如果child全部不处理,就相当于是DecorView拦截了。(我分发给你你不要,和我不分发直接拦截,效果是一样的)
以上分析的仅仅只是关键代码的if条件判断,那么如果child处理的话,会进入if语句,我们再次贴上关键代码
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; }
注意最后两行代码
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
我们进入addTouchTarget方法记录一些数据,待会儿会有用
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
- 1、target进行了初始化
- 2、target.next = mFirstTouchTarget = null
- 3、mFirstTouchTarget = target(mFirstTouchTarget 在这里才会进行赋值)
********注意:步骤2和步骤3按顺序执行,因此当执行target.next = mFirstTouchTarget时,mFirstTouchTarget此时还是为null,所以target.next = null;
再次结合关键代码我们整理得出以下结论,后面我们会用到。
- newTouchTarget = mFirstTouchTarget = target != null
- target.next = null
- alreadyDispatchedToNewTouchTarget = true
接下来直接break;结束我们的for循环,接下来执行下面的代码
if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; }
newTouchTarget 肯定不为空,所以我们不用管这段代码,继续往下走
// 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; } }
最开始我们判断的是进入了拦截if语句块,因为当时mFirstTouchTarget==null,而现在我们的 mFirstTouchTarget已经被赋值了,因此会走else语句块
在else语句块里面有一个while循环,上面我们得到结论
- newTouchTarget = mFirstTouchTarget = target != null
- target.next = null
- alreadyDispatchedToNewTouchTarget = true
因此while循环里面next = target.next = null
在循环末尾有一行代码:target = next; 因此我们知道,循环结束之后target == null,所以这个while循环只会执行一次(多点触控就会执行多次,本文只做单点解析)。
OK,知道了这个以后,我们看看while循环里面做了什么事!
里面有这个if判断 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)
通过结论我们得知,该if条件是满足的,因此进入if语句块执行 handled = true;
因为在之前,我们已经把这个事件交给了他的子view去处理(关键代码的if判断条件里面交给了子view去处理if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))),所以这里直接返回true表示不处理该事件了。
*************注意:我们的角色一直是DecorView,也就是总经理,因为前面交给了DecorView下面的ViewGroup处理(市场总监),所以总经理就不处理该事件了。
以上就是down事件发生时,view以及ViewGroup进行的事件处理和分发的流程,后续补充move时的解析流程。