View的事件分发机制

1. 点击事件的传递规则

所谓点击事件的事件分发,其实就是对MotionEvent事件的分发过程,就是一个MotionEvent产生了以后,系统需要把这个事情传递给一个具体的view,而这个传递的过程就是分发过程。 由下面三个方法来共同完成

1.1 public boolean dispatchTouchEvent(MotionEvent ev)

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

1.2 public boolean onInterceptTouchEvent(MotionEvent ev)

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

1.3 public boolean onTouchEvent(MotionEvent ev)

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

那么上述三个方法到的区别是什么呢。
对于一个跟ViewGroup来说,点击事件产生后,首先会传递给它, 此时它的dispatchTouchEvent就会被调用, 如果这个ViewGroup的onInterceptTouchEvent返回true,表示它要拦截当前事件, 接着事件就会交给这个ViewGroup处理, 就是它的onTouchEvent方法就会被调用。
但是如果这个ViewGroup的onInterceptTouchEvent返回false就表示它不拦截当前事件, 此时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent就会被调用, 如此反复直到事件被最终处理。

1.4 onTouch和onTouchEvent 优先级

当一个View需要处理事件时, 如果它设置了onTouchListener,那么OnTouchListener中的onTouch方法会被回调, 这时事件如何处理还要看onTouch的返回值。 如果返回false, 则当前View的onTouchEvent方法会被调用, 如果返回true则onTouchEvent方法分不回被调用。 因此onTouch的优先级要高于onTouchEvent。

1.5 事件传输过程

如下顺序:
Activity -> Window -> View
顶级View收到事件后,就会按照事件分发机制去分发事件。

还有一种情况是, 如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用。 如果所有元素都不处理将会返回给activity.
打个比方给我说 一个问题由经理交给一个员工进行处理,这个员工没有能力进行处理,那么就会返回给经理。

1.6 总结

  1. 同一个事件序列是指 从手机接触到屏幕到手指离开屏幕。 就是以down事件开始到up事件结束。
  2. 正常情况下,一个事件序列只能被一个view拦截且消耗。 这一条的原因可以参考下一条, 因为一旦一个元素拦截了某个事件, 那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件不能分别交由两个View进行同时处理。 但是可以通过onTouchEvent强行传递给其他View处理。
  3. 某个View一旦要决定拦截,那么这一个事件序列都只能它处理, 并且它的onInterceptTouchEvent不回再被调用。 这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接给它处理。因此不回再调用这个View的onInterceptTtouchEvent方法。
  4. 某个View一旦开始处理,如果它不消耗Action+Down事件即 onTouchEvent返回了false 那么同一个事件序列中的其他事件将不回交给它处理, 并且事件将重新交给它的父容器处理。 就比如经理交代给你一件事情, 你完成不好它就不回再交代你其他事情了
  5. 如果view不消耗除了ActionDown意外的其他事件, 那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用, 并且当前View可以持续收到后续的事件, 最后这些消失的点击事件会传递给Activity处理。
  6. ViewGroup默认不拦截任何事件
  7. View没有onInterceptTouchEvent方法,一旦由点击事件传递给它, 那么它的onTouchEvent将会被调用,
  8. View的onTouchEvent默认都会消耗事件,除非它是不可点击的。
  9. View的enable属性不影响onTouchEvent的默认返回值。 哪怕一个View是disable状态,只要它的clickable或者longClickable由一个是true 它的ontouchEvent就会返回true。

2. 事件分发的源码分析

我们先从顶部来开始看

2.1 Activity如何传递

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

从源码我们可以直到, 事件首先事件交给Activity所属Window进行分发,如果返回true,整个事件循环就结束了。 返回false就意味着事件没人处理Activity就会去进行处理。

2.2 Window是如何传递

我们可以看到getWindow().superDispatchTouchEvent(ev)是个抽象方法, 那么我们必须要找到Window的实现类, PhoneWindow。

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

看到这段代码我们可以直到,window直接将事件交给decorView处理。
DecorView的子View就是我们setContentView时候所用到的。 因为DecorView继承自Framelayout 所有的事件将会分发给子View。

2.3 顶级View对点击事件的分发。

点击事件到达一个ViewGroup后,会调用ViewGroup的dispatchTouchEvent方法, 然后逻辑是这样的, 如果顶级ViewGroup拦截事件, 则onInterceptTouchEvent返回true, 则事件由ViewGroup处理, 这时如果ViewGroup的onTouchListener被设置 则会调用onTouch, 否则onTouchEvent会被调用。
如果都提供的话onTouch会屏蔽onTouchEvent。
如果不拦截会传给子View的dispatchTouchEvent。

final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

通过上面这段代码我们可以得知, ViewGroup只有在如下两种情况,才会判断是否要拦截当前事件, 事件类型为Action_Down 或者mFirstTouchTarget !=null;
mFirstTouchTarget是当事件由子元素成功处理时, mFirstTouchTarget会被赋值并指向子元素, 就是当ViewGroup不拦截事件并由自元素处理时
mFirstTouchTarget !=null
当比如其他MotionEvent事件来时, 不满足这个ActionDown条件 将导致ViewGroup的onInterceptTouchEvent不会被调用。

// Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_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();
            }

通过上面这段代码得知,如果ViewGroup决定拦截事件,则onIntercepetTouchEvent不会被调用。
所以当我们决定要提前处理所有的事件时, 要用dispatchTouchEvent 只有这个方法才会保证每次都能确保调用。

然后会去遍历所有的子元素,来判断事件是否能够接受到点击事件。 是否能够接受点击要受以下两点判断:

  1. 子元素是否在播动画
  2. 点击事件的坐标是否落到子元素的区域内。
    如果某个子元素满足这个两个条件, 那么事件可以交给它来处理

2.4 View的事件分发

if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            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;
            }
        }

首先会判断有没有设置OnTouchListener, 如果OnTouchListener的中的onAttach返回true, 那么onTouchEvent就不会被调用, OnTouchListener的优先级高于onTouchEvent, 这样做的好处是方便在外界处理点击事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值