android事件分发机制
首先我们要明确三个方法的作用
public boolean dispatchTouchEvent(MotionEvent ev)这个方法的作用是对事件进行分发
public boolean onInterceptTouchEvent(MotionEvent ev)对事件进行拦截
public boolean onTouchEvent(MotionEvent event) 处理事件
为什么要进行事件分发呢?
分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
这三个方法会同时出现在viewGroup里,但是在view里少了一个onInterceptTouchEvent方法,这个也不难理解,肯定是因为touch一个View才产生的事件,View是最底层
所以没必要进行拦截了肯定要进行处理了。
了解了这些,那么想事件究竟怎么分发呢?我觉得分发分为三个步骤, 开始---》寻找处理事件的view---》处理
让我们来看下事件究竟从哪里开始?
重写两个ViewGroup继承LinearLayout(这个随便继承),分别是GroupA,和GroupB,然后重写事件有关的三个方法,并进行log的打印。
package com.example.view; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; public class GroupA extends LinearLayout { public GroupA(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub setWillNotDraw(false); } //事件分发 @Override public boolean dispatchTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub Log.v("xys","GroupA-----dispatchTouchEvent"+ev.getAction()); return super.dispatchTouchEvent(ev); } //事件拦截 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub Log.v("xys","GroupA-----onInterceptTouchEvent"+ev.getAction()); //如果返回true说明进行拦截,从而不会往下传递而是自己进行处理 return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub Log.v("xys","GroupA-----onTouchEvent"+event.getAction()); return super.onTouchEvent(event); } }
package com.example.view; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; public class GroupB extends LinearLayout { public GroupB(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub setWillNotDraw(false); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub getParent().requestDisallowInterceptTouchEvent(true); Log.v("xys","GroupB-----dispatchTouchEvent"+ev.getAction()); //请求父控件不拦截事件 return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub Log.v("xys","GroupB-----onInterceptTouchEvent"+ev.getAction()); return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub Log.v("xys","GroupB-----onTouchEvent"+event.getAction()); return super.onTouchEvent(event); } }
最后一个是View,它没有拦截的方法
package com.example.view; import com.example.study.R; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.TextView; public class MyView extends TextView { public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub } public MyView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public MyView(Context context) { super(context); // TODO Auto-generated constructor stub } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub Log.v("xys","MyView-----onTouchEvent"+event.getAction()); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { // TODO Auto-generated method stub Log.v("xys","MyView-----dispatchTouchEvent"+event.getAction()); return super.dispatchTouchEvent(event); } }
然后在布局文件里进行布局
<com.example.view.GroupA 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:orientation="vertical" android:background="#345" > <com.example.view.GroupB android:layout_width="match_parent" android:layout_height="100dp" android:orientation="vertical" android:background="#f9f" > <com.example.view.MyView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="aaaaa" /> </com.example.view.GroupB> </com.example.view.GroupA>
在这里我们可以看见最外层的布局是A,其次是B,最后是我们的View
运行打印的log是这样的。
我点击了下View,可以明显看出布局是从A开始进行传递,
开始先执行分发方法,然后进行拦截,
然后传递给B布局的分发方法,B的拦截方法,
最后传递给View,View没有拦截方法,
只能进行处理。
然后开始进行回传。
这个是没有一个事件进行处理。只是让一个事件放肆的传递。
但事实上我们并不需要这样,我们既然点击了View肯定是要帮我们做一些事情,我们让view对事件进行处理,看下log的打印情况
@Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub Log.v("xys","MyView-----onTouchEvent"+event.getAction()); return true; }
我点击了View,可以很明显看见我们的View处理了事件,并且没有回传,那为什么会打印了这么多的Log呢?
因为我是点击了,点击由两个事件组成,down和up。所以会响应事件两次。
如果我们事件不在View,进行处理,而是在B里进行处理会有什么样的效果呢? 可以大胆猜想下,既然处理了就会终止事件传递,
那么肯定不会调用View的分发方法和处理方法了。我们打印log来验证。
点击了B,结果和我们想的一样,没有往下传递。
我们至此就明白了onTouchEvent()的返回值作用。
那我们现在设想一个场景,ViewPager可以左右滑动,然后有个左侧有个slideMenu,这时候ViewPager右滑动,就会发生滑动冲突,
怎么来解决呢?
首先我们来分析,我本来想让这个布局响应事件,而现在并没有响应,而是父布局消费了事件。
这时候我们要重写Viewpager的dispatchTouchEvent方法,并加上这样一句代码。
@Override public boolean dispatchTouchEvent(MotionEvent ev) { //请求父控件不拦截事件 getParent().requestDisallowInterceptTouchEvent(true); Log.v("xys","GroupB-----dispatchTouchEvent"+ev.getAction()); return super.dispatchTouchEvent(ev); }
这样就可以进行拦截了。
为此我们做个验证,在A进行事件处理。然后滑动B,会不会调用B的onTouch方法?
可以看出B并没有处理这个事件。这时候我们加上那句代码再试下。
可以看出我们进行处理之后就会调用自己的ontouch方法了。