Android 事件分发源码解析(基于API31)

事件分发

从何说起?

学习一个知识一般都是有顺序的,有些时候正确的学习顺序能起到事半功倍的效果。那这里我打算从View的事件分发开始说起。

为什么是从View的事件分发开始说起呢?因为它是在一般意义上的事件分发“终点”,也就是最后环节。我们这里先不考虑一层一层传递下来的复杂情况,只从View 本身如何分发事件说起,这是一个较为简单,不需要考虑各种复杂的情况,这也是最基础的部分。当理解了这部分基础的内容,我再对ViewGroup进行拆解,会更容易理解一些。

以一个什么框架去学习?

学习事件分发,我希望以下面一个框架去学习:

  1. 事件从何而来?
  2. 事件有哪些类型?
  3. 事件的结果有什么影响?
  4. 我能利用这个过程做些什么?或者实现些什么?

可以总结为:我是谁?我从哪里来?我要到哪里去?

重点

关于事件

事件是由一系列事件组成的事件流。

从ACTION_DOWN为起点,中间经历N个ACTION_MOVE,以ACTION_UP或ACTION_CANCEL为结束。
一般我们讨论事件的传递,不止讨论单一事件,而是考虑,而是整个事件流。

事件分发相关的方法
在事件分发过程中,总共有3个方法需要我们注意:

  1. dispatchTouchEvent
  2. onInterceptTouchEvent (只有ViewGroup才有此方法)
  3. onTouchEvent
这里先总览一下这三个方法都要做些什么:

dispatchTouchEvent
负责决定由谁来处理此次事件
onTouchEvent
决定了事件的如何处理此次事件。
onInterceptTouchEvent
决定了在ViewGroup是否要继续往子节点(可能为View,也可能为ViewGroup)继续分发事件。

所有的事件分发,都是dispatchTouchEvent为相对起点,在该方法中,决定是否拦截,或者是否触发onTouchEvent or OnTouchListener。

接下来,我先对View 的事件分发进行学习。

View的事件分发

先明确一下框架中提到的问题:

  1. 事件从何而来? ViewGroup,传递到当前View的dispatchTouchEvent。
  2. 事件有哪些类型?DOWN、MOVE、UP、CANCEL
  3. 事件的结果有什么影响?
    事件的结果其实就是当前View的dispatchTouchEvent方法的返回值,它会决定父布局是否继续找寻下一个可处理事件的View。它的返回值含义是:我(当前View)是否要对当前事件进行处理。
  4. 我能利用这个过程中做些什么?
  5. 首先看Android原生做了些什么?
    1. 处理点击事件:一次按下抬起,记为一次点击事件。(我们设置的OnClickListener是在这种情况下被触发)
    2. 处理长按事件:当一次按下时长超过一个阈值,则触发长按事件。(LongClickListener 是在这种情况下被触发)
  6. Button用来做了什么?
    1. 按下的时候触发波纹动画效果、按下后背景颜色切换等
    2. 抬起时触发点击事件,触发对应行为。
  7. 我可以做些什么?
    1. 在dispatchTouchEvent时,我可以直接返回false,不进行点击事件处理
    2. 在OnTouchEvent时,我可以根据当前触摸位置改变我自己View的位置
    3. 在OnTouchEvent时,我可以根据按下时间长短,自己制造个 OnFiveSecondClickListener
    4. 我可以设置一个OnTouchListener,返回false,里边做一下自己的操作,但是不接管原本的事件处理,比如当View被触摸时,我降低另一个View的透明度,但又不想影响原本的View点击行为,长按行为。
    5. 等等等等,你可以任意想象,只要是与触摸有关的,你想实现的,都可以通过这个两个方法搞一搞。

View 的事件分发源码分析


