安卓View—事件分发机制(二)

安卓View—事件分发机制

一、基础知识

1.事件分发是什么

事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给具体的View,这个过程就是分发过程。

2.点击事件的传递规则

点击事件的分发过程由三个很重要的方法来共同完成:dispatchTouchEvent、onIntercepTouchEvent、onTouchEvent

  • dispatchTouchEvent

    用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,标识是否消耗当前事件。

  • onInterceptTouchEvent

    在上述方法的内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中此方法不会再次被调用,返回结果表示是否拦截当前事件。

  • onTouchEvent

    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

方法名 方法作用 调用时机 返回结果
dispatchTouchEvent 事件分发 事件传递给当前View的时候被调用 是否消耗当前事件
onInterceptTouchEvent 判断是都拦截当前事件 在ViewGroup的dispatchTouchEvent内部调用 是否拦截当前事件
onTouchEvent 处理触摸事件 在dispatchTouchEvent内部调用 是否消耗当前事件
//在一个ViewGroup中
public boolen dispatchTouchEvent(){
	boolen consume = falseif(onInterceptTouchEvent){  
		consume = onTouchEvent(ev); //如果被拦截,调用当前viewGroup的onTouchEvent
	}else{
		consume = child.dispatchTouchEvent(); //如果未被拦截,调用当前的子view的dispatchTouchEvent,即事件传递给子view
	}
	return consume;
}

3.事件分发的顺序

当一个点击事件产生后,它的传递按如下顺序进行:Activity -> Window -> 顶级View(DecorView)-> 子View

事件总是先传递给Activity,Activity再次传递给Window,最后Window传递给顶级View(DecorView),顶级View接收到事件后会按照事件分发机制去分发事件。

二、事件分发源码分析

事件分发其实包含了三个部分的分发:

  • Activity对点击事件的分发过程
  • ViewGroup的事件分发
  • View的事件分发

1.Activity对点击事件的分发过程

当触发一个点击事件的时,最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件分发,具体工作由Activity内部的Window来完成的,Window会将事件传递给decorView。

先来分析Activity的dispatchTouchEvent:

	//Activty.dispatchTouchEvent	
	public boolean dispatchTouchEvent(MotionEvent ev) {
        //down时调用onUserInteraction
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            //如果getWindow().superDispatchTouchEvent(ev)返回true事件分发结束,不调用Activity .onTouchEvent
            return true;
        }
        //都不消耗或拦截最终调用Activity的onTouchEvent
        return onTouchEvent(ev);
    }
	//Activity.onUserInteraction
    public void onUserInteraction() {
        //空方法
    }

	/*
	getWindow获取Window,Window的实现类是PhoneWindow
	*/
	//PhoneWindow.superDispatchTouchEvent
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //mDecor为decorView,PhoneWindow将事件传递给了DecorView
        return mDecor.superDispatchTouchEvent(event);
    }
	
	/*
	 *	DecoreView为根View继承自FrameLayout
	 */
	//DecoreView.superDispatchTouchEvent
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //事件传递给父类的dispatchTouchEvent方法,即传递给ViewGroup的dispatchTouchEvent方法
        return super.dispatchTouchEvent(event);
    }
	
	//Activity.onTouchEvent
    public boolean onTouchEvent(MotionEvent event) {
        //shouldCloseOnTouch为抽象类Window自身的方法而不是PhoneWindow的方法
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

	//Window.shouldCloseOnTouch
	/*
	 *该方法主要是判断点击事件是否为Down按下事件,并且在边界之外,并且还判断了一些标志位。若按下事件在边界外,且标志位为TRUE,否则返回FALSE。返回TRUE的话,正如上文所说,会在onTouchEvent中调用finish方法,并返回TRUE,否则也直接返回FALSE
	 */
        public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
            return true;
        }
        return false;
    }

默认情况下Activity的dispatchTouchEvent调用ViewGroup的dispatchTouchEvent方法,而无论dispatchTouchEvent返回什么都结束分发

