澳门新葡萄京娱乐场 2

澳门新葡萄京娱乐场ViewGroup的事件分发机制

触摸事件传递机制是Android中一块比较重要的知识体系,了解并熟悉整套的传递机制有助于更好的分析各种滑动冲突、滑动失效问题,更好去扩展控件的事件功能和开发自定义控件。

我们在学习View的时候,不可避免会遇到事件的分发,而往往遇到的很多滑动冲突的问题都是由于处理事件分发时不恰当所造成的。因此,深入了解View事件分发机制的原理,对于我们来说是很有必要的。由于View事件分发机制是一个比较复杂的机制,因此笔者将写成两篇文章来详细讲述,分别是ViewGroup和View。因为我们平时所接触的View都不是单一的View,往往是由若干个ViewGroup组合而成,而事件的分发又是由ViewGroup传递到它的子View的,所以我们先从ViewGroup的事件分发说起。注意,以下源码取自安卓5.0。

这两天的文章都是直接正如主题,没有什么开头的描述的,因为刚开始写文章,也不知道说什么。啊,今天天气不错哈。

预备知识

public boolean dispatchTouchEvent(MotionEvent ev)

那么今天呢我讲一下这个View的事件分发机制,这个很多面试的时候都会问到,因为这是View的一个核心点,对View的点击事件的一个分发的机制,所谓的事件分发机制呢,其实就是用户对这个View进行触碰产生的一个运动事件,比如说:点击事件,长按事件这些,这个事件产生了以后呢系统要把这个事件传递给一个View来进行消费,而这个传递的过程就是分发过程。事件分发过程是由三个方法来完成的

MotionEvent

在Android设备中,触摸事件主要包括点按、长按、拖拽、滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作等。一个最简单的用户触摸事件一般经过以下几个流程:

  • 手指按下
  • 手指滑动
  • 手指抬起

Android把这些事件的每一步抽象为MotionEvent这一概念,MotionEvent包含了触摸的坐标位置,点按的数量(手指的数量),时间点等信息,用于描述用户当前的具体动作,常见的MotionEvent有下面几种类型:

  • ACTION_DOWN
  • ACTION_UP
  • ACTION_MOVE
  • ACTION_CANCEL

其中,ACTION_DOWNACTION_MOVEACTION_UP就分别对应于上面的手指按下、手指滑动、手指抬起操作,即一个最简单的用户操作包含了一个ACTION_DOWN事件,若干个ACTION_MOVE事件和一个ACTION_UP事件。

该方法用来进行事件的分发,即无论ViewGroup或者View的事件,都是从这个方法开始的。

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件分发,只要你触碰了任何控件,都会调用这个方法,主要是判断是当前view消费还是往下级view传递。

public boolean onInterceptTouchEvent(MotionEvent ev)

这个方法用来判断是否拦截这个事件,默认为false也就是不拦截,需要进行拦截的时候在dispatchTouchEvent内部调用。

public boolean onTouchEvent(MotionEvent event)

用来处理当前事件,返回结果表示是否消费,如果不消费,则这个事件不会再接收到。

几个方法

事件分发过程中,涉及的主要方法有以下几个:

  • dispatchTouchEvent:
    用于事件的分发,所有的事件都要通过此方法进行分发,决定是自己对事件进行消费还是交由子View处理
  • onTouchEvent: 主要用于事件的处理,返回true表示消费当前事件
  • onInterceptTouchEvent:
    ViewGroup中独有的方法,若返回true表示拦截当前事件,交由自己的onTouchEvent()进行处理,返回false表示不拦截

我们的源码分析也主要围绕这几个方法展开。

public boolean onInterceptTouchEvent(MotionEvent ev)

