Android ViewGroup事件分发机制

来源:互联网 发布:海鹰数据库 编辑:程序博客网 时间:2024/05/01 02:21

1、例子

首先我们接着上一篇的代码,在代码中添加一个自定义的LinearLayout:

[java] view plain copy
  1. package com.example.zhy_event03;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.util.Log;  
  6. import android.view.MotionEvent;  
  7. import android.widget.LinearLayout;  
  8.   
  9. public class MyLinearLayout extends LinearLayout  
  10. {  
  11.     private static final String TAG = MyLinearLayout.class.getSimpleName();  
  12.   
  13.     public MyLinearLayout(Context context, AttributeSet attrs)  
  14.     {  
  15.         super(context, attrs);  
  16.     }  
  17.   
  18.     @Override  
  19.     public boolean dispatchTouchEvent(MotionEvent ev)  
  20.     {  
  21.         int action = ev.getAction();  
  22.         switch (action)  
  23.         {  
  24.         case MotionEvent.ACTION_DOWN:  
  25.             Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
  26.             break;  
  27.         case MotionEvent.ACTION_MOVE:  
  28.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
  29.             break;  
  30.         case MotionEvent.ACTION_UP:  
  31.             Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
  32.             break;  
  33.   
  34.         default:  
  35.             break;  
  36.         }  
  37.         return super.dispatchTouchEvent(ev);  
  38.     }  
  39.   
  40.     @Override  
  41.     public boolean onTouchEvent(MotionEvent event)  
  42.     {  
  43.   
  44.         int action = event.getAction();  
  45.   
  46.         switch (action)  
  47.         {  
  48.         case MotionEvent.ACTION_DOWN:  
  49.             Log.e(TAG, "onTouchEvent ACTION_DOWN");  
  50.             break;  
  51.         case MotionEvent.ACTION_MOVE:  
  52.             Log.e(TAG, "onTouchEvent ACTION_MOVE");  
  53.             break;  
  54.         case MotionEvent.ACTION_UP:  
  55.             Log.e(TAG, "onTouchEvent ACTION_UP");  
  56.             break;  
  57.   
  58.         default:  
  59.             break;  
  60.         }  
  61.   
  62.         return super.onTouchEvent(event);  
  63.     }  
  64.   
  65.     @Override  
  66.     public boolean onInterceptTouchEvent(MotionEvent ev)  
  67.     {  
  68.           
  69.         int action = ev.getAction();  
  70.         switch (action)  
  71.         {  
  72.         case MotionEvent.ACTION_DOWN:  
  73.             Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");  
  74.             break;  
  75.         case MotionEvent.ACTION_MOVE:  
  76.             Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");  
  77.             break;  
  78.         case MotionEvent.ACTION_UP:  
  79.             Log.e(TAG, "onInterceptTouchEvent ACTION_UP");  
  80.             break;  
  81.   
  82.         default:  
  83.             break;  
  84.         }  
  85.           
  86.         return super.onInterceptTouchEvent(ev);  
  87.     }  
  88.   
  89.     @Override  
  90.     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept)  
  91.     {  
  92.         Log.e(TAG, "requestDisallowInterceptTouchEvent ");  
  93.         super.requestDisallowInterceptTouchEvent(disallowIntercept);  
  94.     }  
  95.   
  96. }  

继承LinearLayout,然后复写了与事件分发机制有关的代码,添加上了日志的打印~

然后看我们的布局文件:

[html] view plain copy
  1. <com.example.zhy_event03.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context=".MainActivity" >  
  6.   
  7.     <com.example.zhy_event03.MyButton  
  8.         android:id="@+id/id_btn"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:text="click me" />  
  12.   
  13. </com.example.zhy_event03.MyLinearLayout>  

MyLinearLayout中包含一个MyButton,MyButton都上篇博客中已经出现过,这里就不再贴代码了,不清楚可以去查看~

然后MainActivity就是直接加载布局,没有任何代码~~~

直接运行我们的代码,然后点击我们的Button,依然是有意的MOVE一下,不然不会触发MOVE事件,看一下日志的输出:

[html] view plain copy
  1. 09-06 09:57:27.287: E/MyLinearLayout(959): dispatchTouchEvent ACTION_DOWN  
  2. 09-06 09:57:27.287: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_DOWN  
  3. 09-06 09:57:27.287: E/MyButton(959): dispatchTouchEvent ACTION_DOWN  
  4. 09-06 09:57:27.297: E/MyButton(959): onTouchEvent ACTION_DOWN  
  5. 09-06 09:57:27.297: E/MyButton(959): onTouchEvent ACTION_MOVE  
  6. 09-06 09:57:27.327: E/MyLinearLayout(959): dispatchTouchEvent ACTION_MOVE  
  7. 09-06 09:57:27.327: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_MOVE  
  8. 09-06 09:57:27.337: E/MyButton(959): dispatchTouchEvent ACTION_MOVE  
  9. 09-06 09:57:27.337: E/MyButton(959): onTouchEvent ACTION_MOVE  
  10. 09-06 09:57:27.457: E/MyLinearLayout(959): dispatchTouchEvent ACTION_UP  
  11. 09-06 09:57:27.457: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_UP  
  12. 09-06 09:57:27.457: E/MyButton(959): dispatchTouchEvent ACTION_UP  
  13. 09-06 09:57:27.457: E/MyButton(959): onTouchEvent ACTION_UP  

