banner
fwrite

fwrite

好好生活
twitter
github
email

Android View 事件分发

事件传入#

事件分发.drawio

事件是从 Activity 通过一层层传递至 View 中

事件分发概念#

主要是由 3 个方法来完成

分发重点方法功能返回意义
调度触摸事件:布尔值由上层 View 被触发,传递至目标 View返回结果会由 、子 View 的 影响 (true: 被处理)onTouchEvent``dispatchTouchEvent
onInterceptTouchEvent : boolean在当前 View 中,用来判断是否拦截某个事件返回结果表示该事件是否被拦截 (true: 被拦截)
触摸事件:布尔值当前 View 已经拦截,开始处理是建返回结果代表该事件是否被消耗 (true: 被处理)
public boolean dispatchTouchEvent(MotionEvent e) {
    bool isEventConsume = false;

    if(onInterceptTouchEvent(e)) {     
        isEventConsume = onTouchEvent(e);
    } else {
        isEventConsume = child.dispatchTouchEvent(e);
    }

    return isEventConsume;
}

可以看出分发顺序 dispatchTouchEvent -> onInterceptTouchEvent,在依照是否消耗来决定之后的走向,dispatchTouchEvent 的递归调用,直到找到消耗事件的 View,View 是一个 树状结构,若该 View 已经拦截点击事件,则会触发 onTouch,onTouch 若没消费该事件,才会传递给 onTouchEvent 方法,onTouchEvent 内才会有 onClick 事件。

Activity 接收事件#

//Activity.java
 
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // 这里的 getWindow 就是 PhoneWindow 类
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}


//PhoneWindow.java
    
private DecorView mDecor;

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

//DecorView.java
//DecorView 继承于 FrameLayout,而 FrameLayout 并没有 Override 
//dispatchTouchEvent 方法,所以必须往它的父类寻找
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

// ViewGroup.java
// ViewGroup 继承于 View
// ViewGroup 有重写dispatchTouchEvent,所以不用继续往 View 去
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
}

事件处理#

透过上面分析,我们就知道点击事件是如何传递至 DecorView#ViewGroup 中,这里我们会再分 ViewGroup 点击事件来分析
先了解几个 MotionEvent:

MotionEvent 事件动作其他
ACTION_DOWN手指下压又份为拦截、不拦截
ACTION_UP手指抬起事件结束
ACTION_MOVE在屏幕滑动会被多次触发
ACTION_CANCEL事件取消事件被上层拦截时候触发

ViewGroup 处理 - ChildView 拦截 ACTION_DOWN#

Down 事件只会触发一次(单点触控,多点触控就不只一次),拦截就是该 ViewGroup 自己处理事件,不会对 ChildView 分发
每个点击事件都是以 Action Down 开始,细节请注意以下的注解,而它主要做的事情有 (这里会先列出主要的处理项目)
清除先前的事件:注意 resetTouchState 方法,它会在 ViewGroup#dispatchTouchEvent 事件是 ACTION_DOWN 时执行

Note

清除先前的事件:注意 resetTouchState 方法,它会在 ViewGroup#dispatchTouchEvent 事件是 ACTION_DOWN 时执行

// ViewGroup.java
    
// 从该 ViewGroup 开始,往下串接点击的目标
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {

        // 取得目前行为
        final int action = ev.getAction();
        // 与 Mask 进行 and 操作,取得真正的 Action
        final int actionMasked = action & MotionEvent.ACTION_MASK;


        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 在 Action Down 时才执行 reset
            
            // @ 追踪 cancelAndClearTouchTargets 方法
            cancelAndClearTouchTargets(ev);
            
            // @ 追踪 resetTouchState 方法
            resetTouchState();
        }

    }

    ...

    return handled;
}

private void resetTouchState() {
    // @ 查看 clearTouchTargets 方法
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    
    // 清除 FLAG_DISALLOW_INTERCEPT、允许 ViewGroup 拦截
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

// 清除第一个点击 View 串列的所有事件
private void clearTouchTargets() {
    // TouchTarget 是单向链表
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            // 回收 TouchTarget 方便复用
            target.recycle();
            target = next;
        } while (target != null);
        
        // 将成员 mFirstTouchTarget 至为 null 
        mFirstTouchTarget = null;
    }
}