// 事件分发的入口,从ViewGroup调用到此处。
public boolean dispatchTouchEvent(MotionEvent event) {
    
    
    // 此处剔除掉次要代码
    
    // 定于用于存储此次事件的结果
    boolean result = false;
    
    // 获取当前事件的类型 
    final int actionMasked = event.getActionMasked();
    
    // 如果是按下事件
    if (actionMasked == MotionEvent.ACTION_DOWN) {
    
    
        // 因为按下事件是事件传递的起点,所以此处,出现「按下」事件,则需要停止NestedScroll行为
        stopNestedScroll();
    }

    // 判断是否可以处理此次事件,主要是判断当前View所处Window的状态,是否在展示中
    if (onFilterTouchEventForSecurity(event)) {
    
    
        if ((mViewFlags & ENABLED_MASK) == ENABLED && 
                // 此方法处理,由鼠标 输入时的拖拽滑块行为,
                // 如果返回值为true,则返回true,由鼠标拖拽处理后面行为
                handleScrollBarDragging(event)) {
    
    
            result = true;
        }
        // 获取是否设置了OnTouchListener
        ListenerInfo li = mListenerInfo;
        
        // 满足三个条件则  确认消费此次事件:
        // 1. 设置了 OnTouchListener
        // 2. 当前View 状态为Enable
        // 3. OnTouchListener.onTouchEvent 返回值为 true  
        
        // 由以上可知,只要设置了OnTouchListener
        // OnTouchListener的onTouchEvent一定会执行,无论返回true、false
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
    
    
            result = true;
        }
        
        // 当 onTouchListener 为null  或   OnTouchListener.onTouchEvent返回值为false
        // 执行自身的 onTouchEvent()
        if (!result && onTouchEvent(event)) {
    
    
            result = true;
        }
    }
    
    
    // 如果 此次事件为抬起、或者取消,或  (按下事件,但是自身没有处理此次事件)
    // 则结束NestedScroll
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
    
    
        stopNestedScroll();
    }

    // 返回自身处理结果
    // 这个返回值会影响什么?
    // 如果返回值为 true,则认为当前View消费此次事件,父View不会继续往下分发。
    // 返回值为 true,则认为当前View不处理此次事件,父View会继续将事件传递给下一个View处理
    return result;
}


// 由上边的 dispatchTouchEvent 可知,当OnTouchListener为空,或者OnTouchListener.onTouch()返回
// 值为false时,会调用自身的 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;
    // 根据当前View设置的Flag返回是否消费事件
    // 1. 是否是Enable
    // 2. Disable的时候是否可点击
    if ((viewFlags & ENABLED_MASK) == DISABLED
            && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
    
    
        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) {
    
    
        // 且代理的onTouchEvent返回值为true,则返回消费事件
        if (mTouchDelegate.onTouchEvent(event)) {
    
    
            return true;
        }
    }

    // 在View中,如果clickable 为false,则会直接返回 不消费事件
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    
    
        switch (action) {
    
    
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
    
    
                    handleTooltipUp();
                }
                if (!clickable) {
    
    
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                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();
                            }
                            // 尝试将点击行为通过Handler发送到主线程, 如果发送失败,则调用内部点击事件
                            if (!post(mPerformClick)) {
    
    
                                // 在View 内部执行点击方法:调用OnClickListener的onClick方法
                                performClickInternal();
                            }
                        }
                    }

                    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;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
    
    
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                mHasPerformedLongPress = false;

                if (!clickable) {
    
    
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    break;
                }

                if (performButtonActionOnTouchDown(event)) {
    
    
                    break;
                }

                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();

                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
    
    
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
    
    
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
    
    
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            // 如果接收到了CANCEL 事件
            // 如 ViewParent在某个时机拦截了事件,事件将不会继续传递给子View,会先给子View传递一个Cancel事件
            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:
                if (clickable) {
    
    
                    drawableHotspotChanged(x, y);
                }

                final int motionClassification = event.getClassification();
                final boolean ambiguousGesture =
                        motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
                int touchSlop = mTouchSlop;
                if (ambiguousGesture && hasPendingLongPressCallback()) {
    
    
                    if (!pointInView(x, y, touchSlop)) {
    
    
                        // The default action here is to cancel long press. But instead, we
                        // just extend the timeout here, in case the classification
                        // stays ambiguous.
                        removeLongPressCallback();
                        long delay = (long) (ViewConfiguration.getLongPressTimeout()
                                * mAmbiguousGestureMultiplier);
                        // Subtract the time already spent
                        delay -= event.getEventTime() - event.getDownTime();
                        checkForLongClick(
                                delay,
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    touchSlop *= mAmbiguousGestureMultiplier;
                }

                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, touchSlop)) {
    
    
                    // Outside button
                    // Remove any future long press/tap checks
                    removeTapCallback();
                    removeLongPressCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
    
    
                        setPressed(false);
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                }

                final boolean deepPress =
                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                if (deepPress && hasPendingLongPressCallback()) {
    
    
                    // process the long click action immediately
                    removeLongPressCallback();
                    checkForLongClick(
                            0 /* send immediately */,
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }

                break;
        }

        return true;
    }

    return false;
}

