Android事件分发机制

想要了解Android的事件分发机制,首先需要知道事件是从哪里开始的。从之前的一篇文章View是如何被添加到屏幕上的的最后一幅图,可以知道,我们打开一个界面后,界面的层级从顶层开始是

Activity->PhoneWindow->DecorView->…

所以当我们手指点击到屏幕上之后,事件的分发也是在Activity中开始。执行Activity中的dispatchTouchEvent方法那就从这里开始看

    public boolean dispatchTouchEvent(MotionEvent ev) {
       //如果是DOWN事件
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //此方法里面是空的,可以重写此方法
            onUserInteraction();
        }
        //如果getWindow().superDispatchTouchEvent(ev)返回true
        //那么dispatchTouchEvent方法就返回true
        //反之就执行activity的onTouchEvent方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

首先判断是不是DOWN事件,如果是执行onUserInteraction(),这个方法里面啥也没有,我们可以重写这个方法来实现我们的关于DOWN事件的逻辑。

然后调用getWindow().superDispatchTouchEvent(ev),如果它返回true,那么dispatchTouchEvent就返回true。如果返回false,就执行activity的onTouchEvent方法。

Activity的onTouchEvent方法很简单,如果一个事件没有被Activity下的任何一个veiw接受
就结束返回true,只有点击到Window外面才会返回true,一般情况下都返回false。

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

        return false;
    }

下面来看看getWindow().superDispatchTouchEvent(ev)这个方法,getWindow()方法返回一个Window对象,它是一个抽象类,只有一个子类那就是PhoneWindow,所以无PhoneWindow中查看superDispatchTouchEvent(ev)方法

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

mDecor是一个DecorView对象,public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks,DecorView继承自FrameLayout,所以它也是一个ViewGroup它是我们一个页面的最顶级的View。点进DecorView中可以看到

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

它直接调用了其父类的dispatchTouchEvent方法,继续跟进就进入到了ViewGroup中的dispatchTouchEvent方法了。

现在我们知道,当我们点击屏幕的时候,事件通过Activity传递到PhoneWindow在传递到ViewGroup中开始真正的分发。下面开始查看ViewGroup中的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        //安全验证
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            //开始处理一个DOWN事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
               //当开始处理一个DOWN事件的时候,清除掉之前的所有的事件和标志位
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            //定义一个boolean类型的变量记录是否拦截事件
            final boolean intercepted;
            //如果是DOWN事件并且当前触摸的对象mFirstTouchTarget不为空
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //disallowIntercept可以拦截除了Down事件以外的事件,因为前面的DOWN中清空了所有标志位
                //FLAG_DISALLOW_INTERCEPT可以通过requestDisallowInterceptTouchEvent方法来更改。一般是子view调用这个方法。
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                //onInterceptTouchEvent默认返回false,不拦截事件
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                //没有触摸目标或者不是DOWN事件,说明拦截
                intercepted = true;
            }

            // 如果拦截了,就进行正常的事件分发
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // 检查是否取消
            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) {

                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;

                   //清理指针id防止不同步
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //如果新的触摸对象为空并且子view个数大于0
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        //找到一个可以接收事件的子节点
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //倒叙遍历子view
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                           //如果view是可获取焦点的 
                            if (childWithAccessibilityFocus != null) {
                                //当前view不可获取焦点
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            //如果view是不可见状态或者在执行动画,或者触摸范围不是在view的范围之内,就跳出循环继续循环下面的
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            //找到当前触摸的view
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                               //子view在触摸范围内,在给它一个标志位
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //dispatchTransformedTouchEvent分发事件,如果child不为null,就调用child的dispatchTouchEvent方法,
                            //child为null就调用父类的dispatchTouchEvent方法。
                            //返回值就是子veiw是否处理事件
                            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();
                                //addTouchTarget方法给mFirstTouchTarget赋值,addTouchTarget指向当前的子view。
                                //所以如果所有子view都不消耗事件,mFirstTouchTarget就为null
                                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;
                    }
                }
            }

            // mFirstTouchTarget == null表示没有子veiw消耗事件
            if (mFirstTouchTarget == null) {
                // 就把它当成一个普通的veiw来执行dispatchTransformedTouchEvent方法
                //第三个参数传null,进去后会调用父类的dispatchTouchEvent方法,
                //最终调用onTouchEvent方法来处理事件
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //如果DOWN事件处理完毕
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        除了DOWN事件的其余事件分发给子view处理
                        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;
                }
            }

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

