Android事件分发机制浅析

当我们点击屏幕时,会产生一个点击事件,这个事件由MotionEvent来表示。这个事件最先传递到Activity,会回调dispatchTouchEvent方法

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return super.dispatchTouchEvent(ev);
}

方法内调用父类Activity的dispatchTouchEvent

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

getWindow获取Window对象,并调用它的superDispatchTouchEvent方法,将事件传递给Window,Window是一个抽象类,它的实现是PhoneWindow

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

PhoneWindow中又将事件传递到DecorView

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

这个DecorView继承自FrameLayout,它就是我们通过setContentView方法设置View的父容器,这样事件就从Window传递到了Activity的顶级View,接下来是在ViewGroup中的事件分发过程,继续调用ViewGroup的dispatchTouchEvent方法

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  //...省略部分代码
  // Check for interception.
            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是否为空,当前ViewGroup不拦截事件,该事件由子元素处理时mFirstTouchTarget!= null成立。再判断FLAG_DISALLOW_INTERCEPT标记位,这个标记位由子元素设置,设置后ViewGroup不能拦截除ACTION_DOWN以外的其他事件,如果设置了则返回false不拦截,如果没设置则获取onInterceptTouchEvent的返回值。继续看该方法中的代码

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  if (!canceled && !intercepted) {
    //...
     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 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 (!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);
                            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中的child并将事件分发给他们。canViewReceivePointerEvents方法判断child是可见的以及未在执行动画,isTransformedTouchPointInView判断子child在点击的坐标范围内,如果这方法不满足的话,则continue进入下一个循环。否则,调用dispatchTransformedTouchEvent

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不为空,调用child的dispatchTouchEvent将点击事件分发给child处理。如果这个方法返回true即child将事件处理了,则来到if分支中,调用addTouchTarget方法,将child赋值给mFirstTouchTarget,所以当mFirstTouchTarget不为空时就证明事件传递到了child并由child消耗了。

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

现在看child即View中事件分发的处理

public boolean dispatchTouchEvent(MotionEvent event) {
    //...
    boolean result = false;
        //...
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        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;
}

首先判断有没有设置mOnTouchListener,如果mOnTouchListener中的onTouch方法返回了true的话,onTouchEvent就不会被调用了,可以看出onTouch方法优先级比onTouchEvent要高,这样我们就可以在外面处理View的事件了。

如果没有设置mOnTouchListener,则调用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 (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
  //...
}

如上,当View处于disable不可用状态时,会将clickable状态return回去。所以,即使View是不可用状态但可以点击时,也会消耗当前事件。

public boolean onTouchEvent(MotionEvent event) {
  //...
 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
        case MotionEvent.ACTION_UP:
            //...
            boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                // take focus if we don't have it already and we should in
                // touch mode.
                boolean focusTaken = false;
                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                    focusTaken = requestFocus();
                }

                if (prepressed) {
                    // The button is being released before we actually
                    // showed it as pressed.  Make it show the pressed
                    // state now (before scheduling the click) to ensure
                    // the user sees it.
                    setPressed(true, x, y);
                }

                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    // This is a tap, so remove the longpress check
                    removeLongPressCallback();

                    // Only perform take click actions if we were in the pressed state
                    if (!focusTaken) {
                        // Use a Runnable and post this rather than calling
                        // performClick directly. This lets other visual state
                        // of the view update before click actions start.
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {
                            performClick();
                        }
                    }
                }
                            //...
            mIgnoreNextUpEvent = false;
            break;
        //...
            }
                return true;
            }
            return false;
    }

如果View是可用的,这里会根据点击事件的具体action做对应的处理,我们可以看出当clickable为true事件会被消费,其中包括CLICKABLE、LONG_CLICKABLE和CONTEXT_CLICKABLE这三种。

在ACTION_UP事件触发时,会执行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;
}

如果View设置了mOnClickListener,就会回调onClick方法。

总结

事件的分发流程已经分析完了,分发流程主要是:Activity接收屏幕的点击事件,将事件传递到Window即PhoneWindow上,然后Window又将事件传递到DecorView上,DecorView继承自FrameLayout,它是Activity设置的ContentView的父布局,于是事件最终就传递到了我们根布局上,它一般是一个ViewGroup。最后,由这个ViewGroup分发给它的子View,就完成了事件的分发。

你可能感兴趣的:(Android事件分发机制浅析)