事件分发总结

一、基础知识
1.分发对象

事件:Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
2.事件

主要发生的Touch事件大致分为以下四种:

    MotionEvent.ACTION_DOWN:按下事件(所有事件的开始)

    MotionEvent.ACTION_MOVE:滑动事件

    MotionEvent.ACTION_CANCEL:非人为原因结束本次事件

    MotionEvent.ACTION_UP:抬起事件(与DOWN对应)

3.大体流程

 4.主要方法

5.传递对象

一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View

 二、流程介绍

文字叙述太繁琐,图更好理解

主要方法

 主体流程

三、源码分析
1.Activity的事件分发

当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //一般事件列开始都是DOWN,所以这里基本是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //关注点1
            onUserInteraction();
        }
        //关注点2
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //关注点3
        return onTouchEvent(ev);
    }

关注点1

onUserInteraction:每当Key,Touch,Trackball事件分发到当前Activity就会被调用。如果你想当你的Activity在运行的时候,能够得知用户正在与你的设备交互,你可以override该方法。

关注点2

getWindow()可以得到一个Window对象,Window类是抽象类,且PhoneWindow是Window类的唯一实现类

superDispatchTouchEvent(ev)是抽象方法,通过PhoneWindow类中看一下superDispatchTouchEvent()的作用

    @Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
        //mDecor是DecorView的实例
        //DecorView是视图的顶层view,继承自FrameLayout,是所有界面的父类
    }

接下来看mDecor.superDispatchTouchEvent(event):

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
        //DecorView继承自FrameLayout
        //那么它的父类就是ViewGroup,而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
    }

从这开始ViewGroup的事件分发了

关注点3

当viewGroup分发事件失败,Activity将会自己处理
2.ViewGroup的事件分发

ViewGroup的dispatchTouchEvent()源码分析,该方法比较复杂,截取几个重要的逻辑片段进行介绍,来解析整个分发流程。

    // 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
    final boolean intercepted;
    if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){
        //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
        //可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
        final boolean disallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
        //默认情况下会进入该方法
        if(!disallowIntercept){
            //调用拦截方法
            intercepted=onInterceptTouchEvent(ev);
            ev.setAction(action);
        }else{
            intercepted=false;
        }
    }else{
        // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
        intercepted=true;
    }

这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。

当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。

    /* 从最底层的父视图开始遍历, ** 找寻newTouchTarget,即上面的mFirstTouchTarget ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。 */
    for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
    final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex);
     
    // 如果当前视图无法获取用户焦点,则跳过本次循环
    if (childWithAccessibilityFocus != null) {
            if (childWithAccessibilityFocus != child) {
                continue;
            }
        childWithAccessibilityFocus = null;
        i = childrenCount - 1;
    }
    //如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
    if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
        ev.setTargetAccessibilityFocus(false);
        continue;
    }
     
    newTouchTarget = getTouchTarget(child);
    // 已经开始接收触摸事件,并退出整个循环。
    if (newTouchTarget != null) {
        newTouchTarget.pointerIdBits |= idBitsToAssign;
        break;
    }
     
    //如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // 获取TouchDown的时间点
        mLastTouchDownTime = ev.getDownTime();
        // 获取TouchDown的Index
        if (preorderedList != null) {
            for (int j = 0; j < childrenCount; j++) {
                if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                }
            }
        } else {
            mLastTouchDownIndex = childIndex;
        }
    //获取TouchDown的x,y坐标
    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    //添加TouchTarget,则mFirstTouchTarget != null
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    //表示以及分发给NewTouchTarget
    alreadyDispatchedToNewTouchTarget = true;
    break;
    }

dispatchTransformedTouchEvent()方法实际就是调用子元素的dispatchTouchEvent()方法。 其中dispatchTransformedTouchEvent()方法的重要逻辑如下:

    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }

由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。 如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。

    //添加TouchTarget,则mFirstTouchTarget != null。
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    //表示以及分发给NewTouchTarget
    alreadyDispatchedToNewTouchTarget = true;

其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值。 如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。 如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件。
3.View的事件分发

    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

第一个条件:mOnTouchListener!= null

    //mOnTouchListener是在View类下setOnTouchListener方法里赋值的
    public void setOnTouchListener(OnTouchListener l) {
        //即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
        mOnTouchListener = l;
    }

第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED

    该条件是判断当前点击的控件是否enable

    由于很多View默认是enable的,因此该条件恒定为true