2.ViewGroup对点击事件的分发过程

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

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) { // view没有被遮罩,一般都成立
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(从按下到松手)中的第一个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(); // 作为新一轮的开始,reset所有相关的状态
            }

            // Check for interception.
            final boolean intercepted; // 检查是否要拦截
            //mFirstTouchTarget :当时间有子元素成功处理后,mFirstTouchTarget会被赋值并指向子元素。
            //mFirstTouchTarget!=null 即表示ViewGroup表示未拦截当前事件,
            if (actionMasked == MotionEvent.ACTION_DOWN // down事件
                    || mFirstTouchTarget != null) { // 或者之前的某次事件已经经由此ViewGroup派发给children后被处理掉了

                //FLAG_DISALLOW_INTERCEPT设置后,ViewGroup无法栏除ACTION_DOWN之外的其他点击直接。
                //原因:在ViewGroup分发事件时,如果是ACTION_DOWN,会重置这个标志位
                //设置方法: requestDisallowInterceptTouchEvent
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) { // 只有允许拦截才执行onInterceptTouchEvent方法
                    intercepted = onInterceptTouchEvent(ev); // 默认返回false,不拦截
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false; // 不允许拦截的话,直接设为false
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 在这种情况下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null
                // 第一次的down事件没有被此ViewGroup的children处理掉(要么是它们自己不处理,要么是ViewGroup从一
                // 开始的down事件就开始拦截),则接下来的所有事件
                // 也没它们的份,即不处理down事件的话,那表示你对后面接下来的事件也不感兴趣
                intercepted = true; // 这种情况下设置ViewGroup拦截接下来的事件
            }

            // Check for cancelation.
            //检查CANCEL事件
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了

            // Update list of touch targets for pointer down, if needed.
            // 是否拆分事件,3.0(包括)之后引入的,默认拆分
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null; // 接下来ViewGroup判断要将此touch事件交给谁处理
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件
                if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指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 View[] children = mChildren;

                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        // 从最后一个向第一个找
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue; // 不满足这2个条件直接跳过,看下一个child
                            }

                            // child view能receive touch事件而且touch坐标也在view边界内

                            newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget
                            if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.

                                //子View已经在自己的范围内得到了触摸。
                                //除了它正在处理的那个,给它一个新的指针。
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break; // newTouchTarget已经有了,跳出for循环
                            }

                            resetCancelNextUpFlag(child);
                            // 将此事件交给child处理
                            // 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推
                            // 这样TouchTarget的链就形成了
                            //进行子View的分发
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 如果处理掉了的话,将此child添加到touch链的头部
                                // 注意这个方法内部会更新 mFirstTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了
                                break; // 可以退出for循环了。。。
                            }
                        }
                    }

                    // 本次没找到newTouchTarget但之前的mFirstTouchTarget已经有了
                    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;
                        }
                        // while结束后,newTouchTarget指向了最初的TouchTarget
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            // 非down事件直接从这里开始处理,不会走上面的一大堆寻找TouchTarget的逻辑
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 没有children处理则派发给自己处理
                // 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) { // 遍历TouchTarget形成的链表
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true; // 已经处理过的不再让其处理事件
                    } else {
                        // 取消child标记
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 如果ViewGroup从半路拦截了touch事件则给touch链上的child发送cancel事件
                        // 如果cancelChild为true的话
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true; // TouchTarget链中任意一个处理了则设置handled为true
                        }
                        if (cancelChild) { // 如果是cancelChild的话,则回收此target节点
                            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) {
                // 取消或up事件时resetTouchState
                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.onInterceptTouchEvent() 
* 作用:是否拦截事件 * 说明: 
* b. 返回false = 不拦截(默认)
**/
public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

3.View对点击事件的分发过程

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

        if (onFilterTouchEventForSecurity(event)) { // 一般都成立
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;

            //首先判断是否设置OnTouchListener,如果OnTouchListener中的onTouch方法中返回true,那么onTouchEvent就不会被调用,
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED状态下尝试调用onTouch方法
                return true; // 如果被onTouch处理了,则直接返回true
            }
            // 从这里我们可以看出,当你既设置了OnTouchListener又设置了OnClickListener,那么当前者返回true的时候,
            // onTouchEvent没机会被调用,当然你的OnClickListener也就不会被触发;另外还有个区别就是onTouch里可以
            // 收到每次touch事件,而onClickListener只是在up事件到来时触发。
            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false; // 上面的都没处理,则返回false
    }

    public boolean onTouchEvent(MotionEvent event) { // View对touch事件的默认处理逻辑
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) { // DISABLED的状态下
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false); // 复原,如果之前是PRESSED状态
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE || // CLICKABLE或LONG_CLICKABLE的view标记为对事件处理了,
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); // 只不过是以do nothing的方式处理了。
        }

        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) { // 如果有TouchDelegate的话,优先交给它处理
                return true; // 处理了返回true,否则接着往下走
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE || // View能对touch事件响应的前提要么是CLICKABLE要么是LONG_CLICKABLE
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP: // UP事件
                    // 如果外围有可以滚动的parent的话,当按下时会设置这个标志位
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // 按下了或者预按下了
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        // 尝试requestFocus(),并将focusToken设置为true
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus(); // 能进来这个if,一般都会返回true
                        }

                        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.
                            // 在前面down事件的时候我们延迟显示view的pressed状态
                            setPressed(true); // 直到up事件到来的时候才显示pressed状态
                        }

                        if (!mHasPerformedLongPress) { // 如果没有长按发生的话
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback(); // 移除长按callback

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) { // 看到没,focusTaken是false才会进入下面的if语句
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                        // 也就是说在touch mode下,不take focus的view第一次点击的时候才会触发onClick事件
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) { // 如果post失败了,则直接调用performClick()方法
                                    performClick(); // 这2行代码会触发onClickListener
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState(); // unset按下状态的
                        }

                        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: // 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) { // 如果是在可以滚动的container里面的话
                        mPrivateFlags |= PFLAG_PREPRESSED; // 设置PREPRESSED标志位
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        } // 延迟pressed feedback
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true); // 否则直接显示pressed feedback
                        checkForLongClick(0); // 并启动长按监测
                    }
                    break;

                case MotionEvent.ACTION_CANCEL: // 针对CANCEL事件的话,恢复各种状态,移除各种callback
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE: // 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)) { // 如果移动到view的边界之外了,
                        // Outside button
                        removeTapCallback(); // 则取消Tap callback,这样当你松手的时候onClick不会被触发
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // 当已经是按下状态的话
                            // Remove any future long press/tap checks
                            removeLongPressCallback(); // 移除长按callback

                            setPressed(false); // 恢复按下状态
                        }
                    }
                    break;
            }
            return true; // 最后返回true,表示对touch事件处理过了,消费了
        }

        return false; // 既不能单击也不能长按的View,返回false,表示不处理touch事件
    }