那么他们三个有什么不可描述的关系呢,首先我们要先知道一个点击事件产生后的传递过程,当一个事件产生一定会先传递给当前Activity,然后Activity再传递给window,window会传递给当前页面的顶级View,最后这个View接收到事件以后再按照事件分发机制去分发事件。这里有一种情况就是,如果一个View的onTouchEvent返回了false,那么他的父容器的onTouchEvent也会被调用,以此类推,所有的View都不消费当前事件的话,那么最终这个事件会传递给Activity,Activity的onTouchEvent会进行消费掉。这里采用一个很好的例子,是从别的书籍上看到的,一个领导分配了一个很难的任务给你,然后你不会(onTouchEvent返回false),那么怎么办呢,只能交回给上级(上级的onTouchEvent被调用),那如果你的上级也搞不定呢,那就交给上级的上级,到最后最顶级的上级那里,进行消费掉。这个例子是不是很贴近生活,哈哈我也是从别得地方看到的。

源码分析

在上一个方法内部调用,表示是否拦截当前事件,返回true表示拦截,如果拦截了事件,那么将不会分发给子View。比如说:ViewGroup拦截了这个事件,那么所有事件都由该ViewGroup处理,它内部的子View将不会获得事件的传递。(但是ViewGroup是默认不拦截事件的,这个下面会解释。)注意:View是没有这个方法的,也即是说,继承自View的一个子View不能重写该方法,也无需拦截事件,因为它下面没有View了,它要么处理事件要么不处理事件,所以最底层的子View不能拦截事件。

那么我讲了这么半天大家也肯定还不是很懂,那么我们从源码入手进行分析,打开你们的studio,找到Activity,在Activity里面有个方法叫

Activity

我们从Activity的dispatchTouchEvent方法作为入口进行分析:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

这个方法首先会判断当前触摸事件的类型,如果是ACTION_DOWN事件,会触发onUserInteraction方法。根据文档注释,当有任意一个按键、触屏或者轨迹球事件发生时,栈顶Activity的onUserInteraction会被触发。如果我们需要知道用户是不是正在和设备交互,可以在子类中重写这个方法,去获取通知(比如取消屏保这个场景)。

然后是调用Activity内部mWindowsuperDispatchTouchEvent方法,mWindow其实是PhoneWindow的实例,我们看看这个方法做了什么:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ...
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

        ...
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
        ...
    }
}

原来PhoneWindow内部调用了DecorView的同名方法,而DecorView其实是FrameLayout的子类,FrameLayout并没有重写dispatchTouchEvent方法,所以事件开始交由ViewGroup的dispatchTouchEvent开始分发了,这个方法将在下一节分析。

我们回到Activity的dispatchTouchEvent方法,注意当getWindow().superDispatchTouchEvent(ev)这一语句返回false时,即事件没有被任何子View消费时,最终会执行Activity的onTouchEvent

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

小结:
事件从Activity的dispatchTouchEvent开始,经由DecorView开始向下传递,交由子View处理,若事件未被任何Activity的子View处理,将由Activity自己处理。

public boolean onTouchEvent(MotionEvent ev)

public boolean dispatchTouchEvent(MotionEvent ev) {if(ev.getAction()
== MotionEvent.ACTION_DOWN)
{onUserInteraction();}if(getWindow().superDispatchTouchEvent {return
true;}return onTouchEvent;}

ViewGroup

由上节分析可知,事件来到DecorView后,经过层层调用,来到了ViewGroup的dispatchTouchEvent方法中:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ... 
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        ...
        // 先检验事件是否需要被ViewGroup拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 校验是否给mGroupFlags设置了FLAG_DISALLOW_INTERCEPT标志位
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                // 走onInterceptTouchEvent判断是否拦截事件
                intercepted = onInterceptTouchEvent(ev);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
        ...
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        if (!canceled && !intercepted) {
            // 注意ACTION_DOWN等事件才会走遍历所有子View的流程
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                ...
                // 开始遍历所有子View开始逐个分发事件
                final int childrenCount = mChildrenCount;
                if (childrenCount != 0) {
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        // 判断触摸点是否在这个View的内部
                        final View child = children[i];
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }
                        ...
                        // 事件被子View消费,退出循环,不再继续分发给其他子View
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                            ...
                            // addTouchTarget内部将mFirstTouchTarget设置为child,即不为null
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }
            }
        }
        // 事件未被任何子View消费,自己处理
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 将MotionEvent.ACTION_DOWN后续事件分发给mFirstTouchTarget指向的View
            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;
            }
        }
        // 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);
        }
    }
    return handled;
}
private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while (target != null);
        mFirstTouchTarget = null;
    }
}
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ...

        // 注意传参child为null时,调用的是自己的dispatchTouchEvent
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        return handled;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // 默认不拦截事件
    return false;
}

