摘要
讲解View和ViewGroup的事件传递 & Activity的事件来源。
View和ViewGroup的事件传递:部分转载自图解Android事件分发机制,但稍作修改。
Activity的事件来源:仿照Android中MotionEvent的来源和ViewRootImpl写了一个简单的例子验证。
0.事件来源和ViewRootImpl的事件传递
我们知道View的事件分发顺序为Activity——>PhoneWindow——>DecorView,那么Activity的事件来源在哪里呢?
下面这张图就是事件传递的一个顺序图:
当一个InputEvent到来时,ViewRootImpl会寻找合适它的InputStage来处理。图中以ViewPostImeInputStage为例,事件被ViewPostImeInputStage分发到View,这里的View事在ViewRootImpl#setView()方法中被赋值的,mView就是DecorView,所以DecorView#dispatchTouchEvent()被调用,在Activity#attach()方法中Window.Callback被赋值,所以DecorView#dispatchTouchEvent()中的getCallback()指代的是Activity,最终调用Activity#dispatchTouchEvent()。
仿照Android中MotionEvent的来源和ViewRootImpl写了一个简单的例子验证。
准备工作:
MainActivity代码如下:
|
|
R.layout.test_touch_activity代码如下:
|
|
ViewGroup1的代码如下:
|
|
在ViewGroup1#onTouchEvent()中调用Thread.dumpStack();并return true,表示消耗这次事件。
最终打印结果为:
|
|
可以看到堆栈的打印信息和前面的事件分发传递流程图分析的一致。
1.View和ViewGroup的事件传递
1.1 针对ACTION_DOWN事件的事件走向图
1.1.1 如果事件不被中断,整个事件流向是一个U型图。
如果我们没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity—->ViewGroup—>View,从上往下调用dispatchTouchEvent方法,一直到叶子节点(View)的时候,再由View—>ViewGroup—>Activity从下往上调用onTouchEvent方法。
1.1.2 dispatchTouchEvent方法和onTouchEvent方法一旦return true,事件就停止传递了(到达终点),也就是事件被消费了。
1.1.3 dispatchTouchEvent方法和onTouchEvent方法,return false的时候事件都回传给父控件的onTouchEvent处理。
对于dispatchTouchEvent方法return false的含义是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent方法开始从下往上回传直到某个onTouchEvent方法return true),事件分发机制就像递归,return false的意义就是递归停止然后开始回溯。
对于onTouchEvent方法return false的含义是:不消费事件,并让事件继续往父控件的方向从下往上流动。
1.1.4 ViewGroup和View的dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent方法的默认实现就是会让整个事件安装U型完整走完。
1.1.5 onInterceptTouchEvent方法中return true就会交给自己的onTouchEvent的处理,如果不拦截就是继续往子控件往下传。
默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super.onInterceptTouchEvent()和return false是一样的,是不会拦截的,事件会继续往子View的dispatchTouchEvent传递。
1.1.6 ViewGroup和View的dispatchTouchEvent方法返回super.dispatchTouchEvent()的时候事件流走向。
1.1.6.1 ViewGroup的dispatchTouchEvent方法return true是终结传递。return false是回溯到父View的onTouchEvent方法。
问题:ViewGroup怎样通过dispatchTouchEvent方法把事件分发到自己的onTouchEvent处理?
回答:return true和false都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,dispatchTouchEvent方法return super.dispatchTouchEvent(),在onInterceptTouchEvent方法return true。所以ViewGroup的dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent方法。
1.1.6.2 View的dispatchTouchEvent方法return true是终结传递。return false是回溯到父View的onTouchEvent方法。
问题:View怎样把事件分发给自己的onTouchEvent处理?
回答:return super.dispatchTouchEvent()。View类的dispatchTouchEvent方法默认实现就是能帮你调用View自己的onTouchEvent方法。
1.1.7 结论
——->后面代表事件目标需要怎么做。
1.1.7.1 ViewGroup和View的dispatchTouchEvent方法
自己消费,终结传递。——->return true。
View给自己的onTouchEvent处理。——->调用super.dispatchTouchEvent()默认调用View自己的onTouchEvent()。
ViewGroup给自己的onTouchEvent处理。——->调用super.dispatchTouchEvent()默认调用onInterceptTouchEvent(),在onInterceptTouchEvent方法return true就会把事件分给自己的onTouchEvent处理。
ViewGroup传给子View。——->调用super.dispatchTouchEvent()默认调用onInterceptTouchEvent(),在onInterceptTouchEvent方法return false就会把事件传给子类。
不传给子View,事件终止往下传递,事件开始回溯(从父View的onTouchEvent开始事件从下往上回归执行每个控件的onTouchEvent)。——->return false。
1.1.7.2 ViewGroup和View的onTouchEvent方法
自己消费,终结传递。——->return true。
继续从下往上传,不消费事件,让父View也可以收到这个事件。——->return false;View的默认实现是不消费,所以super==false。
1.1.7.3 ViewGroup的onInterceptTouchEvent方法
拦截下来,给自己的onTouchEvent处理。——->return true。
不拦截,把事件往下传给子View。——->return false;ViewGroup默认是不拦截的,所以super==false。
1.2 针对ACTION_MOVE和ACTION_UP事件的事件走向图
通过几张图看看不同场景下,ACTION_MOVE事件和ACTION_UP事件的具体走向并总结规律。
下图中的布局分布见R.layout.test_touch_activity.xml,也可以见Demo
|
|
1.2.1 dispatchTouchEvent方法相关
1.2.1.1 在ViewGroup1的dispatchTouchEvent方法return true消费这次事件,触摸View
1.2.1.2 在ViewGroup2的dispatchTouchEvent方法return true消费这次事件,触摸View
1.2.1.3 在View的dispatchTouchEvent方法return true消费这次事件,触摸View
1.2.1.4 结论1
某个控件的dispatchTouchEvent方法return true消费事件,终结传递,那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP。1.2.2 onTouchEvent方法相关
1.2.2.1 在View的onTouchEvent方法return true消费这次事件,触摸View
注意:条件ACTION_DOWN return true,ACTION_MOVE/ACTION_UP return super,ACTION_MOVE/ACTION_UP的事件传递不回溯到ViewGroup,直接到顶级Activity。#### 1.2.2.2 在ViewGroup2的onTouchEvent方法return true消费这次事件,触摸View
#### 1.2.2.3 在ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View
#### 1.2.2.4 在Activity的onTouchEvent方法return true消费这次事件,触摸View
#### 1.2.2.5 结论2
在哪个View的onTouchEvent方法return true,那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent并结束本次事件传递过程。
1.2.3 混合
1.2.3.1 在View的dispatchTouchEvent方法return false并且Activity的onTouchEvent方法return true消费这次事件,触摸View
1.2.3.2 在View的dispatchTouchEvent方法return false并且ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View
1.2.3.3 在View的dispatchTouchEvent方法return false并且ViewGroup2的onTouchEvent方法return true消费这次事件,触摸View
1.2.3.4 在ViewGroup2的dispatchTouchEvent方法return false并且ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View
1.2.3.5 在ViewGroup2的onInterceptTouchEvent方法return true拦截此次事件并且在ViewGroup1的onTouchEvent方法return true消费这次事件,触摸View
以上验证结果见View和ViewGroup的事件传递[验证结果].txt
2.ViewGroup的dispatchTouchEvent方法的详细分析
规律总结出来了,但是代码执行的流程又是怎么样的呢?
先看一个test case 4.3,见View和ViewGroup的事件传递[验证结果].txt,我们将
ViewGroup1#dispatchTouchEvent() return super.dispatchTouchEvent();
ViewGroup1#onInterceptTouchEvent() return true;
表示拦截事件传递交给自己的onTouchEvent()处理,触摸View2
结果如下:
|
|
看到ACTION_MOVE和ACTION_UP的事件只有MainActivity响应,感到百思不得其解,最终debug调试才真正明白。见github提交注释
注释内容参考文章Android Deeper(00) - Touch事件分发响应机制
以下是流程图,来源文章Android Deeper(00) - Touch事件分发响应机制:
其中最最重要的是对mFirstTouchTarget这个变量的赋值。
在test case 4.3中,由于ACTION_DOWN事件没有任何dispatchTouchEvent方法和onTouchEvent方法消费事件,所以导致DecorView、FrameLayout、LinearLayout、ViewGroup1、ViewGroup2的mFirstTouchTarget均为null。
前面我们分析了View的事件分发顺序为Activity——>PhoneWindow——>DecorView,记住这一点,我们继续分析DecorView的ACTION_MOVE和ACTION_UP事件:
根据
2213
行的if判断条件,因为action既不是ACTION_DOWN && mFirstTouchTarget==null,所以intercept变量被赋值为true,调用不到ViewGroup的onInterceptTouchEvent()方法。步骤1导致
2250
行的if判断条件,也无法走进。直接走到
2365
行,由于mFirstTouchTarget==null,执行dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
方法,由于child==null,最终调用super.dispatchTouchEvent(event);即View.dispatchTouchEvent()方法,最终调用View.onTouchEvent()方法,因为没有哪个方法消费该事件所以最终2344
行的handle变量的赋值结果为false。因此在DecorView#superDispatchTouchEvent()方法中return false,导致PhoneWindow#superDispatchTouchEvent()方法中也return false,最终导致Activity中的代码中第2个if条件getWindow()#superDispatchTouchEvent()方法rerun false,最终调用Activity#onTouchEvent()方法。也就验证了test case 4.3的结果,ACTION_MOVE和ACTION_UP的事件只会在我们重写的MainActivity中dispatchTouchEvent()和onTouchEvent()两个方法中打印。由于只执行到DecorView相关的方法,所以它的子类的打印均不会被调用。
|
|