Android-事件分发机制源码详解

Android事件分发应该是每个Android开发人员必备的知识储备了。网上各个大神关于事件分发的文章非常多。本文尽量简单、详细、脉络清晰的思路来讲述事件分发机制。

最近也抽时间把Android的一些知识体系夯实,重新梳理一些Android的知识,后期会更新一些梳理的东西。
现在把事件分发机制重新梳理一遍,解决了一些之前疑惑的地方。

装个逼,先问几个问题:

1 如果一个ViewGroup的onInterceptTouchEvent返回true,onTouchEvent返回false,事件会怎样传递?

2 手机屏幕收到一个触摸事件,是怎么一步步传递到处理的View的?都会经历哪些View呢?

3 当ViewGroupA的子ViewB拦截了ACTION_DOWN事件,然后手指滑动,位置移动到ViewGroupA的边界以外的区域,子ViewB还能收到后续的事件吗?为什么?

4 ViewGroupA有子ViewB,子ViewB拦截ACTION_DOWN事件,然后ACTION_MOVE触发,达到一定条件后,ViewGroupA拦截掉ACTION_MOVE事件,并添加处理逻辑。请问子ViewB还能收到后续的事件吗?为什么?

三个方法

Android事件分发最重要的是三个方法:

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onInterceptTouchEvent(MotionEvent ev)

public boolean onTouchEvent(MotionEvent event)

dispatchTouchEvent方法负责事件分发
onInterceptTouchEvent方法负责事件拦截
onTouchEvent负责事件处理

这三个方法都返回一个boolean值。

一 Activity中事件源码

1.1 Activity事件处理逻辑

触摸屏幕接触到一个点击事件,首先最先收到的是Activity的dispatchTouchEvent分发方法。

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

public void onUserInteraction() {
}

当Activity收到ACTION_DOWN事件的时候,首先调用onUserInteraction方法,但是这个方法默认实现是空的。屏保什么的可以放在这里处理,先不管它。

从上面可以看出,当Activity收到点击事件的时候,都会丢给getWindow().superDispatchTouchEvent(ev)去处理,如果它不处理,再调用Activity自己的onTouchEvent方法处理。

Activity的事件处理逻辑很简单了,没有非常复杂的逻辑和调用。

1.2 Activity类中相关源码

下面看Activity类中的源码:

Activity类的源码:

public Window getWindow() {
 return mWindow;
}

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ······

上面Activity的处理逻辑是把事件都交给getWindow方法的对象处理,从上面Activity的源码可以看出,Activity把事件都交给了PhoneWindow的superDispatchTouchEvent来处理。

1.3 PhoneWindow中相关源码

PhoneWindow的源码:

public PhoneWindow(Context context, Window preservedWindow,
           ActivityConfigCallback activityConfigCallback) {
       this(context);
       ······
       if (preservedWindow != null) {
           mDecor = (DecorView) preservedWindow.getDecorView();
           ·······
       }
       ······
   }

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

在PhoneWindow的superDispatchTouchEvent中,把事件统一交给了mDecor对象处理。mDecor对象是DecorView类型,在PhoneWindow对象创建的时候初始化的。

1.4 DecorView中相关源码

下面再看DecorView的superDispatchTouchEvent方法源码:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	······
	public boolean superDispatchTouchEvent(MotionEvent event) {
		return super.dispatchTouchEvent(event);
	}
	
	······

从源码可以看出DecorView中的superDispatchTouchEvent方法直接调用了super类的dispatchTouchEvent方法,而父类FrameLayout类的dispatchTouchEvent方法直接使用了ViewGroup类的dispatchTouchEvent方法。这里简单就不贴代码了。

回顾一下Activity的事件处理逻辑,直接把事件丢给PhoneWindow处理,然后有丢给DecorView处理,最后交给ViewGroup类的dispatchTouchEvent方法。如果不处理返回了false,则直接调用Activity的onTouchEvent方法处理。

如图:
在这里插入图片描述

二 ViewGroup事件处理逻辑

