android点击事件的分发过程
FlorrieDula
8年前
<p>本文将讲述android点击事件的分发过程,我的上一篇文章讲述了<a href="http://www.open-open.com/lib/view/open1466152189045.html">Android点击事件的来源</a>,本文接着讲述当点击事件传输到Activity之后 分发的过程是什么样的。<br> 通过上一篇文章我们知道,事件最终会通过activity分发到PhoneWindow再到DecorView最后到他的子View。</p> <p>那我们就从Activity的dispatchTouchEvent方法看起吧。<br> Activity#dispatchTouchEvent</p> <pre> <code class="language-java"> public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } //调用 phoneWindow的 superDiapatchTouchEvent if (getWindow().superDispatchTouchEvent(ev)) { return true; } // 如果phoneWindow#superDiapatchTouchEvent为false , // 则会调用Activity的 onTouchEvent return onTouchEvent(ev); }</code></pre> <p>从这段代码可以看出:activity把事件交给了 phoneWindow,向下传递。<br> 如果下层没有处理这个事件,那么activity将调用自己的onTouchEvent来处理这个事件。</p> <p>我们接看PhoneWindow的superDispatchTouchEvent<br> PhoneWindow#superDispatchTouchEvent</p> <pre> <code class="language-java">@Override public boolean superDispatchTouchEvent(MotionEvent event) { //传递给DecorView return mDecor.superDispatchTouchEvent(event); }</code></pre> <p>它将事件传递给了DecorView。<br> DecorView的superDispatchTouchEvent</p> <pre> <code class="language-java">public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } </code></pre> <p>它这里调用了super.dispatchTouchEvent(event),实际就是调用了ViewGoup的<br> dispatchTouchEvent方法。<br> ViewGroup#dispatchTouchEvent</p> <pre> <code class="language-java"> @Override public boolean dispatchTouchEvent(MotionEvent ev) { ... if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); // 1、清除 了disallowIntercept 标记 resetTouchState(); } // Check for interception. final boolean intercepted; // 2 、判断是否拦截,这个if语句actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null // 在ACTION_DOWN 或者 mFirstTouchTarget!=null的时候进入 // 通过下文分析会得出:如果不拦截事件,将事件交由子View处理的 //mFirstTouchTarget 不会被赋值,也就是mFirstTouchTarget!=null 不成立。 //那么就会有一条结论:当ViewGroup拦截事件后,那么在这个事件序列中, //将不会进入onInterceptTouchEvent(ev)判断,而是直接交由ViewGroup自身处理。 //原因是如果拦截了,下个事件不可能是ACTION_DOWN,并且mFirstTouchTarget==null ,所以上述结论成立! if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //3、这个标示可以通过requestDisallowInterceptTouchEvent //进行设置,这个标志将影响事件的拦截,即如果这个设了这个标志, //ViewGroup将不拦截事件,但这个对ACTION_DOWN无效, //原因在于ACTION_DOWN时 会清楚标志,看1号注释 if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; // 4、如果不拦截将事件往下传递 if (!canceled && !intercepted) { View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; // 5、遍历子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); if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); 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; } resetCancelNextUpFlag(child); // 6、这个方法将事件分发给子View (即调用子View的 disatchTouchEvent犯法) 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(); // 7、对mFirstTouchEvent进行赋值 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } 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; } } } //8、如果父控件没有子View 或者子View的 disPatchTouchEvent返回fasle , //即没有子View处理事件的话,将会走这个if分支 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 { // 9、如果子View正在处理事件,而此时ViewGroup的onIntercepted返回true, //此时ViewGroup就会偷取事件。 //这个分支会回收target,将重新使得mFirstTouchEvent为null, //并且子View会受到一个Cancel事件 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; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }</code></pre> <p>这个方法代码比较长,内容也比较多。我们先看第一个注释的代码:</p> <pre> <code class="language-java"> private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); //清楚标志位 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; } </code></pre> <p>看以看到如果是ACTION_DOWN事件的话,它会清楚FLAG_DISALLOW_INTERCEPT这个标志</p> <p>接着看第二个注释的地方:</p> <pre> <code class="language-java"> 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 { intercepted = true; }</code></pre> <p>这块代码,注释中已经说得差不多了。2条结论:<br> 1、如果ViewGroup决定拦截事件,那么将不会再进入这个分支判断,后续的事件将都交由它处理。<br> 2、可以使用 requestDisallowInterceptTouchEvent,使得ViewGroup不拦截事件,但为ACTION_DOWN事件无效。</p> <p>接着会遍历所有的子View 并调用dispatchTransformedTouchEvent进行事件分发,我们来看这个方法的代码:</p> <pre> <code class="language-java">private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // 处理cancle 事件 final int oldAction = event.getAction(); 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; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } //传递给子View handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); //返回结果 return handled; } </code></pre> <p>这个方法,将会进行事件分发,如果传入的child不为null,则会传递给子View,调用子View的dispatchTouchEvent。</p> <p>如果child为null 则会掉用 super.dispatchTouchEvent。当然ViewGroup的 父类是View 所以会执行view 的dispatchTouchEvent,这时就会调用ViewGroup的onTouchEvent了。</p> <p>View#dispatchTouchEvent</p> <pre> <code class="language-java"> 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)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //如果 mOnTouchListener.onTouch()返回 true,则不会调用 //onTouchEvent,这里mOnTouchListener的优先级比较高 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // 执行 onTouchEvent 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; }</code></pre> <p>这样事件就分发到子View的onTouchEvent或者 自身的onTouchEvent了。</p> <p>我们回过头,在看一下ViewGroup#dispatchTouchEvent这个方法中,注释7~9,看看它是怎么传递事件到自身 onTouchEvent的,和如果给mFirstTouchEvent赋值的。</p> <p>我们先来看注释7、addTouchTarget方法是怎么赋值的。</p> <pre> <code class="language-java"> private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }</code></pre> <p>在看第8条注释,如果mFirstTouchEvent==null 说明没有子View处理事件,这时将向上冒泡,那我们来看看它怎么处理的。</p> <pre> <code class="language-java"> // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. //调用这个方法,传入的child ==null handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);</code></pre> <p>这是将调用dispatchTransformedTouchEvent 并传入的child==null,那么将会执行<br> dispatchTransformedTouchEvent 方法中的<br> handled = super.dispatchTouchEvent(event);<br> 这个上文已经分析过了。</p> <pre> <code class="language-java"> if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }</code></pre> <p>在看第 9条注释:<br> 这个是发生在,子View可能正在处理事件(mFirstTouchEvent!=null),此时ViewGroup决定拦截事件,这是ViewGroup就会偷取子View的事件,向子View发送一个cancel事件,然后将子View从TouchTarget中移除,将导致mFirstTouchEvent重新为 null, 使得接下来的事件交由 ViewGroup自身处理<br> 相关代码:</p> <pre> <code class="language-java"> final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //传递一个 cancel事件给 子View if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } //回收target target.recycle(); target = next; continue;</code></pre> <p>那么事件的分发到这里,就基本讲完了,其中很多细节目前还不是很懂,需要以后继续学习,下面我上传流程图:<br> <img alt="这里写图片描述" src="https://simg.open-open.com/show/08823f48789e689f1743781947921504.jpg"></p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959674642526883619" rel="nofollow">http://blog.csdn.net/a992036795/article/details/51698023</a></p> <p> </p>