Activity 事件分发机制

我们知道,事件传递机制是从Activity开始的,也是终止于Activity的,我们看一下,发现这里面和View相似,只有两个方法,没有ViewGroup中的 onInterceptTouchEvent 拦截方法,我们看一下代码 

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

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }


代码看着很简单, onUserInteraction() 是个空方法,让子类重写。Activity 的意思很明显,dispatchTouchEvent 方法分发事件,如果 getWindow().superDispatchTouchEvent(ev) 消费了,则返回 true,消费了,否则就传递给自己的 onTouchEvent 方法。这里有个疑问,如果down的时候 dispatchTouchEvent 方法中接收到值是false,还会有后续的move和up事件吗?
答案是肯定的,我们可以写个代码打印日志试一试

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean is = super.dispatchTouchEvent(ev);
        Log.e("TouTestView", "activity  dispatchTouchEvent:  " + ev.getAction() +"   " + is);
        return is;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean is = super.onTouchEvent(event);
        Log.e("TouTestView", "activity  onTouchEvent:  " + event.getAction()+"   " + is);
        return is;
    }


activity 的xml布局里面是个空的FrameLayout,点击屏幕
   E/TouTestView: activity  onTouchEvent:  0   false
   E/TouTestView: activity  dispatchTouchEvent:  0   false
   E/TouTestView: activity  onTouchEvent:  1   false
   E/TouTestView: activity  dispatchTouchEvent:  1   false
手指在屏幕上滑动
   E/TouTestView: activity  onTouchEvent:  0   false
   E/TouTestView: activity  dispatchTouchEvent:  0   false
   E/TouTestView: activity  onTouchEvent:  2   false
   E/TouTestView: activity  dispatchTouchEvent:  2   false
   E/TouTestView: activity  onTouchEvent:  2   false
   E/TouTestView: activity  dispatchTouchEvent:  2   false
   E/TouTestView: activity  onTouchEvent:  1   false
   E/TouTestView: activity  dispatchTouchEvent:  1   false


说明down的时候即使是false,activity也是分发完整的事件传递,这里注意,打印log日志是先打印了 activity  onTouchEvent 后打印了 activity  dispatchTouchEvent,这是因为我们 dispatchTouchEvent(MotionEvent ev) 方法中,我把log打印放在了 boolean is = super.dispatchTouchEvent(ev); 后面,为了拿到 super 的值,如果我们把log日志放在 super.dispatchTouchEvent(ev) 代码的上面,就会发现打印日志顺序就颠倒过来了。不论是原理还是日志,都说明了事件分发机制是从activity开始的。

我们开始看源码,从 dispatchTouchEvent 方法入手,第一行就不用看了,我们重点看 getWindow().superDispatchTouchEvent(ev) 这行代码, getWindow() 方法是指 mWindow,mWindow 是 Window类型,在这里是指 PhoneWindow, 我们看一下 PhoneWindow 的 superDispatchTouchEvent(MotionEvent event) 方法,

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
mDecor 是个根节点的布局,它是 DecorView 类型, DecorView 是FrameLayout 的子类,我们看一下它的简化源码

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Callback cb = getCallback();
        return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                : super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return onInterceptTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        ...
        return false;
    }

}

我们发现 DecorView 是个内部类,并且是 FrameLayout 的子类,说明它也是个ViewGroup,也就是我们xml布局里的根节点,布局中的顶级ViewGroup类,其他view都是在它的容器里。我们来看一下,superDispatchTouchEvent(event) 方法,调用的是 super.dispatchTouchEvent(event) ,也就是说调用的是ViewGroup 的 dispatchTouchEvent(event) 方法,关于ViewGroup 的事件分发我们上一章讲了,View 的事件分发上上章也讲了,这里继续关注,ViewGroup 的 dispatchTouchEvent 方法会对事件做分发,我们就按照上一章思路,比如说

第一种情况:Down 的时候,DecorView 没有拦截事件,DecorView中的子View 也都没有消耗事件,那么就把事件传递给自身的 onTouchEvent(MotionEvent event) 方法,它里面的代码是调用onInterceptTouchEvent(event) 方法,既然没有拦截,说明它的值是false,也就是说 DecorView 的 superDispatchTouchEvent 方法返回的值是false,从而 Activity 中的getWindow().superDispatchTouchEvent(ev) 值为false,所以就继续把事件往下传,交给 Activity 的 onTouchEvent(MotionEvent event) 方法,默认它里面返回的是false,不消费。Down 事件到此结束,下面就是 Move 事件了,也是从 Activity 中传入到 DecorView 中,调用了 ViewGroup 的 dispatchTouchEvent(event) 方法。上一章分析了,如果viewGroup中子View没有消费 Down 事件,那么接下来,move 和 up 都不会传递给子View了,而是直接调用基类的 dispatchTouchEvent(event) 方法 ,传递给自己的 onTouchEvent(MotionEvent event),最终这个值还是返回给 activity 中的 getWindow().superDispatchTouchEvent(ev),根据它的值,事件传递给 activity 的 onTouchEvent(MotionEvent event)。

第二种情况:Down 的时候,DecorView 没有拦截事件, DecorView中的子View 消费了事件,这个子View 可能是 ViewGroup,也可能是 View,如果是ViewGroup的话,就是嵌套关系,事件继续下传,这样就与上一章接壤了,在看一下第一种情况,就能明白上一章中说的 非顶级ViewGroup, down 为false时,后续的 move 和 up 事件就不会传递给它了。

第三种情况:Down 的时候,DecorView 拦截事件,没有传递下去,则直接交给自己的 onTouchEvent(MotionEvent event)  方法处理,把值返回给 activity,决定是否调用activity 的 onTouchEvent 方法。 这种情况基本不存在,应为如果是这样的或,我们展示界面上的控件都获取不到焦点和点击事件了,这个目前没见过。

事件传递是层层传递,由 Activity - ViewGroup - View ,如果子View 不消费,则重新一层层往上传。 事件分发机制,读懂了View 的传递机制,就明白了将近一半,这个是基础;读懂了 ViewGrou 的传递机制,就又明白了一半,这个是中间部分,承上启下,比较重要;读懂了 Activity 的机制,联系 DecorView ,能把 Activity - ViewGroup - View给串联起来,基本就全明白了。剩下的就是一些控件的事件分发的细节和个人感悟应用了。
 

你可能感兴趣的:(Android,知识)