下面再看ViewGroup的事件处理逻辑。

2.1 ViewGroup的事件处理逻辑源码

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ······
    boolean handled = false;
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
	······
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        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 {
        intercepted = true;
    }
    // 第一部分:事件拦截逻辑
    ······
    final boolean canceled = resetCancelNextUpFlag(this)
            || actionMasked == MotionEvent.ACTION_CANCEL;

    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
    	······
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
			······
            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);
             ······
                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)) {
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    }

                    newTouchTarget = getTouchTarget(child);
                    if (newTouchTarget != null) {
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                    }

                    resetCancelNextUpFlag(child);
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        ······
                        mLastTouchDownX = ev.getX();
                        mLastTouchDownY = ev.getY();
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
					······
                }
                if (preorderedList != null) preorderedList.clear();
            }

            if (newTouchTarget == null && mFirstTouchTarget != null) {
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }

	// 第二部分:事件处理对象查找逻辑
    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        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;
        }
    }
    // 第三部分:事件分发逻辑
    ······
    return handled;
}

这个方法很长,删除了一些无关代码,还是很长。
首先我对ViewGroup的dispatchTouchEvent方法分成三个部分(已经标注):
第一部分是事件拦截逻辑。这部分的代码是当前ViewGroup是否需要拦截事件的逻辑

第二部分是事件处理子View查找逻辑。这部分代码是查找当前的ViewGroup的各个子View是否需要处理事件的逻辑

第三部分是事件分发逻辑。这部分代码是当前ViewGroup分发接收到的事件到各个需要处理的子View,当然也包括没有子View要处理,分发给自己处理的逻辑。

下面一步一步分解分析:

2.2 事件拦截逻辑

2.2.1 先看ACTION_DOWN事件处理逻辑

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

如果dispatchTouchEvent方法接受到一个ACTION_DOWN事件,它的处理逻辑是,
actionMasked == MotionEvent.ACTION_DOWN这一行代码会为true,就会走到第一个if语句里面。第二个mFirstTouchTarget对象不为空,也会走到if语句里面。

这个mFirstTouchTarget对象说明一下:

1) 对于ACTION_DOWN事件来说,该对象为null。

原因是,dispatchTouchEvent方法在最开始的代码,如下面的代码在处理MotionEvent.ACTION_DOWN事件之前已经清理了资源。

if (actionMasked == MotionEvent.ACTION_DOWN) {
  cancelAndClearTouchTargets(ev);
  resetTouchState();
}

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}
2) 对于后续的ACTION_MOVE和ACTION_UP事件来说不一定为null。

因为如果当前的ViewGroup对象的子View要处理事件,说明找到了处理点击事件的View,该mFirstTouchTarget值就为要处理点击事件的View对象,该mFirstTouchTarget值就不为null。

如果final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;为true,说明其他地方设置了 FLAG_DISALLOW_INTERCEPT标记位,这个标记位的意思就是不要拦截事件,有别的View要处理的意思。那它就设置intercepted=false,明确自己不拦截事件。默认情况下FLAG_DISALLOW_INTERCEPT这个标记位为false,则就会走ViewGroup类的onInterceptTouchEvent方法。

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

默认情况下onInterceptTouchEvent返回false。如果ViewGroup子类需要拦截事件,需要自己重写该方法添加处理逻辑。至于onInterceptTouchEvent方法里面的if语句的逻辑可以不用管它,它是为了处理SOURCE_MOUSE这类触摸板的设备的。

如果这个onInterceptTouchEvent方法返回true,说明当前ViewGroup想要拦截事件。如果为false,说明当前ViewGroup不拦截事件,需要从子View中找看是否能找到想要处理点击事件的View。

小结:

当ACTION_DOWN事件传递过来以后,如果没有设置不拦截标记位,就调用onInterceptTouchEvent方法,该方法决定是否拦截。如果设置了不拦截标记位,就设置intercepted=false,遍历询问子View是否拦截。如果找到了某个子View需要处理事件,则mFirstTouchTarget对象为这个子View,否则mFirstTouchTarget对象为null。

