深入分析View-事件处理 基于Android 8.0

1.概述

事件类型包括ACTION_DOWN,ACTION_UP,ACTION_MOVE等,常规一次事件的起始都是以ACTION_DOWN开始,ACTION_UP结束。

View的事件处理包括两个阶段:派遣和消费。

一般的事件处理流程(不考虑ViewGroup的onInterceptTouchEvent返回true的情况):

派遣是从Activity开始,然后沿着Activity > PhoneWindow > DecorView > 遍历ViewGroup/View 顺序,到顶层View后事件进入消费阶段。

消费是从顶层View开始,沿原路径逐层处理每个View中定义的OnTouchListener、onTouchEvent等,直到Activity。

当ViewGroup的onInterceptTouchEvent返回true时,此ViewGroup即为顶层View,后续消费过程同上。

在派遣和消费过程中若有一个View(ViewGroup)消耗了ACTION_DOWN(return ture),则该事件后续的ACTION_MOVE,ACTION_UP都会交由该View处理。

原因是在ACTION_DOWN事件派遣流程中,此消耗了事件的View会构造成TouchTarget实例保存在链表mFirstTouchTarget中,后续ACTION_MOVE到来后会直接进入mFirstTouchTarget的判断并将事件交由其中的节点处理。

2.源码分析

事件派遣主要涉及到

View dispatchTouchEvent

ViewGroup dispatchTouchEvent onInterceptTouchEvent

DecorView的superDispatchTouchEvent方法开始派遣,即从ViewGroup的dispatchTouchEvent开始进行

TouchTarget
private static final class TouchTarget {
    private static final int MAX_RECYCLED = 32;
    private static final Object sRecycleLock = new Object[0];
    private static TouchTarget sRecycleBin;
    private static int sRecycledCount;

    public static final int ALL_POINTER_IDS = -1; // all ones

    // The touched child view.
    public View child;

    // The combined bit mask of pointer ids for all pointers captured by the target.
    public int pointerIdBits;

    // The next target in the target list.
    public TouchTarget next;
    .....
}

TouchTarget是以单向链表结构保存某次事件的责任链,仅用于ViewGroup中,成员mFirstTouchTarget即为TouchTarget的实例,mFirstTouchTarget的首节的child属性,指向当前ViewGroup中用来接收事件的View/ViewGroup。

ViewGroup-dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
    boolean handled = false;
  //onFilterTouchEventForSecurity方法检查当前View是否被遮盖,若未遮盖则返回true继续执行
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 如果是ACTION_DOWN事件,清除之前事件的相关数据,包括TouchTarget,flag等
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 判断该ViewGroup是否要拦截该事件,若disallowIntercept的值为false(默认值为false)则进入ViewGroup中的onInterceptTouchEvent方法判断是否需要对此事件进行拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
        ......
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
            ......
            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;
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                  //获取按Z轴排序后的View集合preorderedList
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                  //从位于最上层的View开始遍历preorderedList
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                      .......
                        /* 判断当前child的VISIBLE状态,是否在执行动画,
                        isTransformedTouchPointInView用来判断点击的点是否在该child的区域中*/
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                      //获取当前child对应的TouchTarget
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
                      /* 比较重要的判断分支,在dispatchTransformedTouchEvent方法中若
                      child不为空,会调用child.dispatchTouchEvent(event);从而进入child的
                      dispatchTouchEvent逐层处理直到最上层View。
                      */
                        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();
                          //以child为参数构造TouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        if (mFirstTouchTarget == null) {
            /* 若遍历没有View响应该事件,调用dispatchTransformedTouchEvent并且child参数传null,
            表示把当前ViewGroup当做普通View处理,在dispatchTransformedTouchEvent中会调用其
            super.dispatchTouchEvent(event);即View中定义的dispatchTouchEvent方法,此处与View
            流程一致,即处理OnTouchListener、onTouchEvent等,都未定义返回false
            */
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } 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;
            }
        }

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

整体代码较长,下面分段分析

if (onFilterTouchEventForSecurity(ev)) {
......
}

此处判断是否过滤遮盖后的touch事件,与android:filterTouchesWhenObscured属性对应控制View被遮盖后的事件处理方式

onInterceptTouchEvent相关逻辑

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    intercepted = true;
}

这段代码也说明了onInterceptTouchEvent方法触发的条件,即如果没有调用该ViewGroup的requestDisallowInterceptTouchEvent(true)禁止拦截事件。MotionEvent.ACTION_DOWN代表事件的开始,mFirstTouchTarget != null表示处于某事件的过程中(如在处理ACTION_MOVE),都会进入onInterceptTouchEvent

遍历View

if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        ......
        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
            final float x = ev.getX(actionIndex);
            final float y = ev.getY(actionIndex);
            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
            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 (!canViewReceivePointerEvents(child)
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }

                newTouchTarget = getTouchTarget(child);
                if (newTouchTarget != null) {
                    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;
                }
                ......
            }
            if (preorderedList != null) preorderedList.clear();
        }

        if (newTouchTarget == null && mFirstTouchTarget != null) {
            newTouchTarget = mFirstTouchTarget;
            while (newTouchTarget.next != null) {
                newTouchTarget = newTouchTarget.next;
            }
            newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
    }
}

此代码块循环遍历ViewGroup中符合条件的子节点(包括View,ViewGroup)。需要注意其准入条件:ACTION_DOWN,ACTION_POINTER_DOWN(多点按下)。即在一次事件中,非ACTION_DOWN事件便不会再进行事件的派遣而是直接进入对mFirstTouchTarget的判断,进而将后续事件给已接收此事件的View处理。

final ArrayList<View> preorderedList = buildTouchDispatchChildList();

