Android 编程下 Touch 事件的分发和消费机制

Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。方法与控件的对应关系如下表所示:

Touch事件相关方法方法功能ViewGroupViewActivity
public boolean dispatchTouchEvent(MotionEvent ev)事件分发YesYesYes
public boolean onInterceptTouchEvent(MotionEvent ev)事件拦截YesNoNo
public boolean onTouchEvent(MotionEvent ev)事件响应YesYesYes
从这张表中我们可以看到 ViewGroup 对 Touch 事件相关的三个方法均能响应,而 View和Activity 对 onInterceptTouchEvent(MotionEvent ev) 也就是事件拦截不进行响应。

一、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>


蓝色背景为一个自定义控件 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  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);
     }
   
}


接着实现 Activity 的代码,因为控件所有的事件都是通过 Activity 的 dispatchTouchEvent 进行分发的;除此之外还需要重写 Activity 的 onTouchEvent 方法,这是因为如果一个控件直接从 Activity 获取到事件,这个事件会首先被传递到控件的 dispatchTouchEvent 方法,如果这个方法 return false,事件会以冒泡方式返回给 Activity 的 onTouchEvent 进行消费。实现代码如下:

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);
     }
   
}


用到的Log工具的代码:

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;
     }
       
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值