如果ACTION_MOVE和ACTION_UP事件传递过来,首先判断mFirstTouchTarget对象是否为空。如果为空,直接设置intercepted=true,表示当前对象拦截,也就意味着事件不会继续向下层传递。如果mFirstTouchTarget对象不为空,则判断是否设置了不拦截标记位,如果设置了不拦截标记位,就设置intercepted=false,遍历询问子View是否拦截。如果没设置不拦截标记位,走onInterceptTouchEvent方法,该方法决定是否拦截。

onInterceptTouchEvent方法走不走的条件有几个:
1 ACTION_DOWN事件
2 mFirstTouchTarget != null
3 FLAG_DISALLOW_INTERCEPT

以下两种情况下会走onInterceptTouchEvent方法:
1 当mFirstTouchTarget != null && 没有设置不拦截标记位的情况下,当前ViewGroup的onInterceptTouchEvent方法才会每次都走到

2 ACTION_DOWN事件也走onInterceptTouchEvent方法。

以上是当前ViewGroup是否拦截事件的处理逻辑。

2.3 事件处理子View查找逻辑

接着代码会往下走到if语句的判断:

if (!canceled && !intercepted) {
	......
}

2.3.1 是否查找子View

通常canceled=false,可以暂时不考虑。

这种情况下,intercepted=true或者false决定了是否走if语句里面的逻辑。

如果intercepted=true,说明当前ViewGroup是要拦截事件处理的,或者说事件不需要向下层传递,所以就不走if语句里面的逻辑。

如果intercepted=false,就需要走if语句里面的逻辑,就需要便利循环子View,找到是否需要处理事件的子View。

当intercepted=false的时候,会走if (!canceled && !intercepted)这个if语句里面的逻辑。
在这个if语句里面,会走下面的if语句判断逻辑:

if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

在这个if语句里面,判断事件是ACTION_DOWN或者是ACTION_POINTER_DOWN事件,ACTION_HOVER_MOVE事件忽略,这个是其他设备触发的,暂时不考虑。

有了这个if语句的判断,说明if (!canceled && !intercepted)这个if语句里面的逻辑只有在ACTION_DOWN或者是ACTION_POINTER_DOWN事件时才会遍历子View,查找是否有需要处理事件的子View。其他的事件比如ACTION_MOVE和ACTION_UP都不会走if (!canceled && !intercepted)里面查找是否有需要处理事件的子View。这一点很重要。

当前触发了ACTION_DOWN或者是ACTION_POINTER_DOWN事件时,且intercepted=false,以下代码就会走:

		if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
					······
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        ······
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            ······
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                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;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }

可以看到,这里的代码循环遍历子View。

2.3.2 过滤子View

首先通过判断点击和动画,过滤掉不能处理ACTION_DOWN事件的view:

if (!child.canReceivePointerEvents()
    || !isTransformedTouchPointInView(x, y, child, null)) {
         ev.setTargetAccessibilityFocus(false);
         continue;
     }

这个if语句判断ACTION_DOWN事件的点击位置,是否在子View的边界内,是由isTransformedTouchPointInView()这个方法判断的。如果不在子View的边界内,直接判断下一个子View。这个很容易理解。

另外如果子View正在触发动画,也不处理事件。是由canReceivePointerEvents()方法判断的。

排除上面两种情况以后,走下面的逻辑。

2.3.3 子View处理ACTION_DOWN事件

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    mLastTouchDownTime = ev.getDownTime();
    ······
    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}

这个if语句=true,代表的意思终于找到一个处理当前事件的子View。

判断的方法就是dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法。
如果这个方法返回true,代表当前的子View也就是child要拦截ACTION_DOWN事件,此时addTouchTarget方法就会被调用,mFirstTouchTarget设置值不为null,同时alreadyDispatchedToNewTouchTarget标记位设置为true。由于找到了拦截ACTION_DOWN事件的子View,跳出循环并结束。

如果dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法放回false,代表当前View不拦截ACTION_DOWN事件,循环会继续遍历下一个子View。

