android事件分发(二)源码源码

事件分发在android中非常重要,写了3篇文章总结其中的故事

android事件分发(一)

android事件分发(二)源码源码

android事件分发(三)重要的函数requestDisallowInterceptTouchEvent



上一节做了个事件分发的各种情况总结,今天我们再从源码角度看一下这一系列过程,对其中的一些问题,从代码(源码6.0.0)角度给予答案。

各种情况总结


首先定义down,move,move....,up为一组事件,或者一个cycle(官方说法),从手按下到手放开。
我们从一个viewgroup的角度来分析下一组事件到来,会发生什么事?
我是viewgroup,没有onTouchListener,所以可以简单的认为onTouchEvent的返回值就是dispatchTouchEvent的返回值。

基本的规则是:

0.0我是一个坏父亲,父亲吃到肉了,绝不会再给儿子,儿子吃到肉了,父亲还可能抢
1.0 down事件首先会传递到我的onInterceptTouchEvent()方法
2.0 onInterceptTouchEvent返回true我就会拦截事件,我的儿子们不可能收到这个事件,返回false就相当于神马都不干,把事件传递给子控件
2.1.0 down事件的onInterceptTouchEvent返回true,之后会调用我的view:dispatchTouchEvent(大部分情况其实就是onTouchEvent),如果我的onTouchEvent也返回true,那么之后的move事件和up事件都不会经过onInterceptTouchEvent,而是直接传递到onTouchEvent。简单的说,我在某刻拦下来了事件,那么后面的事件都会直接拦截,根本不再调用onInterceptTouchEvent。
2.1.1.down事件的onInterceptTouchEvent返回true,之后会调用自己的onTouchEvent,如果onTouchEvent也返回false,那后面的move,up事件都不会执行。为什么?还记得吗?onTouchEvent返回了false,那view:dispatchTouchEvent就是返回了false,而一个cycle内的前一个action的dispatchTouchEvent返回了false,后面的action直接就丢弃了。
2.2.0 如果down的时候onInterceptTouchEvent返回false,然后某个move的onInterceptTouchEvent返回了true,move的onTouchEvent也返回了true,那么之后的move和up等事件都不会触发onInterceptTouchEvent,而是直接传递给onTouchEvent(注意这里所有的onInterceptTouchEvent和onTouchEvent都是指父视图内的)
2.2.1 如果down的时候onInterceptTouchEvent返回false,那就丢给了儿子处理,儿子处理在onTouchEvent里返回了true,表示他处理好了,那下一个action  move依然先传给我的onInterceptTouchEvent,我还可以拦截。以前有个错误的理解,我以为action给儿子了并且成功处理了之后,下一个action就直接给儿子了,其实不对,还是会先给父亲,父亲看看是否拦截,再给儿子。父亲比儿子霸道很多,父亲拦下了一个action,后面的action都默认拦下,但是父亲放过了一个action,下一个action来的时候,父亲还是会拦拦看,这其实就是某些滑动冲突的原因。

2.2.2 如果down的时候onInterceptTouchEvent返回false,那就丢给了儿子处理,儿子处理在onTouchEvent里返回了false,那依然会传递到我这里来,会调用我的onTouchEvent

2.2.3如果down的时候onInterceptTouchEvent返回false,那就丢给了儿子处理,儿子处理在onTouchEvent里返回了true。然后下一个事件move来了,我依然不拦截,儿子处理在onTouchEvent里返回了false,那就不会到我的onTouchEvent里面来了,此事件会被丢弃。2.2.2和2.2.3的区别就在于mFirstTouchTarget是否是空。


 

注意,这里说的onTouchEvent严格意义上说,该是dispatchTouchEvent,只是dispatchTouchEvent一般都是调用onTouchEvent

down事件的传递过程

先来看一个down事件的传递过程,假定viewgroup的onInterceptTouchEvent返回false。

首先传递到ViewGroup的dispatchTouchEvent

      public boolean dispatchTouchEvent(MotionEvent ev) {
     
        ...
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;


            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                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);
                    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) {


                // If the event is targeting accessiiblity 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 = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(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) {
                // No touch targets so treat this as an ordinary 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 {
                        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;
    }

L5一般为true,进去

L22满足actionMasked ==MotionEvent.ACTION_DOWN,所以进去,disallowIntercept这个参数我们后面再讲,暂时把他看做false。所以就调用了我们熟悉的onInterceptTouchEvent,这里返回false

接着进入for循环,在dispatchTransformedTouchEvent内会掉child的dispatchTouchEvent,如果child处理了这个事件,就给mFirstTouchTarget赋值并且break出这个for循环。mFirstTouchTarget在哪里赋值的呢?newTouchTarget= addTouchTarget(child, idBitsToAssign);这句话

onTouchEvent

for (int i = childrenCount - 1; i >= 0; i--) {
    。。。
    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;
    }
}
/**
 * Adds a touch target for specified child to the beginning of the list.
 * Assumes the target child is not already present.
 */
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

如果没有child处理这个事件呢?会走到L170 mFirstTouchTarget为null,调用handled = dispatchTransformedTouchEvent(ev, canceled, null,

                       TouchTarget.ALL_POINTER_IDS);,因为传进去的child为null,所以调用的是本身的View:dispatchTouchEvent,大约就是onTouchEvent。

这个地方,实际上有3个分支,

case 1子view处理了down事件 

case 2本viewgroup处理了down事件

case 3本viewgroup也没处理down事件,那就会继续往上层viewgroup传递。

我们先看case2,对着前面总结的0.0,为什么下一个事件会直接拦截,看下面这段代码,因为在down的时候,子view并没有处理事件,而是viewgroup处理的,所以mFirstTouchTarget还是null,我们再来看下一个事件来临的时候,会怎么样?看下边的代码,会调用intercepted = true;,直接拦截。这就是“父亲吃到肉了,绝不会再给儿子”。

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 {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

再来看子view处理了事件,mFirstTouchTarget非空,就会走onInterceptTouchEvent,viewgroup依然可能拦截,这就是“儿子吃到肉了,父亲还可能抢”。

再来看case3,我是viewgroup,我不处理,丢给我的父亲,一直往上丢,如果有人处理了,那就类似于case 2,如果一直没人处理,那会怎么样?会一直掉parent的View: dispatchTouchEvent,或者说onTouchEvent,一直到PhoneWindow$DecorView,看看DecorView的onTouchEvent,与众不同,

@Override
public boolean onTouchEvent(MotionEvent event) {
    return onInterceptTouchEvent(event);
}

DecorView的onTouchEvent一般返回false(因为SWEEP_OPEN_MENU为true)。

因为DecorView的mFirstTouchTarget为null,所以下一个move或者up事件来的时候,DecorView默认拦截自己处理,也返回false。

整个拦截过程中,mFirstTouchTarget非常关键,用来表示一个cycle内的第一个处理了事件的子类。





猜你喜欢

转载自blog.csdn.net/litefish/article/details/51769176
今日推荐