Android事件分发精髓总结篇

本篇对自己目前所理解事件分发做一个阶段性的总结,其中是一些重要的也是必须要理解的部分,共分为三个部分。我相信耐心理解了这些片段,一定能解决事件分发的大部分问题。

一、事件分发的起点:

首先事件传到Activity的dispatchTouchEvent:

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

然后是getWindow()返回的superDispatchTouchEvent,这里实际上是PhoneWindow

public boolean superDispatchTouchEvent(MotionEvent event){
    retuen mDecor.suerDispatchTouchEvent(event);
}

这里的mDecor就是DecorView,就是平常看到的Activity的顶级View。Activity的setContentView设置的就是DecorView的content。
且DecorView继承自FrameLayout,也就是ViewGroup,由此开始了事件由Activity传递到ViewGroup。

值得注意的是,这几个地方都是同步执行,并非异步。也就是说事件到Activity的dispatchTouchEvent后,通过

getWindow().superDispatchTouchEvent(ev)

这一句开始后面所有的分发过程,里面后续的调用流程都一样,层层同步返回,并最终对返回值做判断,如果为true,则结束分发,如果为false则事件最终传递到Activity的onTouchEvent。

二、ViewGroup对事件的拦截逻辑

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

详细阐述一下:
1)当事件为ActionDown或者事件不为ActionDown但已经有子View(mFirstTouchTarget != null成立,这个变量的赋值操作会在下面讲到)要处理当前事件序列的前面的事件的时候,会判断disallowIntercept标志,

2)如果disallowIntercept为false,那么就将拦截标志intercept的值赋值为onInterceptTouchEvent方法执行的返回结果。

3)如果disallowIntercept为true,就将intercept赋值为false。

4)最后,如果当前事件不为ActionDown并且没有找到子View要处理当前事件序列(mFirstTouchTarget != null不成立),那么也将intercept赋值为false。

其实通过以上几点可以看出,这段代码最终都在操作intercept这个标志,这个标志到底有什么用呢?不要着急,它的用处在下面的代码片段,下面会说到,而且下面的代码主要逻辑都会依据这个标志。

不过这里有个问题还需要注意,

  if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) 

当if中的第一个条件成立,也就是说当前事件时ActionDown的时候,

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

这个disallowIntercept 是恒为false的,因为在源码中,每当事件为ActionDown的时候,总会清除这个标志,如下:

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

通过传递事件到子View来寻找要处理事件的子View

  if (!canceled && !intercepted) {
    //此处忽略一些无关紧要的代码...
      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);
                                  //...
                            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;
                            }
  }
}

这段代码的主要作用就是遍历子View,寻找要处理当前事件的那一个。在寻找的过程中会将事件传递到子View的dipatchTouchEvent和onEvent中。下面分要点详细阐述一下:
1)可以看到此片段的第一句有个条件就是!intercepted成立。为什么要这样判断呢?很好理解:如果intercepted为false就代表ViewGroup不拦截这个事件,当然就需要遍历子View去寻找一个View来处理此事件,这也就是上面关于intercepted标志作用的解答。
2)重点方法

   if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

记住,dispatchTransformedTouchEvent这个方法的返回值代表参数child是否要处理消耗这个事件。 也就说如果此次循环的child如果要消耗这个事件再执行方法体内容。这个方法内部重要逻辑如下,

   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) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
}

可以看到,这里的child参数肯定是不为null的,会执行child.dispatchTouchEvent(event)方法,这里相当于就是执行的View的dispatchTouchEvent方法了,这里已经传递到子View中去了,但还没完,下面接着看。

3)接着看View的dispatchTouchEvent方法,重要逻辑如下:

 public boolean dispatchTouchEvent(MotionEvent event) {
        //...
 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;
            }
   return result;
}

可以看到如果View被设置了mOnTouchListener ,那么在View可用的情况下会调用mOnTouchListener 的onTouch方法,如果onTouch方法返回true那么就不再执行后面逻辑。否则,再继续执行onTouchEvent方法。
4)接着再看View的onTouchEvent方法的主要逻辑:

 public boolean onTouchEvent(MotionEvent event) {
//...
 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;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
//...
       if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                case MotionEvent.ACTION_UP:
                  //...
                   if (mPerformClick == null) {
                         mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
          }
    return true;
}
return false;
}

可以看到clickable 变量在CLICKABLE和LONG_CLICKABLE两个变量任意一个为true都会为true,并且如果clickable为true,那么最后onTouchEvent方法会返回true。一句话总结就是,如果CLICKABLE和LONG_CLICKABLE任意一个为true都会消费此次事件。
同时还有一个关注点,在ActionUp的时候,会执行performClickInternal方法,最终会执行到OnClickListener的onClick方法中,这个也好理解,我们有注意到,在设置了OnClickListener方法后,在手指按下并抬起的那一刻才会监听到onClick方法。

5)到此为止,已经把从ViewGroup的dispatchTransformedTouchEvent方法调用到子View的的整个事件分发相关的方法流程走了一遍。现在回到ViewGroup的dispatchTouchEvent方法中,当dispatchTransformedTouchEvent返回true,下面的逻辑

 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//...
   newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
//...
}

而addTouchTarge方法也是将一开始if中的mFirstTouchTarget 进行了赋值操作。

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

最后再总结一下此片段。此片段功能是遍历ViewGroup的子View,并寻找要处理消费此次事件的view。并会执行如下方法流程。dispatchTransformedTouchEvent->View.dispatchTouchEvent->View.onTouch或者View.onTouchEvent(内部调用onClick)。
整个方法流都是同步的,将boolean值层层返回,表示此View是否要处理消耗掉ViewGroup传来的这个事件。
如果当次循环的子View要处理消耗这个事件,那么对mFirstTouchTarget 进行赋值。

三、ViewGroup自己处理事件

 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;
                        }
                 //...
                }
            }
//...
 return handled;

详细阐述一下:如果经过上面的遍历没有找到一个子View来处理此次事件(if (mFirstTouchTarget == null)),那么就调用dispatchTransformedTouchEvent方法,view参数传为null。这个方法刚刚在上面也调用过,还记的里面的重要逻辑是如果参数child不为null就执行child的dispatchTouchEvent,如果为null就执行ViewGroup.super.dispatchTouchEvent,其实都是执行的View.dispatchTouchEvent.只不过这里child为null的时候,执行的是ViewGrouop自己的View中的dispatchTouchEvent。
也就是说,当没有子View处理此次事件,那么ViewGroup自己执行基类(View)的dispatchTouchEvent方法。而View的dispatchTouchEvent方法我们在上面已经走过一遍流程。

到此,事件分发中的重要逻辑点已经梳理完毕。

你可能感兴趣的:(Android事件分发精髓总结篇)