上面就是ViewGroup的事件分发的流程,重要部分都做了注释,总结一下就是,dispatchTouchEvent方法中,onInterceptTouchEvent方法是否拦截事件,默认不拦截,想要拦截我们需要重写此方法。如果拦截就通过dispatchTransformedTouchEvent方法调用自身的onTouchvent()方法,否则就还是通过dispatchTransformedTouchEvent方法调用子view的dispatchTouchEvent方法。如果子view还是ViewGroup,那么重复上面的调用流程,如果子view是View,那么执行View的dispatchTouchEvent方法。下面去看一下View的dispatchTouchEvent方法

  public boolean dispatchTouchEvent(MotionEvent event) {
        //焦点的判断
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        //5.0以后的嵌套滑动
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }
        //安全判断
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //我们是否设置了mOnTouchListener,如果设置了执行它的onTouch方法
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            //执行onTouchEvent方法。到这里我们知道没如果我们设置了一个view的
            //mOnTouchListener方法,那么先执行它的onTouch方法,返回false才执行onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

View的dispatchTouchEvent方法就简单多了,首先判断我们有没有设置OnTouchListener方法,如果设置了就执行它的Touch方法,只有Touch方法返回false的时候,才会继续去执行View的onTouchEvent方法。所以我们知道OnTouchListener的优先级高于onTouchEvent。

OK事件分发看完了,下面去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;
            //如果一个vew设置了点击事件或者长按事件,即使它是DISABLED的,
            //也会消费这个事件,只是不响应。也就是这里直接返回true,但是下面的代码都不执行了。
            return clickable;
        }
        //如果view设置了代理执行下面的方法
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        //如果是可点击的,就开始处理ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE事件。
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // 按钮在我们之前就被释放了
                            // 按下显示。让它显示被压的
                            // 现在状态(在调度单击之前)以确保用户可以看到它。
                            setPressed(true, x, y);
                        }
                        //如果没有处理长按事件或者长按事件返回了false
                        //mHasPerformedLongPress在DOWN的时候置为false,在DOWN中
                        //监测是否有长按事件,如果有长按事件mHasPerformedLongPress会置为true
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 如果没有长按事件,移除掉长按的回调
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                               //使用Runnable来执行一个点击事件,而不是直接执行
                                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();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    //是否处理长按事件
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        //监测长按事件
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // 是否在一个滚动容器中
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // 如果是在容器中,发送一个100毫秒的延时post
                    //在其run方法中监测长按事件
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // 如果不在容器中,直接检查长按事件
                        //长按事件的监测,发送一个500毫秒的延时post,
                        //在run方法中如果检测到是长按就给上面的mHasPerformedLongPress标志位赋值为true
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

onTouchEvent方法中,只要CLICKABLE和LONG_CLICKABLE有一个为true就消费这个事件,在DOWN方法中处理长按事件,在UP方法中处理点击事件。LONG_CLICKABLE默认为false,CLICKABLE的值跟具体的view有关,比如Button默认是true,TextView默认是false。通过setClickable和setLongClickable可以改变这两个值。setOnClickListener和setOnLongClickListener会把这两个值设置为true。

OK,View的事件分发的源码查看完毕,看一下流程图

在这里插入图片描述

总结一下:

  • 一个事件序列是从手指按下屏幕(ACTION_DOWN)开始,到手指离开屏幕(ACTION_UP)结束,中间有一系列的(ACTION_MOVE)事件,非人为的结束事件会走到ACTION_CANCEL中
  • 正常情况下一个事件序列只能由一个View拦截消耗,因为某一个View一旦决定拦截事件,那么这个事件就只能由它来处理。并且它的onInterceptTouchEvent方法不会在被调用。
  • 一旦一个View开始处理事件,如果它不消耗ACTION_DOWN事件,也就是onTouchEvent返回了false,那么同一个事件序列中的其他事件都不会再交给他处理,并且把事件重新交给它的父容器来处理。父容器的onTouchEvent方法会被调用。
  • 事件的分发过程是由外到内的,事件总是先传递到父容器,在由父容器分发给子View,子View可以通过requestDisallowInterceptTouchEvent方法来干预父容器的执行,ACTION_DOWN事件除外,因为它会在ACTION_DOWN事件中清空标志位。
  • ViewGroup中的onInterceptTouchEvent方法默认返回false,所有ViewGroup默认不拦截任何事件。而View中没有onInterceptTouchEvent方法,一旦有事件传递给它,它的onTouchEvent方法就会被调用。
  • View的onTouchEvent方法默认返回true,也就是默认处理事件,除非它是不可点击的(clickable和longClickable都为false)。LONG_CLICKABLE默认为false,CLICKABLE的值跟具体的view有关,比如Button默认是true,TextView默认是false。通过setClickable和setLongClickable可以改变这两个值。setOnClickListener和setOnLongClickListener会把这两个值设置为true。
  • View的enable属性不会影响onTouchEvent的默认返回值,即使它是enable状态,clickable和longClickable只要有一个为true,那么它的onTouchEvent就返回true。
  • onClick会响应的前提是,View是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,当长按事件返回true的时候,onClick是不会响应的。
  • onLongClick是在ACTION_DOWN中判断的,想要执行长按事件,longClickable需要为true。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值