总结

View类型的组件,接收到一个事件,是用dispatchTouchEvent开始执行。
然后分情况进行事件分发:

  1. 若自身设置了OnTouchListener,则执行OnTouchListener的onTouch方法。
  2. 根据OnTouchListener的OnTouch方法的返回值判断是否执行自身的onTouchEvent方法
  3. onTouch 返回值为false,则执行自身的onTouchEvent方法
  4. onTouch返回值为true,直接返回结果。
  5. 若自身没有设置OnTouchListener,则执行自身的onTouchEvent并返回结果。
  6. dispatchTouchEvent的返回结果会告诉父布局,当前View是否处理后续事件。为true,意味着当前View会处理此次事件,否则不处理当前事件。
  7. 在View中没有onInterceptTouchEvent方法,不做讨论。

ViewGroup的事件分发

还是先明确框架中的问题

  1. 事件从何而来?
  2. 也是从ViewGroup而来,传递当当前ViewGroup的dispatchTouchEvent方法中
  3. 当然最初的事件是从框架层,native层传递过来的,传递到DecorView,然后一级一级寻找能够处理当前点击事件的View
  4. 事件有哪些类型?DOWN、MOVE、UP、CANCEL
  5. 事件的结果有什么影响?
    事件的结果其实就是当前View的dispatchTouchEvent方法的返回值,它会决定父布局是否继续找寻下一个可处理事件的View。它的返回值含义是:我(当前View)是否要对当前事件进行处理。
  6. 我能利用这个过程中做些什么?
  7. 首先看Android原生做了些什么?
    1. 先明确,ViewGroup也是View,如果对ViewGroup进行点击事件、长按事件的操作也可以。
    2. ViewGroup 的dispathTouchEvent中,寻找一个可以处理当前事件流的View
    3. 在寻找能够处理当前事件流的子View之前,会先判断是否要自己处理系列事件,若是自己处理,就不会继续传递给子View了。(onInterceptTouchEvent 返回值为true,表示不继续给子View传递事件,但是否要自己处理,还是要看OnTouchListener 或者 onTouchEvent 的返回值)
    4. 处理滑动相关行为(ScrollView,RecyclerView~)。
  8. 我们能做一些什么?
    1. 可以处理滑动冲突,嵌套滚动
    2. 可以决定让特定的子View处理事件
    3. 可以通过拦截事件,做一些算法,来协调子View与当前View的行为。

ViewGroup事件分发源码分析

dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
    // ......
    // 标记此次事件是否被处理了,在方法的最后返回该值。
    // 若该值为false,则相当于告知父布局,当前View以及子View都无法处理这次事件,交由父布局继续处理。
    // 若该值为true,则相当于告知父布局,当前View或者子View中有“人”处理了系列事件,
    // 父布局(一般情况下)不会再继续分发事件,而是交由当前处理事件的View进行处理
    boolean handled = false;
    
    // 处理此次事件是否在安全域内:主要是根据当前Window的状态来判断,大多是情况下,我们不需要考虑。
    if (onFilterTouchEventForSecurity(ev)) {
    
    
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 前面我们说过,每一次事件流都是由按下开始的,所以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.
            // 每次一次事件流中,都会形成一个TouchTarget链表,保存了触摸事件的目标相关信息,后面我们专门说一下。
            // 此处是将之前事件流中所创建的链表,传递Cancel事件,并清除触摸状态。
            cancelAndClearTouchTargets(ev);
            // 重置触摸状态,回收建立的TouchTarget链表
            resetTouchState();
        }

        // Check for interception.
        // 检查是否要拦截此次事件
        final boolean intercepted;
        // 按下事件,或者 之前建立过 FirstTouchTarget链表。
        // 为什么要这么判断?
        // 1. 按下事件是起点,所以可以判断是否拦截。
        // 2. mFirstTouchTarget != null,证明当前ViewGroup中有可以处理事件的子View,
        //   于是才有拦截的必要, 如果没有人可以处理事件,拦截它干嘛呢?所以此处我们知道了,
        //   onInterceptTouchEvent,不是任何时候都能执行的。
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
    
    
            // 获取当前ViewGroup的 标志位:FLAG_DISALLOW_INTERCEPT 是否被置为1,
            // disallowIntercept 标志可以动过调用ViewGroup.requestDisallowInterceptTouchEvent进行设置
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            // 如果 没有被标记为 「不可以拦截事件」
            if (!disallowIntercept) {
    
    
                // 则执行onInterceptTouchEvent();
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
    
    
                // 否则,拦截值为false。
                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;
        }

        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
    
    
            ev.setTargetAccessibilityFocus(false);
        }

        // 检查是否当前事件是「取消」类型
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
                && !isMouseEvent;
        // 此处开始创建新的TouchTarget链表        
        TouchTarget newTouchTarget = null;
        // 初始化是否分发至新的TouchTarget 的flag
        boolean alreadyDispatchedToNewTouchTarget = false;
        // 如果不是取消事件,并且没有被拦截,执行找寻TouchTarget 的行为。
        if (!canceled && !intercepted) {
    
    
            // If the event is targeting accessibility focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            // 首先判断 当前事件是不是被标记为:「无障碍焦点」“优先”,
            // 如果被标记为「无障碍焦点」优先,则会先寻找带有 「无障碍焦点」子View
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;

            // 1. 当前事件 是按下事件
            // 2. 当前事件是 多点模式下的按下事件
            // 3. 当前事件是  悬停移动事件(比如鼠标放在按钮上但不按下时的移动)
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    
    
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                // 清除对应id下的 TouchTargets,如果是单点触摸,一般就是清除所有
                removePointersFromTouchTargets(idBitsToAssign);

                // 当前子节点的个数
                final int childrenCount = mChildrenCount;
                // 如果当前新的 TouchTarget为空,并且子View数目不为0
                if (newTouchTarget == null && childrenCount != 0) {
    
    
                    // 获取当前事件的x坐标
                    final float x =
                            isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    // 获取当前事件的y坐标
                    final float y =
                            isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    // 创建一个 排序好的子View列表,我们在此处可以利用重写 buildTouchDispatchChildList 方法,
                    // 提供我们想要View处理Touch事件的优先级。
                    // 默认实现为根据Z值,返回排序好的子View列表,如果没有子View设置了Z值,此处返回的值为null
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    // 是否是自定义顺序
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                            
                    final View[] children = mChildren;
                    
          [循环开始] for (int i = childrenCount - 1; i >= 0; i--) {
    
    
                        // 获取当前 index对应的 child 的index
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        // 获取当前 index对应的child
                        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;
                        }

                        // 如果当前child无法接收事件: 不可见,或者动画为空
                        if (!child.canReceivePointerEvents()
                                    // 当前按下点,不在View的范围内
                                || !isTransformedTouchPointInView(x, y, child, null)) {
    
    
                            ev.setTargetAccessibilityFocus(false);
                            // 继续寻找
                            continue;
                        }
                        
                        // 在当前的TouchTarget链表中查找是否存在是当前child的TouchTarget
                        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);
                        // 分发转换后的事件,如果结果为true
                        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();
                            // 创建新的TouchTarget
                            // 实际上是讲当前TouchTarget放置到了链表头。
                            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();
                }
                
                // 如果 没有找到新的TouchTarget, 并且TouchTarget链表头不为空, 重置多点触控状态
                if (newTouchTarget == null && mFirstTouchTarget != null) {
    
    
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
    
    
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
    
    
            // No touch targets so treat this as an ordinary view.
            // 如果没有找到能处理当前事件的目标,执行dispatchTransformedTouchEvent
            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;
            // 如果Target 存在值,
            while (target != null) {
    
    
                // 获取下一个节点
                final TouchTarget next = target.next;
                // 如果已经分发到新节点了,并且 当前的target就是,那个新节点。
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    
    
                    // 标记为已处理
                    handled = true;
                } else {
    
    
                    // 否则,重置目标节点的状态
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // 将cancel事件传递给那个目标
                    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;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        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 (!handled && mInputEventConsistencyVerifier != null) {
    
    
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

dispatchTransformedTouchEvent

// 在上边的dispatchTouchEvent中,会调用到当前方法。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        @Nullable 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);
        // 如果 未传递child,则证明事件交给自己处理。
        if (child == null) {
    
    
            handled = super.dispatchTouchEvent(event);
        } else {
    
    
            // 否则调用 子View的 dispatchTouchEvent
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        // 直接返回结果
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    if (newPointerIdBits == 0) {
    
    
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    final MotionEvent transformedEvent;
    // 如果触摸模式并没有发生变化:触摸手指数没发生变化。满足以下条件
    // 1. 并没有由单点触摸变换成多点触摸
    // 2. 没有由多点触摸,切换出单点触摸
    // 3. 没有增加或减少手指
    if (newPointerIdBits == oldPointerIdBits) {
    
    
        // 如果child 为空,或者 子View拥有变换矩阵
        if (child == null || child.hasIdentityMatrix()) {
    
    
            if (child == null) {
    
    
                // 如果child为空,则调用自己的dispatchTouchEvent。具体实现在View中
                handled = super.dispatchTouchEvent(event);
            } else {
    
    
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                // 否则,调用子View 的 dispatchTouchEvent
                handled = child.dispatchTouchEvent(event);

                event.offsetLocation(-offsetX, -offsetY);
            }
            // 返回处理结果
            return handled;
        }
        // 转换事件
        transformedEvent = MotionEvent.obtain(event);
    } else {
    
    
        // 因为触摸点数发生变化,通过split转换事件
        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);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

onInterceptTouchEvent


//ViewGroup中的 onInterceptTouchEvent 实现很简单,需要同时满足以下条件
// 1. 在ActionDown时
// 2. 事件源是鼠标
// 3. 按钮按下
// 4. 在滚动条上
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;
}

ViewGroup中的其他设计

TouchTarget

结构
private static final class TouchTarget {
    
    
    // 最大回收数
    private static final int MAX_RECYCLED = 32;
    // 锁
    private static final Object sRecycleLock = new Object[0];
    // 回收池
    private static TouchTarget sRecycleBin;
    // 已回收的数目
    private static int sRecycledCount;
    
    public static final int ALL_POINTER_IDS = -1; // all ones

    // 目标View
    @UnsupportedAppUsage
    public View child;

    // 触控点数标志
    public int pointerIdBits;

    // 下一个触摸目标节点
    public TouchTarget next;
        
    }
设计思路
  1. 属于单向链表结构
  2. 回收池机制,方便内存复用

其实与Message的设计思路很像,你说是不是?

DisallowIntercept

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
    

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
    
    
        // We're already in this state, assume our ancestors are too
        return;
    }

    if (disallowIntercept) {
    
    
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
    
    
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
    
    
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

在ViewGroup中,提供了requestDisallowInterceptTouchEvent 方法,调用该方法后,此次事件流传递过程中,它将不再拦截,但是当此次事件流结束后,下一次事件流,依然会做拦截。

总结

  1. ViewGroup中分发事件时,先判断是否要拦截事件,如果拦截事件,则交由自己处理。
  2. ViewGroup未拦截事件,ViewGroup寻找能够处理事件的子View。其实只要点击的位置在对应的子View的实际范围内,就会调用其dispatchTouchEvent,然后根据其返回值,决定是否继续寻找下一个可处理的子View。
  3. 如果没有找到可以处理此次事件的子View,会将事件交由自己处理。如果自己仍然返回了false,则事件会继续交会给当前ViewGroup的父布局进行处理。

猜你喜欢

转载自blog.csdn.net/yztbydh/article/details/129929042
今日推荐