美文网首页
Android事件分发机制

Android事件分发机制

作者: BrotherTree | 来源:发表于2019-03-01 15:11 被阅读0次

    基础知识

    • 事件分发的对象

    事件分发的对象是 点击事件(Touch事件),当用户触摸屏幕(View或者ViewGroup派生控件)时,将会产生点击事件(Touch事件)
    Touch事件的相关细节,如触摸位置、时间等被封装为MotionEvent对象

    • 事件类型

    MotionEvent.ACTION_DOWN----按下View(所有事件的开始)
    MotionEvent.ACTION_MOVE----滑动View
    MotionEvent.ACTION_UP----抬起View
    MotionEvent.ACTION_CANCEL----结束事件(所有事件的开始)

    • 事件列
    image.png

    一般,事件列都是以DOWN事件开始,UP事件结束,中间无数个MOVE事件

    • 事件分发的本质

    将点击事件MotionEvent传递到具体某个View以及处理该事件的整个过程

    • 事件分发在哪些对象间进行传递
    image.png

    Activity----控制生命周期,处理事件
    View----所有UI组件的基类
    ViewGroup----一组View的集合,本身也是View的子类

    • 事件分发的顺序

    Activity -> ViewGroup -> View

    • 事件分发由哪些主要方法协作完成

    1、dispatchTouchEvent()

    分发(传递)事件
    当点击事件能够传递到当前View,该方法就会被调用

    2、onTouchEvent()

    处理点击事件
    在dispatchTouchEvent方法内部调用

    3、onInterceptTouchEvent()

    判断是否拦截事件
    该方法只存在于ViewGroup中,在ViewGroup的dispatchTouchEvent方法内部调用。若是重新该方法为true,则表示父容器拦截了事件,子容器View将无法处理该事件

    源码流程分析

    当点击事件发生后,事件先传到Activity、再传到ViewGroup、最终传到View

    image.png

    从上图可知,要充分理解事件分发机制,本质上是需要理解:
    1、Activity对点击事件的分发机制
    2、ViewGroup对点击事件的分发机制
    3、View对点击事件的分发机制
    下面我们将对这三个分发机制进行逐一分析

    • Activity对点击事件的分发机制

    当点击事件发生时,最先响应的是Activity的dispatchTouchEvent方法,那么为什么是它最先响应呢?Activity之dispatchTouchEvent前传

        public boolean dispatchTouchEvent(MotionEvent ev) {
            // 事件都是以按下事件(DOWN)为开始,所以此处为true
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction(); // 分析1
            }
            if (getWindow().superDispatchTouchEvent(ev)) { // 分析2
                return true;
            }
            return onTouchEvent(ev); // 分析3
        }
    
    • 分析1(当前在Activity类)
    onUserInteraction();
    // onUserInteraction方法是一个空方法,主要作用是实现屏保功能
    // 当此activity在栈顶时,触屏点击按home,back,recent键等都会触发此方法
    public void onUserInteraction() {}
    
    • 分析2(当前在Activity类)
    getWindow().superDispatchTouchEvent(ev)
    
    // getWindow方法返回 mWindow,它是一个PhoneWindow对象
    public Window getWindow() {
        return mWindow;
    }
    private Window mWindow;
    // PhoneWindow为Window抽象类的子类
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    
    • 分析2(当前在Window类)
    // 抽象方法,由PhoneWindow类实现
    public abstract boolean superDispatchTouchEvent(MotionEvent event);
    
    • 分析2(当前在PhoneWindow类)
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        // 调用DecorView类中的superDispatchTouchEvent方法
        return mDecor.superDispatchTouchEvent(event);
    }
    private DecorView mDecor;
    
    • 分析2(当前在DecorView类)
    // 调用父类的dispatchTouchEvent方法
    // DecorView是所有UI界面的父类
    // DecorView的父类是FrameLayout,但FrameLayout并没有实现dispatchTouchEvent方法
    // 所以继续向上寻找到FrameLayout的父类,即调用ViewGroup类的dispatchTouchEvent方法
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    

    此时,事件被分发到了ViewGroup

    • 分析3(当前在Activity类)
    return onTouchEvent(ev); 
    // 当点击事件未被任何一个View接收并处理就会执行onTouchEvent方法
    // 场景如:发生在Window边界外的触摸事件
    public boolean onTouchEvent(MotionEvent event) {
        // 点击事件发生在Windows边界外,返回true
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }
    
    • 分析3(当前在Window类)
    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        // 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
        final boolean isOutside =
            event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
            if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
                // 说明事件在边界外,即 消费事件
                return true;
            }
            // 未消费(默认)
            return false;
        }
    
    • Activity对点击事件的分发机制总结
    image.png

    至此,流程已经走到了ViewGroup类中的dispatchTouchEvent方法,继续分析~

    • ViewGroup对点击事件的分发机制
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            // 这是一个验证事件完整性的一个类,一会会看到在本方法结束的地方也有这个类出现
            // 防止event事件在分发过程中不一致,这个类还有记录的功能
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
    
            // If the event targets the accessibility focused view and this is it, start
            // normal event dispatch. Maybe a descendant is what will handle the click.
            // 检测无障碍焦点
            if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
                ev.setTargetAccessibilityFocus(false);
            }
    
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {// 分析1
                // 获取Touch Action
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) { // 分析2
                    // 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();
                }
    
                // Check for interception.
                // 判断事件是否需要拦截
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    // 判断是否运行不允许拦截
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    // 如果允许拦截
                    if (!disallowIntercept) {
                        intercepted = onInterceptTouchEvent(ev); // 分析3
                        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;
                }
    
                // 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);
                }
    
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {// 分析4
    
                    // 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 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(); // 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.
                        removePointersFromTouchTargets(idBitsToAssign);
    
                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            // Find a child that can receive the event.
                            // Scan children from front to back.
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            for (int i = childrenCount - 1; i >= 0; i--) { // 分析8
                                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;
                                }
    
                                // 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();
                        }
    
                        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) {// 分析5
                    // No touch targets so treat this as an ordinary view.
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {// 分析6
                    // 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;
                    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;
                    }
                }
    
                // 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;
        }
    
    • 分析1(当前在View类)
    // 检测是否分发Touch事件(判断窗口是否被遮挡住)
    // 如果该 Touch 事件没有被窗口遮挡,则继续后续逻辑
    // 一般情况下都会返回true
    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                    && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
                // Window is obscured, drop this touch.
                return false;
            }
            return true;
        }
    
    • 分析2(当前在ViewGroup类)
    // 判断当前是否是ACTION_DOWN,刚进来的时候肯定是ACTION_DOWN
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
    private void cancelAndClearTouchTargets(MotionEvent event) {
        ....
        clearTouchTargets();        
        ....
    }
    
    // 将mFirstTouchTarget置为null
    // 回收TouchTarget对象
    // 清空所有接收触摸事件View的引用
    private void clearTouchTargets() {
        // TouchTarget和Handler中的Message一样,都是一个单向链表,链式结构,通过next来访问下一个元素
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        // 默认允许拦截事件
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        // 默认视图不滚动
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
    
    • 分析3(当前在ViewGroup类中)
    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;
        }
        // 默认返回false
        return false;
    }
    

    通常,如果ViewGroup或其派生类重写了onInterceptTouchEvent并返回为true,这说明父容器需要拦截当前点击事件,即onInterceptTouchEvent为true。

    代码到了这里,出现了一个分支:
    即,如果onInterceptTouchEvent为true,则执行 [分析4],否则不执行。

    接下来我们先分析父容器拦截事件的情况。

    • 分析5(当前在ViewGroup类)
    // 此时 mFirstTouchTarget必定为null,因为之前调用cancelAndClearTouchTargets方法时做了清空处理
    // mFirstTouchTarget必定为null,意味着 没有任何 view 消费该 Touch 事件
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        // ev: event事件类型
        // canceled:是否是cancel,因为没有取消,所以当前是false
        // null:View child字段传了null
        // TouchTarget.ALL_POINTER_IDS:位置
        // viewGroup 会调用dispatchTransformedTouchEvent处理,但是child==null,会调用View#dispatchTouchEvent处理
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS); // 分析5-1
    }
    
    • 分析5-1(当前在ViewGroup类)
        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
    
            // Canceling motions is a special case.  We don't need to perform any transformations
            // or filtering.  The important part is the action, not the contents.
            final int oldAction = event.getAction();
            // cancel为false,下方if语句块不会执行
            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;
            }
    
            // 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;
            // 下面if语句块是一个新老事件的处理,也不会进入
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
    
                        handled = child.dispatchTouchEvent(event);
    
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
                transformedEvent = event.split(newPointerIdBits);
            }
    
            // Perform any necessary transformations and dispatch.
            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;
        }
    

    由于child传进来为null,所以调用了super.dispatchTouchEvent方法,而ViewGroup类的父类是View,所以这里进入并执行了View类中的dispatchTouchEvent方法。

    当前 handler返回为true(逻辑见View的事件分发机制流程分析),则回到 [分析5]处, [分析5]的代码也会返回true,则会回到Activity的dispatchEvent方法中。

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

    getWindow().superDispatchTouchEvent(ev)返回为true,至此,在“父容器拦截事件”的情况下的流程已经走完。

    下面分析父容器不拦截子View事件的情况
    再次展示VIewGroup的dispatchTouchEvent方法的代码

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            // 这是一个验证事件完整性的一个类,一会会看到在本方法结束的地方也有这个类出现
            // 防止event事件在分发过程中不一致,这个类还有记录的功能
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
    
            // If the event targets the accessibility focused view and this is it, start
            // normal event dispatch. Maybe a descendant is what will handle the click.
            // 检测无障碍焦点
            if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
                ev.setTargetAccessibilityFocus(false);
            }
    
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {// 分析1
                // 获取Touch Action
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) { // 分析2
                    // 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();
                }
    
                // Check for interception.
                // 判断事件是否需要拦截
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    // 判断是否运行不允许拦截
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    // 如果允许拦截
                    if (!disallowIntercept) {
                        intercepted = onInterceptTouchEvent(ev); // 分析3
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        // 不拦截状态下,intercepted = 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);
                }
    
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {// 分析4
    
                    // 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 childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                            ? findChildWithAccessibilityFocus() : null;
    
                    // 因为还是处于按下状态,所以当前为ACTION_DOWN
                    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.
                        removePointersFromTouchTargets(idBitsToAssign);
                        // 对于当前事件,有处理权限的View的个数(肯定不为0)
                        final int childrenCount = mChildrenCount;
                        // 因为newTouchTarget还未赋值,所以为null
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            // Find a child that can receive the event.
                            // Scan children from front to back.
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();// 分析7
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final int childIndex = getAndVerifyPreorderedIndex(
                                        childrenCount, i, customOrder);
                                final View child = getAndVerifyPreorderedView(
                                        preorderedList, children, childIndex);
    
                                // If 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;
                                }
    
                                // 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();
                        }
    
                        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) {// 分析5
                    // No touch targets so treat this as an ordinary view.
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {// 分析6
                    // 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;
                    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;
                    }
                }
    
                // 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;
        }
    

    父容器不拦截子View事件,intercepted 为 false,canceled也为false,在[分析4]处的代码块会被执行

    • 分析7(当前在ViewGroup类)
    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
    
    public ArrayList<View> buildTouchDispatchChildList() {
        return buildOrderedChildList();
    }
    
        ArrayList<View> buildOrderedChildList() {
            final int childrenCount = mChildrenCount;
            if (childrenCount <= 1 || !hasChildWithZ()) return null;
    
            if (mPreSortedChildren == null) {
                mPreSortedChildren = new ArrayList<>(childrenCount);
            } else {
                // callers should clear, so clear shouldn't be necessary, but for safety...
                mPreSortedChildren.clear();
                mPreSortedChildren.ensureCapacity(childrenCount);
            }
    
            final boolean customOrder = isChildrenDrawingOrderEnabled();
            for (int i = 0; i < childrenCount; i++) {
                // add next child (in child order) to end of list
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View nextChild = mChildren[childIndex];
                final float currentZ = nextChild.getZ();
    
                // insert ahead of any Views with greater Z
                int insertIndex = i;
                while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
                    insertIndex--;
                }
                mPreSortedChildren.add(insertIndex, nextChild);
            }
            return mPreSortedChildren;
        }
    

    对当前有处理事件权限的View做一个排序,将View按照getZ()方法为排序依据一个个的放入ArrayList数组中
    getZ方法拿到的是View的Z坐标,getZ越大,在数组中的位置越靠后。

    image.png

    如果一个Activity中包含一个LinearLayout,LinearLayout中有子View为Button,那么数组的排序为Activity、LinearLayout、Button。

    在[分析7]处,依据拿到了排过序的ArrayList,进入[分析8]

    • 分析8(当前在ViewGroup类)
    // 这里做了倒序处理(如上图,就是先把Button取出来了)
    for (int i = childrenCount - 1; i >= 0; i--) {
        // 获取child对应的index
        final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
        // 从数组中获取View
        final View child = getAndVerifyPreorderedView(
                 preorderedList, children, childIndex);
        ...
    
         if (!canViewReceivePointerEvents(child)
             || !isTransformedTouchPointInView(x, y, child, null)) { // 分析8-1
             ev.setTargetAccessibilityFocus(false);
             continue;
        }
    
         newTouchTarget = getTouchTarget(child); // 分析8-2
         ...
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 分析8-3
            // 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;
        }
    }
    
    • 分析8-1(当前在ViewGroup类)
    // 如果是View不可见或者正在执行动画,则返回false;否则返回true
    private static boolean canViewReceivePointerEvents(@NonNull View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                    || child.getAnimation() != null;
    }
    // 判断当前位置
    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
    

    [分析8-1]处的代码,主要是分析当前View是否符合处理事件的条件,如果不符合,continue。

    • 分析8-2(当前在ViewGroup类)
    newTouchTarget = getTouchTarget(child);
    
    private TouchTarget getTouchTarget(@NonNull View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }
    

    由于mFirstTouchTarget目前暂未赋值,还是为null,所以方法体返回null,即newTouchTarget为null

    • 分析8-3(当前在ViewGroup类)

    这次流程进入了dispatchTransformedTouchEvent方法中,和之前遇到的[分析5-1]代码流程不同,这时的第三个入参View child是有值的,按照之前分析的流程来看,这里传入的应该是button。

        private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
    
            // Canceling motions is a special case.  We don't need to perform any transformations
            // or filtering.  The important part is the action, not the contents.
            final int oldAction = event.getAction();
            if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
                event.setAction(MotionEvent.ACTION_CANCEL);
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
    
            // 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;
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
    
                        handled = child.dispatchTouchEvent(event);
    
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
                transformedEvent = event.split(newPointerIdBits);
            }
    
            // Perform any necessary transformations and dispatch.
            // 此时child不为null
            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());
                }
                // 重点代码,调用了child(也就是View)的dispatchTouchEvent方法。
                handled = child.dispatchTouchEvent(transformedEvent);
            }
    
            // Done.
            transformedEvent.recycle();
            return handled;
        }
    

    调用 child.dispatchTouchEvent方法,流程就进入到了View类中的 dispatchTouchEvent方法里了
    dispatchTransformedTouchEvent方法会返回true,也就是[分析8-3]处的代码返回为true,进入if语句块。

    再次放出[分析8-3]处if语句块中代码

        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); // 分析9
            alreadyDispatchedToNewTouchTarget = true;
            // 跳出for循环
            break;
        }
    

    我们知道,如果是子View处理了事件,那么其父容器是无法处理该事件的,为什么呢?我们继续分析addTouchTarget方法

    • 分析9(当前在ViewGroup类)
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        // 根据child获取到TouchTarget
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        // 此时 mFirstTouchTarget依然为null,所以target.next为null
        target.next = mFirstTouchTarget;
        // target赋值给mFirstTouchTarget,此时mFirstTouchTarget不为null
        mFirstTouchTarget = target;
        return target;
    }
    
    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        ...
        final TouchTarget target;
        // 同步方法
        synchronized (sRecycleLock) {
            if (sRecycleBin == null) {
                // 首次进来 new 一个TouchTarget
                target = new TouchTarget();
            } else {
                target = sRecycleBin;
                sRecycleBin = target.next;
                sRecycledCount--;
                target.next = null;
            }
        }
        // 传进来的View赋值给target.child,并将target返回出去
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
    }
    

    此时再看[分析8-3]处的代码:
    newTouchTarget不为null,
    newTouchTarget.next为null,
    mFirstTouchTarget不为null,
    mFirstTouchTarget.next为null,
    且newTouchTarget = mFirstTouchTarget,
    alreadyDispatchedToNewTouchTarget为true

    根据刚才我们得到的结果,可以看到流程已经开始执行下面代码

    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    // 首次target不为null
    while (target != null) {
        // 由于 target.next为null,此时next为null
        final TouchTarget next = target.next;
        // alreadyDispatchedToNewTouchTarget为true
        // target == newTouchTarget根据之前分析的为true
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            // handled为true
            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;
        // 将next赋值给target,而此时的next为null,所以target为null,跳出while循环
        target = next;
    }}
    

    流程到此,ViewGroup的dispatchTouchEvent方法返回为true, 则回到 [分析5]处, [分析5]的代码也会返回true,则会回到Activity的dispatchEvent方法中。

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

    getWindow().superDispatchTouchEvent(ev)返回为true,至此,在“父容器不拦截事件”的情况下的流程已经走完。

    • ViewGroup对点击事件的分发机制总结
    image.png
    • View对点击事件的分发机制
        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();
            }
            // 检测是否分发Touch事件(判断窗口是否被遮挡住)
            // 如果该 Touch 事件没有被窗口遮挡,则继续后续逻辑
            // 一般情况下都会返回true
            if (onFilterTouchEventForSecurity(event)) {
                if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                    result = true;
                }
                //noinspection SimplifiableIfStatement
                ListenerInfo li = mListenerInfo; // 分析10
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {// 分析11
                    result = true;
                }
    
                if (!result && onTouchEvent(event)) {// 分析12
                    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;
        }
    
    • 分析10(当前在View类)
     ListenerInfo li = mListenerInfo; 
    

    那么mListenerInfo是在哪里赋值的呢?

    • 分析10(当前在View类)
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }
    

    继续寻找getListenerInfo方法调用的地方

    • 分析10(当前在View类)
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
    

    setOnClickListener方法是Android开发者最最常用的一个方法了,执行此方法,传递一个OnClickListener进来。

    • 分析11(当前在View类)
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    
    • 分析12(当前在View类)
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    

    [分析11]和[分析12]可以结合起来看,如果某个View重写了onTouch方法并返回为true,就不能执行onTouchEvent方法。也就是说执行了[分析11]处的代码,[分析12]就不会执行了。

    当然,如果不重写onTouch,或者onTouch方法返回false,就会执行[分析12]处的onTouchEvent。我们经常使用的onClick方法就是在此。

    public boolean onTouchEvent(MotionEvent event) {
          ...
         if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                 // 如果是点击事件,就得满足先DOWN后UP
                 // 那么就可以直接在ACTION_UP中查看方法
                 case MotionEvent.ACTION_UP:
                     ...
                      performClickInternal();
                     ...
                 case MotionEvent.ACTION_DOWN:
                ...
            }
         return true;
        }
     return false;
    }
    

    查看performClickInternal方法源码

    private boolean performClickInternal() {
        notifyAutofillManagerOnClick();
        return performClick();
    }
    

    继续查看performClick方法源码

    public boolean performClick() {
        notifyAutofillManagerOnClick();
        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;
    }
    

    可以看到,这里执行了OnClickListener接口的onClick回调,也就是我们经常使用的onClick方法了。

    • View对点击事件的分发机制总结
    image.png

    相关文章

      网友评论

          本文标题:Android事件分发机制

          本文链接:https://www.haomeiwen.com/subject/czmduqtx.html