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

Android View 事件分发机制

作者: VanceKing | 来源:发表于2022-02-21 10:43 被阅读0次

    源码基于 sdk 30(Android 11.0/R)。

    概述

    Android 事件机制包含系统启动流程、输入管理(InputManager)、系统服务和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分发等一系列的环节。

    Android 系统中将输入事件定义为 InputEvent,根据输入事件的类型又分为了 KeyEvent(键盘事件) 和 MotionEvent(屏幕触摸事件)。这些事件统一由系统输入管理器 InputManager 进行分发。

    在系统启动的时候,SystemServer 会启动 WindowManagerService,WMS 在启动的时候通过 InputManager 来负责监控键盘消息。

    InputManager 负责从硬件接收输入事件,并将事件通过 ViewRootImpl 分发给当前激活的窗口处理,进而分发给 View。

    Window 和 InputManagerService 之间通过 InputChannel 来通信,底层通过 socket 进行通信。

    Android Touch 事件的基础知识:

    1. 所有的 Touch 事件都封装到 MotionEvent 里面。
    2. 事件类型分为 ACTION_DOWN(手指按下屏幕的瞬间), ACTION_MOVE(手指在屏幕上移动),ACTION_UP(手指离开屏幕瞬间), ACTION_CANCEL(取消手势,一般由程序产生,不会由用户产生)等。每个事件传递过程都是由一个 ACTION_DOWN 开始,经过 n 个 ACTION_MOVE,最终以一个 ACTION_UP/ACTION_CANCEL 结束。
    3. 事件处理包括三种情况,分别为:
      1. 传递 View#dispatchTouchEvent(MotionEvent event)
      2. 拦截 ViewGroup#onInterceptTouchEvent(MotionEvent ev)
      3. 消费 View#OnTouchListener.onTouch(View v, MotionEvent event) 和 View#onTouchEvent(MotionEvent event)。
    4. 所有的触摸事件都会到达 Activity,Activity 内部通过 Window 连接着一个 View 的根布局 DecorView,事件会从 Activity 一层一层传递到最内层的 View。
    View 事件分发

    事件架构

    Android 事件分发体系

    InputEvent:输入事件抽象

    /**
     * Common base class for input events.
     */
    public abstract class InputEvent implements Parcelable {}
    
    /**
     * Object used to report key and button events.
     */
    public class KeyEvent extends InputEvent implements Parcelable {}
    
    /**
     * Object used to report movement (mouse, pen, finger, trackball) events.
     */
    public final class MotionEvent extends InputEvent implements Parcelable {}
    

    KeyEvent 对应了键盘的输入事件;MotionEvent 就是手势事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于 MotionEvent。

    InputManager:输入管理器

    InputEvent 统一由 InputManager 进行分发,负责与硬件通信并接收输入事件。

    system_server 进程启动时会创建 InputManagerService 服务。

    // SystemServer.java
    private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
        InputManagerService inputManager = new InputManagerService(context);
        // 实例化 InputManagerCallback
        WindowManagerService wm = WindowManagerService.main(context, inputManager, ...);
        inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
        inputManager.start();
    }
    
    public class InputManagerService extends IInputManager.Stub {
        public InputManagerService(Context context) {
            this.mContext = context;
            this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
            mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
        }
    
        private static native long nativeInit(InputManagerService service,
                Context context, MessageQueue messageQueue);
    }
    

    system_server 进程启动时同时会启动 WMS,WMS 在启动的时候就会通过 IMS 启动 InputManager 来监控键盘消息。

    InputEventReceiver:事件接收器

    App 端与服务端建立了双向通信之后,InputManager 就能够将产生的输入事件从底层硬件分发过来,Android 提供了 InputEventReceiver 类,以接收分发这些消息:

    /**
     * Provides a low-level mechanism for an application to receive input events.
     * @hide
     */
    public abstract class InputEventReceiver {
        public InputEventReceiver(InputChannel inputChannel, Looper looper) {
            mInputChannel = inputChannel;
            mMessageQueue = looper.getQueue();
            mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);
        }
    
        // Called from native code.
        private void dispatchInputEvent(int seq, InputEvent event) {
            mSeqMap.put(event.getSequenceNumber(), seq);
            onInputEvent(event);
        }
    
        public void onInputEvent(InputEvent event) {
            finishInputEvent(event, false);
        }
    
        public final void finishInputEvent(InputEvent event, boolean handled) {
            nativeFinishInputEvent(mReceiverPtr, seq, handled);
            event.recycleIfNeededAfterDispatch();
        }
    }
    
    // ViewRootImpl.java
    final class WindowInputEventReceiver extends InputEventReceiver {
        @Override
        public void onInputEvent(InputEvent event) {
            // 将输入事件加入队列,开始事件分发
            enqueueInputEvent(event, this, 0, true);
        }
    }
    

    InputChannel

    Window 和 IMS 之间通过 InputChannel 通信。InputChannel 是一个 pipe,底层通过 socket 进行通信。在 ViewRootImpl.setView() 过程中注册 InputChannel。

    /**
     * An input channel specifies the file descriptors used to send input events to a window in another process.
     * It is Parcelable so that it can be sent to the process that is to receive events.
     * Only one thread should be reading from an InputChannel at a time.
     **/
    public final class InputChannel implements Parcelable {}
    
    // ViewRootImpl.java
    public void setView(){
        requestLayout();
        InputChannel inputChannel = new InputChannel();
        // 通过 Binder 在 system_server 进程中完成 InputChannel 的注册
        res = mWindowSession.addToDisplayAsUser(mWindow, inputChannel, ...);
        if (mInputQueueCallback != null) {
            mInputQueue = new InputQueue();
            mInputQueueCallback.onInputQueueCreated(mInputQueue);
        }
        mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());
    
        // 设置 InputStage
        mSyntheticInputStage = new SyntheticInputStage();
        InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
        // nativePostImeStage earlyPostImeStage imeStage viewPreImeStage nativePreImeStage
    }
    

    InputStage

    public class ViewRootImpl{
        /**
         * Base class for implementing a stage in the chain of responsibility
         * for processing input events.
         */
        abstract class InputStage {
            protected int onProcess(QueuedInputEvent q) {
                return FORWARD;
            }
        }
    
        /**
        * Delivers post-ime input events to the view hierarchy.
        */
        final class ViewPostImeInputStage extends InputStage {
            @Override
            protected int onProcess(QueuedInputEvent q) {
                if (q.mEvent instanceof KeyEvent) {
                    return processKeyEvent(q);
                } else {
                    final int source = q.mEvent.getSource();
                    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                        return processPointerEvent(q);
                    } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                        return processTrackballEvent(q);
                    } else {
                        return processGenericMotionEvent(q);
                    }
                }
            }
        }
    
        private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            // mView 是 DecorView 类型
            boolean handled = mView.dispatchPointerEvent(event);
            return handled ? FINISH_HANDLED : FORWARD;
        }
    }
    
    public class DecorView {
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Window.Callback cb = mWindow.getCallback();
            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                    ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
        }
    }
    
    1. ViewPostImeInputStage 是 Touch 事件传递的起点;
    2. Activity 实现了 Window.Callback 接口,DecorView 会把事件分发到 Activity#dispatchTouchEvent(ev) 方法。

    事件传递流程

    Android 事件传递机制是先分发再处理,先由外部的 View 接收,然后依次传递给其内层的 View,再从最内层 View 反向依次向外层传递。

    1. 事件从 Activity#dispatchTouchEvent() 开始传递,再传递给 DecorView;
    2. DecorView 中先执行 dispatchTouchEvent() 方法,在其内部会先调用 onInterceptTouchEvent() 询问是否拦截事件,若拦截则执行 onTouchEvent() 方法处理这个事件;
    3. 若不拦截,则执行子元素的 dispatchTouchEvent() 方法,进入向下分发的传递,直到事件被处理;
    4. 如果事件从外向内传递过程中一直没有被拦截,且最底层子 View 没有消费事件,这时父 View 可以进行消费,如果还是没有被消费的话,最后会到 Activity#onTouchEvent() 函数。
    5. 如果 View 没有对 ACTION_DOWN 进行消费,之后的其他类型的事件也不会传递过来,也就是说 ACTION_DOWN 必须返回 true,之后的事件才会传递进来。
    6. 事件处理优先级是
      OnTouhListener#onTouch(View v, MotionEvent event) -> onTouchEvent() -> OnClickListener#onClick(View v)

    三个方法的关系如下:

    public boolean dispatchTouchEvent(MotionEvent event){
        boolean consum = false;
        if(onInterceptTouchEvent(event)){
            consum = onTouchEvent(event);
        } else {
            consum = child.dispatchTouchEvent(event);
        }
        return consum;
    }
    
    思维导图

    源码分析

    Activity 的事件处理

    public class Activity {
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            // 调用 mDecor.superDispatchTouchEvent(event);
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
    
        public boolean onTouchEvent(MotionEvent event) {
            if (mWindow.shouldCloseOnTouch(this, event)) {
                finish();
                return true;
            }
    
            return false;
        }
    }
    
    public class PhoneWindow extends Window {
        @Override
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return mDecor.superDispatchTouchEvent(event);
        }
    }
    
    public class Window{
        public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
            final boolean isOutside =
                    event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
                    || event.getAction() == MotionEvent.ACTION_OUTSIDE;
            if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
                return true;
            }
            return false;
        }
    }
    
    1. Activity 接收到 ACTION_DOWN 事件后,调用 onUserInteraction() 方法,表示和用户进行了交互;
    2. Activity 接收到事件后经 PhoneWindow 传递给 DecorView;
    3. 传递给 DecorView 后如果此事件没有被消费,则事件交给 Activity#onTouchEvent() 处理。

    ViewGroup 的事件处理

    分发事件:

    // ViewGroup.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        // ACTION_DOWN 说明是新事件,重置状态
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 清空 TouchTarget 链表
            cancelAndClearTouchTargets(ev);
            // 重置事件状态
            resetTouchState();
        }
    
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 判断是否拦截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 如果拦截,事件由当前 ViewGroup 处理
                intercepted = onInterceptTouchEvent(ev);
                // 防止onInterceptTouchEvent()的时候改变Action
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            //没有 TouchTarget 而且非 ACTION_DOWN 类型的事件,由 ViewGroup 处理
            intercepted = true;
        }
    
        // 如果给子View发送cancel事件后mFirstTouchTarget会变null,
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        // 事件没有取消也没有拦截,向子 View 传递
        if (!canceled && !intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int childrenCount = mChildrenCount;
                // 遍历查找第一个消费这个事件的子View,并设置为 Target
                if (newTouchTarget == null && childrenCount != 0) {
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final View[] children = mChildren;
                    // 按 View 添加顺序倒序查找
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final View child = getAndVerifyPreorderedView(...);
                        // 判断事件坐标是否在 View 内
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
                        // 更新 target
                        newTouchTarget = getTouchTarget(child);
                        // 分发事件到点击的 View
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // View 处理该事件,添加到 TouchTarget 调用链,赋值 mFirstTouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        // ...
                    }
                }
            }
    
            if (mFirstTouchTarget == null) {
                // 没有找到处理此事件的子 View。比如:点击按钮之外
                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;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        // 给子 View 发送 cancel 事件
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
        }
        return handled;
    }
    
    // 第二个参数cancel包含两种含义,一种是外部收到了取消事件,另一种是事件被拦截
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        // cancel 事件处理
        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;
        }
    
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            handled = child.dispatchTouchEvent(transformedEvent);
        }
    
        return handled;
    }
    

    应用了树的深度优先搜索算法(Depth-First-Search,简称 DFS 算法),每个 ViewGroup 都持有一个 mFirstTouchTarget, 当接收到 ACTION_DOWN 时,通过递归遍历找到 View 树中真正对事件进行消费的 Child,并保存在 mFirstTouchTarget 属性中,依此类推组成一个完整的分发链。在这之后,当接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 时,则会跳过递归流程,将事件直接分发给下一级的 Child。

    ViewGroup 分发事件的主要的任务是找一个 Target,并且用这个 Target 处理事件,主要逻辑如下 :

    1. 在 ACTION_DOWN 事件并且当前 ViewGroup 不拦截时会查找 TouchTarget。按 View 添加顺序倒序遍历子 View,判断子 View 的 dispatchTouchEvent() 是否为 true。查找 TouchTarget 的过程只有在 ACTION_DOWN 事件中才会触发。
    2. 如果子 View 的 dispatchTouchEvent() 返回 true,则这个子 View 就是当前 ViewGroup 的 Target。
    3. 如果 TouchTarget 不存在,则调用当前 ViewGroup#onTouchEvent() 方法,仍不消费,会向上调传递直到 Activity#onTouchEvent()。
    4. 子 View 可以调用父 View 的 requestDisallowInterceptTouchEvent(true) 请求父 View 不拦截此事件,交给 子 View 处理。只限于一个 Touch 过程(Down->Up/Cancel)。

    为什么倒序查找 TouchTarget?
    如果按添加顺序遍历,当 View 重叠时(FrameLayout),先添加的 View 总是能消费事件,而后添加的 View 不可能获取到事件。

    // ViewGroup.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = ...;
            final View child = ...;
            // 重叠的子 View 都包含点击区域
            if (!child.canReceivePointerEvents()
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                continue;
            }
            // 非容器 View 默认都会消费事件,从而跳出循环
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
        }
    }
    

    拦截事件:

    // ViewGroup.java
    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;
    }
    
    1. 默认返回 false,不拦截事件。
    2. ViewGroup 子类可以重新该方法,决定是否拦截事件。
    3. 子 View 通过 parentView.requestDisallowInterceptTouchEvent(true) 强制父 View 不拦截此事件。

    View 的事件处理

    // View.java
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (event.isTargetAccessibilityFocus()) {
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;
    
        // 停止嵌套滑动
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            stopNestedScroll();
        }
    
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        // 如果 View 可用并设置了 OnTouchListener,如果回调方法 onTouch() 返回 true 则消费事件
        // 并且不会调用 onTouchEvent() 方法
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
    
        if (!result && onTouchEvent(event)) {
            result = true;
        }
        return result;
    }
    
    1. 安全监测。如果 View#setFilterTouchesWhenObscured(true) 开启了安全检测,当 View 所在的 Window 被覆盖时不处理 Touch 事件;
    2. 停止嵌套滑动(5.0 以后添加);
    3. View 可用时才调用 OnTouchListener#onTouch() 方法;
    4. 不管 View 是否可用,只要 OnTouchListener 不消费事件,就会让 onTouchEvent() 处理;
    5. View 不可用时,不处理 OnTouchListener#onTouchEvent(),直接调用 View#onTouchEvent() 方法。
    // View.java
    public boolean onTouchEvent(MotionEvent event) {
        // 判断是否可以点击
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
        // View 不可用时可以消费事件,只是没有响应
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            return clickable;
        }
    
        // 事件代理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
    
        // 不管是否可用,只有是可点击的就消费事件
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    // 只有在 press 的情况下,才 click,longClick
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // 如果我们在当前View还没获取焦点,并且能在touch下foucus
                        // 那么第一次点击只会将这个View的状态改成focus,而不会触发click
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }
    
                        // 设置 Pressed 状态,更新 View 背景
                        if (prepressed) {
                            setPressed(true, x, y);
                        }
    
                        // 检查 longClick 是否已执行
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
    
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }
    
                        removeTapCallback();
                    }
                    break;
    
                case MotionEvent.ACTION_DOWN:
    
                    if (!clickable) {
                        // 400 ms
                        checkForLongClick(ViewConfiguration.getLongPressTimeout(),...);
                        break;
                    }
    
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
    
                    boolean isInScrollingContainer = isInScrollingContainer();
    
                    if (isInScrollingContainer) {
                        // ...
                    } else {
                        setPressed(true, x, y);k
                        checkForLongClick(ViewConfiguration.getLongPressTimeout(),...);
                    }
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }
    
                    final int motionClassification = event.getClassification();
                    final boolean ambiguousGesture = ...;
                    int touchSlop = mTouchSlop;
                    if (ambiguousGesture && hasPendingLongPressCallback()) {
                        if (!pointInView(x, y, touchSlop)) {
                            removeLongPressCallback();
                            checkForLongClick(...);
                        }
                    }
    
                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, touchSlop)) {
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                    }
    
                    final boolean deepPress = ;
                    if (deepPress && hasPendingLongPressCallback()) {
                        // process the long click action immediately
                        removeLongPressCallback();
                        checkForLongClick(...);
                    }
    
                    break;
            }
    
            return true;
        }
    
        return false;
    }
    
    1. 如果 View 不可用但是可点击,会直接消费事件,只是不做其他任何操作;
    2. 如果 View 可用并设置了 TouchDelegate,则事件交给 TouchDelegate 处理;
    3. 不管 View 是否可用,只要 View 可点击,默认都会消费事件;
    4. 处理 focus,press,click,longclick 等。

    View 滑动冲突

    问题

    1. View.GONE 后仍消费事件;
      可能该 View 执行了动画,要移除。
      android View with View.GONE still receives onTouch and onClick
      View with visibility View.GONE still generates touch events

    总结

    1. 一个点击事件产生后,它的传递过程如下:
      Activity->Window->DecorView->View。如果一个 View 的 onTouchEvent 方法返回 false,那么将会交给父容器的 onTouchEvent 方法进行处理,逐级往上,如果所有的 View 都不处理该事件,则交由 Activity 的 onTouchEvent 进行处理。
    2. ViewGroup 默认不拦截任何事件,可以通过重写 onInterceptTouchEvent() 方法拦截事件,执行自己对应的 onTouchEvent() 方法。
    3. 子 View 可以通过调用父 View 的 requestDisallowInterceptTouchEvent(true) 阻止 ViewGroup 对事件进行拦截;
    4. 如果 ViewGroup 找到了能够处理该事件的 View(TouchTarget),则直接交给子 View 处理,自己的 onTouchEvent() 方法不会执行;
    5. 如果某一个 View 不消耗 ACTION_DOWN 事件,则同一事件序列中不会再交给该 View 处理;
    6. 非容器的 View,一旦接收到事件默认都会消耗事件,除非它是不可点击的(clickable 和 longClickable 都为 false),那么就会由父容器的 onTouchEvent() 处理;
    7. View 的 visible 属性对事件传递没有影响;
    8. 点击事件分发过程是 Activity#dispatchTouchEvent() -> DecorView#dispatchTouchEvent() -> View#OnTouchListener.onTouch() -> View#onTouchEvent() -> View#OnClickListener.onClick()
    9. 如果当前 View 是可点击的,并且它收到了 down 和 up 事件,则它的 click 事件就会触发;对于 onLongClick,则只要当前 View 接收到 down 事件超过了系统默认的时间;
    10. 当某个子 View 消费事件时,会中止 Down 事件的分发,同时在 ViewGroup 中记录该子 View。接下去的 Move 和 Up 事件将由该子 View 直接进行处理。
    11. 当子 View 都不消费 Down 事件时,将调用 ViewGroup#onTouchEvent() 方法。触发的方式是调用 super.dispatchTouchEvent() 函数,即父类 View#dispatchTouchEvent 方法。如果所有子 View 都不处理,则调用 Acitivity#onTouchEvent() 方法。
    12. 如果 View 没有对 ACTION_DOWN 进行消费,那后续事件不会传递过来。如果 View 消费了 ACTION_DOWN,在不拦截的情况下,后续事件会直接传递给这个 View;
    13. 接收了 ACTION_DOWN 事件的函数不一定能收到后续的 ACTION_MOVE 和 ACTION_UP 事件。
    14. 如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。
    15. 子 View 重叠并都是可点击时,最后添加的 View,即离用户最近的 View 最先消费事件。

    参考

    [1] Android 事件分发机制的设计与实现
    [2] Android 事件拦截机制的设计与实现

    相关文章

      网友评论

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

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