第三个条件:mOnTouchListener.onTouch(this, event)

    如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。

    如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。

    public boolean onTouchEvent(MotionEvent event) {
     
        final int viewFlags = mViewFlags;
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
     
        //如果该控件是可以点击的就会进入到下两行的switch判断中去;
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    // 在经过重重判断之后,会执行到performClick()方法。
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }
                        if (!mHasPerformedLongPress) {
                            removeLongPressCallback();
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;
     
                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    mHasPerformedLongPress = false;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    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();
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            removeLongPressCallback();
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
            //如果该控件是可以点击的,就一定会返回true
            return true;
        }
        //如果该控件是不可以点击的,就一定会返回false
        return false;
    }

performClick()的源码分析

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

只要mOnClickListener不为null,就会去调用onClick方法;

那么,mOnClickListener又是在哪里赋值的呢?

    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        mOnClickListener = l;
    }

四、总结

android事件产生后的传递过程是从Activity--->Window--->View的,即隧道式传递,而View又分为不包含子 View的View以及包含子View的ViewGroup,事件产生之后首先传递到Activity上面,而Activity接着会传递到 PhoneWindow上,PhoneWindow会传递给RootView,而RootView其实就是DecorView了,接下来便是从 DecorView到View上的分发过程了,具体就可以分成ViewGroup和View的分发两种情况了;

        对于ViewGroup而言,当事件分发到当前ViewGroup上面的时候,首先会调用他的dispatchTouchEvent方法,在 dispatchTouchEvent方法里面会调用onInterceptTouchEvent来判断是否要拦截当前事件,如果要拦截的话,就会调用 ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent返回false的话表示不拦截当前事件,那么 事件将会继续往当前ViewGroup的子View上面传递了,如果他的子View是ViewGroup的话,则重复ViewGroup事件分发过程,如 果子View就是View的话,则转到下面的View分发过程;

        对于View而言,事件传递过来首先当然也是执行他的dispatchTouchEvent方法了,如果我们为当前View设置了 onTouchListener监听器的话,首先就会执行他的回调方法onTouch了,这个方法的返回值将决定事件是否要继续传递下去了,如果返回 false的话,表示事件没有被消费,还会继续传递下去,如果返回true的话,表示事件已经被消费了,不再需要向下传递了;如果返回false,那么将 会执行当前View的onTouchEvent方法,如果我们为当前View设置了onLongClickListener监听器的话,则首先会执行他的 回调方法onLongClick,和onTouch方法类似,如果该方法返回true表示事件被消费,不会继续向下传递,返回false的话,事件会继续 向下传递,为了分析,我们假定返回false,如果我们设置了onClickListener监听器的话,则会执行他的回调方法onClick,该方法是 没有返回值的,所以也是我们事件分发机制中最后执行的方法了;可以注意到的一点就是只要你的当前View是clickable或者 longclickable的,View的onTouchEvent方法默认都会返回true,也就是说对于事件传递到View上来说,系统默认是由 View来消费事件的,但是ViewGroup就不是这样了;

        上面的事件分发过程只是正常情况下的,如果有这样一种情况,比如事件传递到最里层的View之后,调用该View的oonTouchEvent方法返回了 false,那么这时候事件将通过冒泡式的方式向他的父View传递,调用它父View的onTouchEvent方法,如果正好他的父View的 onTouchEvent方法也返回false的话,这个时候事件最终将会传递到Activity的onTouchEvent方法了,也就是最终就只能由 Activity自己来处理了;

事件分发机制需要注意的几点:

        (1):如果说除Activity之外的View都没有消费掉DOWN事件的话,那么事件将不再会传递到Activity里面的子View了,将直接由Activity自己调用自己的onTouchEvent方法来处理了;

        (2):一旦一个ViewGroup决定拦截事件,那么这个事件序列剩余的部分将不再会由该ViewGroup的子View去处理了,即事件将在此 ViewGroup层停止向下传递,同时随后的事件序列将不再会调用onInterceptTouchEvent方法了;

        (3):如果一个View开始处理事件但是没有消费掉DOWN事件,那么这个事件序列随后的事件将不再由该View来处理,通俗点讲就是你自己没能力就别瞎BB,要不以后的事件就都不给你了;

        (4):View的onTouchEvent方法是否执行是和他的onTouchListener回调方法onTouch的返回值息息相关 的,onTouch返回true,onTouchEvent方法不执行;onTouch返回false,onTouchEvent方法执行,因为 onTouchEvent里面会执行onClick,所以造成了onClick是否执行和onTouch的返回值有了关系;
---------------------
作者:幽蓝丶流月
来源:CSDN
原文:https://blog.csdn.net/qq_37482202/article/details/89087606
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-06-12 18:09  天涯海角路  阅读(127)  评论(0)    收藏  举报