Android事件分发机制浅分析

我们知道Android的事件分发机制指的就是点击触摸屏幕时触摸事件的一个传递和消费。下面我们就看看是怎么回事?

1、onTouchListener、onTouchEvent、onClickListener 的优先级?
2、当View没有消费ACTION_DOWN事件,则不会接收其他MOVE,UP等事件,为什么?
3、ACTION_CANCEL何时执行?

Android事件分发机制浅分析_第1张图片
事件分发传递简易图

Android事件分发机制主要涉及的就是三个主要的函数dispatchTouchEvent()、onInterceptTouchEvent()(只有ViewGroup有这个函数)和onTouchEvent(),那接下来就看看这三个函数。这里我们分别对View和ViewGroup来分析:

View:

先来看看view的dispatchTouchEvent()函数,这个比较简单:

    public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
 //以上代码忽略,直接看重点,这里就是真正决定了是否消费这个事件
//这里判断条件事件侦听器是不是为null、有没有为这个View设置OnTouchListener事
//件、view是不是ENABLED状态以及设置的OnTouchListener中的返回值是不是为
//true。同时满足时则返回true代表消费了该事件
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //如果不满足以上条件时,则调用自身的onTouchEvent()函数,若onTouchEvent返回true,这代表消费这个事件

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

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

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

经过上面的分析,我们再来看看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();
    //判断是否是DISABLED状态
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }
    
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    //如果是可点击
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        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();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;
               //部分源代码略
        }
        return true;
    }
    return false;
}

从上面的MotionEvent.ACTION_UP事件是可以找到performClick()方法,在这个方法里会执行View设置的onClickListener方法。

通过上面这两个方法我们可以得出事件的执行顺序是dispatchTouchEvent(),如果view设置了onTouchListener()并且返回true,则onTouchEvent()不会执行,否则则执行,在onTouchEvent方法中的MotionEvent.ACTION_UP事件中会执行onClickListener事件。

ViewGroup:

还是先来看dispatchTouchEvent这个方法,因为这个方法是最先执行的而且一定会执行(当View没有消费ACTION_DOWN事件,则不会接收其他MOVE,UP等事件的原因也在其中)