是否会呼叫 ViewGroup#onInterceptTouchEvent 方法:会有两个条件,再加上一个 FLAG 判断:

  • 目前是 ACTION_DOWN 事件
  • 已经有 ChildView 处理这个点击事件 (如果有子 View 处理事件就会给 mFirstTouchTarget 赋值)
  • 目前 ViewGroup 是否被禁止拦截 (一般 ViewGroup 接收到 时,如果没有禁止拦截的话,就会执行 onInterceptTouchEvent 方法)ACTION_DOWN
// ViewGroup.java
    
// 从该 ViewGroup 开始,往下串接点击的目标
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {

        // 取得目前行为
        final int action = ev.getAction();
        // 与 Mask 进行 and 操作,取得真正的 Action
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Action down 时,初始化 FLAG_DISALLOW_INTERCEPT

        final boolean intercepted;
        // 这里有两个判断 决定是否呼叫 ViewGroup 自己的 onInterceptTouchEvent 方法
        // 1. 目前是 ACTION_DOWN 
        // 2. 已经有子 View 处理这个点击事件 
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {

            // 判断是否禁止拦截
            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 {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }

    }

    ...

    return handled;
}

若 ViewGroup 自身没有拦截,就会 递归 ChildView:并一一执行 ViewGroup#dispatchTransformedTouchEvent 分发给每个 ChildView 处理,若 ChildView 消耗事件则返回 true,则跳出循环,否则往下一个 ChildView 询问。

// ViewGroup
    
// 从该 ViewGroup 开始,往下串接点击的目标
private TouchTarget mFirstTouchTarget;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {

        final int action = ev.getAction();
        // 与 Mask 进行 and 操作,取得真正的 Action
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Action down 时 初始化 FLAG_DISALLOW_INTERCEPT

        final boolean intercepted;
        // 判断自身 ViewGroup 是否可以拦截事件

        // 目前假设 ViewGroup 不拦截
        if (!canceled && !intercepted) {
            ...

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                ...

                if (newTouchTarget == null && childrenCount != 0) {
                    ...
                    // 重新排列在该 ViewGroup 中的 ChildView 们的顺序
                    final ArrayList<View> preorderedList = 
                                    buildTouchDispatchChildList();
                    ...

                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);

                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        ...

                        // 是否动画中 canReceivePointerEvents
                        // 点击是否在 child 元素內 isTransformedTouchPointInView 
                        if (!child.canReceivePointerEvents()
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        // 获取点击中的 View 
                        // 新的 View 事件才会返回  非 null
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Give it the new pointer in addition to the ones it is handling.
                            // Child View 已经接收到点击
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }


                        // 重点在 dispatchTransformedTouchEvent 方法
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // ChildView 处理事件成功

                            ...

                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();

                            // addTouchTarget 方法
                            // 內会赋予 mFirstTouchTarget 值
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            // 跳出循环
                            break;
                        }


                    }
                }

            }
        }

    }

    ...

    return handled;
}

ViewGroup#dispatchTransformedTouchEvent方法:ViewGroup 会通过该方法把事件传递给 ChildView,若是传入的 child 是 null 就呼叫 ViewGroup 父类的 dispatchTouchEvent,不是 null 则呼叫指定 View 的 dispatchTouchEvent (当前情况就是有传入 View 对象,所以会转跳到指定 View#dispatchTouchEvent 方法)

// ViewGroup.java

private boolean dispatchTransformedTouchEvent(MotionEvent event, 
                              boolean cancel,
                              View child, 
                              int desiredPointerIdBits) {

    final boolean handled;

    ...

    if (child == null) {
        // 如果 Child view 为 null 则回传给 ViewGroup 的 Parent
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        ...

        handled = child.dispatchTouchEvent(transformedEvent);
    }

    return handled;
}

ViewGroup#addTouchTarget:赋予 成员mFirstTouchTarget
ViewGroup#addTouchTarget:配备 mFirstTouchTarget 成员

// ViewGroup.java

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        // 取复用的 TouchTarget 物件

        final TouchTarget target = TouchTarget
                    .obtain(child, pointerIdBits);
        
        // 串接上一个事件的 View
        target.next = mFirstTouchTarget;
        
        // 第一个 处理点击事件 的 View
        mFirstTouchTarget = target;
        return target;
    }

在 ChildView 接收并拦截事件后 (通过 方法),会赋予该 ViewGroup#mFirstTouchTarget 值、并跳出循环dispatchTouchEventChildView 处理完事件后,返回到 ViewGroup 继续处理

// ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    ...

    if (onFilterTouchEventForSecurity(ev)) {


        if (mFirstTouchTarget == null) {
            ...
        } else {
            ... 

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

        }

    }

    ...

    return handled;
}

