一、触摸响应逻辑概述
1.用户一个点击屏幕的动作会产生下面一系列触摸事件 :
1)触摸事件被驱动上报给android的wms(windowsmanagerservice);
2)wms将这些触摸事件分发给当前活动的Activity;
3)当前活动的Activity拿到事件,调用VIewRoot类的dispatchTouchEvent,给当前活动窗口的根view;
4)根view按照从下到上的顺序调用他的dispatchTouchEvent方法把事件到子view;
5)每个view有一个方法onInterceptTouchEvent,可以拦截触摸事件,返回true则不再继续向上传递触摸事件;
6)分发到最上层的view后,会调用他的OnTouchEvent方法,然后按照从上到下的顺序调用下一层view的OnTouchEvent方法;
7)如果某层的view的OnTouchEvent方法返回true,就中断传递触摸事件,不再调用下一层view的OnTouchEvent方法。
这样的触摸事件处理逻辑使得每一层的view都有机会响应触摸消息,非常的灵活。
二、示例说明
还是拿一个Activity举例说明,如下图所示:
附上xml文件:
- <com.test.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <com.test.LayoutView2
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent" android:gravity="center">
- <com.test.MyTextView
- android:layout_width="wrap_content" android:layout_height="wrap_content"
- />
- </com.test.LayoutView2>
- </com.test.LayoutView1>
这个activity里面从下到上的view有:LayoutView1、LayoutView2、MyTxtView
那么首先看一下默认的触屏事件的在onInterceptTouchEvent 、onTouchEvent两个函数之间的传递流程。如下图:
如果仅仅想让MyTextView来响应触屏事件,让MyTextView的OnTouchEvent返回true,那么事件流就变成如下图,可以看到layoutview1,layoutview2已经不能进入OnTouchEvent:
另外一种情况,就是外围容器想独自处理触屏事件,那么就应该在相应的onInterceptTouchEvent函数中返回true,表示要截获触屏事件,比如layoutview1作截获处理,处理流变成如下图:
以此类推,我们可以得到各种具体的情况,整个layout的view类层次中都有机会截获,而且能看出来外围的容器view具有优先截获权。
当然,通常外围的layoutview1,layoutview2,只是布局的容器不需要响应触屏的点击事件,仅仅Mytextview需要相应点击。但这只是一般情况,一些特殊的布局可能外围容器也要响应,甚至不让里面的mytextview去响应。更有特殊的情况是,动态更换响应对象。
三、多层次动态处理触摸事件
当我们去做一些相对来讲具有更复杂的触屏交互效果的应用时候,经常需要动态变更touch event的处理对象,比如launcher待机桌面和主菜单(见下图),从滑动屏幕开始到停止滑动过程当中,只有外围的容器view才可以处理touch event,否则就会误点击上面的应用图标或者widget.反之在静止不动的状态下则需要能够响应图标(子view)的touch事件。摘取framework中abslistview代码如下
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- if (touchMode == TOUCH_MODE_FLING) {
- return true; //fling状态,截获touch,因为在滑动状态,不让子view处理
- }
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- switch (mTouchMode) {
- case TOUCH_MODE_DOWN:
- final int pointerIndex = ev.findPointerIndex(mActivePointerId);
- final int y = (int) ev.getY(pointerIndex);
- if (startScrollIfNeeded(y - mMotionY)) {
- return true;//开始滑动状态,截获touch事件,不让子view处理
- }
- break;
- }
- break;
- }
- }
四、 运用
前几天我需要实现一个功能:点击屏幕任意区域挂断通话并退出窗口,直接实现方法dispatchTouchEvent即可:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
Log.d(TAG,"test-- monitor dispatchTouchEvent!");
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
SystemServer.hangUp(CallId);
break;
}
return super.dispatchTouchEvent(ev);
}