这个方法比较长,只要把握住主要脉络,修枝剪叶后还是非常清晰的:

(1) 判断事件是够需要被ViewGroup拦截

首先会根据mGroupFlags判断是否可以执行onInterceptTouchEvent方法,它的值可以通过requestDisallowInterceptTouchEvent方法设置:

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) {
        // 层层向上传递,告知所有父View不拦截事件
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

所以我们在处理某些滑动冲突场景时,可以从子View中调用父View的requestDisallowInterceptTouchEvent方法,阻止父View拦截事件。

如果view没有设置FLAG_DISALLOW_INTERCEPT,就可以进入onInterceptTouchEvent方法,判断是否应该被自己拦截,
ViewGroup的onInterceptTouchEvent直接返回了false,即默认是不拦截事件的,ViewGroup的子类可以重写这个方法,内部判断拦截逻辑。

注意:只有当事件类型是ACTION_DOWN或者mFirstTouchTarget不为空时,才会走是否需要拦截事件这一判断,如果事件是ACTION_DOWN的后续事件(如ACTION_MOVEACTION_UP等),且在传递ACTION_DOWN事件过程中没有找到目标子View时,事件将会直接被拦截,交给ViewGroup自己处理。mFirstTouchTarget的赋值会在下一节提到。

(2) 遍历所有子View,逐个分发事件:

执行遍历分发的条件是:当前事件是ACTION_DOWNACTION_POINTER_DOWN或者ACTION_HOVER_MOVE三种类型中的一个(后两种用的比较少,暂且忽略)。所以,如果事件是ACTION_DOWN的后续事件,如ACTION_UP事件,将不会进入遍历流程!

进入遍历流程后,拿到一个子View,首先会判断触摸点是不是在子View范围内,如果不是直接跳过该子View;
否则通过dispatchTransformedTouchEvent方法,间接调用child.dispatchTouchEvent达到传递的目的;

如果dispatchTransformedTouchEvent返回true,即事件被子View消费,就会把mFirstTouchTarget设置为child,即不为null,并将alreadyDispatchedToNewTouchTarget设置为true,然后跳出循环,事件不再继续传递给其他子View。

可以理解为,这一步的主要作用是,在事件的开始,即传递ACTION_DOWN事件过程中,找到一个需要消费事件的子View,我们可以称之为目标子View,执行第一次事件传递,并把mFirstTouchTarget设置为这个目标子View

(3) 将事件交给ViewGroup自己或者目标子View处理

经过上面一步后,如果mFirstTouchTarget仍然为空,说明没有任何一个子View消费事件,将同样会调用dispatchTransformedTouchEvent,但此时这个方法的View child参数为null,所以调用的其实是super.dispatchTouchEvent(event),即事件交给ViewGroup自己处理。ViewGroup是View的子View,所以事件将会使用View的dispatchTouchEvent(event)方法判断是否消费事件。

反之,如果mFirstTouchTarget不为null,说明上一次事件传递时,找到了需要处理事件的目标子View,此时,ACTION_DOWN的后续事件,如ACTION_UP等事件,都会传递至mFirstTouchTarget中保存的目标子View中。这里面还有一个小细节,如果在上一节遍历过程中已经把本次事件传递给子View,alreadyDispatchedToNewTouchTarget的值会被设置为true,代码会判断alreadyDispatchedToNewTouchTarget的值,避免做重复分发。

小结:
dispatchTouchEvent方法首先判断事件是否需要被拦截,如果需要拦截会调用onInterceptTouchEvent,若该方法返回true,事件由ViewGroup自己处理,不在继续传递。
若事件未被拦截,将先遍历找出一个目标子View,后续事件也将交由目标子View处理。
若没有目标子View,事件由ViewGroup自己处理。

此外,如果一个子View没有消费ACTION_DOWN类型的事件,那么事件将会被另一个子View或者ViewGroup自己消费,之后的事件都只会传递给目标子View(mFirstTouchTarget)或者ViewGroup自身。简单来说,就是如果一个View没有消费ACTION_DOWN事件,后续事件也不会传递进来。

这个方法表示对事件进行处理,在dispatchTouchEvent方法内部调用,如果返回true表示消耗当前事件,如果返回false表示不消耗当前事件。

通过上面的代码我们可以看出来,Activity将时间交给附属的Window进行分发,如果返回true的话,那么事件循环就结束了,如果返回false的话就代表着事件没人处理,那么最后会交给Activity的onTouchEvent处理。

View

现在回头看上一节的第2、3步,不管是对子View分发事件,还是将事件分发给ViewGroup自身,最后都殊途同归,调用到了View的dispatchTouchEvent,这就是我们这一节分析的目标。

public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        if (onFilterTouchEventForSecurity(event)) {
            // 判断事件是否先交给ouTouch方法处理
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }
            // onTouch未消费事件,传给onTouchEvent
            if (onTouchEvent(event)) {
                return true;
            }
        }
        ...
        return false;
    }

