这是touch事件传递系列博客的第二篇,如果想了解touch和click的那些事,请浏览投产事件传递系列的第一篇。http://blog.csdn.net/bingospunky/article/details/43603397
理理思路,我发现touch传递这部分的内容很多,所以每篇博客介绍一个方面比较好。这篇博客主要介绍touch事件传递的现象,一个简单的demo,让大家可以看到touch一步一步传递的过程。下篇博客中在研究源码是怎么实现的。再后面的博客会试图改变这篇文章看到的touch的传递过程,比如viewpager里嵌套listview。
demo
代码A:
- public class LayoutView1 extends {
- private final String TAG = "qingtian";
- public LayoutView1(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "LayoutView1 Intercept DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "LayoutView1 Intercept MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "LayoutView1 Intercept UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "LayoutView1 Intercept CANCEL");
- break;
- }
- return false;
- }
- @SuppressLint("ClickableViewAccessibility")
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "LayoutView1 Touch DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "LayoutView1 Touch MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "LayoutView1 Touch UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "LayoutView1 Touch CANCEL");
- break;
- }
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.d(TAG, "LayoutView1 dispatch " );
- boolean r = super.dispatchTouchEvent(ev);
- Log.d(TAG, "LayoutView1 dispatch 结果 "+r);
- return r;
- }
- }
代码B:
- public class LayoutView2 extends LinearLayout {
- private final String TAG = "qingtian";
- public LayoutView2(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "LayoutView2 Intercept DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "LayoutView2 Intercept MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "LayoutView2 Intercept UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "LayoutView2 Intercept CANCEL");
- break;
- }
- return false;
- }
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "LayoutView2 Touch DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "LayoutView2 Touch MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "LayoutView2 Touch UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "LayoutView2 Touch CANCEL");
- break;
- }
- return false;
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.d(TAG, "LayoutView2 dispatch ");
- boolean r = super.dispatchTouchEvent(ev);
- Log.d(TAG, "LayoutView2 dispatch 结果 "+r);
- return r;
- }
- }
- public class MyTextView extends TextView {
- private final String TAG = "qingtian";
- public MyTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public boolean onTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- Log.d(TAG, "MyTextView Touch DOWN");
- break;
- case MotionEvent.ACTION_MOVE:
- Log.d(TAG, "MyTextView Touch MOVE");
- break;
- case MotionEvent.ACTION_UP:
- Log.d(TAG, "MyTextView Touch UP");
- break;
- case MotionEvent.ACTION_CANCEL:
- Log.d(TAG, "MyTextView Touch CANCEL");
- break;
- }
- return false;
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.d(TAG, "MyTextView dispatch " );
- boolean r = super.dispatchTouchEvent(ev);
- Log.d(TAG, "MyTextView dispatch 结果 "+r);
- return r;
- }
- // public void onClick(View v) {
- // Log.d(TAG, "onClick");
- // }
- //
- // public boolean onLongClick(View v) {
- // Log.d(TAG, "onLongClick");
- // return false;
- // }
- }
- <?xml version="1.0" encoding="utf-8"?>
- <com.example.testontouch.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <com.example.testontouch.LayoutView2
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:gravity="center"
- android:orientation="vertical" >
- <com.example.testontouch.MyTextView
- android:layout_width="200dp"
- android:layout_height="150dp"
- android:background="#FFFFFF"
- android:text="Hello World"
- android:textColor="#0000FF"
- android:textSize="40sp"
- android:textStyle="bold" />
- </com.example.testontouch.LayoutView2>
- </com.example.testontouch.LayoutView1>
你可以改变这三个方法的返回值,观察touch都会传递到哪里。
PS:网上有很多类似的demo,基本没有改变dispatchTouchEvent的返回值的(虽然实际开发中基本不会改变这个返回值),强烈建议修改这个方法的返回值,尤其对于:如果你了解touch传递的U形,改变这个值会改变U形,不是改变形,而是使U形变长或变短。
总结
Android官方文档上
onInterceptTouchEvent()与onTouchEvent()的机制:
1.down事件首先会传递到onInterceptTouchEvent()方法2.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理
3.如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
4.如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理
5.如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
我的理解
1.touch事件传递与dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent这三个方法相关,可以看到这三个方法都返回boolean类型的值,如果我们能了解这三个方法的返回值的含义,就在自定义组件的时候通过控制返回的值影响touch事件的传递,这是理解的第一个层次。如果能从源码里真正理解来这三个返回值是怎么被使用的,那么我们在自定义view的时候可以重写使用返回值的部分,那么我们就可以改变touch传递的U形,更深的理解touch,比如我一直想弄明白的一个问题:viewpager里嵌套listview,怎么实现区分viewpage响应滑动,还是listview响应滑动,这是我理解的更高的一个层次。当然第二层我还没有达到,写完这个系列的博客后应该可以达到。
2.这篇博客里只有viewgroup和view这两种组件,viewgroup又会存在嵌套的关系,所以你在理解事件传递的时候一定要用点递归的概念。
3.当一个touch事件到达一个viewgroup时,先到达dispatchTouchEvent这个方法,然后到onInterceptTouchEvent方法:
a、如果onInterceptTouchEvent返回true,那么执行该viewgroup的onTouchEvent。由该viewgroup处理touch,不再传递到child组件。viewgroup的onInterceptTouchEvent的返回值根据该viewgroup的onTouchEvent的返回值确定。
b、如果onInterceptTouchEvent返回false,那么寻找对应位置上的child:
b1、如果child存在,执行该child的dispatchTouchEvent:
b1一、如果child的dispatchTouchEvent返回true,表示child消化了该事件,viewgroup不做处理,viewgroup的onInterceptTouchEvent的返回值为true。
b1二、如果child的dispatchTouchEvent返回false,那么再执行viewgroup的onTouchEvent,viewgroup的onInterceptTouchEvent的返回值根据该viewgroup的onTouchEvent的返回值确定。
b2、如果child不存在,那么执行该viewgroup的onTouchEvent,viewgroup的onInterceptTouchEvent的返回值根据该viewgroup的onTouchEvent的返回值确定。
4.当一个touch事件到达一个view时,先到达dispatchTouchEvent这个方法,然后执行view的onTouchEvent方法。
5.这里忽略掉来view的mOnTouchListener,因为这个地方不会影响到dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent,我们可以理解为mOnTouchListener和onTouchEvent是一个方法就行,只是再执行的时候他们二选其一。
6.我们把down->move........move->up成为一个完整的touch事件。对于一个完整的touch事件,touch在被传递的时候看起来是有记录它的传递路径的功能。对于viewgroup和view,我们不去重写它的方法,touch记录传递路径的功能有以下规律:
6a、如果down事件没有被任何一个view消化而被抛到最外面,那么不会有move和up事件。
6b、down事件被哪个view(包含viewgroup,因为viewgroup继承自view)消化,以后的move和up事件都会被这个view消化。
6c、如果down事件被消化了,后续的move和up会被传递到同一个方法里,但是传递的路径可能会不一样,可能会少经历一些方法。
demo下载路径:http://download.csdn.net/detail/u011647962/8440275
写在最后
博客都写来20多篇了,一个回复都木有,很受伤啊,各位道友看到了给个回复啊,作为对一个菜鸟的鼓励。顺便说声:大家,过年好。
- 1楼best777前天 08:42
- 支持哈。
- Re: u011647962前天 09:52
- 回复best777n谢谢喽。