ViewGroup 处理 - ChildView 不拦截 ACTION_DOWN#

ViewGroup ChildView 拦截事件:处理方式相同 (上一小节),这里主要看看不同的部分,该事件由 ViewGroup 自己处理 (dispatchTransformedTouchEvent 方法)

// ViewGroup.java

    // 从 ViewGroup 开始
    private TouchTarget mFirstTouchTarget;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {

          
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;


            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 在 Action Down 时才执行 reset

                cancelAndClearTouchTargets(ev);

                resetTouchState();
            }

        }

        ...

        return handled;
    }

清除先前的事件:注意 resetTouchState 方法,它会在ViewGroup#dispatchTouchEvent,事件是 ACTION_DOWN 时执行,是否会呼叫 ViewGroup#onInterceptTouchEvent 方法:会有两个条件,再加上一个 FLAG 判断,目前是 事件ACTION_DOWN,已经有 ChildView 处理这个点击事件 (如果有子 View 处理事件就会给 赋值)mFirstTouchTarget,目前 ViewGroup 是否被禁止拦截 (一般 ViewGroup 接收到 时,如果没有禁止拦截的话,就会执行,onInterceptTouchEvent 方法)ACTION_DOWN,若 ViewGroup 自身没有拦截,就会递归 ChildView:并一一执行 ViewGroup#dispatchTransformedTouchEvent 分发给每个 ChildView 处理

// ViewGroup
    
    // 从 ViewGroup 开始
    private TouchTarget mFirstTouchTarget;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            ...
                
            if (!canceled && !intercepted) {
                // 目前沒有 ChildView 要处理这个事件
            }
        }
        ...
    }

ChildView 接收但 全部都不拦截事件,ViewGroup#mFirstTouchTarget 为 null

// ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ...

        if (onFilterTouchEventForSecurity(ev)) {

            ...

            // 目前情況,没有 ChildView 处理事件
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                ...
            }

        }

        ...

        return handled;
    }


    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        ...

        if (child == null) {
            // 回到 ViewGroup 的 Parent dispatchTouchEvent
            handled = super.dispatchTouchEvent(transformedEvent);

        } else {
            ...
        }
        ...
        return handled;
    }

View 收到事件 - 处理 ACTION_UP#

View 首先分发给 onTouch 若是没有处理则分发到 onTouchEvent,顺序如下

  • 最先执行 onTouch 接口,若是已经处理,就 onTouchEvent 就不会接收到事件 onTouch
  • 若 onTouch 没有处理这个事件,就会轮到该 View 的 onTouchEvent 处理事件onTouchEvent
// View.java

    public boolean dispatchTouchEvent(MotionEvent event) {
        ...

        boolean result = false;

        ...

        if (onFilterTouchEventForSecurity(event)) {
            ...

            // 事件分发到 onTouch
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    // 判断该 View 是 enable
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            
            // 分析 onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...

        return result;
    }

View#onTouchEvent 方法中,可以看到 onClick、onLongClick 接口的呼叫

// View.java

    // PerformClick 代表该 View 的点击事件
    private PerformClick mPerformClick;

    public boolean onTouchEvent(MotionEvent event) {

        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();


        // 只要设定 CLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE 其中一个,
        // 该 View 可点击
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;


        // 点击事件的代理 TouchDelegate
        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;

                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        ...

                        // 长按任务 mHasPerformedLongPress
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 移除长按 Callback
                            removeLongPressCallback();
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {

                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }

                                // 分析 performClickInternal
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

                        ...
                    }
                    mIgnoreNextUpEvent = false;
                    break;
            }
        }
    }

这边可以看到 View 的点击事件是通过 Handler 传送 Click 点击任务 (Runnable),这样就不会造成点击事件堵塞

// View.java

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        
        if (attachInfo != null) {
            // 将 Click 任务放入 Handler 
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

    private boolean performClickInternal() {
        notifyAutofillManagerOnClick();
        
        // 分析 performClick
        return performClick();
    }

    public boolean performClick() {
        notifyAutofillManagerOnClick();
        
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            // 执行 OnClick
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        notifyEnterOrExitForAutoFillIfNeeded(true);
        return result;
    }

ViewGroup - ACTION_DOWN#

  1. 判断事件是否被 ViewGroup 拦截:
    ViewGroup 拦截事件 -> 直接走 3
    ViewGroup 不拦截事件 -> 先走 再走 2 3
  2. ViewGroup 不拦截,并有其中一个 ChildView 拦截事件并处理
    ViewGroup#newTouchTarget 被赋予值
    分发到自身的 View ( 呼叫自己的父类)super.dispatchTouchEvent
// ViewGroup.java

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    ...

    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}
  1. 事件分发、处理:有两种情况
    所有 ChildView 不拦截这个事件,所以 mFirstTouchTarget 为 null,相当于是最后一个 View
