Activity的事件分发机制

683 阅读8分钟

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

事件分发的概述

事件分发,其实就是对MotionEvent事件的分发过程。此过程由三个很重要的方法来共同完成。dispatchTouchEventonInterceptTouchEventonTouchEvent

dispatchTouchEvent(分发)

返回值为true表示事件被当前视图消费掉;返回为super.dispatchTouchEvent表示继续分发该事件

onInterceptTouchEvent(拦截)

返回值为true,表示拦截这个事件并交由自身的onTouchEvent方法进行消费;

返回false表示不拦截,需要继续传递给子视图。

如果return super.onInterceptTouchEvent(ev), 事件拦截分两种情况

  1. 如果该View(ViewGroup)存在子View且点击到了该子View, 则不拦截, 继续分发 给子View 处理, 此时相当于return false。
  2. 如果该View(ViewGroup)没有子View或者有子View但是没有点击中子View(此时ViewGroup 相当于普通View), 则交由该View的onTouchEvent响应,此时相当于return true。
onTouchEvent(消费)

返回值为true表示当前视图可以处理对应的事件;

返回值为false表示当前视图不处理这个事件,它会被传递给父视图的onTouchEvent方法进行处理。

如果return super.onTouchEvent(ev),事件处理分为两种情况:

  1. 如果该View是clickable或者longclickable的,则会返回true, 表示消费 了该事件, 与返回true一样;
  2. 如果该View不是clickable或者longclickable的,则会返回false, 表示不 消费该事件,将会向上传递,与返回false一样.

事件分发的顺序

image.png

Activity -> ViewGroup -> View

控件分发拦截消费
Activity
ViewGroup
View

View的事件分发

进入dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent event) {
    。。。
    boolean result = false;
    。。。。
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    。。。
    return result;
}

if(onFilterTouchEventForSecurity(event)),这个主要是判断当前事件到来的时候,窗口有没有被遮挡,如果被遮挡则会直接返回false,从而中断事件的处理。

如果窗口没被遮挡,那么会正常处理事件。

进入ListenerInfo 类中

static class ListenerInfo {

        public OnClickListener mOnClickListener;

        protected OnLongClickListener mOnLongClickListener;

        private OnKeyListener mOnKeyListener;

        private OnTouchListener mOnTouchListener;
        ...
    }

这是View里面的一个内部类,定义了一系列的Listener,其中有我们经常用到的onClickListener,这里是获取当前View所设置的Listener。

进入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;
    
    //判断了当前View是否可用,如果不可用则进入if体,根据注释我们知道,
    //即使是不可 以状态下的View,
    //如果它自身是可点击或者可长按的话,一样会消耗事件,只是不作出任何反应罢了。
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        return clickable;
    }
    
    //这里判断是否设置了mTouchDelegate,这个表示View的代理,即如果设置了代理,
    //那么当前View的点击事件会交给代理的View来处理,调用代理View的onTouchEvent方法,
    //如果代理View消耗了事件,那么相当于当前View消耗了事件。
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    //首先是判断当前View是否可以点击或者长按,其中一个为true的话,就会进入if内。
    //进入if后,是对事件进行判断,可以看到最后会返回true,即事件最后会被消耗。
    //也就是说,如果一个View是clickable或者long_clickable的话onTouchEvent
    //方法会返回true,把事件消耗掉。
    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);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        removeLongPressCallback();

                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClickInternal();
                            }
                        }
                    }

                    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;

           。。。
        }

        return true;
    }

    return false;
}

我们看看up响应的部分。

首先会判断当前View是否是pressed状态,即按下状态,如果是按下状态就会触发performClick()方法

public boolean performClick() {
    notifyAutofillManagerOnClick();

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;
}

检测当前View是否设置了onClickListener,如果设置了那么回调它的onClick方法,所以我们平时对一个Button设置点击事件之后,都会在其onTouchEvent方法的ACTION_UP逻辑里面得到回调

这里可以得出结论: onTouchListener>onTouchEvent>onClickListener

小结
  1. 事件传递给View的时候,会调用dispatchTouchEvent()方法,但是View没有onIntercept方法,所以会接着调用onTouchEvent()方法。
  2. 如果一个View是可点击的(clickable或long_clickable),那么它默认会消耗事件。对于一个Button来说,默认是可点击的,对于一个textView来说,默认是不可点击的,而对于一个自定义View来说,默认也是不可点击的,可以在xml布局中设置View的点击性质。
  3. 如果对一个View设置了onClickListener监听,那么确保它的可点击的,而且接收到了ACTION_DOWN和ACTION_UP事件。

