Android事件分发分两个方面:
1.View的事件分发(例如button这种控件)
2.ViewGroup的事件分发(我们平时项目里经常用到的各种布局,全都属于ViewGroup的子类)
先做简单分析,然后穷举栗子(对,你没看错,基本是穷举了),看完你就懂了,看不懂一、二部分分析的,直接看第三部分例子,找到规律,其实事件分发只要知道这个规律就行了,并不需要知道里面具体源码的流程,看源码只是加深理解。
一.View的事件分发
例如button注册了onClick和onTouch
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});
首先会调用dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
首先判断mOnTouchListener是否为空,mViewFlags & ENABLED_MASK判断当前点击的控件是否是enable的,按钮默认都是enable然后就是调用mOnTouchListener的onTouch(),如果onTouch返回true,则直接返回true,如果返回false,则调用onTouchEvent(event)
在onTouchEvent(event)中会调用performClick(),如果注册了clicklistener则调用其onClick,并返回一个true,否则返回false
这样在dispatchTouchEvent返回了true,所以会传递给下一个action。注意:只有前一个action返回true,才会触发后一个action(move,up)
二.ViewGroup的事件传递机制
linearlayout,relativelayout继承自viewgroup,viewgroup继承自view(比view增加了可以添加子view的功能)
public class MyLayout extends LinearLayout {
public MyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
myLayout.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "myLayout on touch");
return false;
}
});
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "You clicked button1");
}
});
效果:点击button,只会有click事件,myLayout的touch事件不会执行,只有点击空白区域,myLayout的touch才会执行
1.onInterceptTouchEvent,默认返回false,不拦截事件,会在dispatchTouchEvent被调用
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
2.Android中touch事件的传递,是先传递到ViewGroup,再传递到View的
当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,
然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。
如果我们点击了MyLayout中的按钮,会先去调用MyLayout的dispatchTouchEvent方法,可是你会发现MyLayout中并没有这个方法。
那就再到它的父类LinearLayout中找一找,发现也没有这个方法。那只好继续再找LinearLayout的父类ViewGroup,
你终于在ViewGroup中看到了这个方法,按钮的dispatchTouchEvent方法就是在这里调用的
3.在布局的dispatchTouchEvent中
首先会判断onInterceptTouchEvent是否拦截了事件,如果没有,则遍历子控件,并判断点击在那个子控件上,并调用子控件的child.dispatchTouchEvent(如此就进入子view 的dispatchTouchEvent中)
我们也因此证实了,按钮点击事件的处理确实就是在这里进行的。
调用子View的dispatchTouchEvent后是有返回值的。如果一个控件是可点击的,那么点击该控件时,
dispatchTouchEvent的返回值必定是true(这样就导致后面的代码无法执行到了),就会把MyLayout的touch事件拦截掉。
那如果我们点击的不是按钮,而是空白区域呢?这种情况就一定不会在第31行返回true了,而是会继续执行后面的代码
执行super.dispatchTouchEvent(ev)。这句代码会调用到哪里呢?当然是View中的dispatchTouchEvent方法了。之后的处理逻辑又和前面所说的是一样的了,也因此MyLayout中注册的onTouch方法会得到执行。
4.简单梳理一下
Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。
在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。
子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。
(1)这一系列的传递流程都是dispatchTouchEvent()方法来控制的,如果不人为地干预,事件将由上自下依次传递(因为默认是返回false不会拦截的),
传递到最底层的View,就由它的onTouchEvent()方法来处理事件,若处理成功返回true,若处理失败返回false,事件依次向上传递,
每个View都调用自己的onTouchEvent()方法来处理事件,若处理成功就终止传递,若处理失败就继续向上传递。
(2)经过人为的干预,若在向下传递的过程中被拦截了,即onInterceptTouchEvent()方法返回true,则事件将停止向下传递,
直接由当前的onTouchEvent()方法来处理,若处理成功则OK,若处理不成功,则事件会向上传递。
(3)另外,dispatchTouchEvent()方法中还有“记忆”的功能,如果第一次事件向下传递到某View,
它把事件继续传递交给它的子View,它会记录该事件是否被它下面的View给处理成功了,(怎么能知道呢?
如果该事件会再次被向上传递到我这里来由我的onTouchEvent()来处理,那就说明下面的View都没能成功处理该事件);
当第二次事件向下传递到该View,该View的dispatchTouchEvent()方法机会判断,若上次的事件由下面的view成功处理了,
那么这次的事件就继续交给下面的来处理,若上次的事件没有被下面的处理成功,那么这次的事件就不会向下传递了,
该View直接调用自己的onTouchEvent()方法来处理该事件。
(4)“记忆”功能的信息只在一系列事件完成之前有效,如从ACTION_DOWN事件开始,直到后续事件ACTION_MOVE,ACTION_UP结束后,
“记忆”的信息就会清除。也就是说如果某View处理ACTION_DOWN事件失败了(onTouchEvent()返回false),
那么后续的ACTION_MOVE,ACTION_UP等事件就不会再传递到该View了,由其父View自己来处理。在下一次发生ACTION_DOWN事件的时候,还是会传递到该View的。
a.触摸控件(View)首先执行dispatchTouchEvent方法。
b.在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法(onClick方法在onTouchEvent中执行,下面会分析)。
c.如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。
d.如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理(上面已经处理分析了),dispatchTouchEvent返回值与onTouchEvent返回一样。
e.如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。
f.当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action(也就是说
dispatchTouchEvent返回true才会进行下一次action派发)。
三.例子分析
条件1:在activity中放入一个linearlayout
linearlayout设置了onTouchListener,onTouchListener.ouTouch的返回值在条件中具体设置
在上述条件下,分如下情况:
情况A:
linearlayout设置了onClickListerner,在onTouchListener.ouTouch返回true
点击空白出:
03-24 14:12:10.984 . activity dispatchTouchEvent
03-24 14:12:10.984 . linearLayout dispatchTouchEvent: action_down
03-24 14:12:10.984 . linearLayout onInterceptTouchEvent: action_down
03-24 14:12:10.984 . linearLayout onTouchListener.onTouch execut event: action_down
03-24 14:12:11.013 . activity dispatchTouchEvent
03-24 14:12:11.013 . linearLayout dispatchTouchEvent: action_move
03-24 14:12:11.013 . linearLayout onTouchListener.onTouch execut event: action_move
03-24 14:12:11.059 . activity dispatchTouchEvent
03-24 14:12:11.060 . linearLayout dispatchTouchEvent: action_move
03-24 14:12:11.060 . linearLayout onTouchListener.onTouch execut event: action_move
03-24 14:12:11.060 . activity dispatchTouchEvent
03-24 14:12:11.060 . linearLayout dispatchTouchEvent: action_up
03-24 14:12:11.060 . linearLayout onTouchListener.onTouch execut event: action_up
结果分析:事件在linearLayout的onTouchListener.ouTouch中消化完毕,不会走到dispatchTouchEvent的onTouchEvent中
情况2:
linearlayout设置了onClickListerner,在onTouchListener.ouTouch返回false
点击空白出:
03-24 14:35:08.641 . activity dispatchTouchEvent
03-24 14:35:08.672 . linearLayout dispatchTouchEvent: action_down
03-24 14:35:08.672 . linearLayout onInterceptTouchEvent: action_down
03-24 14:35:08.672 . linearLayout onTouchListener.onTouch execut event: action_down
03-24 14:35:08.688 . activity dispatchTouchEvent
03-24 14:35:08.688 . linearLayout dispatchTouchEvent: action_move
03-24 14:35:08.688 . linearLayout onTouchListener.onTouch execut event: action_move
03-24 14:35:08.689 . activity dispatchTouchEvent
03-24 14:35:08.689 . linearLayout dispatchTouchEvent: action_up
03-24 14:35:08.689 . linearLayout onTouchListener.onTouch execut event: action_up
03-24 14:35:08.691 . linearLayout onClick execut
结果分析:onTouchListener.ouTouch返回false,会走到onClick中,只要设置了onClick,事件就会被消化掉,最后在ACTION_UP的时候,执行onClick
情况3:
linearlayout没设置onClickListerner,在onTouchListener.ouTouch返回true
点击空白出:
03-24 14:41:00.720 . activity dispatchTouchEvent
03-24 14:41:00.732 . linearLayout dispatchTouchEvent: action_down
03-24 14:41:00.732 . linearLayout onInterceptTouchEvent: action_down
03-24 14:41:00.732 . linearLayout onTouchListener.onTouch execut event: action_down
03-24 14:41:00.793 . activity dispatchTouchEvent
03-24 14:41:00.794 . linearLayout dispatchTouchEvent: action_move
03-24 14:41:00.794 . linearLayout onTouchListener.onTouch execut event: action_move
03-24 14:41:00.810 . activity dispatchTouchEvent
03-24 14:41:00.810 . linearLayout dispatchTouchEvent: action_move
03-24 14:41:00.810 . linearLayout onTouchListener.onTouch execut event: action_move
03-24 14:41:00.812 . activity dispatchTouchEvent
03-24 14:41:00.812 . linearLayout dispatchTouchEvent: action_up
03-24 14:41:00.812 . linearLayout onTouchListener.onTouch execut event: action_up
结果分析:同情况1,事件在linearLayout的onTouchListener.ouTouch中消化完毕,不会将事件向上传递给activity
情况4:
linearlayout没设置onClickListerner,在onTouchListener.ouTouch返回false
点击空白出:
03-24 14:37:30.106 . activity dispatchTouchEvent
03-24 14:37:30.118 . linearLayout dispatchTouchEvent: action_down
03-24 14:37:30.118 . linearLayout onInterceptTouchEvent: action_down
03-24 14:37:30.118 . linearLayout onTouchListener.onTouch execut event: action_down
03-24 14:37:30.118 . activity onTouchEvent execut event: action_down
03-24 14:37:30.133 . activity dispatchTouchEvent
03-24 14:37:30.133 . activity onTouchEvent execut event: action_move
03-24 14:37:30.175 . activity dispatchTouchEvent
03-24 14:37:30.175 . activity onTouchEvent execut event: action_up
结果分析:linearLayout没有消化事件,所以向上传递给activity
条件2:在activity中放入一个linearlayout,在linearlayout中放入一个button
linearlayout,button都设置了onTouchListener,ouTouch的返回值在条件中具体设置
在上述条件下,再分如下情况:
情况1:
button设置了onClickListerner,在onTouchListener.ouTouch返回true
linearlayout设置了onClickListerner,在onTouchListener.ouTouch返回true
点击button:
03-24 14:54:48.110 5941-5941/. activity dispatchTouchEvent
03-24 14:54:48.110 5941-5941/. linearLayout dispatchTouchEvent: action_down
03-24 14:54:48.110 5941-5941/. linearLayout onInterceptTouchEvent: action_down
03-24 14:54:48.110 5941-5941/. button dispatchTouchEvent: action_down
03-24 14:54:48.110 5941-5941/. btn onTouchListener.onTouch execut event: action_down
03-24 14:54:48.152 5941-5941/. activity dispatchTouchEvent
03-24 14:54:48.152 5941-5941/. linearLayout dispatchTouchEvent: action_move
03-24 14:54:48.152 5941-5941/. linearLayout onInterceptTouchEvent: action_move
03-24 14:54:48.152 5941-5941/. button dispatchTouchEvent: action_move
03-24 14:54:48.152 5941-5941/. btn onTouchListener.onTouch execut event: action_move
03-24 14:54:48.247 5941-5941/. activity dispatchTouchEvent
03-24 14:54:48.247 5941-5941/. linearLayout dispatchTouchEvent: action_move
03-24 14:54:48.247 5941-5941/. linearLayout onInterceptTouchEvent: action_move
03-24 14:54:48.247 5941-5941/. button dispatchTouchEvent: action_move
03-24 14:54:48.247 5941-5941/. btn onTouchListener.onTouch execut event: action_move
03-24 14:54:48.247 5941-5941/. activity dispatchTouchEvent
03-24 14:54:48.247 5941-5941/. linearLayout dispatchTouchEvent: action_up
03-24 14:54:48.247 5941-5941/. linearLayout onInterceptTouchEvent: action_up
03-24 14:54:48.248 5941-5941/. button dispatchTouchEvent: action_up
03-24 14:54:48.248 5941-5941/. btn onTouchListener.onTouch execut event: action_up
结果分析:button的onTouchListener.onTouch返回true消化了事件,不会触发点击事件,更不会向上传递事件
linearlayout不管怎样改变设置,log结果不变
情况2:
button设置了onClickListerner,在onTouchListener.ouTouch返回false
linearlayout设置了onClickListerner,在onTouchListener.ouTouch返回true
点击button:
03-24 15:02:19.825 13097-13097/. activity dispatchTouchEvent
03-24 15:02:19.851 13097-13097/. linearLayout dispatchTouchEvent: action_down
03-24 15:02:19.852 13097-13097/. linearLayout onInterceptTouchEvent: action_down
03-24 15:02:19.852 13097-13097/. button dispatchTouchEvent: action_down
03-24 15:02:19.852 13097-13097/. btn onTouchListener.onTouch execut event: action_down
03-24 15:02:19.872 13097-13097/. activity dispatchTouchEvent
03-24 15:02:19.872 13097-13097/. linearLayout dispatchTouchEvent: action_move
03-24 15:02:19.872 13097-13097/. linearLayout onInterceptTouchEvent: action_move
03-24 15:02:19.872 13097-13097/. button dispatchTouchEvent: action_move
03-24 15:02:19.872 13097-13097/. btn onTouchListener.onTouch execut event: action_move
03-24 15:02:20.022 13097-13097/. activity dispatchTouchEvent
03-24 15:02:20.022 13097-13097/. linearLayout dispatchTouchEvent: action_up
03-24 15:02:20.022 13097-13097/. linearLayout onInterceptTouchEvent: action_up
03-24 15:02:20.022 13097-13097/. button dispatchTouchEvent: action_up
03-24 15:02:20.022 13097-13097/. btn onTouchListener.onTouch execut event: action_up
03-24 15:02:20.025 13097-13097/. btn onclick execut
结果分析:button的onTouchListener.onTouch返回false,因为设置了onClickLisTener,依然消化了事件,在up时触发onCLick,不会向上传递事件
linearlayout不管怎样改变设置,log结果不变
情况3:
button不设置onClickListerner(除此之外,还要设置clickable为false,否则还是可点击),在onTouchListener.ouTouch返回true
linearlayout设置了onClickListerner,在onTouchListener.ouTouch返回true
点击button:
03-24 15:10:52.810 26450-26450/. activity dispatchTouchEvent
03-24 15:10:52.819 26450-26450/. linearLayout dispatchTouchEvent: action_down
03-24 15:10:52.819 26450-26450/. linearLayout onInterceptTouchEvent: action_down
03-24 15:10:52.819 26450-26450/. button dispatchTouchEvent: action_down
03-24 15:10:52.820 26450-26450/. btn onTouchListener.onTouch execut event: action_down
03-24 15:10:52.852 26450-26450/. activity dispatchTouchEvent
03-24 15:10:52.852 26450-26450/. linearLayout dispatchTouchEvent: action_move
03-24 15:10:52.852 26450-26450/. linearLayout onInterceptTouchEvent: action_move
03-24 15:10:52.852 26450-26450/. button dispatchTouchEvent: action_move
03-24 15:10:52.852 26450-26450/. btn onTouchListener.onTouch execut event: action_move
03-24 15:10:52.937 26450-26450/. activity dispatchTouchEvent
03-24 15:10:52.937 26450-26450/. linearLayout dispatchTouchEvent: action_move
03-24 15:10:52.937 26450-26450/. linearLayout onInterceptTouchEvent: action_move
03-24 15:10:52.937 26450-26450/. button dispatchTouchEvent: action_move
03-24 15:10:52.937 26450-26450/. btn onTouchListener.onTouch execut event: action_move
03-24 15:10:52.937 26450-26450/. activity dispatchTouchEvent
03-24 15:10:52.937 26450-26450/. linearLayout dispatchTouchEvent: action_up
03-24 15:10:52.937 26450-26450/. linearLayout onInterceptTouchEvent: action_up
03-24 15:10:52.937 26450-26450/. button dispatchTouchEvent: action_up
03-24 15:10:52.937 26450-26450/. btn onTouchListener.onTouch execut event: action_up
结果分析:button的onTouchListener.onTouch返回true消化了事件,不会向上传递事件
linearlayout不管怎样改变设置,log结果不变
情况4:
button不设置onClickListerner(除此之外,还要设置clickable为false,否则还是可点击),在onTouchListener.ouTouch返回false
linearlayout设置了onClickListerner,在onTouchListener.ouTouch返回true
点击button:
03-24 15:14:56.441 31668-31668/. activity dispatchTouchEvent
03-24 15:14:56.442 31668-31668/. linearLayout dispatchTouchEvent: action_down
03-24 15:14:56.442 31668-31668/. linearLayout onInterceptTouchEvent: action_down
03-24 15:14:56.442 31668-31668/. button dispatchTouchEvent: action_down
03-24 15:14:56.442 31668-31668/. btn onTouchListener.onTouch execut event: action_down
03-24 15:14:56.442 31668-31668/. linearLayout onTouchListener.onTouch execut event: action_down
03-24 15:14:56.522 31668-31668/. activity dispatchTouchEvent
03-24 15:14:56.522 31668-31668/. linearLayout dispatchTouchEvent: action_move
03-24 15:14:56.523 31668-31668/. linearLayout onTouchListener.onTouch execut event: action_move
03-24 15:14:56.584 31668-31668/. activity dispatchTouchEvent
03-24 15:14:56.584 31668-31668/. linearLayout dispatchTouchEvent: action_move
03-24 15:14:56.584 31668-31668/. linearLayout onTouchListener.onTouch execut event: action_move
03-24 15:14:56.584 31668-31668/. activity dispatchTouchEvent
03-24 15:14:56.584 31668-31668/. linearLayout dispatchTouchEvent: action_up
03-24 15:14:56.584 31668-31668/. linearLayout onTouchListener.onTouch execut event: action_up
结果分析:其实这个时候变为了条件A的情况1,button的这种设置,其实就是条件A了,只是ACTION_DOWN时事件回传递到button
总结:
1.activity,linearlayout,button模型中,事件总是从activity调用dispatchTouchEvent开始分发,然后到linearLayout又开始分发并判断是否拦截事件
2.事件分发到button后,如果button不处理就向上传递给linearlayout,linearlayout不处理就向上传递给activity,一旦一个节点处理了,事件不会向上传递
3.onTouchListener.onTouch返回ture,或者设置了onClickListener,事件就会被拦截处理,而不会向上传递了
想到之前某一个博客上的类比:
类似中央,省,市,县
命令是一级一级下发,最后到县,县里处理好了,那市里就不用处理,如果县里没处理好,那就该市里处理,以此类推。
在down,move,up这样一套动作里,如果你down处理好了,后续的move和up还会给你处理。如果没处理好,后续的动作也不会给你处理了。
下一套动作再从头开始以上的逻辑。