public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
//根据隐私策略而来决定是否过滤本次触摸事件,
if (onFilterTouchEventForSecurity(ev)) {  // [见小节2.4.1]
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 发生ACTION_DOWN事件, 则取消并清除之前所有的触摸targets
        cancelAndClearTouchTargets(ev);
        resetTouchState(); // 重置触摸状态
    }

    // 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN;才进入此区域,主要功能是拦截器
    //只有发生过ACTION_DOWN事件,则mFirstTouchTarget != null;
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        //可通过调用requestDisallowInterceptTouchEvent,不让父View拦截事件
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        //判断是否允许调用拦截器
        if (!disallowIntercept) {
            //调用拦截方法
            intercepted = onInterceptTouchEvent(ev);  // [见小节2.4.2]
            ev.setAction(action);
        } else {
            intercepted = false;
        }
    } else {
        // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
        intercepted = true;
    }
    ...


    //不取消事件,同时不拦截事件, 并且是Down事件才进入该区域
    if (!canceled && !intercepted) {
        //把事件分发给所有的子视图,寻找可以获取焦点的视图。
        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(); // down事件等于0
            final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                    : TouchTarget.ALL_POINTER_IDS;

            removePointersFromTouchTargets(idBitsToAssign); //清空早先的触摸对象

            final int childrenCount = mChildrenCount;
            //第一次down事件,同时子视图不会空时
            if (newTouchTarget == null && childrenCount != 0) {
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);
                // 从视图最上层到下层,获取所有能接收该事件的子视图
                final ArrayList preorderedList = buildOrderedChildList(); // [见小节2.4.3]
                final boolean customOrder = preorderedList == null
                        && isChildrenDrawingOrderEnabled();
                final View[] children = mChildren;

                /* 从最底层的父视图开始遍历, ** 找寻newTouchTarget,并赋予view与 pointerIdBits; ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = customOrder
                            ? getChildDrawingOrder(childrenCount, i) : i;
                    final View child = (preorderedList == null)
                            ? children[childIndex] : preorderedList.get(childIndex);

                    // 如果当前视图无法获取用户焦点,则跳过本次循环
                    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;
                    }

                    newTouchTarget = getTouchTarget(child);
                    // 已经开始接收触摸事件,并退出整个循环。
                    if (newTouchTarget != null) {
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                    }

                    //重置取消或抬起标志位
                    //如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // [见小节2.4.4]
                        // 获取TouchDown的时间点
                        mLastTouchDownTime = ev.getDownTime();
                        // 获取TouchDown的Index
                        if (preorderedList != null) {
                            for (int j = 0; j < childrenCount; j++) {
                                if (children[childIndex] == mChildren[j]) {
                                    mLastTouchDownIndex = j;
                                    break;
                                }
                            }
                        } else {
                            mLastTouchDownIndex = childIndex;
                        }

                        //获取TouchDown的x,y坐标
                        mLastTouchDownX = ev.getX();
                        mLastTouchDownY = ev.getY();
                        //添加TouchTarget,则mFirstTouchTarget != null。 [见小节2.4.5]
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        //表示以及分发给NewTouchTarget
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                    ev.setTargetAccessibilityFocus(false);
                }
                // 清除视图列表
                if (preorderedList != null) preorderedList.clear();
            }

            if (newTouchTarget == null && mFirstTouchTarget != null) {
                //将mFirstTouchTarget的链表最后的touchTarget赋给newTouchTarget
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
    }

    // mFirstTouchTarget赋值是在通过addTouchTarget方法获取的;
    // 只有处理ACTION_DOWN事件,才会进入addTouchTarget方法。
    // 这也正是当View没有消费ACTION_DOWN事件,则不会接收其他MOVE,UP等事件的原因
    if (mFirstTouchTarget == null) {
        //没有触摸target,则由当前ViewGroup来处理
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        //如果View消费ACTION_DOWN事件,那么MOVE,UP等事件相继开始执行
        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;
        }
    }

    //当发生抬起或取消事件,更新触摸targets
    if (canceled
            || actionMasked == MotionEvent.ACTION_UP
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        resetTouchState();
    } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
        final int actionIndex = ev.getActionIndex();
        final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
        removePointersFromTouchTargets(idBitsToRemove);
    }
} //此处大括号,是if (onFilterTouchEventForSecurity(ev))的结尾

//通知verifier由于当前时间未处理,那么该事件其余的都将被忽略
if (!handled && mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}

与View不同的是ViewGroup里多了个onInterceptTouchEvent()方法,默认是不拦截的:

public boolean onInterceptTouchEvent(MotionEvent ev) {    
return false;
}

这里主要是对Android中事件分发进行了个简要的分析。了解了事件分发流程后,我们应对一些滑动冲突时就应该知道怎么办了。
例如:ViewPager中嵌套HorizontalScrollView,那么横向滑动的时候就会产生冲突,那么我们可以有两种解决方案:
1、ViewPager是继承了ViewGroup,我们在ViewPager中重写onInterceptTouchEvent和onTouchEvent方法去拦截滑动事件,这样HorizontalScrollView就不能横向滑动了;
2、在HorizontalScrollView中重写dispatchTouchEvent()方法,这里需要调用getParent().requestDisallowInterceptTouchEvent(true);方法,这个方法就是让父容器不要拦截事件,将事件分发给自己来处理。

总结一下:
一个事件传递一般是从ViewGroup的dispatchTouchEvent()方法往下分发,其中经过onInterceptTouchEvent(),看他的返回值(是否拦截事件),如果返回true这事件就不往下传递,由这个ViewGroup处理这个事件,这时,如果这个ViewGroup设置了onTouchListener事件则会调用onTouch()方法,否则onTouchEvent()方法将会调用。如果onInterceptTouchEvent()返回false,则将事件分发给子View的dispatchTouchEvent(),然后View执行他的onTouchEvent()方法,View的onTouchEvent默认是返回true的,除非是这个View是不可点击的,在onTouchEvent方法中的MotionEvent.ACTION_UP事件中会执行onClickListener事件。这里还要注意的是如果在onInterceptTouchEvent()中拦截的是down事件,那么它将不再继续往下分发,如果拦截的不是down事件,那么将会给下面的控件传递一个cancel事件。

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