代码量不多,主要做了三件事:

  1. 若View设置了OnTouchListener,且处于enable状态时,会先调用mOnTouchListener的onTouch方法
  2. 若onTouch返回false,事件传递给onTouchEvent方法继续处理
  3. 若最后onTouchEvent也没有消费这个事件,将返回false,告知上层parent将事件给其他兄弟View

这样,我们的分析转到了View的onTouchEvent方法:

public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
            mPrivateFlags &= ~PRESSED;
            refreshDrawableState();
        }
        // 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    ...
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {

                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                   }
                    if (!mHasPerformedLongPress) {
                        // 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)) {
                                performClick();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                if (performButtonActionOnTouchDown(event)) {
                    break;
                }
                // Walk up the hierarchy to determine if we're inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();
                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    mPrivateFlags |= PRESSED;
                    refreshDrawableState();
                    checkForLongClick(0);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                removeTapCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
        }
        return true;
    }
    return false;
}
public final boolean isFocusable() {
    return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
}
public final boolean isFocusableInTouchMode() {
    return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE);
}

onTouchEvent方法的主要流程如下:

  1. 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件,只是不会再走下面的流程;
  2. 如果View是enable的且处于可点击状态,事件将被这个View消费:
    在方法返回前,onTouchEvent会根据MotionEvent的不同类型做出不同响应,如调用refreshDrawableState()去设置View的按下效果和抬起效果等。
    这里我们主要关注ACTION_UP分支,这个分支内部经过重重判断之后,会调用到performClick方法:

public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}

可以看到,如果设置了OnClickListener,就会回调我们的onClick方法,最终消费事件

以上三个方法非常重要,贯穿整个View事件分发的流程,它们的关系可以用如下伪代码呈现:

那么我接着Window往下看,Window是怎么处理这个事件的呢,我们看源码知道了Window是个抽象类,抽象类怎么处理呢?所以我们要找他Window的实现类PhoneWindow,我们找到PhoneWindow的dispatchEvent方法

总结

通过上面的源码解析,我们可以总结出事件分发的整体流程:

澳门新葡萄京娱乐场 1

下面做一个总体概括:

事件由Activity的dispatchTouchEvent()开始,将事件传递给当前Activity的根ViewGroup:mDecorView,事件开始自上而下进行传递,直至被消费。

事件传递至ViewGroup时,调用dispatchTouchEvent()进行分发处理:

  1. 检查送否应该对事件进行拦截:onInterceptTouchEvent(),若为true,跳过2步骤;
  2. 将事件依次分发给子View,若事件被某个View消费了,将不再继续分发;
  3. 如果2中没有子View对事件进行消费或者子View的数量为零,事件将由ViewGroup自己处理,处理流程和View的处理流程一致;

事件传递至ViewdispatchTouchEvent()时,
首先会判断OnTouchListener是否存在,倘若存在,则执行onTouch(),若onTouch()未对事件进行消费,事件将继续交由onTouchEvent处理,根据上面分析可知,View的onClick事件是在onTouchEventACTION_UP中触发的,因此,onTouch事件优先于onClick事件。

