想要弄明白android的touch事件分发响应机制需要先充分理解一下几个知识点:
- View和ViewGroup
- touch事件的构成
- ViewGroup如何对事件分发和拦截
- View和ViewGroup如何对事件进行响应
View和ViewGroup
- 先看一下官方文档对view类的部分介绍
View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The ViewGroup subclass is the base class for layouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.
从这里我们就可以看出View是android中所有界面布局组件的基类,而ViewGroup 是View的一个子类,ViewGroup是一个容器类,可以往里面添加子View。
- 再看一下android的界面是如何构成的
这里有一篇官方文档UI Overview
下面是该文章中的一部分内容:
All user interface elements in an Android app are built using View
and ViewGroup objects. A View is an object that draws something on the screen that the user can interact with. A ViewGroup is an object that holds other View (and ViewGroup) objects in order to define the layout of the interface.
Android provides a collection of both View and ViewGroup subclasses that offer you common input controls (such as buttons and text fields) and various layout models (such as a linear or relative layout).
上面的内容大概告诉大家android的界面的布局是一个树状的层级结构。
由顶级的ViewGroup其中包含一个或者多个View和ViewGroup来实现的。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a TextView" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a Button" />
</LinearLayout>
这时候我们是不是得思考一下了,View和ViewGroup在界面布局的时候作用是不一样的,那么在touch事件的分发及响应上是不是也不一样呢。
touch事件的构成
在Android中,事件主要包括点按、长按、拖拽、滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作。所有这些都构成了Android中的事件响应。总的来说,所有的事件都由如下三个ACTION作为基础:
- 按下(ACTION_DOWN)
- 移动(ACTION_MOVE)
- 抬起(ACTION_UP)
所有的touch事件首先必须执行的是按下操作(ACTION_DOWN),之后所有的操作都是以按下操作作为前提,当按下操作完成后,接下来可能是一段移动(ACTION_MOVE)然后抬起(ACTION_UP),或者是按下操作执行完成后没有移动就直接抬起。
事件分发拦截以及响应
下面就开始本文的重点内容了:事件的分发和拦截。
说到分发和拦截,就拿上面xml中那个布局来说,Button已经是界面层级中最末端的元素了,所以它已经无法再把touch事件往下传递了,所以事件的分发和拦截其实是对ViewGroup来说的(有子view才需要把事件往下传递给子view或者拦截掉事件自己处理)。
这里主要牵涉到2个方法:
-
public boolean dispatchTouchEvent (MotionEvent event)
从下面的方法描述中可以看出这个方法是用来分发事件的
-
public boolean onInterceptTouchEvent (MotionEvent event)
从下面的方法描述中可以看出这个方法是用来拦截事件的
需要注意的是onInterceptTouchEvent方法是ViewGroup的方法,View没有。
Touch事件的响应式通过下面的方法来实现的:
-
public boolean onTouchEvent (MotionEvent event)
返回true代表自己消费了这个事件
涉及到的3个方法都讲过了,那么下面来讲一下事件分发传递及响应的流程。
-
事件分发流程
从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View -
事件响应流程
从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity
上面是一个简单的描述,下面我们通过例子来了解详细的流程。
自定义一个ParentLayout 继承RelativeLayout;再自定义一个CustomImageView继承ImageView。
对ParentLayout来说实现它的dispatchTouchEvent ,onInterceptTouchEvent以及onTouchEvent方法,并在其中打印输出接收到的事件
对CustomImageView来说实现它的onTouchEvent方法,并在其中打印输出接收到的事件
界面效果图:
点击左侧汪星人的效果。
左侧的图片设置了onclick事件,所以他的touch事件是可以消费的。
16:04:53.172 D/MainActivity.dispatchTouchEvent(MainActivity.java:47)﹕ ACTION_DOWN
16:04:53.177 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:35)﹕ ACTION_DOWN
16:04:53.177 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:59)﹕ ACTION_DOWN
16:04:53.177 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:04:53.182 D/CustomImageView.onTouchEvent(CustomImageView.java:35)﹕ ACTION_DOWN
16:04:53.182 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return true
16:04:53.182 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return true
16:04:53.182 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:40)﹕ ACTION_MOVE
16:04:53.187 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:64)﹕ ACTION_MOVE
16:04:53.197 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:04:53.197 D/CustomImageView.onTouchEvent(CustomImageView.java:40)﹕ ACTION_MOVE
16:04:53.197 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return true
16:04:53.197 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return true
16:04:53.217 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:40)﹕ ACTION_MOVE
16:04:53.217 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:64)﹕ ACTION_MOVE
16:04:53.222 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:04:53.222 D/CustomImageView.onTouchEvent(CustomImageView.java:40)﹕ ACTION_MOVE
16:04:53.222 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return true
16:04:53.227 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return true
16:04:53.237 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:40)﹕ ACTION_MOVE
16:04:53.237 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:64)﹕ ACTION_MOVE
16:04:53.247 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:04:53.257 D/CustomImageView.onTouchEvent(CustomImageView.java:40)﹕ ACTION_MOVE
16:04:53.267 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return true
16:04:53.277 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return true
16:04:53.287 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:40)﹕ ACTION_MOVE
16:04:53.292 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:64)﹕ ACTION_MOVE
16:04:53.322 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:04:53.327 D/CustomImageView.onTouchEvent(CustomImageView.java:40)﹕ ACTION_MOVE
16:04:53.327 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return true
16:04:53.327 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return true
16:04:53.327 D/MainActivity.dispatchTouchEvent(MainActivity.java:51)﹕ ACTION_UP
16:04:53.332 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:43)﹕ ACTION_UP
16:04:53.332 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:67)﹕ ACTION_UP
16:04:53.332 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:04:53.337 D/CustomImageView.onTouchEvent(CustomImageView.java:43)﹕ ACTION_UP
16:04:53.337 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return true
16:04:53.337 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return true
16:04:53.342 I/MainActivity.clickImage(MainActivity.java:133)﹕ clickImage
点击左侧喵星人的效果。
右侧的喵星人没有设置事件监听,所以他没有对此次Touch事件消费,事件又向上传递回了ParentLayout的onTouchEvent。
16:12:24.882 D/MainActivity.dispatchTouchEvent(MainActivity.java:47)﹕ ACTION_DOWN
16:12:24.882 D/ParentLayout.dispatchTouchEvent(ParentLayout.java:35)﹕ ACTION_DOWN
16:12:24.882 D/ParentLayout.onInterceptTouchEvent(ParentLayout.java:59)﹕ ACTION_DOWN
16:12:24.887 E/ParentLayout.onInterceptTouchEvent(ParentLayout.java:73)﹕ return false
16:12:24.887 D/CustomImageView.onTouchEvent(CustomImageView.java:35)﹕ ACTION_DOWN
16:12:24.887 E/CustomImageView.onTouchEvent(CustomImageView.java:49)﹕ return false
16:12:24.892 D/ParentLayout.onTouchEvent(ParentLayout.java:83)﹕ ACTION_DOWN
16:12:24.892 E/ParentLayout.onTouchEvent(ParentLayout.java:97)﹕ return false
16:12:24.892 E/ParentLayout.dispatchTouchEvent(ParentLayout.java:49)﹕ return false
16:12:24.922 D/MainActivity.dispatchTouchEvent(MainActivity.java:51)﹕ ACTION_UP
TODO 此次只是分析了touch事件简单流程。后续有时间再去分析这三个方法如果返回不同的值会对事件的分发和响应有什么影响。