ViewGroup的事件分发

进入ViewGroup中找dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
    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) {
            cancelAndClearTouchTargets(ev);
            //重置状态
            resetTouchState();
        }
        //检查当前View是否拦截事件
        final boolean intercepted;
        // ViewGroup在如下两种情况下会判断是否拦截当前事件:事件类型为down或者mFirstTouchTarget != null。
        // 当ViewGroup不拦截事件并将事件交由子元素处理时,mFirstTouchTarget会被赋值也就是mFirstTouchTarget != null。
        // 这样当move事件和up事件到来时,并且事件已经被分发下去,那么onInterceptTouchEvent这个方法将不会再被调用。
        //所以当前ViewGroup拦截事件之后就不会再次调用onInterceptTouchEvent方法;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            // disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
            if (!disallowIntercept) {
                //调用onInterceptTouchEvent方法判断是否拦截当前事件,ViewGroup默认返回false;
                //如果拦截事件将intercepted置为true;
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }  
       。。。
        // 如果事件未被取消且未被拦截,如果拦截事件会将intercepted置为true;
        if (!canceled && !intercepted) {
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;
             //down事件
            if (actionMasked == MotionEvent.ACTION_DOWN|| 
            (split && actionMasked ==MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
。。。。
                // 当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x =isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                    final float y =isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                    //从上至下寻找一个可以接收该事件的子view
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    //遍历所有ViewGroup的所有子元素,然后判断子元素是否收到点击事件
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                        if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        resetCancelNextUpFlag(child);
                        // 如果某个子元素满足条件,那么事件就会传递给它处理,
                        // dispatchTransformedTouchEvent这个方法实际上就是调用子元素的dispatchTouchEvent方法,
                       // 如果子元素仍然是一个ViewGroup,则递归调用重复此过程。
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            // 如果子元素的dispatchTouchEvent返回true,表示子元素已经处理完事件,
                            // 那么mFirstTouchTarget就会被赋值同时跳出for循环。
                            // mFirstTouchTarget的赋值在addTouchTarget内部完成
                            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;
                }
            }
        }
        // 如果遍历完所有的子元素事件没有被合适处理,有两种情况:
        // 1. ViewGroup没有子元素
        // 2. 子元素处理了点击事件,但是dispatchTouchEvent返回false
        // 这时ViewGroup会自己处理点击事件。
        if (mFirstTouchTarget == null) {
           // 这里的第三个参数child为null,此时会调用handled = super.dispatchTouchEvent(event),最终会调用ViewGroup自身的onTouchEvent来处理事件;
            handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
        } else {
            TouchTarget predecessor = null;
            //将处理该事件的子view复制给target,由子元素来处理该事件
            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;
                    //该方法最终会调用子元素的dispatchTouchEvent,传给给子元素来处理事件   
                    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) {
               //UP事件到来,重置状态,例如将处理该事件的子view mFirstTouchTarget置为null;   
            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来询问是否拦截事件

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

onInterceptTouchEvent()返回false(即不拦截事件),返回true(即拦截事件)

viewgroup不拦截事件

事件继续向下传递,调用View的dispatchTouchEvent(),将执行View的事件分发(见上面)

Activity的事件分发

点击事件发生线传到activity的dispatchTouchEvent()进行事件分发

public boolean dispatchTouchEvent(MotionEvent ev) {
    //事件都是down开始的
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
进入onUserInteraction() 方法
public void onUserInteraction() {

}

该方法是一个空方法。实现屏保功能

当activity在栈顶时,触屏点击按钮home,back,menu键等都会触发这个方法

getWindow().superDispatchTouchEvent(ev)
public Window getWindow() {
    return mWindow;
}

获取Window类的对象

mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow是PhoneWindow对象

进入PhoneWindow的superDispatchTouchEvent 方法

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

mDecor是一个DecorView对象

mDecor = (DecorView) preservedWindow.getDecorView();
进入DecorView的superDispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}
调用父类的dispatchTouchEvent方法
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

}

进入FrameLayout类中发现没有dispatchTouchEvent方法

public class FrameLayout extends ViewGroup{
}

进入ViewGroup的事件分发(参考上面)。

总结

image.png