若事件在自上而下的传递过程中一直没有被消费,而且最底层的子View也没有对其进行消费,事件会反向向上传递,此时,父ViewGroup可以对事件进行消费,若仍然没有被消费的话,最后会回到Activity的onTouchEvent

如果一个子View没有消费ACTION_DOWN类型的事件,那么事件将会被另一个子View或者ViewGroup自己消费,之后的事件都只会传递给目标子View(mFirstTouchTarget)或者ViewGroup自身。简单来说,就是如果一个View没有消费ACTION_DOWN事件,后续事件也不会传递进来。

public boolean dispatchTouchEvent(MotionEvent ev){ boolean handle = false; if(onInterceptTouchEvent{ handle = onTouchEvent; }else{ handle = child.dispatchTouchEvent; } return handle;}

@Overridepublic boolean superDispatchTrackballEvent(MotionEvent event)
{return mDecor.superDispatchTrackballEvent;}

由以上伪代码可得出如下结论:如果一个事件传递到了ViewGroup处,首先会判断当前ViewGroup是否要拦截事件,即调用onInterceptTouchEvent()方法;如果返回true,则表示ViewGroup拦截事件,那么ViewGroup就会调用自身的onTouchEvent来处理事件;如果返回false,表示ViewGroup不拦截事件,此时事件会分发到它的子View处,即调用子View的dispatchTouchEvent方法,如此反复直到事件被消耗掉。接下来,我们将从源码的角度来分析整个ViewGroup事件分发的流程是怎样的。

通过上面的代码我们可以看到PhoneWindow把事件传递给了mDecor,那么这个mDecor是什么呢,我继续往下看

我们知道,事件产生于用户按下屏幕的一瞬间,事件生成后,经过一系列的过程来到我们的Activity层,那么事件是怎样从Activity传递到根ViewGroup的呢?由于这个问题不在本文的讨论范围,所以这里简单提一下:事件到达Activity时,会调用Activity#dispatchTouchEvent方法,在这个方法,会把事件传递给Window,然后Window把事件传递给DecorView,而DecorView是什么呢?它其实是一个根View,即根布局,我们所设置的布局是它的一个子View。最后再从DecorView传递给我们的根ViewGroup。所以在Activity传递事件给ViwGroup的流程是这样的:Activity->Window->DecorView->ViewGroup

// This is the top-level view of the window, containing the window
decor.private DecorView mDecor;

接下来便是本文的重要,对ViewGroup#dispatchTouchEvent()方法源码进行解读,由于源码比较长,所以这里分段贴出。

那么我们可以看到,mDecor是DecorView,那么这个DecorView又是什么呢,注意看上面的注释,翻译过来就是,这是窗口的顶级视图,包含窗口装饰。也就是说,PhoneWindow把事件传递给了DecorView也就是顶级视图,而DecorView是继承FrameLayout,而FrameLayout又是继承的Viewgroup,也就是说,事件一定会传递到Viewgroup里面去,所以我们直接去看Viewgroup的dispatchTouchEvent是怎么处理的。

首先看如下所示的源码:

// Check for interception.

final booleanintercepted;

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

final booleandisallowIntercept =
(mGroupFlags&FLAG_DISALLOW_INTERCEPT) !=0;

if(!disallowIntercept) {

intercepted = onInterceptTouchEvent;

ev.setAction;// 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;

}

 ... // 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. //这里把mFirstTouchTarget设置为null cancelAndClearTouchTargets; resetTouchState(); }

Viewgroup的dispatchTouchEvent的代码逻辑比较多,我们就分段说明,先看上面这段代码,上面注释Check
for
interception.表示的意思是检查拦截,也就是这段代码的逻辑是是否拦截处理这个事件,然后我们从代码中可以看出,Viewgroup在两种情况下会判断是否拦截事件,那两种呢,一个是MotionEvent.ACTION_DOWN也就是按下的时候,还有一个是mFirstTouchTarget!=null的时候,那这个是什么情况呢,这里我们可以从后面的代码中可以看出来,在后面的代码中我们可以看出如果ViewGroup的子元素处理成功时,mFirstTouchTarget会被赋值并指向子元素,也就是当Viewgroup不拦截事件交由子元素处理时,mFirstTouchTarget
!=null,反过来说,ViewGroup如果拦截了当前事件的话,那么(actionMasked ==
MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null)这个判断就会不成立返回false,导致ViewGroup中的onInterceptTouchEvent不会再被调用,并且同一事件系列的其他事件也会默认交给他处理。

首先这里先判断事件是否为DOWN事件,如果是,则初始化,把mFirstTouchTarget置为null。由于一个完整的事件序列是以DOWN开始,以UP结束,所以如果是DOWN事件,那么说明是一个新的事件序列,所以需要初始化之前的状态。这里的mFirstTouchTarget非常重要,后面会说到当ViewGroup的子元素成功处理事件的时候,mFirstTouchTarget会指向子元素,这里要留意一下。

我们接着再看ViewGroup不拦截事件交由子元素处理的时候,看源码:

接着我们往下看:

finalView[] children =mChildren;

for(inti = childrenCount -1; i >=0; i–) {

final intchildIndex = getAndVerifyPreorderedIndex(

childrenCount, i, customOrder);

finalView child =getAndVerifyPreorderedView(

preorderedList, children, 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;

}

if(!canViewReceivePointerEvents|| !isTransformedTouchPointInView(x, y,
child,null)) {

ev.setTargetAccessibilityFocus;

continue;

}

newTouchTarget = getTouchTarget;

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;

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(intj =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;

}

if(preorderedList !=null) preorderedList.clear();

}

// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 1 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent; // 2 ev.setAction; // 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; } ... // Check for cancelation. final boolean canceled = resetCancelNextUpFlag || actionMasked == MotionEvent.ACTION_CANCEL;

上面这段代码呢,我们可以看出来在遍历ViewGroup所有的子元素,然后通过子元素是否在播动画和点击事件的坐标是否在子元素的区域内来判断子元素是否能够接受这个事件。如果这两个条件都满足就交给这个子元素处理,我们看到dispatchTransformedTouchEvent这个方法,点击进去看,你会发现

以上代码主要判断ViewGroup是否要拦截事件。定义了一个布尔值intercept来记录是否要进行拦截,这在后面发挥很重要的作用。①号代码处,首先执行了这个语句:if(actionMasked
== MotionEvent.ACTION_DOWN || mFirstTouchTarget !=
null),也即是说,如果事件是DOWN或者mFirstTouchTatget值不为空的时候,才有可能执行②号代码,否则会直接跳过判断是否拦截。为什么要有这个判断呢?这里解释一下,比如说,子View消耗了ACTION_DOWN事件,然后这里可以由ViewGroup继续判断是否要拦截接下来的ACTION_MOVE事件之类的;又比如说,如果第一次DOWN事件最终不是由子View消耗掉的,那么显然mFirstTouchTarget将为null,所以也就不用判断了,直接把intercept设置为true,此后的事件都是由这个ViewGroup处理。②号处调用了onInterceptTouchEvent()方法,那么我们可以跟进去看看这个onInterceptTouchEvent()做了什么。2.1、ViewGroup#onInterceptTouchEvent()

if(child ==null) {

handled =super.dispatchTouchEvent;

}else{

handled = child.dispatchTouchEvent;

}

public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }

他在判断child也就是子元素是否为空,不为空的话就交给子元素处理了,从而完成了一轮事件分发。如果子元素的dispatchTouchEvent返回true,这时我们暂时不考虑事件在子元素内部是怎么处理的,那么mFirstTouchTarget就会被赋值同时跳出for循环,看源码

可以看出,ViewGroup#onInterceptTouchEvent()方法是默认返回false的,即ViewGroup默认不拦截任何事件,如果想要让ViewGroup拦截事件,那么应该在自定义的ViewGroup中重写这个方法。2.2、我们再看看原来的代码,会发现还有一个FLAG_DISALLOW_INTERCEPT标志位,这个标志位的作用是禁止ViewGroup拦截除了DOWN之外的事件,一般通过子View的requestDisallowInterceptTouchEvent来设置。2.3、最后判断是否是CANCEL事件。

newTouchTarget= addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget =true;

break;

根据以上分析,这里小结一下:当ViewGroup要拦截事件的时候,那么后续的事件序列都将交给它处理,而不用再调用onInterceptTouchEvent()方法了,所以该方法并不是每次事件都会调用的。

这里完成了mFirstTouchTarget的赋值并终止对子元素的遍历。吐过dispatchTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素(如果还有下一个子元素的话)。

返回ViewGroup#dispatchTouchEvent()源码,我们继续往下看。接下来是一个If判断语句,内部还有若干if语句,以下先省略所有if体的内容,我们从大体上认识这块代码的作用:

看到上面,你们一定会想mFirstTouchTarget是在哪里赋值的啊,我们看addTouchTarget这个方法

TouchTarget newTouchTarget = null; // 1boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) { ...// IF体1 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { ...// IF体2 }}

privateTouchTarget addTouchTarget(@NonNull View
child,intpointerIdBits) {

finalTouchTarget target = TouchTarget.obtain(child, pointerIdBits);

target.next=mFirstTouchTarget;

mFirstTouchTarget= target;

returntarget;

}

首先,在每一次调用这个方法的时候,会执行①号代码:在进行判断之前,已经把newTouchTarget和alreadyDispatchedToNewTouchTarget置为null了,这里尤其注意。接着,判断if(!canceled
&&
!intercepted),表示如果不是取消事件以及ViewGroup不进行拦截则进入IF体1,接着又是一个判断if
(actionMasked == MotionEvent.ACTION_DOWN
…)这表示事件是否是ACTION_DOWN事件,如果是则进入IF体2,根据以上两个IF条件,事件是ACTION_DOWN以及ViewGroup不拦截,那么IF体2内部应该是把事件分发给子View了,我们展开IF体2,看看内部实现了什么:

mFirstTouchTarget是一种单链表结构,mFirstTouchTarget是否被赋值直接影响到ViewGroup对事件的拦截,如果mFirstTouchTarget为空,那么ViewGroup将默认拦截同一序列中的所有点击事件。

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) { ...// IF体3 } if (newTouchTarget == null && mFirstTouchTarget != null) { ... }

今天就先讲ViewGroup的事件分发,如果大家觉得还是不理解的话,就去按照郭神的例子敲一下,这里附上一个连接:

可以看出,这里获取了childrenCount的值,表示该ViewGroup内部有多少个子View,如果有则进入IF体3,意思就是说,如果有子View就在IF体3里面开始遍历所有子View判断是否要把事件分发给子View。我们展开IF体3:

谢谢大家对我的支持。

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--) { // 1 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 || !isTransformedTouchPointInView(x, y, child, null)) { // 2 ev.setTargetAccessibilityFocus; continue; } newTouchTarget = getTouchTarget; 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; //把事件分发给子View if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 3 // 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); // 4 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; } if (preorderedList != null) preorderedList.clear();}

代码也比较长,我们只关注重点部分。先看①处的代码,是一个for循环,这里表示对所有的子View进行循环遍历,由于以上判断了ViewGroup不对事件进行拦截,那么在这里就要对ViewGroup内部的子View进行遍历,一个个地找到能接受事件的子View,这里注意到它是倒序遍历的,即从最上层的子View开始往内层遍历,这也符合我们平常的习惯,因为一般来说我们对屏幕的触摸,肯定是希望最上层的View来响应的,而不是被覆盖这的底层的View来响应,否则这有悖于生活体验。然后②号代码是If语句,根据方法名字我们得知这个判断语句是判断触摸点位置是否在子View的范围内或者子View是否在播放动画,如果均不符合则continue,表示这个子View不符合条件,开始遍历下一个子View。接着③号代码,这里调用了dispatchTransformedTouchEvent()方法,这个方法有什么用呢?3.1、我们看看这个方法,ViewGroup#dispatchTransformedTouchEvent():

...final boolean handled;if (child == null) { handled = super.dispatchTouchEvent; } else { handled = child.dispatchTouchEvent; } event.setAction(oldAction); return handled;...

方法大体上是这样,做了适当删减,显然,当传递进来的的child不为null时,就会调用子View的dispatchTouchEvent方法,表示把事件交给子View处理,也即是说,子Viwe符合所有条件的时候,事件就会在这里传递给了子View来处理,完成了ViewGroup到子View的事件传递,当事件处理完毕,就会返回一个布尔值handled,该值表示子View是否消耗了事件。怎样判断一个子View是否消耗了事件呢?如果说子View的onTouchEvent()返回true,那么就是消耗了事件。3.2、在③号代码处判断子View是否消耗事件,如果消耗了事件那么最后便会执行到④号代码:addTouchTarget()。我们来看看这个方法:ViewGroup#addTouchTarget():

private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }

可以看到,在这个方法里面,把mFirstTouchTarget指向了child,同时把newTouchTarget也指向child,也即是说,如果子View消耗掉了事件,那么mFirstTouchTarget就会指向子View。在执行完④号代码后,直接break了,表示跳出了循环,因为已经找到了处理事件的子View,所以无需继续遍历了。

小结:整一个if(!canceled && !intercepted){ …
}代码块所做的工作就是对ACTION_DOWN事件的特殊处理。因为ACTION_DOWN事件是一个事件序列的开始,所以我们要先找到能够处理这个事件序列的一个子View,如果一个子View能够消耗事件,那么mFirstTouchTarget会指向子View,如果所有的子View都不能消耗事件,那么mFirstTouchTarget将为null

第3点是对ACTION_DOWN事件的处理,那么不是ACTION_DOWN的事件将从以下开始处理:

// 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); // 1 } 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)) { // 2 handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } ... return handled;}

首先是一个if判断语句,判断mFirstTouchTarget是否为Null,如果为null,那么调用①处的代码:dispatchTransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS),这个方法上面出现过了,这里第三个参数为null,那么我们看方法体,会执行super.dispatchTouchEvent;这里意思是说,如果找不到子View来处理事件,那么最后会交由ViewGroup来处理事件。接着,如果在上面已经找到一个子View来消耗事件了,那么这里的mFirstTouchTarget不为空,接着会往下执行。接着有一个if
(alreadyDispatchedToNewTouchTarget && target ==
newTouchTarget)判断,这里就是区分了ACTION_DOWN事件和别的事件,因为在第3.2点的分析我们知道,如果子View消耗了ACTION_DOWN事件,那么alreadyDispatchedToNewTouchTarget和newTouchTarget已经有值了,所以就直接置handled为true并返回;那么如果alreadyDispatchedToNewTouchTarget和newTouchTarget值为null,那么就不是ACTION_DOWN事件,即是ACTION_MOVE、ACTION_UP等别的事件的话,就会调用②号代码,把这些事件分发给子View。

小结:最后这段代码处理除了ACTION_DOWN事件之外的其他事件,如果ViewGroup拦截了事件或者所有子View均不消耗事件那么在这里交由ViewGroup处理事件;如果有子View已经消耗了ACTION_DOWN事件,那么在这里继续把其他事件分发给子View处理。

至此,关于ViewGroup的事件分发机制源码已经分析完毕。下面给出一幅流程图来描述一下以上所分析的内容:

澳门新葡萄京娱乐场 2ViewGroup事件分发流程

1、ACTION_DOWN事件为一个事件序列的开始,中间有若干个ACTION_MOVE,最后以ACTION_UP结束。2、ViewGroup默认不拦截任何事件,所以事件能正常分发到子View处(如果子View符合条件的话),如果没有合适的子View或者子View不消耗ACTION_DOWN事件,那么接着事件会交由ViewGroup处理,并且同一事件序列之后的事件不会再分发给子View了。如果ViewGroup的onTouchEvent也返回false,即ViewGroup也不消耗事件的话,那么最后事件会交由Activity处理。即:逐层分发事件下去,如果都没有处理事件的View,那么事件会逐层向上返回。3、如果某一个View拦截了事件,那么同一个事件序列的其他所有事件都会交由这个View处理,此时不再调用View(ViewGroup)的onIntercept()方法去询问是否要拦截了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注