if (mFirstTouchTarget == null) {
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
        TouchTarget.ALL_POINTER_IDS);
}

有 ChildView 拦截,所以 mFirstTouchTarget 不为 null,并且 while 只循环一次,因为 ChildView 已处理 (分发时处理)

if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
    handled = true;
}

ACTION_MOVE - 查看收到的 MOVE#

Move 事件仍会先走 DecorView 这个 ViewGroup,在执行 事件时先需要知道几件事情ACTION_MOVE,ViewGroup 不会清理点击的 Flag,TouchTarget 类型的 mFirstTouchTarget 元素 不为空 (因为已经有 ChildView 元素处理),ACTION_MOVE 事件的判断流程图如下,它主要做的事情是 ViewGroup 是否拦截事件 (以下默认不拦截事件),不拦截、Move 不分发事件分发 or 处理,同样先来观察 ViewGroup#dispatchTouchEvent 方法,并从这里开始分析,不会进入 reset 环节

// ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            ...
        }
    }

判断当前 ViewGroup 是否有禁止拦截 ,如果没有的话就调用自身的 方法 检查是否拦截FLAG_DISALLOW_INTERCEPT onInterceptTouchEvent

// ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
        ...

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {

            // 判断 ViewGroup 时候禁止拦截
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

            // 预设 ViewGroup 不拦截
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
    }

不是 事件,所以不会进入最初的 事件分发环节 ACTION_DOWN

// ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

            ...

        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
      
        if (!canceled && !intercepted) {

            // 不是 Down 事件
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
               ...

            }
        }

        ... 
    }

ACTION_MOVE 的重点在事件分发

// ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ...

        if (mFirstTouchTarget == null) {
            ...

        } else {
          
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;

                // alreadyDispatchedToNewTouchTarget 是 false
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;

                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;

                    // 分析 dispatchTransformedTouchEvent 分發給 ChildView
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    ...
                }
                predecessor = target;
                target = next;
            }
        }
    }

接下来通过 dispatchTransformedTouchEvent 方法:分发 事件给 ChildView 的 dispatchTouchEventACTION_MOVE

// ViewGroup.java 

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) 

        ...

        if (child == null) {
            ...
        } 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);
        }
    }

ACTION_MOVE - ViewGroup 拦截 MOVE#

先了解目前情况:已经有 ChildView 处理 ACTION_DOWN 事件,而我们自订一个 ViewGroup 并复制 ViewGroup#onInterceptTouchEvent 方法,在 ACTION_ VIEW 时返回 true 拦截

// 自定义 ViewGroup.java

public class MyViewGroup extends View {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_MOVE) {
            // ViewGroup 自己消耗事件
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

}

ACTION_MOVE 事件是多次发生,第一个 ACTION_MOVE (从 ParentView 进来)的目的是:

  1. 取消 ChildView 事件(事件改为 ACTION_CANCEL)
  2. ViewGroup#mFirstTouchTarget 置为空,这时 ParentView 是不处理事件的
    结果:这时 ChildView 会收到 ACTION_CANCEL 事件(事件被取消,之后都会由 ViewGroup 处理事件)
