之前对这篇文章写的不够详细,觉得有必要在对这篇文章完善一下。本文使用Log方式分析,不涉及源码。log方式更直观易懂一些。
首先,View的几个基本的继承关系:
本博客案例的图层:
要实现上边这个图层结构,需要自定义View,并在里面加入log打印,只有自定义View才能测试log情况。我们这里自定义View只需要重写几个分发事件方法即可。如下:
MainActivity:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * activity的 */ @Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "Activity+分发+dispatchTouchEvent"); return super.dispatchTouchEvent(event); } /** * activity的 */ @Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "Activity+处理+onTouchEvent"); return super.onTouchEvent(event); } }
由于Activity只有dispatchTouchEvent和onTouchEvent两个方法。因此重写之
然后,新建两个容器类型的View:
自定义View,第一个容器View----类型属于ViewGroup:(LinearLayout就属于容器类型的View,因此继承自它。继承其他容器View也可以比如RelativeLayout也可以的)
public class ViewGroupOne extends LinearLayout { public ViewGroupOne(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroupOne(Context context) { super(context); } /** * 分发事件 */ @Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+分发+dispatchTouchEvent"); return super.dispatchTouchEvent(event); //return true; //return false; } /** * 拦截事件 */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent"); return super.onInterceptTouchEvent(event); // return true; // return false; } /** * 处理事件 */ @Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+处理+onTouchEvent"); return super.onTouchEvent(event); // return true; } }
自定义View,第二个容器View----属于ViewGroup:
public class ViewGroupTwo extends LinearLayout { public ViewGroupTwo(Context context, AttributeSet attrs) { super(context, attrs); } public ViewGroupTwo(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent event) { //getParent().requestDisallowInterceptTouchEvent(true);//本质上不让父组件的dispatchtouchevent生效 ActionUtiles.processEvent(event, "ViewGroupTwo+分发+dispatchTouchEvent"); return super.dispatchTouchEvent(event); // return false; // return true; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent"); return super.onInterceptTouchEvent(event); // return true; } @Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+处理+onTouchEvent"); return super.onTouchEvent(event); } }
加入一个没有子组件的View,类型类似TextView:
public class MyTextView extends TextView { public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } public MyTextView(Context context) { super(context); } @Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "MyTextView+分发+dispatchTouchEvent"); return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "MyTextView+处理+onTouchEvent"); return super.onTouchEvent(event);//回传机制 //return true;//事件消费,不回传 //return false;//和默认效果一样,回传机制 } }
因为每个类,每个事件方法都需要写如下代码:
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(name, "按下ACTION_DOWN");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(name, "取消ACTION_CANCEL");
break;
case MotionEvent.ACTION_MOVE:
Log.e(name, "移动ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.w(name, "松开ACTION_UP");
break;
case MotionEvent.ACTION_OUTSIDE:
Log.v(name, "点击外面ACTION_OUTSIDE");
break;
default:
break;
}
功能大家都一样,因此抽取一个Utis类来处理这个方法即可,如下:
public class ActionUtiles { /** * * @param event处理的事件 * @param name组件的名称哪个类触发打印的) */ public static void processEvent(MotionEvent event,String name){ switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(name, "按下ACTION_DOWN"); break; case MotionEvent.ACTION_CANCEL: Log.d(name, "取消ACTION_CANCEL"); break; case MotionEvent.ACTION_MOVE: Log.e(name, "移动ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.w(name, "松开ACTION_UP"); break; case MotionEvent.ACTION_OUTSIDE: Log.v(name, "点击外面ACTION_OUTSIDE"); break; default: break; } } }
为了测试不同事件的打印情况,log使用不同的日志级别进行区分。
接着写一个复合上边图层的布局:
<com.example.event.ViewGroupOne xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ff0000" android:orientation="vertical" > <com.example.event.ViewGroupTwo android:layout_width="200dp" android:layout_height="200dp" android:background="#00ff00" > <com.example.event.MyTextView android:layout_width="100dp" android:layout_height="100dp" android:background="#0000ff" > </com.example.event.MyTextView> </com.example.event.ViewGroupTwo> </com.example.event.ViewGroupOne>
注意:记得自定义View需要写类的全称。
好了,废话不多说,直接运行程序说话。通多点击手机屏幕。对所有的可能情况全部罗列如下:
一、默认情况,对生成的事件方法返回值什么也不做。返回值都是默认情况:
事件1:点击红色位置。看log输出
按下:可以看到,整个事件是Activity先得到事件,调用它的dispatchTouchEvent分发事件,Activity返回值默认传递给ViewGroupOne,调用ViewGroupOne的dispatchTouchEvent方法,此时这个返回值默认return super.dispatchTouchEvent(event);默认的时候它会把事件传递给自己的onInterceptTouchEvent(MotionEvent event)拦截事件看自己是否需要拦截这个事件,但是由于它的返回值也是默return super.onInterceptTouchEvent(event);认,即没做拦截;事件继续传递,传递给自己的onTouchEvent(MotionEvent event)看自己是否处理这个事件; 自己的onTouchEvent(MotionEvent event)处理方法被调用,但是返回值也是默认,return super.onTouchEvent(event);自己并不处理事件。这样事件到了最里层的View也没有被消费,事件开始回传,回传给父容器Activity的onTouchEvent(MotionEvent event) ,Activity也是默认返回值,表示不做任何处理。整个过程事件都没有做处理和拦截,事件作废。松开后:看一下松开事件:由Activity的dispatchTouchEvent开始分发事件自己返回值是默认情况,把事件直接分发给自己的onTouchEvent(MotionEvent event)方法处理事件,自己返回值是默认情况,事件作废。 整个事件过程,并没有任何地方要处理事件, 由up事件log日志,也可以看到:最后松手时的事件,已经与ViewGroupOne没有任何关系。
事件2:点击绿色区域l。看log输出:
这里就会显而易见了。多了ViewGroupTwo,就多往下传递一层,最里面这层没做处理,最后还是回传回来。一直回传到activity事件全部消失
事件3:点击蓝色区域:
这个肯定在意料之中,不用解释也很清楚为何打印此log了。
二、增加处理:
处理1:蓝色区域,MyTextView修改如下代码:
@Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "MyTextView+处理+onTouchEvent"); return true;//事件消费,不回传 }
此时要重新运行程序了再点击蓝色区域,看log:
MyTextView的onTouchEvent(MotionEvent event)返回true的意思是,事件由我来处理。它处理了事件,就不可能出现回传了,出现这种log也是顺理成章了。再看UP事件与分发拦截是一致的。最后在MyTextView中消失。这个时候可以对比一了,一都是默认,可以称之为狭义的回传机制,而二的处理1:我们可以称之为狭义的拦截机制,每一次的往下分发,又可称为狭义的传递机制(称之为狭义,可能逼格略高些,是因为不能代表全部,却也不失一般性;最起码拦截与回传分发大致是什么很清楚了)。那接着就细致开来,从狭义走到广义。
处理2:蓝色区域,MyTextView修改如下代码:
@Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "MyTextView+处理+onTouchEvent"); return false;//和默认效果一样,回传机制 }
再运行,打印看log:
和默认效果是一样的。回看默认解析。
处理3:蓝色区域,MyTextView的dispatchTouchEvent方法修改如下代码:
@Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "MyTextView+分发+dispatchTouchEvent"); return true; }
看log:
MyTextView的dispatchTouchEvent(MotionEvent event)返回值为true,表示不再分发事件也可以说是拦截事件。此时MyTextView的onTouchEvent方法不会被调用,既然是拦截了事件也不会回传。
处理4:蓝色区域,MyTextView的dispatchTouchEvent方法修改如下代码:
@Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "MyTextView+分发+dispatchTouchEvent"); return false; }
看log输出:
MyTextView的dispatchTouchEvent(MotionEvent event)返回值为false,表示我自己MyTextView不做事件的分发,那么,我自己的后续方法(onTouchEvent方法)不再被调用执行。直接回传给父控件的onTouchEvent处理方法处理事件,父控件都是默认情况。事件一直回传,直到Activity,事件消失。
说明:上边都是针对蓝色区域点击;这是因为,只有点击了蓝色位置,事件才有可能传递到MyTextView,点击其他位置根本不会调MyTextView的任何方法。
第一次总结:
TextView(没有子组件类型)
dispatchTouchEvent(MotionEvent event)
return true; //不再分发事件,表示拦截事件,拦截掉不会回传,事件在此消失
return false;//表示不做分发事件,事件不被拦截,回传给父控件的处理方法
return super.onTouchEvent() ;//事件传递机制,传递给自己的onTouchEvent方法。
onTouchEvent()
return true; //自己处理消费掉事件,事件消费不再回传
return false;或者 return super.onTouchEvent() ;//效果一样,事件回传给父组件
处理5:点击绿色区域,ViewGroupTwo的dispatchTouchEvent方法做如下修改:
@Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+分发+dispatchTouchEvent"); return true; }
看log:
由于ViewGroupTwo的dispatchTouchEvent方法返回值为true,则事件被拦截,不会往下传递同时也不会回传。事件在此消失。
处理6:点击绿色区域,ViewGroupTwo的dispatchTouchEvent方法做如下修改:
@Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+分发+dispatchTouchEvent"); return false; }
看log:
由于ViewGroupTwo的dispatchTouchEvent方法返回值为false,这跟MyTextView的dispatchTouchEvent返回值为false时候原因是一样的。这个时候,表示我自己不做事件分发,自己的onInterceptTouchEvent方法不会被调用(由于MyTextView没有onInterceptTouchEvent,它是onTouchEvent方法不会被调用),做回传机制。回传给父控件的处理方法,调用父控件的 onTouchEvent方法。父控件默认值,一直传递到Activity,最终事件消失。
说明:这里点击绿色区域和蓝色区域效果是一样的,确切的说没必要点击蓝色区域测试。因为,dispatchTouchEvent并不会直接影响子控件的调用情况。他只决定自己的onInterceptTouchEvent方法是否会被调用。
处理6:点击蓝色区域,ViewGroupTwo的onInterceptTouchEvent方法做如下修改:
@Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent"); return true; }
看log:
由于ViewGroupTwo的onInterceptTouchEvent方法返回值为true,表示我要拦截事件,而且事件交给我自己的处理事件,看处理不处理,如果不处理,则回传机制,回传事件给父控件的处理方法,看是否处理事件。
说明:这里点击绿色区域和蓝色区域效果是一样的,虽然ViewGroupTwo的onInterceptTouchEvent方法可能与孩子控件的dispatchTouchEvent方法有影响,但是由于自己的onInterceptTouchEvent的返回值为true,表示拦截事件,那么直接调用自己的onTouchEvent方法,而不会去调用孩子的dispatchTouchEvent方法。因而,点击蓝色绿色位置其实效果是一样的。
处理7:点击蓝色区域,ViewGroupTwo的onInterceptTouchEvent方法做如下修改:
@Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent"); return false; }
可以看到,此时跟默认return super.onInterceptTouchEvent(event);效果是一摸一样的。
说明:这个时候点击绿色区域就不行了。因为点击绿色区域,看不到事件传递给孩子组件的过程。
处理8:点击蓝色区域,ViewGroupTwo的onTouchEvent方法做如下修改:
注意:这个方法想要执行,前提是在ViewGroupTwo的onInterceptTouchEvent方法返回true的时候,或者孩子组件都不做事件处理的时候。因为,自己的onInterceptTouchEvent为true拦截后调用自己的onTouchEvent方法是否做处理。孩子组件如果都是默认,为回传机制,最终肯定会回到自己的onTouchEvent处理事件。
@Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+处理+onTouchEvent"); return true; }
看log:
ViewGroupTwo的onTouchEvent方法返回了true,表示调用了我之后,我就做处理事件。由于ViewGroupTwo的onInterceptTouchEvent返回值为默认或者false,不会立即调用自己的onTouchEvent处理事件, 我们按下的时候,事件还是继续分发给孩子组件,孩子组件没处理事件,就会回传给自己的onTouchEvent,问自己是否处理?true,处理事件,事件在此消失。同样地,这里点击绿色区域也没法测试,原因同上。我们可以看一下UP事件,UP事件直观的告诉我们,事件是由自己处理的。
测试ViewGroupTwo的onTouchEvent方法返回了false的时候,跟默认的return super.onTouchEvent(event);效果是一样的。这个可以自行测试。
处理9:混合处理。点击蓝色区域,ViewGroupTwo的onTouchEvent方法和onInterceptTouchEvent方法做如下修改:
@Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+拦截+onInterceptTouchEvent"); return true; } @Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupTwo+处理+onTouchEvent"); return true; }
看log日志:
相信这是在意料之中的事情了吧?由于我自己的onInterceptTouchEvent方法返回值true,表示我要做拦截事件,那么孩子的任何方法肯定不会被调用了,不会得到事件。直接调用自己的处理方法onTouchEvent。问自己处理不处理?返回值为true,处理事件了,则事件终止,被自己消费掉。这个时候,点击绿色区域效果也是一样的,这是因为自己的onInterceptTouchEvent方法返回值true,孩子压根得不到点击事件。
第二次总结:
2,ViewGroup
dispatchTouchEvent
return true;//事件分发拦截、事件回传拦截。自己消费,不往下(子组件)(不分发)、不往上传递(不回传)
return false;//事件拦截、事件回传。自己不处理,回传给父组件onTouchEvent方法处理
return super.dis.....;//事件分发。问自己onInterceptTouchEvent是否拦截
问自己 >onInterceptTouchEvent
true: 肯定会调用自己onTouchEvent();看是否自己消费事件。但是可能会去孩子那里执行一下MotionEvent.ACTION_CANCEL:,给孩子打声招呼。
false 或 super: 两者效果相同,事件传递,默认往下传递,调用孩子组件的dispatchTouchEvent
onInterceptTouchEvent返回true时:问自己>onTouchEvent()
true: 自己消费(不回传)
false 或 super: 两者效果相同,继续回传,回传给父亲组件的onTouchEvent()
处理10:ViewGroupOne的dispatchTouchEvent方法做如下修改,点击任意区域:
@Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+分发+dispatchTouchEvent"); return true; }
运行程序,点击蓝色位置看log:
其实你会发现。不管点击屏幕哪个位置,都打印这样的log
这是因为,ViewGroupOne的dispatchTouchEvent返回true了,相当于在ViewGroupOne的分发事件地方就拦截了事件(称之为拦截,也因为没有回传,从UP日志里也可以看出没有回传)。事件消失。
处理11:ViewGroupOne的dispatchTouchEvent方法做如下修改,点击任意位置:
@Override public boolean dispatchTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+分发+dispatchTouchEvent"); return false; }
返回false,表示自己的dispatchTouchEvent不做处理,即不做分发事件。做回传,回传给activity的onTouchEvent处理,最后事件消失。这里同样点击任意位置都是这个log,因为dispatchTouchEvent只有返回默认的时候,才会分发事件。
注意:ViewGroupOne的dispatchTouchEvent方法默认返回super.dispatchTouchEvent(event);的时候是要传递给自己的onInterceptTouchEvent方法的,问这个方式是否做拦截。上边其实已经不知不觉介绍了这个情况。此时可以总结,只有在ViewGroupOne的dispatchTouchEvent方法默认返回super.dispatchTouchEvent(event);的时候才会去调用自己的onInterceptTouchEvent(MotionEvent event) 拦截事件。其他都属于拦截事件,返回true的话甚至连回传机制都消失。
处理12:ViewGroupOne的onInterceptTouchEvent方法做如下修改,点击红色区域:
@Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent"); return true; }
log情况
自己的onInterceptTouchEvent(MotionEvent event)拦截事件返回为true,表名自己要拦截,不向下传递。调用自己的onTouchEvent(MotionEvent event)问自己是否处理。由于自己默认不处理,又回传到activity事件消失。同样的,这里点击任何位置log日志都是如此。因为,我拦截事件,只允许事件调用自己的onTouchEvent方法问是否自己做处理,如果自己不作处理事件回传了,如果自己处理事件,事件就会消失。
处理13:ViewGroupOne的onInterceptTouchEvent(MotionEvent event)拦截事件返回false
@Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent"); return false; }
又是默认的机制。
处理14:ViewGroupOne的onTouchEvent做如下处理:
注意:这个方法想要执行,前提是在ViewGroupOne的onInterceptTouchEvent方法返回true的时候,或者孩子组件都不做事件处理的时候。因为,自己的onInterceptTouchEvent为true拦截后调用自己的onTouchEvent方法是否做处理。孩子组件如果都是默认,为回传机制,最终肯定会回到自己的onTouchEvent处理事件。
@Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+处理+onTouchEvent"); return true; }
看log:
返回true。当孩子组件都做默认处理,事件会回传到自己的onTouchEvent方法。 自己处理事件,也就没了回传机制。事件在此消失。
处理15:ViewGroupOne的onTouchEvent返回为false。不用打印也可以清楚,与默认效果是一样的。
处理16:混合处理。ViewGroupOne的onTouchEvent和onInterceptTouchEvent做如下处理,点击蓝色区域:
@Override public boolean onInterceptTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+拦截+onInterceptTouchEvent"); return true; } @Override public boolean onTouchEvent(MotionEvent event) { ActionUtiles.processEvent(event, "ViewGroupOne+处理+onTouchEvent"); return true; }
看log:
由于自己的onInterceptTouchEvent方法返回为true,表示我要处理事件,不再往下传递事件,调用自己的onTouchEvent方法,问自己是否处理,返回了true,说明自己处理事件。事件在此消费,事件消失。
对于ViewGroupTwo和ViewGroupOne总结部分是一摸一样的。就不再多赘述。最后,再用一张草图做一个收尾::
此草图虽然潦草,但是最能说明问题,基本包含了所有可能的情况。
对于拦截机制详细介绍就完毕了,但是除了上边这些情况外,还有许多的分支情况;但是大同小异,仔细分析一下,就能得出正确的结论。以后此专栏可能还会再次探讨类似问题,下一次讨论应该是直接通过源码来分析这个问题。