可以看到大体的事件流程为:

MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent 

2,方法的介绍和执行的顺序


dispatchTouchEcent:


只要事件传递到了当前View,那么dispatchTouchEcent方法就一定会被调用。返回结果表示是否消耗当前事件。


onInterceptTouchEvent:

在dispatchTouchEcent方法内部调用此方法,用来判断是否拦截某个事件。如果当前View拦截了某个事件,那么在这同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前事件。


onTouchEvent:


在dispatchTouchEcent方法内调用此方法,用来处理事件。返回结果表示是否处理当前事件,如果不处理,那么在同一个事件序列里面,当前View无法再收到后续的事件。

2,事件分发的顺序;

当点击事件传递到根ViewGroup里,会执行dispatchTouchEvent,

重点:在其内部会先调用onInterceptTouchEvent询问是否拦截事件,若拦截,则执行onTouchEvent方法处理这个事件;

若不拦截,则执行子元素的dispatchTouchEvent,onTouchEvent进入向下分发的传递,直到事件被处理。


3、关于拦截

1、如何拦截

上面的总结都是基于:如果没有拦截;那么如何拦截呢?

复写ViewGroup的onInterceptTouchEvent方法:

[java] view plain copy
  1. @Override  
  2.     public boolean onInterceptTouchEvent(MotionEvent ev)  
  3.     {  
  4.         int action = ev.getAction();  
  5.         switch (action)  
  6.         {  
  7.         case MotionEvent.ACTION_DOWN:  
  8.             //如果你觉得需要拦截  
  9.             return true ;   
  10.         case MotionEvent.ACTION_MOVE:  
  11.             //如果你觉得需要拦截  
  12.             return true ;   
  13.         case MotionEvent.ACTION_UP:  
  14.             //如果你觉得需要拦截  
  15.             return true ;   
  16.         }  
  17.           
  18.         return false;  
  19.     }  
默认是不拦截的,即返回false;如果你需要拦截,只要return true就行了,这要该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件;如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件。

原因很简单,当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget 置为null ; 

2、如何不被拦截

如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件;

此时子View希望依然能够响应MOVE和UP时该咋办呢?

Android给我们提供了一个方法:requestDisallowInterceptTouchEvent(boolean) 用于设置是否允许拦截,我们在子View的dispatchTouchEvent中直接这么写:

[java] view plain copy
  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent event)  
  3.     {  
  4.         getParent().requestDisallowInterceptTouchEvent(true);    
  5.         int action = event.getAction();  
  6.   
  7.         switch (action)  
  8.         {  
  9.         case MotionEvent.ACTION_DOWN:  
  10.             Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
  11.             break;  
  12.         case MotionEvent.ACTION_MOVE:  
  13.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
  14.             break;  
  15.         case MotionEvent.ACTION_UP:  
  16.             Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
  17.             break;  
  18.   
  19.         default:  
  20.             break;  
  21.         }  
  22.         return super.dispatchTouchEvent(event);  
  23.     }  
重点:getParent().requestDisallowInterceptTouchEvent(true);  这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。

4、案例说明


(1)内外滑动方向不一致


看代码看不出所以然,我们通过实例来看看滑动冲突是怎么样的。我们先模拟第一种场景,内外滑动方向不一致,我们先自定义一个父控件,让其可以左右滑动,类似于ViewPaper:




然后将里面换成三个ListView:




可以看到左右滑动失效了,说明确实冲突了。那么我们就来解决一下,首先我们要明白滑动规则是什么,这个例子中如果我们竖直滑动就让ListView消耗事件,水平滑动就让我们自定义的父容器滑动。


知道了这个我们只需要将其替换到之前伪代码里的拦截条件里即可。


先用外部拦截法



这里我们判断横向滑动的距离与竖直滑动距离的长短。若是竖直滑动的长,则判断为竖直滑动,那么就是ListView的滑动,就将intercepted置为false,让父容器不拦截,交由子元素ListView处理。若是横向,则intercepted置为true,交由父容器处理。


效果如下:




接下来看看内部拦截法


先自定义一个MyListView继承ListView,重写其dispatchTouchEvent方法:



再重写外部父容器的oninterceptTouchEvent方法:



效果和外部拦截法一样。

好了,这篇文章到此结束,希望各位看了能对事件分发机制有个大致的了解,并且遇到了滑动冲突的问题能够迎刃而解。


1 0