Android event distribution source code analysis (based on API31)

event distribution

Where to start?

Learning a piece of knowledge generally follows a sequence, and sometimes the correct learning sequence can achieve twice the result with half the effort. So here I plan to start with the event distribution of View.

Why start with View's event distribution? Because it is the "end point" of event distribution in a general sense, that is, the final link. Let's not consider the complex situations passed down layer by layer here. We only start with how the View itself distributes events. This is relatively simple and does not need to consider various complicated situations. This is also the most basic part. After understanding this basic content, I will disassemble the ViewGroup and it will be easier to understand.

What framework should we use to study?

To learn event distribution, I hope to learn the following framework:

  1. Where did the incident come from?
  2. What types of events are there?
  3. What impact did the outcome of the incident have?
  4. What can I do with this process? Or achieve something?

It can be summarized as: Who am I? Where do I come from? Where am I going?

focus

About the event

An event is an event stream consisting of a series of events.

Start from ACTION_DOWN, go through N ACTION_MOVEs in the middle, and end with ACTION_UP or ACTION_CANCEL.
Generally, when we discuss the delivery of events, we don’t just discuss a single event, but consider the entire event stream.

Methods related to event distribution
During the event distribution process, there are a total of 3 methods that require our attention:

  1. dispatchTouchEvent
  2. onInterceptTouchEvent (only ViewGroup has this method)
  3. onTouchEvent
Here is an overview of what these three methods do:

dispatchTouchEvent
is responsible for deciding who will handle this event
onTouchEvent
determines how to handle this event events.
onInterceptTouchEvent
determines whether the ViewGroup should continue to distribute events to child nodes (which may be View or ViewGroup).

All event distribution is based on dispatchTouchEvent as the relative starting point. In this method, it is decided whether to intercept or whether to trigger onTouchEvent or OnTouchListener.

Next, I will first learn about View's event distribution.

View event distribution

Let’s first clarify the issues mentioned in the framework:

  1. Where did the incident come from? ViewGroup, passed to the dispatchTouchEvent of the current View.
  2. What types of events are there? DOWN, MOVE, UP, CANCEL
  3. What is the impact of the outcome of the event?
    The result of the event is actually the return value of the dispatchTouchEvent method of the current View. It will determine whether the parent layout continues to search for the next View that can handle the event. The meaning of its return value is: whether I (current View) want to process the current event.
  4. What can I do with this process?
  5. First look at what Android has done natively?
    1. Handle click events: pressing and lifting once is recorded as a click event. (The OnClickListener we set is triggered in this case)
    2. Process the long press event: When the duration of a press exceeds a threshold, the long press event is triggered. (LongClickListener is triggered in this case)
  6. What is Button used for?
    1. Trigger ripple animation effects when pressed, background color switching after pressing, etc.
    2. Trigger click events when lifted, triggering corresponding behaviors.
  7. What can I do?
    1. During dispatchTouchEvent, I can return false directly without performing click event processing.
    2. During OnTouchEvent, I can change myself based on the current touch position. The position of the View
    3. During OnTouchEvent, I can create an OnFiveSecondClickListener myself based on the length of the press time
    4. I can set an OnTouchListener and return false, Do your own operations inside, but do not take over the original event processing. For example, when the View is touched, I reduce the transparency of another View, but I do not want to affect the original View click behavior and long press behavior.
    5. Wait, wait, you can imagine whatever you want, as long as it is related to touch, you can do it through these two methods.

View event distribution source code analysis


// 事件分发的入口,从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;
}

Summarize

View type component receives an event and starts execution using dispatchTouchEvent.
Then distribute the event according to the situation:

  1. If OnTouchListener is set by itself, the onTouch method of OnTouchListener is executed.
  2. Determine whether to execute its own onTouchEvent method based on the return value of OnTouchListener's OnTouch method.
  3. If the return value of onTouch is false, execute its own onTouchEvent method
  4. The return value of onTouch is true and the result is returned directly.
  5. If OnTouchListener is not set by itself, it executes its own onTouchEvent and returns the result.
  6. The return result of dispatchTouchEvent will tell the parent layout whether the current View handles subsequent events. If true, it means that the current View will handle this event, otherwise it will not handle the current event.
  7. There is no onInterceptTouchEvent method in View and will not be discussed.

ViewGroup event distribution

Let’s first clarify the issues in the framework

  1. Where did the incident come from?
  2. It also comes from ViewGroup and is passed to the dispatchTouchEvent method of the current ViewGroup.
  3. Of course, the initial event is passed from the framework layer and native layer to DecorView, and then it searches for the View that can handle the current click event level by level.
  4. What types of events are there? DOWN, MOVE, UP, CANCEL
  5. What is the impact of the outcome of the event?
    The result of the event is actually the return value of the dispatchTouchEvent method of the current View. It will determine whether the parent layout continues to search for the next View that can handle the event. The meaning of its return value is: whether I (current View) want to process the current event.
  6. What can I do with this process?
  7. First look at what Android has done natively?
    1. First of all, make it clear that ViewGroup is also a View. It is also possible to perform click events and long press events on ViewGroup.
    2. In the dispathTouchEvent of ViewGroup, look for a View that can handle the current event flow.
    3. Before looking for a sub-View that can handle the current event flow, it will first Determine whether to handle the series of events yourself. If you handle them yourself, they will not be passed to the child View. (The return value of onInterceptTouchEvent is true, which means that the event will not continue to be passed to the sub-View, but whether you want to handle it yourself still depends on the return value of OnTouchListener or onTouchEvent)
    4. Handle sliding-related behaviors (ScrollView, RecyclerView~).
  8. What can we do?
    1. Can handle sliding conflicts and nested scrolling
    2. Can decide to let specific sub-Views handle events
    3. You can intercept events and do some algorithms to coordinate the behavior of the sub-View and the current View.

ViewGroup event distribution source code analysis

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

Other designs in ViewGroup

TouchTarget

structure
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;
        
    }
Design ideas
  1. Belongs to a one-way linked list structure
  2. Recycling pool mechanism to facilitate memory reuse

In fact, it is very similar to the design idea of ​​Message, wouldn’t you say?

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

In ViewGroup, the requestDisallowInterceptTouchEvent method is provided. After calling this method, it will no longer intercept during the event flow delivery process, but when the event flow ends, The next event stream will still be intercepted.

Summarize

  1. When distributing events in ViewGroup, first determine whether to intercept the event. If the event is intercepted, handle it yourself.
  2. ViewGroup does not intercept the event, and ViewGroup looks for sub-Views that can handle the event. In fact, as long as the clicked position is within the actual range of the corresponding sub-View, its dispatchTouchEvent will be called, and then based on its return value, it will be decided whether to continue looking for the next processable sub-View.
  3. If no sub-View is found that can handle this event, the event will be handled by itself. If you still return false, the event will continue to be handed over to the parent layout of the current ViewGroup for processing.

Guess you like

Origin blog.csdn.net/yztbydh/article/details/129929042