先上这三个事件处理的说明文档:
第一个是:ViewGroup的dispathTouchEvent(MotionEvent ev) : 传递Touch事件至target view(可以是自己)。
第二个是:ViewGroup的onInterceptTouchEvent(MotionEvent ev):在ViewGroup中定义,用于拦截Touch事件的传递。
第三个是:View的onTouchEvent(MotionEvent event): Touch事件处理函数。
翻译一下:第二个ViewGroup的ViewGroup的onInterceptTouchEvent(MotionEvent ev)方法
Implement this method to intercept all touch screen motion events. This allows you to watch events as they are dispatched to your children, and take ownership of the current gesture at any point.
实现这个方法能够拦截所有的屏幕触摸事件,允许每个节点在事件分发给子view时查看事件,并且拥有当前手势的所有权
Using this function takes some care, as it has a fairly complicated interaction with View.onTouchEvent(MotionEvent)
, and using it requires implementing that method as well as this one in the correct way. Events will be received in the following order:
使用这个功能要非常小心,因为这个方法和View.onTouchEvent(MotionEvent)有相当复杂的影响,使用这个方法要求必须正确实现这两个方法。事件按照下面的流程被接收:
1.You will receive the down event here.
首先会在这里接收到down 事件
2.The down event will be handled either by a child of this view group, or given to your own onTouchEvent() method to handle; this means you should implement onTouchEvent() to return true, so you will continue to see the rest of the gesture (instead of looking for a parent view to handle it). Also, by returning true from onTouchEvent(), you will not receive any following events in onInterceptTouchEvent() and all touch processing must happen in onTouchEvent() like normal.
一个down事件可以被view group的子控件处理,或者view group自己处理。意思是,如果你实现了一个 onTouchEvent()并且返回了true,那么你将看到下面的手势(而不是继续寻找一个父控件来处理这个事件)。同时,如果你从onTouchEvent()中返回了一个true,onInterceptTouchEvent()中将不会再收到任何接下来的事件,所有的触屏处理都将在onTouchEvent()。
3.For as long as you return false from this function, each following event (up to and including the final up) will be delivered first here and then to the target's onTouchEvent().
如果在在这个方法中返回了false,接下来的所有事件(包含直到up)将被先传递到这然后在到目标的onTouchEvent().
4.If you return true from here, you will not receive any following events: the target view will receive the same event but with the action ACTION_CANCEL
, and all further events will be delivered to your onTouchEvent() method and no longer appear here.
如果在这返回了一个ture,你将不会再收到接下来的任何事件:目标view会接收到同一个事件,action却是ACTION_CANCEL,,而且所有后续的事件将会被传递到这个view的onTouchEvent()方法中处理,不会再出现在这儿。
Parameters 参数
ev The motion event being dispatched down the hierarchy. |
ev 向下传递层次的动作事件
Returns 返回值
Return true to steal motion events from the children and have them dispatched to this ViewGroup through onTouchEvent(). The current target will receive an ACTION_CANCEL event, and no further messages will be delivered here.
返回真值到动作事件从子控件,并且通过onTouchEvent()方法把它们派送到这个组视图。当前这个目标收到一个ACTION_CANCEL事件,而且没有更多的信息将会传递到这里。
再说一下事件传递的两种方式:
隧道方式:从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递。
冒泡方式:从最内层子元素依次往外传递直到根元素或在中间某一元素中由于某一条件停止传递。
下面用一个简单的实验说明上述复杂的规则。视图自底向上共3层,其中LayoutView1和LayoutView2就是LinearLayout, MyTextView就是TextView:对应的xml布局文件如下:
<com.touchstudy.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.touchstudy.LayoutView2
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<com.touchstudy.MyTextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:text="AB"
android:textColor="#0000FF"
android:textSize="40sp"
android:textStyle="bold" />
</com.touchstudy.LayoutView2>
</com.touchstudy.LayoutView1>
主Activity:MainActivity.java
package com.touchstudy;
import android.os.Bundle;
import android.app.Activity;
import android.view.Window;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
}
}
LayoutView1.java
package com.touchstudy;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class LayoutView1 extends LinearLayout {
private final String TAG = "LayoutView1";
public LayoutView1(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, TAG);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "onTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "dispatchTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
}
LayoutView2.java
package com.touchstudy;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class LayoutView2 extends LinearLayout {
private final String TAG = "LayoutView2";
public LayoutView2(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, TAG);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "onInterceptTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "onTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "dispatchTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
}
MyTextView.java
package com.touchstudy;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
public class MyTextView extends TextView {
private final String TAG = "MyTextView";
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, TAG);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent action:ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent action:ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent action:ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "onTouchEvent action:ACTION_CANCEL");
break;
}
return false;
}
}
前奏,这里一共有三个视图LayoutView1,LayoutView2,MyTextView。他们包含关系是LayoutView1>LayoutView2>MyTextView。也就是说LayoutView1、LayoutView2是ViewGroup,而MyTextView是View。(ViewGroup是View的直接子类)所以LayoutView1拥有dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()三个方法。LayoutView2拥有dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()三个方法。MyTextView只有onTouchEvent()一个方法。再根据隧道方式的事件传递,所以可以知道LayoutView1的dispatchTouchEvent()是测试的入口方法(也这个也就是第一响应我们的Touch事件的方法)。我们测试变量:1.是否执行父类的处理方法。2.返值true或false。
dispatchTouchEvent()方法:
1.dispatchTouchEvent()方法,这个返回值决定是否屏蔽后续事件。
false,屏蔽后续事件ACTION_MOVE,ACTION_UP。
true,不屏蔽后续事件ACTION_MOVE,ACTION_UP。
2.dispatchTouchEvent()方法,是否执行super.dispatchTouchEvent(ev)。
执行,调用onInterceptTouchEvent()方法和onTouchEvent()方法。
不执行,不调用onInterceptTouchEvent()方法和onTouchEvent()方法。
第二部分,onInterceptTouchEvent()方法分析:
1.onInterceptTouchEvent()方法,对于这个返回值
true,拦截,不向下一层次的dispatchTouchEvent()方法传递
false,不拦截,向下一层次的dispatchTouchEvent()方法传递
这两种情况与父类的方法的是否执行都无关。
第三部分,onTouchEvent()方法分析:
1.onTouchEvent()方法,对于这个返回值
false,屏蔽后续事件ACTION_MOVE,ACTION_UP。
true,不屏蔽后续事件ACTION_MOVE,ACTION_UP。
这两种情况与父类的方法的是否执行都无关。
Touch事件通过dispatchTouchEvent以隧道方式从上往下传递。如果在一个View中执行onTouchEvent时返回true的话,接下来的事件(ACTION_DOWN后的ACTION_UP,及可能在中间包含的若干个ACTION_MOVE,从ACTION_DOWN至ACTION_UP为一个连续事件,这是自己想的,不知道准确否)仍会传递到这个View的onTouchEvent,如果返回false的话,接下来的事件就不会再传递到这个View,而是执行其Parent View的onTouchEvent,每当一个View的onTouchEvent事件返回false,接下来的事件(如果还有的话)就会止步于这个View的Parent View,每次上升一个层次,类似于冒泡方式。
Touch事件传递过程中经过的元素都是一个View,但是事件处理的最外层的元素却不是View,除下跟Window有关的事件,当一个Touch事件发生的时候,会首先调用当前Activity的dispatchTouchEvent函数,然后才将事件传递至下层的View元素。当dispatchTouchEvent经过一个View往下传递的时候,如果这个View是一个ViewGroup,会调用其onInterceptTouchEvent函数,这个函数表示是否拦截Touch事件,如果这个函数返回true,表示这个ViewGroup拦截了事件的传递,Touch事件不会再往下传递给它的子View,而是由它处理,所以会调用它的onTouchEvent函数,如果在传递的过程中没有ViewGroup拦截事件,即经过的所有ViewGroup都返回false,那么事件最终会传递至最内层的View,一般是一个Widget,当然也可以是一ViewGroup(其内部不包含任何元素),如果最后事件传递到一View(非ViewGroup),那么会首先调用这个View的onTouchListener(如果设置了的话),如果onTouchListener返回false则继续调用View的onTouchEvent(默认返回true),如果最后事件传递到一ViewGroup(无子View),会调用它的onTouchEvent函数,默认返回false。
如果调用一个View的onTouchEvent函数时返回true的话,那么接下来的Touch Event事件(ACTION_DOWN后的ACTION_UP,及可能在中间包含的若干个ACTION_MOVE,从ACTION_DOWN至ACTION_UP为一个连续事件,这是自己想的,不知道准确否)仍会传递到这个View并调用它的onTouchEvent函数,在onTouchEvent函数中可以根据条件返回不同的值,如果某一次在此函数中返回了false那么接下来的Touch Event事件就不会再传递到这个View,而会在其Parent View终止,调用其Parent View的onTouchEvent。如果所有的View都的onTouchEvent函数都返回false,那么接下来的Touch Event事件会由Activity处理,即调用Activity的onTouchEvent函数。
当调用ViewGroup的dispatchTouchEvent函数时,会首先调用onInterceptTouchEvent函数判断有没有拦截事件,如果没有拦截(返回false),则会依次调用这个ViewGroup的所有子View的dispatchTouchEvent函数。比如一个FrameLayout上层叠了三个ViewGroup,那么在这个FrameLayout的dispatchTouchEvent中会依次调用这三个ViewGroup的dispatchTouchEvent函数,而在这三个ViewGroup的dispatchTouchEvent中也会依次调用他们的子View的dispatchTouchEvent函数,直到其中一个View的dispatchTouchEvent返回true,表示已经处理了这个Touch事件,不需要再调用这个View的Slibling Views。比如调用这三个层叠的ViewGroup的dispatchTouchEvent函数时,如果第一个ViewGroup的dispatchTouchEvent函数就返回了true(已经消耗掉了这个事件),那么其他两个ViewGroup的dispatchTouchEvent就不会再被调用。可以自定义一个ViewGroup的子类并重载他的dispatchTouchEvent函数,使其处理过Touch事件后仍返回false,那么就还会调用其他兄弟View的dispatchTouchEvent函数。