将ViewGroup的子View按Z的值有小到大进行排序,

for (int i = childrenCount - 1; i >= 0; i--) {...

从数组尾部开始遍历,即按View的Z值从大到小遍历

总结整个View事件的派遣规则为:从外到内,从上到下。外指嵌套关系的外部,FrameLayout中包含Button则先是FrameLayout后Button。从上到下指在同一个ViewGroup中的View之间,如同一个FrameLayout中的两个互相遮盖的FrameLayout,点击交集位置时,会将事件先派遣到上面的FrameLayout,然后才是下面的。

if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
        .....
        continue;}

isTransformedTouchPointInView根据传入的x,y坐标,判断点击的点是否在View的范围内,不在则continue

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

child不为null,执行child.dispatchTouchEvent(event);将事件event给子View/ViewGroup继续派遣。

根据mFirstTouchTarget进行后续处理

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} 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;
            }
            ......
        }
        predecessor = target;
        target = next;
    }
}

ACTION_DOWN的后续事件,如ACTION_MOVE等都不会再进行派遣而进入此代码块处理,

若mFirstTouchTarget为null表示没有接收的View,调用dispatchTransformedTouchEvent并且child参数的值为null,将当前ViewGroup当做View处理,后续流程与View一致。

若mFirstTouchTarget不为空,表示有接收事件的View,遍历链表mFirstTouchTarget,并将target.child传入dispatchTransformedTouchEvent方法中,继续遍历。

这里核心是通过dispatchTransformedTouchEvent进行后续操作,后面具体分析参数child是否为空时的逻辑处理。

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

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    ......
    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);
    }
    ......
    return handled;
}

不考虑其他参数,只分析child参数的影响,分两种情况

1.在遍历子View过程或处理mFirstTouchTarget不为空的情况下,都需要调用dispatchTransformedTouchEvent方法遍历子View,参数child不为空,进入后会执行child.dispatchTouchEvent(event);将事件event给子View/ViewGroup继续派遣。

2.若当前ViewGroup无子View,或子View无法接收事件,则mFirstTouchTarget为空,需要当前VIewGroup处理此事件,则参数child传入null,会调用super.dispatchTouchEvent(event),把当前ViewGroup当做View处理,后续处理与View一致。

总结:dispatchTransformedTouchEvent是当前ViewGroup与子View连接的桥梁,通过其child参数的是否为空来控制在当前ViewGroup处理事件还是交由子View进行处理

ViewGroup-requestDisallowInterceptTouchEvent

FLAG_DISALLOW_INTERCEPT是由requestDisallowInterceptTouchEvent进行设置,需要注意该方法会遍历其所有的parent,即从当前ViewGroup向下的ViewGroup的FLAG_DISALLOW_INTERCEPT都会被设置

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-onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
    ......
    return false;
}

当需要ViewGroup拦截事件时需要重写该方法,并最终返回true。

有两点需要注意

一.该方法被执行的时机

被执行情况:

requestDisallowInterceptTouchEvent(false)为前提

1.actionMasked == MotionEvent.ACTION_DOWN事件开始时的ACTION_DOWN一定会调用

2.事件过程中如actionMasked为ACTION_MOVE,mFirstTouchTarget不为空即有View接收当前事件,也会进行调用,以便随时切换事件的控制权(比如处理滑动冲突)

不被执行情况:

1.requestDisallowInterceptTouchEvent(true)

2.非ACTION_DOWN事件并且mFirstTouchTarget==null即没有接收事件的View

二.onInterceptTouchEvent返回true的影响

1.事件派遣过程中,ViewGroup.dispatchTouchEvent方法中的局部变量intercepted为true,事件不再向子View派遣,mFirstTouchTarget的值为null所以将当前ViewGroup当做View处理,调用其super.dispatchTouchEvent(event),后续同View。

2.事件消费过程中,此时与第一条不同的是mFirstTouchTarget不为空,intercepted同样为true,则会执行dispatchTransformedTouchEvent方法并且第二个参数cancel的值为true,进入其cancel流程。结果是当前处理事件的View/ViewGroup,接收到MotionEvent.ACTION_CANCEL事件,各自执行其cancel流程。

参考代码ViewGroup.dispatchTouchEvent

if (mFirstTouchTarget == null) {
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
} else {
......
    while (target != null) {
        ......
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
      //此处cancelChild为true
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            .......
        }
        predecessor = target;
        target = next;
    }
}
View-dispatchTouchEvent

View中dispatchTouchEvent的定义相对于ViewGroup比较简单。

public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    boolean result = false;
    ......
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //若设置了OnTouchListener,当前View是ENABLED的,则调用mOnTouchListener.onTouch并返回true
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //否则进入onTouchEvent进行事件的处理
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ......

    return result;
}
View-onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        return clickable;
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        ......
        return true;
    }

    return false;
}

对DISABLED状态的View处理

if ((viewFlags & ENABLED_MASK) == DISABLED) {
    if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
        setPressed(false);
    }
    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
    return clickable;
}

注意:DISABLED的View一样可以处理点击事件

if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
        return true;
    }
}

若定义了TouchDelegate,将事件交由其处理

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
    ....}
    return true;
}

在后续的switch块中,分别对ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE进行了处理并更新相关变量的值。但无论事件如何处理,若clickable为true则一定会消费此事件。

3.总结
1.事件的传递顺序(以一个ViewGroup为一层):

嵌套外层>嵌套内层

同层内上层>下层

2.方法调用顺序

ViewGroup.dispatchTouchEvent

ViewGroup.onInterceptTouchEvent

重复以上调用….

View.dispatchTouchEvent

View.onTouchEvent

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值