ViewGroup事件分发机制详解

事件的定义?

定义:当用户触摸屏幕时,将产生触摸行为(Touch事件

事件的类型?

四种:

1 MotionEvent.ACTION_DOWN
2 MotionEvent.ACTION_ACTION_MOVE
3 MotionEvent.ACTION_ACTION_UP
4 MotionEvent.ACTION_CANCEL

事件序列

ViewGroup事件分发机制详解_第1张图片

事件分发的主要方法?

主要有三个方法
ViewGroup事件分发机制详解_第2张图片
接下来我们从源码的角度来看看Activity中事件是怎样分发的

打开Activity的源码,找到 dispatchTouchEvent(MotionEvent ev) 这个方法,Activity的事件分发就是从这里开始的

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

我们可以看到,首先会判断 这个事件 是否的 DOWN事件,如果是,就执行onUserInteraction() 这个方法
定位到onUserInteraction() 这个方法中来

 public void onUserInteraction() {
    }

我们发现这个方法中没有任何的实现,是一个空的方法,该方法主要的作用是实现屏保功能并且当此 Activity 在栈顶的时候,触屏点击 Home、Back、Recent 键等都会触发这个方法,我们可以在自己的Activity类中重写这个方法,实现自己想要的功能

我们再来看看下面这段代码

 if (getWindow().superDispatchTouchEvent(ev)) 
 {
      return true;
 }

getWindow().superDispatchTouchEvent(ev) 这个getWindow() 拿到的是什么了? 我们定位到getWindow()方法里面来看看

    
      public Window getWindow() 
      {
        return mWindow;
      }

可以看到 调用这个方法返回的就是一个 Window 对象,那么这个window类里面又是什么了?进入window类的源码看看

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * 

The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */ public abstract class Window { /** Flag for the "options panel" feature. This is enabled by default. */ public static final int FEATURE_OPTIONS_PANEL = 0; /** Flag for the "no title" feature, turning off the title at the top * of the screen. */ public static final int FEATURE_NO_TITLE = 1; /** * Flag for the progress indicator feature. * * @deprecated No longer supported starting in API 21. */

翻译上面类的注释

/ **
  *用于顶层窗口外观和行为策略的抽象基类。 一个
  *此类的实例应用作添加到
  *窗口管理器。 它提供标准的UI策略,例如背景,标题
  *区域,默认密钥处理等
  *
  * 

此抽象类的唯一现有实现是 * android.view.PhoneWindow,您需要在需要 *窗口。 * /

我们可以知道,这是一个抽象类,用于我们视图窗口的管理,并且它只有唯一一个子类 PhoneWindow

所以

getWindow().superDispatchTouchEvent(ev)

这行代码实际上是调用了 PhoneWindow 这个子类里面 实现的 superDispatchTouchEvent(ev) 这个方法
我们定位到 PhoneWindow 里面 superDispatchTouchEvent(ev)这个方法里面来

   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

可以看到 这个方法执行的是一个 mDecor.superDispatchTouchEvent(event)

这个mDecor 是什么了?

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

可以看到 它是 DecorView 的一个对象 ,从上面的注释翻译就是说 DecorView 是我们界面的顶层视图
我们再来到 DecorView 源码看看

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    private static final String TAG = "DecorView";

    private static final boolean DEBUG_MEASURE = false;`

而FrameLayout 又是继承于 ViewGroup FrameLayout extends ViewGroup

public class FrameLayout extends ViewGroup 

mDecor这个对象我们弄明白了,它就是DecorView 的一个对象而已,所以mDecor.superDispatchTouchEvent(event) 执行的就是 DecorView里面的 superDispatchTouchEvent(event)的这个方法
我们定位到DecorView这个方法里面来

 public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

可以看到 这个方法里面再做什么了? super.dispatchTouchEvent(event) 在执行这一行代码,super 这个关键字
这明显是在调用父类的方法 那么根据我们上面的分析得知,DecorView 的父类 是 FrameLayout
我们打开 FrameLayout源码,并没有发现 dispatchTouchEvent这个方法,但是我们知道 FrameLayout它的父类是ViewGroup,根据继承的特性,这个方法必然在 ViewGroup里面

所以 super.dispatchTouchEvent(event)这个方法调用的是 ViewGroup 里面的 dispatchTouchEvent(event)

打开ViewGroup 源码。可以看到这个方法了

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

那么现在我们来总结一下 Activity 事件分发的一个走向

Activity--------》PhoneWindow--------》DecorView--------ViewGroup

也就是说,当我们用手指触摸手机屏幕,这当中的一系列事件传递到了ViewGroup中,在ViewGroup里进行事件的分发工作

我们知道的事件传递到了ViewGroup中的dispatchTouchEvent(MotionEvent ev) 这个方法里面,我们接下来分析一下这个方法里面做了些什么事情?

 // 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();
            }

首先判断是否是 MotionEvent.ACTION_DOWN)事件,如果是

cancelAndClearTouchTargets(ev);//取消并清除所有触摸目标。
resetTouchState()      		   //重置所有触摸状态,为新的周期做准备。

接下来我们分析这段代码

           // Check for interception.
            final boolean intercepted;//定义一个bolean类型的变量,用于标记事件是否拦截
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//进行为运算
                if (!disallowIntercept) {//如果为运算出来的结果不为true的画
                    intercepted = onInterceptTouchEvent(ev);//进行是否拦截的判定计算
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;//当上面条件不满足时,赋值false 表示不拦截此次事件
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                //没有触摸目标并且action 不是最初的down事件,就继续拦截
                intercepted = true;
            }

final boolean intercepted;//定义一个boolean 类型的变量,用于标记事件是否拦截
当满足了 actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)这两个条件后
才进行 intercepted 的赋值操作

这里还需要一个值得注意的地方, 当事件为DOWN时 执行了这两个方法 cancelAndClearTouchTargets(ev); resetTouchState();,把很多状态重置后

 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//进行为运算

这段代码运行的结果永远为false ,所以 intercepted = onInterceptTouchEvent(ev);一定会执行,下面我们注意这个方法requestDisallowInterceptTouchEvent(boolean disallowIntercept)

 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

这个方法一般由子view来调用,用来决定viewgroup是否拦截除DOWN事件以外的其它事件,假设我们把这里的onInterceptTouchEvent(ev) 重写并始终返回为true,那么子view将会永远得不到事件的分发,所有的事件都会被ViewGroup拦截。

平时开发过程中可能会有这种需求:在触控的时候,按下那下(ACTION_DOWN)需要子View进行事件反馈,但在滑动(ACTION_MOVE)或者抬起(ACTION_UP)的时候需要父布局进行拦截操作,这个时候就要用到我们的requestDisallInterceptRouchEvent(boolean disallowIntercept)方法了,该方法可以在子类通过调用getParent().requestDisallInterceptRouchEvent(true) 来让父布局禁用拦截事件功能,这样子view只会相应DOWN事件,不会得到后续事件,因为父布局已拦截了

下面我们继续分析

 intercepted = onInterceptTouchEvent(ev);
 public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

ViewGroup中的onInterceptTouchEvent(MotionEvent ev)这个方法,默认是返回false的,也就是说ViewGroup默认不会拦截子view的事件,当为false时,所有的事件都会传递给子view
如果不拦截 intercepted 就为false,就会执行到下面代码,源码没有全部贴出,

  if (!canceled && !intercepted) {

            // If the event is targeting accessibility focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            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;
                //=====================================================
                //如果newTouchTarget 等于空并且 子view 的数量不为0
                 //=====================================================                 
                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 preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                      //=====================================================
                      //for循环倒序遍历,得到子view
                      //=====================================================                 
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
						//这个子view不可见或者在执行动画 又或者 触摸的范围不在这个子view的范围之内,就继续遍历
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        
						//如果有满足上面条件的子view,就将子view(child)当做参数传递进去,执行   
						//dispatchTransformedTouchEvent()这个方法
						//如果这个方法返回true,表示子view消耗了此事件
					 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;
                        }
	//判断这个子view是可见的并且没有执行动画
 private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

newTouchTarget = addTouchTarget(child, idBitsToAssign);
查看这个方法

  private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

表示让mFirstTouchTarget 指向消耗事件的子view,如果所有的子view都不消费事件,mFirstTouchTarget 就为null

我们来到

dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits)

这个方法中

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
            //当child为空时 调用 super.dispatchTouchEvent(event); 调用父类的dispatchTouchEvent(event)
                handled = super.dispatchTouchEvent(event);
            } else {
            
            	//如果子view child 不为空 把这个事件传递给子view
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
  //这样就完成了事件从ViewGroup到子view的传递过程
  handled = child.dispatchTouchEvent(event);
  //当事件处理完毕,就会返回一个 handled  表示子view是否消费的此次事件
//当child为空时 调用 super.dispatchTouchEvent(event); 调用父类的dispatchTouchEvent(event)
//也就是调用 View的 dispatchTouchEvent(event)
 handled = super.dispatchTouchEvent(event);

注意

  if (child == null) {
            //当child为空时 调用 super.dispatchTouchEvent(event); 调用父类的dispatchTouchEvent(event)
            当child为空时 如果此时容器为viewGroup类型,当viewGroup类型的容器中重写了onTouchEvent() 
            方法,那么  super.dispatchTouchEvent(event); 实际上是调用了viewGroup类型的容器中 重写的onTouchEvent() 
			View中有 onTouchEvent()这个方法,viewGroup继承于View,但是没有重新onTouchEvent()这个方法,如果此时
			我们自定义控件继承了viewGroup,并且重写了onTouchEvent()根据继承的关系执行  super.dispatchTouchEvent(event)
			这个调用又会调用View当中的onTouchEvent()方法,而此时我们自定义的控件从写了这个方法,那么最终会回调我们自定义控件中的onTouchEvent()方法
            
                handled = super.dispatchTouchEvent(event);
                
            } else {
            
            	//如果子view child 不为空 把这个事件传递给子view
            	回调子view的onTouchEvent() 如果子view重写了onTouchEvent()
                handled = child.dispatchTouchEvent(event);
            }
 // Dispatch to touch targets.  没有触摸目标,因此请将其视为普通视图。也就是没有子view消费事件

            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;
                }
            }

总结:

ViewGroup事件分发机制详解_第3张图片
当ViewGroup进行事件分发工作时,如果onInterceptTouchEvent()方法返回true,拦截了此次事件,那么此次事件将不再传递给子view了,从而调用ViewGroup自身的onTouchEvent()方法
如果不拦截,就将此次事件继续传递给子view的dispatchTouchEvent()方法,子view的dispatchTouchEvent()
方法里又继续分发给自己,也就是分发给自己的onTouchEvent()方法,
注意 :dispatchTouchEvent()这个方法都只做事件分发工作,真正的事件处理工作都是由onTouchEvent()方法来完成的

你可能感兴趣的:(事件分发)