ViewGroup的事件拦截、事件分发、事件处理

上篇博客说了下View的事件分发和事件处理,接着这里说下ViewGroup,ViewGroup多了一个事件拦截,涉及到有三个相应的方法;

dispatchTouchEvent   事件分发
onInterceptTouchEvent   事件拦截
onTouchEvent   事件处理

先看下下面几种不同情况运行的结果;

正常情况:
这里写图片描述

ACTION_DOWN:
ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent
ACTION_UP:
ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent—>View.onClick

注释掉View.onClick
这里写图片描述
ACTION_DOWN:
ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent—>ViewGroup.onTouchEvent

将View中的onTouchEvent返回值改成true
这里写图片描述
ACTION_DOWN:
ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent
ACTION_UP:
ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent

将ViewGroup中的onInterceptTouchEvent返回值改成true
这里写图片描述
ACTION_DOWN:
ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>ViewGroup.onTouchEvent

结合上面的运行结果,点进ViewGroup源码去看看并分析下出现上面结果的原因;
点击源码找到dispatchTouchEvent()方法,

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            ...
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //清楚掉一些标记,主要是mFirstTouchTarget
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            //intercepted用来标识事件拦截  默认是false
            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); 
                } 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;
            //intercepted为true也就是事件拦截那里返回为true就不会走这个if里面
            if (!canceled && !intercepted) {
                ...
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    ...
                    if (newTouchTarget == null && childrenCount != 0) {
                        ...
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                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 (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                ...
            }
            ...
        return handled;
    }

根据拿到的action来进行判断,为MotionEvent.ACTION_DOWN的时候首先调用cancelAndClearTouchTargets(ev);清除掉target

// 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();
    }
/**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

会走到clearTouchTargets()方法中将mFirstTouchTarget至为null;mFirstTouchTarget设置为null后,就定义了一个事件拦截的标识,模式是false,

// Check for interception.
final boolean intercepted;

如果事件没有被拦截掉的话,就是说intercepted为false的时候,就会调用这里,

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

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //如果child为null直接调用自己中的dispatchTouchEvent,否则调用子child中的dispatchTouchEvent
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
        return handled;
    }

走到这里的话,已经调用了ViewGroup.dispatchTouchEvent、ViewGroup.onInterceptTouchEvent、View.dispatchTouchEvent,到了View.dispatchTouchEvent,接着就会走View.onTouch、View.onTouchEvent、View.onClick这个在View的事件处理里面已经说过了,这个时候运行的就是第一个结果;
如果将View的点击事件屏蔽掉,performClick()方法返回的就是false,就是说没有消费事件,
将View中onTouchEvent的返回值改成true,在View的事件中已经详细说了,
将ViewGroup中onInterceptTouchEvent的返回值改成true,下面这个判断就不会走,

if (!canceled && !intercepted){
...
}

没有走,就不会调用dispatchTransformedTouchEvent方法,就不会去走View中的那些方法了;通过ViewGroup和View的源码知道:
如果说子View没有一个地方返回true,只会进来一次响应DOWN事件,代表不需要消费该事件,如果想响应MOVE,UP必须找个地方返回true
对于ViewGroup来说,如果想拦截子View的touch事件,重写onInterceptTouchEvent返回true即可
如果onInterceptTouchEvent返回的是true会执行ViewGroup的onTouchEvent方法,如果子view没有消费事件也会执行onTouchEvent方法
上面如有对ViewGroup的事件处理写的不对的地方,欢迎交流


版权声明:本文为wangwo1991原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。