2.4 事件分发逻辑

循环结束以后,以下代码会走:

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
    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;
    }
}

这里的代码不管是ACTION_DOWN还是ACTION_MOVE、ACTION_UP事件都会走到。
区别在于mFirstTouchTarget的值是否为null。

如果mFirstTouchTarget=null,则说明没有子View要处理事件,直接调用dispatchTransformedTouchEvent方法。不管是ACTION_DOWN、ACTION_MOVE还是ACTION_UP事件都会直接走该方法。

如果mFirstTouchTarget!=null,说明有子View要处理事件,这个时候会走到while (target != null)的循环。这个while循环的目的是告诉各个需要处理事件的子View去处理事件。这是因为TouchTarget是个单链表结构,且mFirstTouchTarget始终指向第一个元素,所以要用到while循环。对于ACTION_DOWN事件来说,mFirstTouchTarget刚刚找到要处理该事件的子View,alreadyDispatchedToNewTouchTarget标记位为true,所以这里的while循环标记handled=true即结束了循环。而对于ACTION_MOVE和ACTION_UP事件来说,alreadyDispatchedToNewTouchTarget=false,此时while循环内部循环遍历TouchTarget单链表结构的元素,并调用dispatchTransformedTouchEvent方法,只要有一个元素在dispatchTransformedTouchEvent方法中返回true,即标记handled=true。

这个while循环内部还有一个需要解读的地方是,intercepted=true的情况下,cancelChild=true,此时会遍历TouchTarget单链表元素,此时会传递子View一个ACTION_CANCEL事件。这个事件是内部代码触发的,不能通过用户手动触发。这个目的是告诉之前处理事件的子View,事件在当前ViewGroup处理了,不需要子View处理了,然后通知之前处理事件的各个子View一个ACTION_CANCEL事件做一些资源清理工作。这里回答了文章开始的问题4。

最后就是handled值作为dispatchTouchEvent方法的返回值。
这个handled的值有三个地方会对它赋值。
1)mFirstTouchTarget=null的情况下,dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)方法的返回值就是handled的值。
2) mFirstTouchTarget!=null的情况下,对于ACTION_DOWN事件来说,不考虑其他剁手指的情况下,alreadyDispatchedToNewTouchTarget=true,直接handled=true。
3) mFirstTouchTarget!=null的情况下,对于ACTION_MOVE和ACTION_UP事件来说,循环遍历TouchTarget单链条,并将每个元素作为参数调用dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)方法,只要单链表中有一个元素中该方法返回true,即将handled=true。

在以上三种情况不满足的情况下,handled=false返回。

2.5 具体对象的事件分发

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

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

  final int oldPointerIdBits = event.getPointerIdBits();
  final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

  if (newPointerIdBits == 0) {
      return false;
  }

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

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

      handled = child.dispatchTouchEvent(transformedEvent);
  }

  transformedEvent.recycle();
  return handled;
}

这个方法可以简化一下逻辑。
1 如果cancel=true或者action=ACTION_CANCEL事件,这种情况下,会调用super.dispatchTouchEvent或者child的dispatchTouchEvent方法。

2 如果不是上面的情况,调用super.dispatchTouchEvent或者child的dispatchTouchEvent方法。

归根揭底,这个方法的调用逻辑是,如果child=null,就是调用super.dispatchTouchEvent方法,其实就是调用了View的dispatchTouchEvent方法。如果child != null,则调用child自己的dispatchTouchEvent方法。

如图:
在这里插入图片描述

三 View事件处理逻辑

public boolean dispatchTouchEvent(MotionEvent event) {
	······
    boolean result = false;
	······
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

View的dispatchTouchEvent方法要简单许多。

3.1 ENABLED标记位

对于View来说,ENABLED_MASK标记位很重要。
如果这个标记位为false,则

if (li != null && li.mOnTouchListener != null
      && (mViewFlags & ENABLED_MASK) == ENABLED
           && li.mOnTouchListener.onTouch(this, event)) {
       result = true;
   }

这个if语句直接就是false返回了。即使对View设置了mOnTouchListener监听器也没用。所以这个标记位相当于一个总开关。如果关闭了,点击事件什么都没有了点击就没有了反应。这个开关对于View来讲,是需要各个子View自己实现的。比如Button这个开关默认是打开的,比如ImageView或者TextView这个开关是关闭的。
还有一种情况是,setOnClickListener或者setOnLongClickListener两个监听器的方法,代码如下:

 public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
}

