Android事件传递机制
Android事件传递机制主要涉及到三个方法:dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent,分别是关于事件分发、事件拦截、事件处理的。涉及到的组件主要包括Activity,ViewGroup,View。
需要注意的是,ViewGroup也是继承自View的,这里的View指的是没有子View的控件,比如TextView和Button。
ViewGroup有上诉三个方法,但是Activity和View是没有onInterceptTouchEvent 方法的。原因也是很简单,当用户触摸屏幕的时候,Activity作为事件传递的顶点,如果它把事件拦截后,那么整个屏幕就没有反应了。View作为最底层的控件,它没有子View,不需要拦截。
我认为要了解一个知识点还是需要自己去写一个Demo。
思路:
1.自定义layout继承LinearLayout,复写dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent方法。
2.自定义View继承Button,复写dispatchTouchEvent,onTouchEvent,方法。
3.在Activity中复写dispatchTouchEvent,onTouchEvent方法。
MyLinearLayout,在三个事件方法中打印Log
public class MyLinearLayout extends LinearLayout {
String Tag = "TouchEventDemo";
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyLinearLayout: dispatch:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyLinearLayout: dispatch:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyLinearLayout: dispatch:action_up");
break;
}
boolean b = super.dispatchTouchEvent(ev);
Log.e(Tag, "MyLinearLayout: superDispatch: " + b);
return b;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyLinearLayout: intercept:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyLinearLayout: intercept:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyLinearLayout: intercept:action_up");
break;
}
boolean b = super.onInterceptTouchEvent(ev);
Log.e(Tag, "MyLinearLayout: superIntercept: " + b);
return b;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyLinearLayout: onTouchEvent:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyLinearLayout: onTouchEvent:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyLinearLayout: onTouchEvent:action_up");
break;
}
boolean b = super.onTouchEvent(event);
Log.e(Tag, "MyLinearLayout: superTouch: " + b);
return b;
}
}
MyButton,在事件方法中打印Log。
public class MyButton extends Button {
String Tag = "TouchEventDemo";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyButton: dispatch:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyButton: dispatch:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyButton: dispatch:action_up");
break;
}
boolean b = super.dispatchTouchEvent(ev);
Log.e(Tag, "MyButton: superDispatch: " + b);
return b;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyButton: onTouchEvent:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyButton: onTouchEvent:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyButton: onTouchEvent:action_up");
break;
}
boolean b = super.onTouchEvent(event);
Log.e(Tag, "MyButton: superTouch: " + b);
return b;
}
}
MainActivity,在事件方法中打印Log
使用上面自定义的两个类,设置setOnTouchListener和setOnclickListener方法,并在事件方法中打印Log。
public class MainActivity extends AppCompatActivity {
String Tag = "TouchEventDemo";
private MyLinearLayout mLayout;
private MyButton mBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLayout = ((MyLinearLayout) findViewById(R.id.layout));
mBtn = ((MyButton) findViewById(R.id.btn));
mLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyLinearLayout:setOnTouchListener: action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyLinearLayout:setOnTouchListener: action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyLinearLayout:setOnTouchListener: action_up");
break;
}
return false;
}
});
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "MyButton:setOnTouchListener: action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "MyButton:setOnTouchListener: action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "MyButton:setOnTouchListener: action_up");
break;
}
return false;
}
});
mLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(Tag, "layout is onClicked");
}
});
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(Tag, "button is onClicked");
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "Activity: dispatch:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "Activity: dispatch:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "Activity: dispatch:action_up");
break;
}
boolean b = super.dispatchTouchEvent(ev);
Log.e(Tag, "Activity: superDispatch: " + b);
return b;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(Tag, "Activity: onTouchEvent:action_down");
break;
case MotionEvent.ACTION_MOVE:
Log.e(Tag, "Activity: onTouchEvent:action_move");
break;
case MotionEvent.ACTION_UP:
Log.e(Tag, "Activity: onTouchEvent:action_up");
break;
}
boolean b = super.onTouchEvent(event);
Log.e(Tag, "Activity: superOnTouchEvent: " + b);
return b;
}
}
打印这么多Log,其实就是在每一个方法中打印了行为(是按下:ACTION_DOWN,是移动:ACTION_MOVE,还是抬起:ACTION_UP)。目的就是要了解它的每一个动作是怎么传递下去的,以及已经方法之间的影响。
先研究下MyButton的处理事件的行为是什么样子的
一. 当一切默认的情况下,打印结果为:
Activity: dispatch:action_down
MyLinearLayout: dispatch:action_down
MyLinearLayout: intercept:action_down
MyLinearLayout: intercept return : false
MyButton: dispatch:action_down
MyButton:setOnTouchListener: action_down
MyButton: onTouchEvent:action_down
MyButton: onTouchEvent return : true
MyButton: dispatch return : true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_move
MyLinearLayout: dispatch:action_move
MyLinearLayout: intercept:action_move
MyLinearLayout: intercept return : false
MyButton: dispatch:action_move
MyButton:setOnTouchListener: action_move
MyButton: onTouchEvent:action_move
MyButton: onTouchEvent return : true
MyButton: dispatch return : true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_up
MyLinearLayout: dispatch:action_up
MyLinearLayout: intercept:action_up
MyLinearLayout: intercept return : false
MyButton: dispatch:action_up
MyButton:setOnTouchListener: action_up
MyButton: onTouchEvent:action_up
MyButton: onTouchEvent return : true
MyButton: dispatch return : true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
button is onClicked
总结:
1.dispatchTouchEvent方法默认返回true
2.onInterceptTouchEvent方法默认返回false
3.onTouchEvent方法默认返回true
4.事件默认到最小单位(MyButton)则会被他自己处理。
二. 当MyButton的onTouchEvent不调用super.onTouchEvent,而是直接返回true的情况下,
// boolean b = super.onTouchEvent(event);
boolean b = true;
Log.e(Tag, "MyButton: onTouchEvent return : " + b);
return b;
打印结果为:
Activity: dispatch:action_down
MyLinearLayout: dispatch:action_down
MyLinearLayout: intercept:action_down
MyLinearLayout: intercept return : false
MyButton: dispatch:action_down
MyButton:setOnTouchListener: action_down
MyButton: onTouchEvent:action_down
MyButton: onTouchEvent return : true
MyButton: dispatch return : true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_move
MyLinearLayout: dispatch:action_move
MyLinearLayout: intercept:action_move
MyLinearLayout: intercept return : false
MyButton: dispatch:action_move
MyButton:setOnTouchListener: action_move
MyButton: onTouchEvent:action_move
MyButton: onTouchEvent return : true
MyButton: dispatch return : true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_up
MyLinearLayout: dispatch:action_up
MyLinearLayout: intercept:action_up
MyLinearLayout: intercept return : false
MyButton: dispatch:action_up
MyButton:setOnTouchListener: action_up
MyButton: onTouchEvent:action_up
MyButton: onTouchEvent return : true
MyButton: dispatch return : true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
总结:
唯一的影响就是MyButton的onClick方法不执行了。
查看源码:
//super(也就是View)的onTouchEvent方法中,有处理关于ACTION_UP的逻辑。其中会回调用onclick()方法。
public boolean onTouchEvent(MotionEvent event) {
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick();
}
...
break;
}
}
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
onClick方法是在动作抬起后执行的。
虽然都是返回的ture,但是由于没有调用super.onTouchEvent方法,后续的onClick事件就得不到调用了。
三. 当MyButton的onTouchEvent直接返回false的情况下,
// boolean b = super.onTouchEvent(event);
boolean b = false;
Log.e(Tag, "MyButton: onTouchEvent return : " + b);
return b;
打印结果为:
Activity: dispatch:action_down
MyLinearLayout: dispatch:action_down
MyLinearLayout: intercept:action_down
MyLinearLayout: intercept return : false
MyButton: dispatch:action_down
MyButton:setOnTouchListener: action_down
MyButton: onTouchEvent:action_down
MyButton: onTouchEvent return : false
MyButton: dispatch return : false
MyLinearLayout:setOnTouchListener: action_down
MyLinearLayout: onTouchEvent:action_down
MyLinearLayout: onTouchEvent return: true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_move
MyLinearLayout: dispatch:action_move
MyLinearLayout:setOnTouchListener: action_move
MyLinearLayout: onTouchEvent:action_move
MyLinearLayout: onTouchEvent return: true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_up
MyLinearLayout: dispatch:action_up
MyLinearLayout:setOnTouchListener: action_up
MyLinearLayout: onTouchEvent:action_up
MyLinearLayout: onTouchEvent return: true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
layout is onClicked
总结:
1.MyButton,onTouchEvent方法在down时返回false,则其dispatch方法返回false,代表它以后不会对事件进行分发了。之后父布局(MyLinearLayout)拿到这个false,并记录这个false,直接调用了自己的onTouchEvent。并在发生move和up事件时也直接调用自己的onTouchEvent,这时候这个父布局相当于最底层的View。类似于之前MyButton的地位。也就是说没有onInterceptedTouchEvent方法了(可以这么理解)。
2.MyButton在Action_down的时候返回false,之后的move和up事件就不会被继续分发给它了。
3.由于事件交给了MyLinearLayout来处理,action_down之后,它的interceptTouchEvent方法已经不再执行,onTouchEvent和onClick方法得到了执行。
说明:在1.中为什么说onTouchEvent方法返回false,则dispatch方法返回false呢?
源码中有这样的一段:
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;
}
意思就是:
if(
1.在外部设置了onTouchListener(是的,我们之前在MainActivity中设置了)
&& 2.控件处于可用状态(也没错,现在是Enable状态)
&& 3.onTouchListener.onTouch方法返回true(我们返回的的是false))
{
//这里最后一个条件不符合,
return true;
}
//所以继续向下执行。
if(onTouchEvent) {
return true;
}
所以,onTouchEvent方法返回false,则dispatch方法返回false。
总结:onTouchEvent方法,返回true,表示事件已经被消费。返回false,则将事件交给父布局来处理。
再研究下MyLinearLayout处理事件的行为是什么样子的
测试onInterceptTouchEvent方法。
该方法调用super.onInterceptTouchEvent方法和直接返回false
// boolean b = super.onInterceptTouchEvent(ev);
boolean b = false;
Log.e(Tag, "MyLinearLayout: intercept return : " + b);
return b;
结果都是一样的
MyLInearLayout 的onInterceptTouchEvent方法返回true
打印结果:
Activity: dispatch:action_down
MyLinearLayout: dispatch:action_down
MyLinearLayout: intercept:action_down
MyLinearLayout: intercept return : true
MyLinearLayout:setOnTouchListener: action_down
MyLinearLayout: onTouchEvent:action_down
MyLinearLayout: onTouchEvent return: true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_move
MyLinearLayout: dispatch:action_move
MyLinearLayout:setOnTouchListener: action_move
MyLinearLayout: onTouchEvent:action_move
MyLinearLayout: onTouchEvent return: true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
---------------------------------------------
Activity: dispatch:action_up
MyLinearLayout: dispatch:action_up
MyLinearLayout:setOnTouchListener: action_up
MyLinearLayout: onTouchEvent:action_up
MyLinearLayout: onTouchEvent return: true
MyLinearLayout: dispatch return : true
Activity: dispatch return : true
layout is onClicked
总结:
1.当拦截方法返回true时。事件会直接交给自己的onTouchEvent方法。
2.当拦截方法再action_down返回true后,之后的move和up事件就不会再去执行该拦截方法了。
MyLInearLayout 的onTouchEvent方法也和MyButton一样。
总结:
以上诉Demo为例。
当点击button时,事件会先传到LienarLayout中,调用LienarLayout的dispatchTouchEvent()方法,这时,会先检测以前有没有子控件的dispatchTouchEvent方法有没有返回false,如果没有,再调用onInterceptTouchEvent()方法,
1.如果onInterceptTouchEvent返回true,则调用自己的onTouchEvent()方法。
2.如果onInterceptTouchEvent返回false,则调用button的dispatchTouchEvent()方法,button的dispatchTouchEvent()方法会调用自己的onTouchEvent()方法,onTouchEvent()方法返回true,则dispatchTouchEvent()方法返回true,事件被消费,反之dispatchTouchEvent()方法返回false。LinearLayout的dispatchTouchEvent()方法拿到这个false后,会直接调用自己的onTouchEvent()方法。
注意,不是因为MyButton的onTouchEvent方法返回false而直接调用MyLinearLayout的onTouchEvent方法。而是onTouchEvent导致dispatchTouchEvent返回了false,然后让MyLinearLayout的dispatchTouchEvent得到,然后不调用onInterceptTouchEvent,直接调用了onTouchEvent方法。