三、事件分发总结

  1. Android 事件分发总是遵循 Activity => ViewGroup => View 的传递顺序
  2. onTouch()执行优先于onClick()
  3. 同一事件序列是指从手指接触到屏幕的那一刻起,到手指离开的屏幕的那一瞬间结束,在这个过程中所产生的一系列的事件,这个事件以down事件开始,中间含有数量不等的move事件,最终以up事件结束
  4. 正常情况下,一个事件序列只能被一个View拦截且消耗,这一条的原因可以参考(3),因为一旦一个元素拦截了此事件,那么同一事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但通过特殊手段可以做到,比如同一个事件序列中的事件不能分别由两个View同时处理,但通过特殊手段/可以做到,比如一个View将本该自己处理的事情通过onTouchEvent强行传递给其他View处理。
  5. 某个View一旦决定拦截,那么这一个事件序列都只能由他处理,并且onInterceptTouchEvent都不会在被调用。
  6. 某个View如果不消耗ACTION_DOWN事件。那么同一事件序列的其他事件也不会嫁给他,并且把事件重新交给它的父元素处理,即调用父元素的onTouchEvent。
  7. ViewGroup默认不拦截任何事件。即源码中onInterceptTouchEvent方法中默认 返回false
    View没有onIntercept方法,一旦有点击事件传递给他,那么他的onTouchEvent就会被调用
  8. View的onTouchEvent默认都会消耗事件,除非他是不可点击事件。(clickbale和longClickable同时为false),Veiw的longClickable默认属性都是false,chickable属性要分情况,比如button的clicjable属性默认是true,而TextView的clickanle的默认属性为fasle。
  9. onClick会发生的前提是当前View是可点击的。并且它收到了down和up事件
发布了13 篇原创文章 · 获赞 24 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sinat_28502161/article/details/103602469