public void setOnLongClickListener(@Nullable OnLongClickListener l) {
    if (!isLongClickable()) {
        setLongClickable(true);
    }
    getListenerInfo().mOnLongClickListener = l;
}

这两个方法设置监听器后,原来不可点击的状态设置为可点击状态。

3.2 OnTouchListener监听器

回到dispatchTouchEvent(MotionEvent event)方法的处理逻辑,ENABLED_MASK标记位为ENABLED的情况下,首先走mOnTouchListener的onTouch方法。
这个方法的设置在代码如下:

View源码:
public void setOnTouchListener(OnTouchListener l) {
   getListenerInfo().mOnTouchListener = l;
}

通过调用View的setOnTouchListener方法可以设置该监听器。同时该监听器的ouTouch方法返回一个boolean值,决定了后续的事件处理逻辑。
如果onTouch方法返回true,则result=true。则后面的代码:

if (!result && onTouchEvent(event)) {
  result = true;
}

result通过OnTouchListener对象的onTouch方法返回true得情况下,则onTouchEvent方法就不会走。只有在返回false的情况和没有设置OnTouchListener对象的情况下,才会走到onTouchEvent方法。

总结一下:
1 ENABLED_MASK标记位为ENABLED情况下才会走事件,否则不处理事件逻辑
2 如果设置了mOnTouchListener对象,该对象的onTouch方法决定是否调用onTouchEvent方法。

如图:
在这里插入图片描述

3.3 View的onTouchEvent方法

public boolean onTouchEvent(MotionEvent event) {
   final float x = event.getX();
   final float y = event.getY();
   final int viewFlags = mViewFlags;
   final int action = event.getAction();

   final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
           || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
           || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

   if ((viewFlags & ENABLED_MASK) == DISABLED) {
       if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
           setPressed(false);
       }
       mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
       return clickable;
   }
	······
   if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
       switch (action) {
           case MotionEvent.ACTION_UP:
               ······
                               performClickInternal();
			   ······
               break;

           case MotionEvent.ACTION_DOWN:
               ······
               break;

           case MotionEvent.ACTION_CANCEL:
               if (clickable) {
                   setPressed(false);
               }
               removeTapCallback();
               removeLongPressCallback();
               mInContextButtonPress = false;
               mHasPerformedLongPress = false;
               mIgnoreNextUpEvent = false;
               mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
               break;

           case MotionEvent.ACTION_MOVE:
               ······
               break;
       }

       return true;
   }

   return false;
}

对onTouchEvent方法进行了简化,可以看出,ENABLED_MASK标记位为DISABLED的情况下,直接返回了,不再处理事件了。

后面的逻辑是收到ACTION_DOWN、ACTION_MOVE和ACTION_UP事件情况下,onTouchEvent方法直接返回true。也就是说View来讲,默认情况下是消耗事件的。同时ACTION_CANCEL事件收到以后做一些清理资源的逻辑。另外对于ACTION_UP事件来说,performClickInternal方法会被调用,这个方法内部源码如下:

private boolean performClickInternal() {
   notifyAutofillManagerOnClick();

    return performClick();
}

public boolean performClick() {
        ······
        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;
    }

这里就会调用到我们最常用的setOnClickListenenr的点击监听器了。

四 讨论

这一部分讨论一些问题,不能说解答的很好,算是一家之言而已,如果大家看到有什么问题,非常欢迎在评论区留言,我会认真观看的!

4.1

问题:如果一个ViewGroup的onInterceptTouchEvent返回true,onTouchEvent返回false,事件会怎样传递?

