1.touch事件分发
touch事件分发指对触摸事件MotionEvent的分发过程。当一个MotionEvent产生后,系统需要把这个事件传递给某个具体的View进行处理,这个传递的过程就是分发过程。
当一个touch事件产生后,它的传递过程遵循如下顺序:Activity -> Window -> View,即事件总是先传递给Activity,Activity传递给Window,最后Window传递给DecorView。DecorView接收到事件后,就会按照事件分发机制从ViewGroup->View去分发事件。
由于事件总是先传递到Activity,看一下Activity的dispatchTouchEvent方法是怎样把触摸事件从activity传递到ViewGroup的。
Activity.class:
public boolean dispatchTouchEvent( MotionEvent ev) {
if(getWindow().superDispatchTouchEvent( ev) {
return true;
}
//所有view都不处理事件,则直接调用Activity的onTouchEvent方法
return onTouchEvent();
}
首先调用Window的superDispatchTouchEvent方法由Window去分发事件,如果Window不处理该事件则会调用Activity的onTouchEvent方法来进行处理。
跟踪源代码会发现事件是从Activity -> PhoneWindow -> DecoreView-> dispatchTouchEvent,如下:
DecorView是一个FrameLayout,由DecorView分发事件,最终调用DecorView的dispatchTouchEvent事件。
考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,依此类推。如果所有的元素都不处理这个事件,那么这个事件最终将传递给Activity处理,即Activity的onTouchEvent方法会被调用(因为没有子view处理事件,dispatchTouchEvent返回false,因此会调用Activity的onTouchEvent函数)。
2.事件分发流程
注:上图中的事件都是针对ACTION_DOWN,对于ACTION_MOVE和ACTION_UP稍后单独分析。
仔细看整个图,得出事件流走向的几个结论:
①如果事件不被中断,整个事件流向是一个类U型图。
如果没有对控件里面的方法进行重写或更改返回值,而直接用super调用父类的默认实现,那么整个事件流向应该是从Activity–>ViewGroup–> View从上往下调用dispatchTouchEvent方法,一直到叶子节点,再由View–> ViewGroup–>Activity从下往上调用onTouchEvent方法。
②dispatchTouchEvent和onTouchEvent一旦返回true,事件就停止传递了(即到达终点,没有谁能再收到这个事件)。所以return true表示事件被消费了。
③dispatchTouchEvent和onTouchEvent返回false时,事件都回传给父控件的onTouchEvent处理。
dispatchTouchEvent返回false指的是事件停止往子View传递和分发,同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传,直到某个控件的onTouchEvent返回true),事件分发机制就像递归,return false就是递归停止然后开始回溯。
onTouchEvent返回false指的是不消费事件,并让事件继续往父控件的方向从下往上流动。
④ViewGroup和View的dispatchTouchEvent方法返回super.dispatchTouchEvent()时事件流走向:
首先看ViewGroup的dispatchTouchEvent:return true是终结传递,return false是回溯到父View的onTouchEvent,那么ViewGroup怎样通过dispatchTouchEvent方法把事件分发到自己的onTouchEvent处理呢,return true和false都不行,那么只能通过Interceptor把事件拦截下来给自己的onTouchEvent,所以ViewGroup dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent。
而对于View的dispatchTouchEvent,由于View没有拦截器,return true是终结,return false是回溯回父类的onTouchEvent,怎样把事件分发给自己的onTouchEvent处理呢,那只能return super.dispatchTouchEvent了,所以view类的dispatchTouchEvent方法默认实现就是调用View自己的onTouchEvent方法的。
3.关于action_move和action_up
ACTION_MOVE和ACTION_UP在传递过程中并不总是和ACTION_DOWN一样。如果ACTION_DOWN返回false,则后面一系列其它的action就不会再执行了。也就是说,当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP事件,即并不是哪个函数收到了ACTION_DOWN就会收到 ACTION_MOVE等后续事件的。
具体分析一下,讨论的布局如下:
通过几张图看看不同场景下ACTION_MOVE事件和ACTION_UP事件的具体走向并总结一下规律:
①在ViewGroup1的dispatchTouchEvent方法返回true消费这次事件。
红色的箭头代表ACTION_DOWN 事件的流向,蓝色的箭头代表ACTION_MOVE 和 ACTION_UP事件的流向。
②在ViewGroup2的dispatchTouchEvent返回true消费这次事件。
③在View的dispatchTouchEvent返回true消费这次事件
效果和在ViewGroup2的dispatchTouchEvent return true的差不多,同样的收到ACTION_DOWN 的dispatchTouchEvent函数都能收到 ACTION_MOVE和ACTION_UP。
所以得出结论:如果在某个控件的dispatchTouchEvent返回true消费终结事件,那么收到ACTION_DOWN 的函数也能收到 ACTION_MOVE和ACTION_UP。
④在View的onTouchEvent返回true消费这次事件。
⑤在ViewGroup2的onTouchEvent返回true消费这次事件。
⑥在ViewGroup1的onTouchEvent返回true消费这次事件。
⑦在Activity的onTouchEvent 返回true消费这次事件。
⑧在View的dispatchTouchEvent返回false并且Activity的onTouchEvent返回true消费这次事件。
⑨在View的dispatchTouchEvent返回false并且ViewGroup1的onTouchEvent返回true消费这次事件。
⑩在View的dispatchTouchEvent返回false并且在ViewGroup2的onTouchEvent返回true消费这次事件
⑩在ViewGroup2的dispatchTouchEvent返回false并且在ViewGroup1的onTouchEvent返回true消费这次事件
⑩在ViewGroup2的onInterceptTouchEvent返回true拦截此次事件并且在ViewGroup1的onTouchEvent返回true消费这次事件。
可以看出规律了,对于在onTouchEvent消费事件的情况:在哪个View的onTouchEvent返回true,那么ACTION_MOVE和ACTION_UP事件就会从上往下传到这个View后就不再往下传递了,而是直接传给自己的onTouchEvent处理并结束本次事件传递过程。
对于ACTION_MOVE、ACTION_UP总结:ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,而且只会传到这个控件,不会继续往下传。如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。
3.View的事件分发源码解析
从一个简单的例子入手。一个Activity中只有一个按钮,给这个按钮注册一个点击事件:
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.d(“TAG”, “onClick execute”);
}
});
再给这个按钮注册一个touch事件:
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(“TAG”, "onTouch execute, action " + event.getAction());
return false;
}
});
onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。
现在两个事件都注册了,哪一个会先执行呢?运行程序点击按钮,打印结果:
onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(可能还会有多次ACTION_MOVE,比如手抖了一下)。因此可以知道事件传递的顺序是先经过onTouch,再传递到onClick。
onTouch方法是有返回值的,刚刚返回的false,现在把onTouch方法的返回值改成true再运行一次:
此时onClick方法不再执行了。也就是说onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。
从源码的角度分析一下出现该现象的原理。
看一下View类的dispatchTouchEvent方法:
public boolean dispatch