事件分发总结

一、基础知识

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的返回值有了关系;

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幽蓝丶流月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值