View的点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是分发过程。
分发过程主要由以下3个方法共同完成:
public boolean dispatchTouchEvent(MotionEvent event)
用来进行事件的分发。如果事件能够传递给当前的View,那么此方法一定会被调用,返回结果受当前的View的onTouchEvent和下级View的dispatchTouchEvent方法的
影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法方法的内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会再次被调用,返回结果表示是否拦截当前事件。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
三者关系用伪代码表示:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onIterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return comsume; }
通过上面的伪代码我们可以大致了解到点击事件的传递规则:
对于一个ViewGroup来说,点击事件产生后,首先传递给它,这是它会调用dispatchTouchEvent,如果这个ViewGroup的onIterceptTouchEvent方法返回true就表示它要拦截当前事件,
接着事件就由这个ViewGroup处理,即它的onTouchEvent方法就会被调用;如果这个ViewGroup的onIterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前时间就会传递
给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件最终被处理。
当一个view需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用。当onTouch返回false时,当前View的onTouch方法会被调用;如果返回
true,那么onTouchEvent方法将不会被调用。在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。
优先级: OnTouchListener > onTouchEvent > OnClickListener
下面用一个实例证明验证上面的结论:
本例整体布局为:
对于ViewGroup重写了如下三个方法:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupA dispatchTouchEvent:" + ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyViewGroupA onTouchEvent:" + event.getAction()); return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupA onInterceptTouchEvent:" + ev.getAction()); return super.onInterceptTouchEvent(ev); }
View的写法
public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyView_OnTouchEvent" + event.getAction()); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "MyView_dispatchTouchEvent" + event.getAction()); return super.dispatchTouchEvent(event); }
源码在后面会有提供!
点击View后的Log如下所示:
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onInterceptTouchEvent:0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyView_dispatchTouchEvent0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyView_OnTouchEvent0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:0 09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onTouchEvent:0
在没有处理的情况下,点击事件的流程与伪代码中的过程一致。
我们可以把整个过程看作:ViewGroupA(总经理) ---> ViewGroupB(部长)---> View(你自己)
事件传递是从总经理到部长在到你,首先总经理先执行dispatchTouchEvent,在执行onIterceptTouchEvent。如果不拦截然后到部长执行,同样是这两个方法,
部长也不拦截,然后事情交给你,由于你是最后一个所以无需拦截。调用dispatchTouchEvent,在调用OnTouchEvent,你发现你解决不了,返回了一个false
值(默认是false)。然后交给部长,部长调用OnTouchEvent方法,发现也处理不了,然后交给总经理处理,总经理调用onTouchEvent方法。
修改ViewGroupB中的代码如下:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupB onInterceptTouchEvent:" + ev.getAction()); return true; }
打印的Log为:
09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:0 09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:0 09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:0 09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onInterceptTouchEvent:0 09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:0 09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onTouchEvent:0
这个过程可以描述为:部长拦截了该事件,进行了处理,所以不需要你了,处理完后发现无法解决,然后提交给总经理。
再次修改ViewGroupB代码:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupB onInterceptTouchEvent:" + ev.getAction()); return true; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyViewGroupB onTouchEvent:" + event.getAction()); return true; }
打印Log为:
09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:0 09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:0 09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:0 09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onInterceptTouchEvent:0 09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:0 09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:1 09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:1 09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:1 09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:1
这个过程可以描述为:部长拦截事件,且完美处理了,该事件就此结束。点击事件是一个事件序列包括:
MotionEvent.ACTION_DOWN 和 MotionEvent.ACTION_UP两个事件。由于ViewGroupB已经拦截过了,之后的时间只要经理没有拦截,都交给部长处理。
源码:
package com.example.huangyichun.myviewtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; public class MyViewGroupA extends LinearLayout { private static final String TAG = "hyc"; public MyViewGroupA(Context context) { super(context); } public MyViewGroupA(Context context, AttributeSet attrs) { super(context, attrs); } public MyViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupA dispatchTouchEvent:" + ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyViewGroupA onTouchEvent:" + event.getAction()); return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupA onInterceptTouchEvent:" + ev.getAction()); return super.onInterceptTouchEvent(ev); } }
package com.example.huangyichun.myviewtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.LinearLayout; public class MyViewGroupB extends LinearLayout { private static final String TAG = "hyc"; public MyViewGroupB(Context context, AttributeSet attrs) { super(context, attrs); } public MyViewGroupB(Context context) { super(context); } public MyViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupB onInterceptTouchEvent:" + ev.getAction()); return true; } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "MyViewGroupB onTouchEvent:" + event.getAction()); return true; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.d(TAG, "MyViewGroupB dispatchTouchEvent:" + ev.getAction()); return super.dispatchTouchEvent(ev); } }
package com.example.huangyichun.myviewtest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class MyView extends View { private static final String TAG = "hyc"; 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 onTouchEvent(MotionEvent event) { Log.d(TAG, "MyView_OnTouchEvent" + event.getAction()); return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { Log.d(TAG, "MyView_dispatchTouchEvent" + event.getAction()); return super.dispatchTouchEvent(event); } }
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.huangyichun.myviewtest.MainActivity"> <com.example.huangyichun.myviewtest.MyViewGroupA android:layout_width="300dp" android:layout_height="300dp" android:background="@color/colorPrimaryDark"> <com.example.huangyichun.myviewtest.MyViewGroupB android:layout_width="200dp" android:layout_height="200dp" android:background="@android:color/holo_green_dark"> <com.example.huangyichun.myviewtest.MyView android:layout_width="100dp" android:layout_height="100dp" android:background="@android:color/holo_red_dark"/> </com.example.huangyichun.myviewtest.MyViewGroupB> </com.example.huangyichun.myviewtest.MyViewGroupA> </RelativeLayout>