浅谈Android 触摸事件分发机制
Android分发机制概述:
Android如此受欢迎,就在于其优秀的交互性,这其中,Android优秀的事件分发机制功不可没。那么,作为一个优秀的程序员,要想做一个具有良好交互性的应用,必须透彻理解Android的事件分发机制。
Touch触摸事件
在Android中Touch触摸事件主要包括点击(onClick)、长按(onLongClick)、拖拽(onDrag)、滑动(onScroll)等,点击又包括单击和双击,另外还包括单指操作和多指操作。其中Touch的第一个状态是 ACTION_DOWN,表示按下了屏幕后,touch将会有后续事件,比如移动、抬起等,一个Action_DOWN,一个ACTION_UP,许多个ACTION_MOVE,就构成了Android中众多的事件。所有的操作事件首先必须执行的是按下操作(ACTIONDOWN),之后所有的操作都是以此作为前提,当按下操作完成后,接下来可能是一段移动(ACTIONMOVE)然后抬起(ACTION_UP),或者是按下操作执行完成后没有移动就直接抬起。
Android中的Touch事件都是从ACTION_DOWN开始的:
1.单指操作:ACTION_DOWN---ACTION_MOVE----ACTION_UP
2.多指操作:ACTION_DOWN---ACTION_POINTER_DOWN---ACTION_MOVE--ACTION_POINTER_UP---ACTION_UP
触摸事件分发机制涉及的三个重要方法
1.publicbooleandispatchTouchEvent(MotionEventev)
2.publicbooleanonInterceptTouchEvent(MotionEventevent)
3.publicbooleanonTouchEvent(MotionEventevent)
上面的图为测试案例的布局文件 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>
蓝色背景为一个自定义控件
TouchEventFather,该控件为外层 View,继承自 LinearLayout,实现代码如下:
package
cn.sunzn.tevent;
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,实现代码如下:
package
cn.sunzn.tevent;
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);
}
}
接着实现 Activity 的代码,因为控件所有的事件都是通过 Activity 的 dispatchTouchEvent 进行分发的;除此之外还需要重写 Activity 的 onTouchEvent 方法,这是因为如果一个控件直接从 Activity 获取到事件,这个事件会首先被传递到控件的 dispatchTouchEvent
方法,如果这个方法 return false,事件会以冒泡方式返回给 Activity
的 onTouchEvent 进行消费。实现代码如下:
package cn.sunzn.tevent;
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);
}
}
最后再附上 TouchEventUtil 的代码,TouchEventUtil 中并没有做多少事情,只是将以上 2 个自定义控件中各个方法的 MotionEvent 集中到一个工具类中并将其对应的动作以 String 形式返回,这样处理更便于实时观察控件的事件。代码如下:
package cn.sunzn.tevent;
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;
}
}
Android触摸事件流程总结
1.一个事件序列是指从手指触摸屏幕开始,到手指离开屏幕结束,这个过程中产生的一系列事件。同一个事件序列是以ACTION_DOWN事件开始,中间含有数量不定的MOVE事件,最终以ACTION_UP事件结束。
2.事件传递的顺序是:Activity->Window->View,即事件总是先传递给Activity,然后在传递给Window,最后在传递给View,顶级View接收到事件后,就会按照事件分发机制去分发事件。
3.事件的传递过程是由外向内的,即事件总是由父元素分发给子元素。
4.正常情况下,一个事件序列只能被一个View拦截且消耗。一旦一个元素拦截了某次事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。
5.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件,那么同一事件序列的其他事情都不会再交给它来处理,并且事件将重新交给它的父容器去处理(调用父容器的onTouchEvent方法);如果它消耗ACTION_DOWN事件,但是不消耗其他类型事件,那么这个点击事件会消失,父容器的onTouchEvent方法不会被调用,当前view依然可以收到后续的事件,但是这些事件最后都会传递给Activity处理。
6.Android点击事件分发是到达顶级View后(一般是ViewGroup),会调用ViewGroup的dispatchTouchEvent方法,其中它的onInterceptTouchEvent方法如果返回true,则会对事件传递进行拦截,事件由ViewGroup处理;如果onInterceptTouchEvent方法返回false,则代表不对事件进行拦截,默认返回false。则此时子View中的dispatchTouchEvent方法将被调用,到此,事件已经由顶级View传递给了下一层的View,接下来的过程是一个递归循环的过程,和顶级View事件分发过程是一致的,直到完成整个事件分发。