1.Activity对事件的分发过程
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
点击事件用MotionEvent表示,事件先传递给Activity,由它的dispatchTouchEvent进行事件分发,具体由Window来完成,Window会将事件传递给Decor view(当前界面底层容器,即setContextView所设置的View的父容器)。
先分析dispatchTouchEvent:
可以看出,事件首先交给Activity附属的Window进行分发,返回true,整个循环结束,false表示没人处理该事件。所有View的onTouchEvent都返回false,那么Activity的onTouchEvent就会被调用。
2.接下来看事件是怎么传递给Group View的
Window的实现类是PhoneWindow类,所以再看PhoneWindow如何处理事件:
/frameworks/base/core/java/android/view/Window.java
/** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */ public abstract boolean superDispatchTouchEvent(MotionEvent event);
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback { mDecor = (DecorView) preservedWindow.getDecorView(); @Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
@Override public final @NonNull View getDecorView() { if (mDecor == null || mForceDecorInstall) { installDecor(); } return mDecor; }
通过(ViewGroup)getWindow().getDecorview.findViewById(android.R.id.context)).getChildAt(0)获取Activity设置的View,mDecor为getWindow().getDecorView()返回的View。通过setContentView设置View是它的一个子View。所以事件首先会传递给DecroView,由于DecorView继承自FrameLayout,且为父View,所以事件最终会传递给View。
\frameworks\base\core\java\com\android\internal\policy\DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
进入 FrameLayout /frameworks/base/core/java/android/widget/FrameLayout.java
3.顶级View对点击事件的分发过程
分发过程:首先一个事件到顶级的View(ViewGroup),会调用ViewGroup的dispatchTouchEvent,如果拦截,ViewGroup的onInterceptTouchEvent返回true,则事件由ViewGroup处理,具体会根据事件处理的优先级,消耗事件。返回false,则事件将传递给下一级View,依次类推
3.1.ViewGroup对事件的分发
首先看ViewGroup的dispatchTouchEvent方法
// Handle an initial down. 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(); } 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; }
可见ViewGroup在ACTION_DOWN或者mFirstTouchTarget != null,其中mFirstTouchTarget != null,即,当事件由VIewGroup子控件处理时,mFirstTouchTarget会被指向子元素。
当然还有一种特殊情况:通过requestDisallowInterceptTouchEvent来设置的FLAG_DISALLOW_INTERCEPT标记位,它设置后ViewGroup无法拦截除了ACTIOND_DOWN以外的点击事件。为什么是ACTION_DOWN以外?是因为ViewGroup接收到ACTION_DOWN时,会重置一次标记位,导致子View设置无效。ViewGroup对待ACTION_DOWN事件,会调用自己的onInterceptTouchEvent方法来拦截
上面代码可以看出,当ViewGroup决定拦截事件后,后续的事件都将默认交给它处理,并且不会再调用onInterceptTouchEvent…
参考价值有两点:
第一、onInterceptTouchEvent 不是每次都会调用,如果我们需要在前一节点处理事务,要选择dispatchTouchEvent方法,但必须续保事件可以传到ViewGroup中。
第二、FLAG_DISALLOW_INTERCEPT标记位,可以帮我们解决滑动冲突的问题。
3.2ViewGroup对事件不拦截,事件如何向下分发
final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex); final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { 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); 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(); }
上述代码可见,首先遍历ViewGroup的每个子View,判断子view是否得到了点击事件。判断标准:1.点击事件的坐标是否落到了子元素的区域内;2.子元素是否在播动画如果满足这两个条件,则交由子元素处理。而dispatchTransformedTouchEvent实际调用了子元素的dispatchTouchEvent,这样就把事件传递给子View了,也将完成了一次循环。