Android事件分发机制(2)——从源码角度分析

    在上一篇博客中,我们以实例的形式介绍了Android的事件分发机制的三个关键函数:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。今天,我们从源码的角度来分析Android的事件分发机制的具体实现。

   1、Activity的事件处理机制

     在上一篇博客我们知道,Android的触屏事件首先是从Activity的dispatchTouch Event开始分发的,所以我们先来看看Activity的dispatchTouchEvent代码:

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

    当是ACTION_DOWN事件时,首先会调用onUserInteraction这个函数,这个方法在Activity里的实现是空的,用户在继承Activity时可以重写该方法。往下看到getWindow这个方法:

public Window getWindow() {
    return mWindow;
}

    返回的是一个mWindow的Window对象,而这个mWindow实际上是一个PhoneWindow对象,于是就变成了调用PhoneWindow类的superDispatchTouchEvent,继续看源码:

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

    这里又跑出了一个mDecor对象,它是DecorView的对象,这里简单提一下Activity的组成,Activity实际是有Window来管理视图的,而这个Window一般由PhoneWindow实现,但PhoneWindow实际并不程序界面效果,这个操作就由DecorView来实现。DecorView继承自Framelayout,是整个视图的根,如下图所示:


    所以,上面mDecor.superDispatchTouchEvent最终调的方法是ViewGroup的dispatchTouchEvent这个方法。

2、ViewGroup的事件处理流程

    我们接下来看ViewGroup的源码,由于ViewGroup源码较长,我们挑关键部分解析:

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();
}
    这一段代码主要是在检测到ACTION_DOWN事件做一些标记清除和初始化事件

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

    从上面代码我们可以看到,当事件类型为ACTION_DOWN或者 mFirstTouchTarget 不为空时,ViewGroup会调用onInterceptTouchEvent来判断当前事件是否要拦截,ACTION_DOWN我们很熟悉了,那么mFirstTouchTarget 是什么呢?在ViewGroup的子元素成功处理事件时, mFirstTouchTarget 会被赋值并指向子元素,回头看初始化那一段代码,cancelAndClearTouchTargets实际上就是清除mFirstTouchTarget 所指向的元素。从这一段代码可以看到这两种情况:当事件类型为ACTION_DOWN时,VIewGroup肯定会进入拦截判断;而在后续的如果有子元素处理了即消费了事件,那么这个mFirstTouchTarget 不为空,那么ViewGroup也会进入拦截判断。这样印证了我们上一节中如果是ViewGroup自己消费了事件,后续的Move和up事件是不会回调onInterceptTouchEvent的。所以如果我们想提前处理所有的点击事件,一般使用dispatchTouchEvent而不是onInterceptTouchEvent。

    接着往下看:

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

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

    当ViewGroup不拦截事件时,ViewGroup会遍历子元素,判断子元素是否能够接收到点击事件。如果子元素View能接收到事件,则调用dispatchTransformedTouchEvent这个方法,我们看看这个方法:

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

这个方法实际会调用子元素的dispatchTouchEvent方法,这里就把事件传递给子View了,从而完成了这一轮的事件分。子元素的事件处理我们后面在分析,我们回到上面的代码,可以看到如果这个 dispatchTransformedTouchEvent返回true,就会在

newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;

    给mFirstTouchTarget赋值,也就是我们前面锁看到的进入拦截判断的那个条件。而假如遍历完整个ViewGroup,mFirstTouchTarget仍为null,由上面代码可知一种情况是子元素的dispatchTouchEvent返回false(即子元素没有消费事件),另一种情况是ViewGroup没有子元素,此时:

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

    这里又再一次调用了dispatchTransformedTouchEvent这个方法,只不过第三个参数(参数传的是子元素)设为null,回头看刚刚dispatchTransformedTouchEvent的方法:

if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }

    假如子元素不为null才会调子元素的dispatchTouchEvent,而如果子元素为null,则调ViewGroup的super.dispatchTouchEvent自己处理事件。这里的super.dispatchTouchEvent即调的是View的dispatchTouchEvent方法来处理事件(ViewGroup实际也是View,它继承自View)。

3、View的事件处理过程

    接下来我们来看View的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    ...//省略部分代码

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

    View的dispatchTouchEvent相对简单一些,从上面的代码可以看到,View在处理事件时,首先会判断有没有设置OnTouchEListener,如果设置了则会先执行onTouch来处理事件,如果onTouch返回true,则不会进入onTouchEvent这个方法,这里正印证了上一篇博客最后面的分析,onTouch先于onTouchEvent执行,且如果onTouch消费了事件,则后面的onTouchEvent不会执行的结论。

    接下来分析View的onTouchEvent的实现:

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

    先进来判断View是否是disabled不可用的状态,由上面的代码可知,即使VIew处于不可用的状态,也是会消费事件的,只是不会对事件做出响应(看注释翻译偷笑),接着往下看

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
        case MotionEvent.ACTION_UP:
            ...//省略部分代码
            boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                .../

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

               ...//省略部分代码
            }
            break;
    }

    return true;

    这里的clickable的赋值过程在上面的代码里已经给出,由此可以知道只要当CLICKABLE或者LONG_CLICKABLE当中一个为true,onTouchEvent就会返回true,即消耗了这个事件,回顾上一章,也印证了这个结论。然后在ACTION_UP触发时,会调用performClick(),如果View设置了OnclickListener,performClick内部就会调用它的onClick方法:

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的OnCickListener源码:

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

public void setClickable(boolean clickable) {
    setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
    这里把CLICKABLE标志位设置为true,然后给mOnClickListener赋值,结合上面的代码,逻辑就清晰了。

猜你喜欢

转载自blog.csdn.net/u011598031/article/details/80580798