这个是返回值问题。通过ViewGroup的源码dispatchTouchEvent方法的返回值就可以确定这个问题的答案。

onInterceptTouchEvent方法的返回值不是dispatchTouchEvent方法的返回值。
onInterceptTouchEvent方法的返回值影响了事件分发逻辑,决定了事件交给谁来处理。

如果onInterceptTouchEvent方法的返回值为true,则说明不会去查找子View是否处理事件,这个时候会直接走自己的onTouchEvent方法。这个时候onTouchEvent方法返回值就是dispatchTouchEvent方法的返回值。这里就是把false返回了上层,上层收到false的结果,说明这个ViewGroup不消耗事件,那么父View接下来接收的事件就不会交给它处理了。

4.2

手机屏幕收到一个触摸事件,是怎么一步步传递到处理的View的?都会经历哪些View呢?

Activity —> PhoneWindow ->DecorView->FrameLayout->ViewGroup->布局xml的根ViewGroup->子View

点击之后,第一个接收到事件的是Activity这很容易理解。从activity的源码分析可以看出,事件一层层传递通过PhoneWindow传递给DecorView,而DecorView继承自FrameLayout,而FrameLayout没有重写dispatchTouchEvent方法的,所以就会走到ViewGroup的dispatchTouchEvent方法。至此这个事件还没有传递到布局的根ViewGroup。下一步才会传递到根ViewGroup,然后从根ViewGroup中查找各个子View是否需要处理事件。

4.3

当ViewGroupA的子ViewB拦截了ACTION_DOWN事件,然后手指滑动,位置一直移动到ViewGroupA的边界以外的区域,子ViewB还能收到后续的事件吗?为什么?

能!

原因在于ViewGroup的源码dispatchTouchEvent方法中,通过ACTION_DOWN事件查找到了子ViewB拦截事件,后续ACTION_MOVE事件也交给它处理,当触摸移动的位置超过ViewGroupA的边界以外的话,子ViewB依然能够接收到后续事件。
从dispatchTouchEvent方法源码角度来讲,mFirstTouchTarget对象不为空,并且此时是ACTION_MOVE事件和ACTION_UP事件不会走上文分析的2.3 事件处理子View查找逻辑,只有在ACTION_DOWN事件过程中才会查找需要处理事件的子View。只要查找到后续的事件就会交给它处理,当然前提的父ViewGroup(这里就是ViewGroupA)不拦截事件的前提下。

4.4

ViewGroupA有子ViewB,子ViewB拦截ACTION_DOWN事件,然后ACTION_MOVE触发,达到一定条件后,ViewGroupA的onInterceptTouchEvent方法返回true,拦截掉ACTION_MOVE事件,并添加处理逻辑。请问子ViewB还能收到后续的事件吗?为什么?

不能!
刚开始子ViewB处理事件,当满足一定条件后,ViewGroupA拦截了事件,不把后续事件分发给子ViewB,此时从dispatchTouchEvent方法源码角度来讲,上文分析的2.4 事件分发逻辑 中,当ViewGroupA拦截的那一个ACTION_MOVE事件开始,mFirstTouchTarget!=null,并且由于ViewGroupA拦截了事件,intercepted=true,此时看代码:

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

源码中的cancelChild=true,此时子ViewB作为参数,调用dispatchTransformedTouchEvent,并向ViewB发送一个ACTION_CANCEL事件,同时调用target.recycler()方法回收了。

接下来ViewGroupA再次接收到ACTION_MOVE事件时,mFirstTouchTarget=null,直接调用了下面的代码:

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {

在dispatchTransformedTouchEvent方法中,直接走了super.dispatchTouchEvent方法,也就意味着不会走到子View了。

通过上文中2.3 事件处理子View查找逻辑 分析可以看出,查找子View只会在ACTION_DOWN事件中进行,所以此时ACTION_MOVE事件和ACTION_UP事件是不会走事件处理对象的查找逻辑的。

基于这两个原因,一旦ViewGroupA拦截了事件,子View想要处理的权限就没有了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值