Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。方法与控件的对应关系如下表所示:
Touch事件相关方法 | 方法功能 | ViewGroup | View | Activity |
public boolean dispatchTouchEvent(MotionEvent ev) | 事件分发 | Yes | Yes | Yes |
public boolean onInterceptTouchEvent(MotionEvent ev) | 事件拦截 | Yes | No | No |
public boolean onTouchEvent(MotionEvent ev) | 事件响应 | Yes | Yes | Yes |
一、Touch 事件分析
▐ 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
分两种情况:
1、Activity的dispatchTouchEvent 的事件分发:
如果return true 和return false,事件会分发给当前 Activity 并由 dispatchTouchEvent 方法进行消费,同时事件会停止传递;
如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给Activity所包含根元素(父View)的dispatchTouchEvent方法来开始这个事件的分发;
2、View的dispatchTouchEvent的事件分发:
如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止传递;
如果 return false,事件分发分为两种情况:
如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。
如果返回系统默认的 super.dispatchTouchEvent(ev),分两种情况:
如果View中还有子View,则事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。
如果View中没有子View或者该View本身继承了View(即是最小单位View),则事件会自动分发到当前 View 的 onTouchEvent 进行消费。
▐ 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在当前 View(父View) 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件和返回false时相同。
▐ 事件响应:public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true的情况下 onTouchEvent 会被调用。
onTouchEvent 的事件响应逻辑如下:
如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
如果返回了 true 则会接收并消费该事件。
如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
到这里,与 Touch 事件相关的三个方法就分析完毕了。下面的内容会通过各种不同的的测试案例来验证上文中三个方法对事件的处理逻辑。
二、Touch 案例介绍
同样在开始进行案例分析之前,我需要说明测试案例的结构,因为所有的测试都是针对这一个案例来进行的,测试中只是通过修改每个控件中与 Touch 事件相关的三个方法的返回值来体现不同的情况。先来看张图:
上面的图为测试案例的布局文件 UI 显示效果,布局文件代码如下:
<?xml version=
"1.0"
encoding=
"utf-8"
?>
<cn.sunzn.tevent.TouchEventFather xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:background=
"#468AD7"
android:gravity=
"center"
android:orientation=
"vertical"
>
<cn.sunzn.tevent.TouchEventChilds
android:id=
"@+id/childs"
android:layout_width=
"200dp"
android:layout_height=
"200dp"
android:layout_gravity=
"center"
android:background=
"#E1110D"
android:text=
"@string/hello"
/>
</cn.sunzn.tevent.TouchEventFather>
import
android.content.Context;
import
android.util.AttributeSet;
import
android.util.Log;
import
android.view.MotionEvent;
import
android.widget.LinearLayout;
public
class
TouchEventFather
extends
LinearLayout {
public
TouchEventFather(Context context) {
super
(context);
}
public
TouchEventFather(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
boolean
dispatchTouchEvent(MotionEvent ev) {
Log.e(
"sunzn"
,
"TouchEventFather | dispatchTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.dispatchTouchEvent(ev);
}
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
Log.i(
"sunzn"
,
"TouchEventFather | onInterceptTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.onInterceptTouchEvent(ev);
}
public
boolean
onTouchEvent(MotionEvent ev) {
Log.d(
"sunzn"
,
"TouchEventFather | onTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.onTouchEvent(ev);
}
}
红色背景为一个自定义控件 TouchEventChilds,该控件为内层 View,为 TouchEventFather 的子 View,同样继承自 LinearLayout,实现代码如下:
import
android.content.Context;
import
android.util.AttributeSet;
import
android.util.Log;
import
android.view.MotionEvent;
import
android.widget.LinearLayout;
public
class
TouchEventChilds
extends
LinearLayout {
public
TouchEventChilds(Context context) {
super
(context);
}
public
TouchEventChilds(Context context, AttributeSet attrs) {
super
(context, attrs);
}
public
boolean
dispatchTouchEvent(MotionEvent ev) {
Log.e(
"sunzn"
,
"TouchEventChilds | dispatchTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.dispatchTouchEvent(ev);
}
public
boolean
onInterceptTouchEvent(MotionEvent ev) {
Log.i(
"sunzn"
,
"TouchEventChilds | onInterceptTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.onInterceptTouchEvent(ev);
}
public
boolean
onTouchEvent(MotionEvent ev) {
Log.d(
"sunzn"
,
"TouchEventChilds | onTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.onTouchEvent(ev);
}
}
import
android.app.Activity;
import
android.os.Bundle;
import
android.util.Log;
import
android.view.MotionEvent;
public
class
TouchEventActivity
extends
Activity {
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public
boolean
dispatchTouchEvent(MotionEvent ev) {
Log.w(
"sunzn"
,
"TouchEventActivity | dispatchTouchEvent --> "
+ TouchEventUtil.getTouchAction(ev.getAction()));
return
super
.dispatchTouchEvent(ev);
}
public
boolean
onTouchEvent(MotionEvent event) {
Log.w(
"sunzn"
,
"TouchEventActivity | onTouchEvent --> "
+ TouchEventUtil.getTouchAction(event.getAction()));
return
super
.onTouchEvent(event);
}
}
import
android.view.MotionEvent;
public
class
TouchEventUtil {
public
static
String getTouchAction(
int
actionId) {
String actionName =
"Unknow:id="
+ actionId;
switch
(actionId) {
case
MotionEvent.ACTION_DOWN:
actionName =
"ACTION_DOWN"
;
break
;
case
MotionEvent.ACTION_MOVE:
actionName =
"ACTION_MOVE"
;
break
;
case
MotionEvent.ACTION_UP:
actionName =
"ACTION_UP"
;
break
;
case
MotionEvent.ACTION_CANCEL:
actionName =
"ACTION_CANCEL"
;
break
;
case
MotionEvent.ACTION_OUTSIDE:
actionName =
"ACTION_OUTSIDE"
;
break
;
}
return
actionName;
}
}