https://www.jianshu.com/p/b9cbc264ba03
(我在简书上的文章)
还是站在巨人的肩膀上
https://blog.csdn.net/a553181867/article/details/51287844 (有点难懂,记住他最后的总结即可。)
https://blog.csdn.net/zhangjin12312/article/details/78340998 (自称最好的事件分发博客)
立马找到一个错误:
Activity的dispatchTouchEvent并不是ViewGroup处理的
后来发现这篇文章是转载的,原文链接应该是:https://www.jianshu.com/p/38015afcdb58 (从源码出发分析)
目录
由Demo来分析
在上面这篇文章的评论中我又发现了一篇佳作:https://www.cnblogs.com/fuly550871915/p/4983682.html (从Demo出发分析,更加直观易懂,但是可能忽略源码许多细节的地方),它将事件分发一分为二。
事件的传递
针对的是OnInterceptTouchEvent方法,传递是由外到内,自上而下,也就是Activity --->ViewGroup--->View
事件的处理
针对的是onTouchEvent方法,和事件的传递顺序正好相反,处理是由内到外,自下而上,也就是View--->ViewGroup--->Activity
只有事件传递过来了,才有资格说处理。
由源码来分析
下面,对https://www.jianshu.com/p/38015afcdb58 分析。
首先,我要纠正我上面指出的那个错误,其实那个是正确的。activity的事件分发流程是activity ---> PhoneWindow ---> DecorView(ViewGroup),所以那个作者说的没有错,是跨度太大了。
-
关于"2.2.3 Demo讲解"
作者在ViewGroup的事件分发里写了这个Demo,但是Demo中Button是个View,所以例子的结尾没太说明白,这里我做一下补充:
//Android 5.0 View.java源码 public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. (即使View是disable状态,只要它是可点击的,它也会获取触摸事件,只是不去响应它们。) return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //设置了点击事件,会走到这里。 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { ... if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } } ... } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break; ... } return true; //消费这个事件,不会再向上回调。 } return false; }
所以作者博客里下的结论:Button的onClick()将事件消费掉了,从事件分发上说,就是Button(View)的onTouchEvent直接返回了true,所以它的父类ViewGroup不会收到事件的处理请求 。
-
关于"2.3.3 Demo讲解"
这个View涉及的是同一个View的事件分发,setOnTouchListener与setOnClickListener,
setOnTouchListener的onTouchEvent的返回值为啥会干扰setOnClickListener的执行呢?//Android 4.4 View.java源码 public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; //mOnTouchListener.onTouch结果返回值是true的话,就直接返回了,不会往后执行onTouchEvent。 } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
结合源码分析,mOnTouchListener.onTouch结果返回值为true,onTouchEvent方法不会执行,而click方法是在onTouchEvent里执行,所以click方法也不会执行。
-
作者的伪代码说明3个方法间的关系
/** * 点击事件产生后 */ // 步骤1:调用dispatchTouchEvent() public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; //代表 是否会消费事件 // 步骤2:判断是否拦截事件 if (onInterceptTouchEvent(ev)) { // a. 若拦截,则将该事件交给当前View进行处理 // 即调用onTouchEvent ()方法去处理点击事件 consume = onTouchEvent (ev) ; } else { // b. 若不拦截,则将该事件传递到下层 // 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程 // 直到点击事件被最终处理为止 consume = child.dispatchTouchEvent (ev) ; } // 步骤3:最终返回通知 该事件是否被消费(接收 & 处理) return consume; }
-
Down的后续事件
众多的事件分发博客都会详说到Down事件,但是很少说明MOVE、UP等后续事件对事件分发的影响。
所以DOWN事件是否消费直接影响MOVE、UP事件是否执行。代码里是如何体现的呢?
我自己的总结
1.关于事件分发的那几个方法
- ViewGroup独有onInterceptTouchEvent方法,View独有onTouchEvent方法(但是可以被它的子View复写,而且通常直接继承它的View都会复写。继承ViewGroup的类,如纯布局类,一般不会对onTouchEvent进行复写,但是也有例外,如ToolBar。所以,正是由于对onTouchEvent方法的复写,导致各种控件嵌套后冲突不断。)
-
ViewGroup和View都有dispatchTouchEvent方法,ViewGroup复写了View的dispatchTouchEvent方法,实现不同。
- 1.ViewGroup的dispatchTouchEvent方法:
判断onInterceptTouchEvent方法是否拦截事件,缺省情况下会遍历子View,如果View满足条件(如touch事件的坐标落在View上)就会调用dispatchTransformedTouchEvent方法,然后可能会调用到子View的dispatchTouchEvent方法或者父类的dispatchTouchEvent方法。这就是事件传递,我们最需要关注的地方就是事件传递的条件。
关于ViewGroup的dispatchTouchEvent方法,我还要强调一个重点: mFirstTouchTarget。我们总是在说,如果View的onTouchEvent返回false,事件就会交由它的父布局的onTouch方法处理。这个结论如何从代码里推导出来呢?
》》Down事件触发,mFirstTouchTarget置为null。
》》假如有某个子View处理了事件,addTouchTarget方法会执行,里面给mFirstTouchTarget赋值了。
》》假如没有一个子View处理事件,mFirstTouchTarget是null,就会执行下面代码:
看dispatchTransformedTouchEvent方法
super.dispatchTouchEvent
注意是super,否则就是死循环了。也就会调用到了onTouchEvent方法(这个方法是View类里的)。所以“下级View不处理,上级View就处理”这个判断是在上级View里判断的,并非是我们一贯的监听回调模式,并非在下级View里回调上级View的onTouch事件。
- 2.View的dispatchTouchEvent方法:
关于View的dispatchTouchEvent方法,我只想强调2点:
1)通过setOnTouchListener设置的监听者比View本身的onTouchEvent处理优先级高,监听者一旦处理了Touch事件,View本身的onTouchEvent方法则不会被执行。
2)View的dispatchTouchEvent的返回值代表着View对事件的处理结果,看官方的注释:
- 1.ViewGroup的dispatchTouchEvent方法:
对于ViewGroup而言,dispatchTouchEvent的返回值代表着内层View或者自己对事件的处理结果。
由此可见,View的dispatchTouchEvent方法只是对onTouchEvent的一个封装而已,而ViewGroup的dispatchTouchEvent方法是对View的dispatchTouchEvent方法遍历和对自己的onTouchEvent的一个封装而已。
2.实践是检验真理的唯一标准
下面,我就亲自写几个Demo亲自验证上面的理论。
自定义一个View和ViewGroup,用来打印日志。(执行方法后的日志,带有handle的,只是标识当前方法的执行结果,日志打印出来的执行顺序是乱序的,不要管。)
View类
public class MyView extends View {
private static final String TAG = "MyView";
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//打印方法的执行顺序
Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev));
boolean result = super.dispatchTouchEvent(ev);
//打印方法的执行结果(不要管顺序,看handle即可。)
Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev) + ",handle:" + result);
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
boolean result;
if(MotionEvent.ACTION_DOWN == event.getAction()){
result = true;
} if(MotionEvent.ACTION_MOVE == event.getAction()){
result = false;
}
else{
result = super.onTouchEvent(event);
}
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result);
return result;
}
private String getEventName(MotionEvent ev){
if(MotionEvent.ACTION_DOWN == ev.getAction()){
return "ACTION_DOWN";
}else if(MotionEvent.ACTION_MOVE == ev.getAction()){
return "ACTION_MOVE";
}else if(MotionEvent.ACTION_UP == ev.getAction()){
return "ACTION_UP";
}
return "other";
}
}
ViewGroup类
public class MyViewGroup extends ViewGroup {
private static final String TAG = "MyViewGroup";
public MyViewGroup(@NonNull Context context) {
super(context);
}
public MyViewGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//打印方法的执行顺序
Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev));
boolean result = super.dispatchTouchEvent(ev);
//打印方法的执行结果(不要管顺序,看handle即可。)
Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev) + ",handle:" + result);
return result;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, "onInterceptTouchEvent: " + getTag() + ",name:" + getEventName(ev) );
boolean result = super.onInterceptTouchEvent(ev);
Log.e(TAG, "onInterceptTouchEvent: " + getTag() + ",name:" + getEventName(ev) + ",handle:" + result);
return result;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
boolean result;
if(MotionEvent.ACTION_DOWN == event.getAction()){
result = true;
}else if(MotionEvent.ACTION_MOVE == event.getAction()){
result = true;
}
else{
result = super.onTouchEvent(event);
}
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result);
return result;
}
private String getEventName(MotionEvent ev){
if(MotionEvent.ACTION_DOWN == ev.getAction()){
return "ACTION_DOWN";
}else if(MotionEvent.ACTION_MOVE == ev.getAction()){
return "ACTION_MOVE";
}else if(MotionEvent.ACTION_UP == ev.getAction()){
return "ACTION_UP";
}
return "other";
}
}
-
Demo1:只有View-最简单,只有一个单独的MyView。
布局:
输出日志1-1:MyView: dispatchTouchEvent: btn,name:ACTION_DOWN MyView: onTouchEvent: btn,name:ACTION_DOWN MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:false MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:false
可以发现,View默认onTouchEvent的ACTION_DOWN事件处理为false,所以也不会收到后续的MOVE、UP事件。
》》那么,在onTouchEvent里判断一下,如果ACTION_DOWN事件就返回true,看看是什么结果?
修改MyView的onTouch方法:@Override public boolean onTouchEvent(MotionEvent event) { Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)); boolean result; if(MotionEvent.ACTION_DOWN == event.getAction()){ result = true; } else{ result = super.onTouchEvent(event); } Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result); return result; }
轻轻快速点击,输出日志1-2:
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN MyView: onTouchEvent: btn,name:ACTION_DOWN MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:true MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true MyView: dispatchTouchEvent: btn,name:ACTION_MOVE MyView: onTouchEvent: btn,name:ACTION_MOVE MyView: onTouchEvent: btn,name:ACTION_MOVE,handle:false MyView: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:false MyView: dispatchTouchEvent: btn,name:ACTION_UP MyView: onTouchEvent: btn,name:ACTION_UP MyView: onTouchEvent: btn,name:ACTION_UP,handle:false MyView: dispatchTouchEvent: btn,name:ACTION_UP,handle:false
可以发现View的ACTION_DOWN事件处理为true,后续MOVE、UP事件都能接收,且默认处理结果都为false。
》》为了满足我的好奇心,在onTouchEvent里,在上面的基础上修改一下,如果ACTION_DOWN 处理结果为true,ACTION_MOVE处理结果为true,看看是什么结果?
继续修改MyView的onTouch方法:@Override public boolean onTouchEvent(MotionEvent event) { Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)); boolean result; if(MotionEvent.ACTION_DOWN == event.getAction()){ result = true; }else if(MotionEvent.ACTION_MOVE == event.getAction()){ result = true; }else{ result = super.onTouchEvent(event); } Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result); return result; }
输出日志1-3:
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN MyView: onTouchEvent: btn,name:ACTION_DOWN MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:true MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true MyView: dispatchTouchEvent: btn,name:ACTION_MOVE MyView: onTouchEvent: btn,name:ACTION_MOVE MyView: onTouchEvent: btn,name:ACTION_MOVE,handle:true MyView: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:true MyView: dispatchTouchEvent: btn,name:ACTION_UP MyView: onTouchEvent: btn,name:ACTION_UP MyView: onTouchEvent: btn,name:ACTION_UP,handle:false MyView: dispatchTouchEvent: btn,name:ACTION_UP,handle:false
由此可见,MOVE事件的处理结果不影响UP事件的传递和处理结果。
当然,除了修改源码,也可以通过setOnTouchListener来修改各个事件的处理结果。 -
Demo2:只有ViewGroup- 只有一个单独的MyViewGroup。
布局:
输出日志2-1:MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN,handle:false MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN,handle:false MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:false
可见,ViewGroup默认是不拦截事件且不处理onTouchEvent的,所以也就没有后续的MOVE、UP事件。
》》那么,在onTouchEvent里判断一下,如果ACTION_DOWN事件就返回true,看看是什么结果?@Override public boolean onTouchEvent(MotionEvent event) { Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)); boolean result; if(MotionEvent.ACTION_DOWN == event.getAction()){ result = true; }else{ result = super.onTouchEvent(event); } Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result); return result; }
输出日志2-2:
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN,handle:false MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN,handle:true MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE,handle:false MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:false MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP MyViewGroup: onTouchEvent: btn,name:ACTION_UP MyViewGroup: onTouchEvent: btn,name:ACTION_UP,handle:false MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP,handle:false
可以看到,ViewGroup的DOWN事件处理结果若为true,MOVE、UP事件都会处理,且默认处理结果都为false(和View一致的)。
》》为了满足我的好奇心,在onTouchEvent里,在上面的基础上修改一下,如果ACTION_DOWN 处理结果为true,ACTION_MOVE处理结果为true,看看是什么结果?
继续修改MyRelativeLayout的onTouch方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
boolean result;
if(MotionEvent.ACTION_DOWN == event.getAction()){
result = true;
}else if(MotionEvent.ACTION_MOVE == event.getAction()){
result = true;
}
else{
result = super.onTouchEvent(event);
}
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result);
return result;
}
输出日志2-3:
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN,handle:false
MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN
MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE
MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE
MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP
MyViewGroup: onTouchEvent: btn,name:ACTION_UP
MyViewGroup: onTouchEvent: btn,name:ACTION_UP,handle:false
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP,handle:false
可见,对MOVE的处理结果修改,不影响UP事件的传递和默认处理结果。
对Demo1、Demo2做一个总结:
对于onTouchEvent方法,View和ViewGroup的处理结果默认是一致的,默认都是不处理。一旦DOWN事件处理结果为true,MOVE、UP事件也会处理且处理结果默认为false。其实不用打印日志也知道,ViewGroup直接继承的View,且没有对onTouchEvent作复写,自然而然处理结果是一致的呢。
对于onTouchEvent方法,View的直接子View一般都会复写,默认的处理过程都有点不太一样。比如TextView默认就是处理全部的事件。这也导致了事件冲突由于控件不同,现象千奇百怪,所以有必要对发生冲突的控件的onTouchEvent的实现过程有一定的了解。
-
Demo3:ViewGroup+View。
由于ViewGroup无法直接布局,所以使用RelativeLayout来替代ViewGroup(RelativeLayout直接继承ViewGroup并且没有对onTouchEvent进行复写,所以不会对我们后面的事件分析产生误导。)public class MyRelativeLayout extends RelativeLayout { private static final String TAG = "MyRelativeLayout"; public MyRelativeLayout(@NonNull Context context) { super(context); } public MyRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { //打印方法的执行顺序 Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev)); boolean result = super.dispatchTouchEvent(ev); //打印方法的执行结果(不要管顺序,看handle即可。) Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev) + ",handle:" + result); return result; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e(TAG, "onInterceptTouchEvent: " + getTag() + ",name:" + getEventName(ev) ); boolean result = super.onInterceptTouchEvent(ev); Log.e(TAG, "onInterceptTouchEvent: " + getTag() + ",name:" + getEventName(ev) + ",handle:" + result); return result; } @Override public boolean onTouchEvent(MotionEvent event) { Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)); boolean result = super.onTouchEvent(event); Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result); return result; } private String getEventName(MotionEvent ev){ if(MotionEvent.ACTION_DOWN == ev.getAction()){ return "ACTION_DOWN"; }else if(MotionEvent.ACTION_MOVE == ev.getAction()){ return "ACTION_MOVE"; }else if(MotionEvent.ACTION_UP == ev.getAction()){ return "ACTION_UP"; } return "other"; } }
布局:
日志输出3-1(不要理会dispatchTouchEvent方法带handle的日志的顺序):
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:false
MyRelativeLayout: onTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onTouchEvent: rl2,name:ACTION_DOWN,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN,handle:false
由以上日志我们可以分析出以下几点信息:
1.事件传递的顺序:由外到内(由最外层布局向最里层)
2.事件处理的顺序:外层优先拦截,如果拦截,自己处理,不会再交由内层处理;如果不拦截,交由内层处理,内层如果处理结果为false,那么外层就会去处理。(看倒数第二条日志可以分析定论)
但是这里有个大前提:这里的事件一定是DOWN事件,对于内层没有处理的其它事件,外层是不会进行处理的。
所以事件处理,记住上面2条规律即可。其它的都是在上面的基础之上排列组合,然后不同的View的拦截、事件处理流程不一样而已。
为了验证上面的结论,现在通过一些实验来证明一下。
》》修改MyView的onTouchEvent方法,DOWN事件处理结为true。
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
boolean result;
if(MotionEvent.ACTION_DOWN == event.getAction()){
result = true;
}
else{
result = super.onTouchEvent(event);
}
Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event) + ",handle:" + result);
return result;
}
输出日志3-2:
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:true
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN,handle:true
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_MOVE
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_MOVE
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_MOVE,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_MOVE
MyView: onTouchEvent: btn,name:ACTION_MOVE
MyView: onTouchEvent: btn,name:ACTION_MOVE,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_MOVE,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_UP
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_UP
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_UP,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_UP
MyView: onTouchEvent: btn,name:ACTION_UP
MyView: onTouchEvent: btn,name:ACTION_UP,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_UP,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_UP,handle:false
可以看到内层的MyView对MOVE、UP的处理结果为false,和DOWN事件不一样的是,外层的MyRelativeLayout不会再做处理。
3.事件冲突实战
-
冲突类型的划分
按照冲突的对象来划分,可以分为:
1.同一控件内部冲突
如onTouch与onClick的事件冲突。
2.不同控件嵌套冲突
按照冲突的类型划分,可以分为:
1.点击冲突
2.滑动冲突
典型事件冲突分析
1.同一控件的onTouch与onClick的事件冲突。(点击冲突)
看这篇博客:https://www.jianshu.com/p/d07bd8e0ac70
作者的需求是:在View移动之后,不要执行点击事件。
作者的解决思路:onTouch仍然全部return true,然后在UP事件里判断View的移动范围,认为是点击事件就直接手动执行click事件(他用的反射,其实performClick就可以,下面评论区已经有人指出)。
但是,作者并没有对这个问题的根本性原因作出解答,为什么onTouch事件返回true,点击事件失效?
原因:
上面的博文中我已经强调,View的setOnTouchListener的onTouch方法优先于View自身的onTouchEvent方法执行,并且互斥。而View的点击等事件,是在View自身的onTouchEvent方法里实现的,在UP事件分支里执行。调用View的setOnTouchListener方法并且onTouch返回true,导致了View自身的onTouchEvent方法不会被执行,那么点击事件就更加不会被执行了。
2.滑动冲突
提起滑动冲突,最臭名昭著的当属ScrollView了,关于它的各种常见冲突,我总结了一下网上的资料如下:
深入
上面的博客只涉及到Activity的dispatchTouchEvent方法讲解,那么activity的这个方法又是在哪里被调用的呢
https://blog.csdn.net/stonecao/article/details/6759189
0 条评论