/**
 * ViewGroup.java
 * 
 * 由于覆写 onInterceptTouchEvent 方法会让 Move 事件为 ture,所以 cancelChild 也为 ture,
 * 传入 dispatchTransformedTouchEvent 方法的 cancel 参数为 ture,
 * 
 * 这时事件就会被改为 ACTION_CANCEL,下面的 ChildView 就会接收到 ACTION_CANCEL 事件
 */ 
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        ...

        if (mFirstTouchTarget == null) {
            ...

        } else {

            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    // ACTION_MOVE 第一次进入
                    // intercepted 是 true,所以 cancelChild = true
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;

                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }

                    // mFirstTouchTarget = next,next为 null
                    // 所以 mFirstTouchTarget = null
                    if (cancelChild) {
                        if (predecessor == null) {
                            // next 为空,所以 mFirstTouchTarget 为空
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        ...
    }


    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {

        final boolean handled;

        final int oldAction = event.getAction();
        // cancel 为 true
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {

            // 改变事件为 ACTION_CANCEL
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                ...
            } else {
                // 往下分发 ChildView 的事件就为 cancel
                // 事件被上层拦截时触发
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        ...
            
        return handled;
    }

第二个 ACTION_MOVE(从 ParentView 进来),由于 mFirstTouchTarget 为空,所以 ParentView 不会分发
结果:ParentView 自己处理事件

//ViewGroup.java

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

        // 在第一个 ACTION_MOVE 时 mFirstTouchTarget 被置为空
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {

            ...
        } else {
            intercepted = true;
        }

        // intercepted = true 
        if (!canceled && !intercepted) {
            ... 
        }


        if (mFirstTouchTarget == null) {
            // ChildView 为空
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {

            ...
        }

        ...
}


private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {

    final boolean handled;

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

事件冲突#

为何会发生冲突 ? 因为事件只会有一个,若是响应的组件不是自己要的 View 原件,这时就可以称之为事件冲突
分为两种方法处理

  • 内部拦截法:需要 ChildView#dispatchTouchEvent 处理 & 需要配合改动父容器 (ParentView) 的 onInterceptTouchEvent 拦截
  • 外部拦截法(较常使用):只需要 ParentView 处理

内部拦截 - ChildView 处理#

在 ChildView 中使用 requestDisallowInterceptTouchEvent 方法,控制 FLAG_DISALLOW_INTERCEPT 元素,让 ParentView 不会往下分发事件
内部拦截作法
ChildView 在适当时机通过 方法,要求 ParentView 不要拦截事件requestDisallowInterceptTouchEvent

public class MyView extends View {

    int dispatchX, dispatchY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch(event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 要求 ParentView 不拦截
                getParent().requestDisallowInterceptTouchEvent(true);
                break;

            case MotionEvent.ACTION_UP:
                break;

            case MotionEvent.ACTION_MOVE:
                int deltaX = x - dispatchX;
                int deltaY = y - dispatchY;
                
                // 事件交由 ViewGroup 处理
                if(Math.abs(deltaX) > Math.abs(deltaY)) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
        }

        dispatchX = x;
        dispatchY = y;

        return super.dispatchTouchEvent(event);
    }

}

重写 ParentView 的 方法,设定在 ACTION_DOWN 时不拦截事件,这样事件才能传入 ChildView onInterceptTouchEvent

public class MyViewGroup extends ViewGroup {

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN) {
            // ViewGroup 不拦截
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }

}

requestDisallowInterceptTouchEvent 细节说明: 为啥 requestDisallowInterceptTouchEvent 无法控制 ACTION_DOWN 事件

// ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {

                cancelAndClearTouchTargets(ev);
                // 查看 resetTouchState
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {    // mFirstTouchTarget != null

                // 控制 FLAG_DISALLOW_INTERCEPT
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // 如果为 ture 则 ParentView 就不往下分发
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            //
        }
    }


    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

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

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

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

从上面可看出 ViewGroup 时 ACTION_DOWN 使用 resetTouchState () 方法,这里会清理 这个 Flag,导致事件 ACTION_DOWN 一定会被分发FLAG_DISALLOW_INTERCEPT

// ViewGroup.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
        ...

        final boolean intercepted;

        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {

            // 清理后 FLAG_DISALLOW_INTERCEPT 为空
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            
            if (!disallowIntercept) {
                // 判断 ViewGroup#onInterceptTouchEvent
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        }
        ...
    }

Note

ViewGroup 中复写 onInterceptTouchEvent,让 ViewGroup 在 ACTION_DOWN 事件时返回 false(不撷取),这样 ACTION_DOWN 就会分发,这样才能往下分发到 ChildView,而其他的动作则会拦截

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction() == ACTION_DOWN) {
            return false;
        }
        return true;
    }

外部拦截 - ParentView 处理#

由 ParentView 处理事件拦截比较简单,只需要覆盖 ParentView#onInterceptTouchEvent 决定哪个时间(条件)拦截事件

public class MyExternalViewGroup extends ViewGroup {

    int interceptX, interceptY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        boolean defaultIntercept = super.onInterceptTouchEvent(event);

        int x = (int) event.getX();
        int y = (int) event.getY();

        switch(event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - interceptX;
                int deltaY = y - interceptY;
                
                // 水平滑动,拦截
                if(Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercept = true;
                } else {
                    intercept = false;
                }
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
                
            default:
                intercept = defaultIntercept;
                break;
        }

        interceptX = x;
        interceptY = y;

        return intercept;
    }
}
加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。