认识一下Android 事件分发机制

1、引子

由于Android是采用分层布局(可以想象成PS时的图层概念一样),这样才可以在有限大小的手机屏幕上完成一些复杂的操作。当手指点击屏幕开始,这些动作在各层之间如何传递?就引出了Android的事件分发机制。之所以称为事件,是由于在Android中将所有在屏幕的动作封装成3个事件

ACTION_DOWN:手指按下

ACTION_MOVE:手指在屏幕滑动

ACTION_UP:手指从屏幕抬起

每次都是从ACTION_DOWN开始,到ACTION_UP结束,中间伴随着ACTION_MOVE;有了事件就对应着事件处理3个重要的方法

【1】事件分发dispatchTouchEvent(MotionEvent ev) 

【2】事件拦截  onInterceptTouchEvent(MotionEvent ev)

【3】事件响应 onTouchEvent(MotionEvent ev)

初始情况返回值都是False,表示自身未处理需要继续流程

其中ViewGroup以及继承ViewGroup的容器控件如布局文件RelativeLayout等,需回调这三个方法;通常View则只回调【1】【3】,对应一些显示控件如button等。

三者之间的关系如下(以ViewGroup为例):

这里一定要注意的是调用自身的dispatchTouchEvent后若继续让后面view处理,则再调用后面view的dispatchTouchEvent,直到哪个控件开始处理则会调用对应的onTouchEvent方法,这在源码中可以看出。

2、生活中的实例

先来感性认识一下:

Activity——部门boss

MyViewGroup(重写的RelativeLayout——ViewGroup容器控件)——项目组boss

MyButton(重写的Button控件——view控件)——屌丝程序员

正常情况下

事件传递的顺序:(Activity—Window(ViewGroup布局)——View)

Activity(部门boss)——>MyViewGroup(项目组boss)——>重写的MyButton(屌丝程序员)

事件处理的顺序:

重写的MyButton(屌丝程序员)——>MyViewGroup(项目组boss)——>Activity(部门boss)

事件传递的返回值布尔类型   True 表示拦截在拦截处消化无需上传(下发)   False不拦截继续流程

事件处理的返回值布尔类型   True 已处理无需上级审核    False未处理需要上级审核

情形一:正常流程

因此对于上面的例子正常情况下层层处理流程如下

三大函数默认都是返回false,所以可以不拦截,直接分发上传,走完整个流程。可以形象解释为:部门boss把任务指派给项目组boss,项目组boss再指派给程序员,然后程序员搞定了汇报给他的上级项目组boss,然后由项目组汇报给部门boss

情形2:遇到好心项目组boss

要是某一步处理返回true表示本层已经处理完无需下发,比如MyViewGroup的 onInterceptTouchEvent 返回true,则流程如下

解释为:好比部门boss把任务下发给项目组boss,他觉得比较简单,就自己搞定了,然后告诉了部门boss

3、示例代码

重写其3个事件处理函数,函数方法内内打上LOG便于观察

关键代码如下,Mybutton重写【1】【3】同样是在函数内部打上Log

MyViewGroup继承RelativeLayout,(Mybutton继承Button控件)

[java] view plain copy

  1. <span style="font-size:18px;">/** 
  2.  * Created by ELVIS on 2015/10/18. 
  3.  * 
  4.  */  
  5. public class MyViewGroup extends RelativeLayout{  
  6.     private final  static String TAG = "MyViewGroup";  
  7.     public MyViewGroup(Context context, AttributeSet attrs) {  
  8.         super(context, attrs);  
  9.     }  
  10.     @Override  
  11.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  12.         switch (ev.getAction()) {  
  13.             case MotionEvent.ACTION_DOWN:  
  14.                 Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_DOWN");  
  15.                 break;  
  16.             case MotionEvent.ACTION_MOVE:  
  17.                 Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_MOVE");  
  18.                 break;  
  19.             case MotionEvent.ACTION_UP:  
  20.                 Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_UP");  
  21.                 break;  
  22.         }  
  23.  //       return super.dispatchTouchEvent(ev);  
  24.         return true;  
  25.     }  
  26.   
  27.     @Override  
  28.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  29.         switch (ev.getAction()) {  
  30.             case MotionEvent.ACTION_DOWN:  
  31.                 Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_DOWN");  
  32.                 break;  
  33.             case MotionEvent.ACTION_MOVE:  
  34.                 Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_MOVE");  
  35.                 break;  
  36.             case MotionEvent.ACTION_UP:  
  37.                 Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_UP");  
  38.                 break;  
  39.         }  
  40.         return super.onInterceptTouchEvent(ev);  
  41.     }  
  42.   
  43.     @Override  
  44.     public boolean onTouchEvent(MotionEvent ev) {  
  45.         switch (ev.getAction()) {  
  46.             case MotionEvent.ACTION_DOWN:  
  47.                 Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_DOWN");  
  48.                 break;  
  49.             case MotionEvent.ACTION_MOVE:  
  50.                 Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_MOVE");  
  51.                 break;  
  52.             case MotionEvent.ACTION_UP:  
  53.                 Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_UP");  
  54.                 break;  
  55.         }  
  56.         return super.onTouchEvent(ev);  
  57.     }  
  58.   
  59. }</span>  

MyBotton.Java

[java] view plain copy

  1. /** 
  2.  * Created by ELVIS on 2015/10/18. 
  3.  * 自定义测试BUtton 
  4.  */  
  5. public class MyButton extends Button {  
  6.     public static  final String TAG = "MyButton";  
  7.   
  8.   
  9.     public MyButton(Context context, AttributeSet attrs) {  
  10.         super(context, attrs);  
  11.     }  
  12.   
  13.     public MyButton(Context context) {  
  14.         super(context);  
  15.     }  
  16.   
  17.     @Override  
  18.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  19.         switch (ev.getAction()) {  
  20.             case MotionEvent.ACTION_DOWN:  
  21.                 Log.i(TAG, "MyButton dispatchTouchEvent--ACTION_DOWN");  
  22.                 break;  
  23.             case MotionEvent.ACTION_MOVE:  
  24.                 Log.i(TAG,"MyButton dispatchTouchEvent--ACTION_MOVE");  
  25.                 break;  
  26.             case MotionEvent.ACTION_UP:  
  27.                 Log.i(TAG,"MyButton dispatchTouchEvent--ACTION_UP");  
  28.                 break;  
  29.         }  
  30.         return super.dispatchTouchEvent(ev);  
  31.        // return true;  
  32.     }  
  33.   
  34.     @Override  
  35.     public boolean onTouchEvent(MotionEvent ev) {  
  36.         switch (ev.getAction())  
  37.         {  
  38.             case MotionEvent.ACTION_DOWN:  
  39.                 Log.i(TAG,"MyButton onTouchEvent--ACTION_DOWN");  
  40.                 break;  
  41.             case MotionEvent.ACTION_MOVE:  
  42.                 Log.i(TAG,"MyButton onTouchEvent--ACTION_MOVE");  
  43.                 break;  
  44.             case MotionEvent.ACTION_UP:  
  45.                 Log.i(TAG,"MyButton onTouchEvent--ACTION_UP");  
  46.                 break;  
  47.         }  
  48.   
  49.         return super.onTouchEvent(ev);  
  50.         //return true;  
  51.     }  
  52.   
  53.   
  54.   
  55. }  

布局文件使用自己的控件,只是在自定义布局文件中放了一个BUtton,如下

[html] view plain copy

  1. <span style="font-size:18px;"><com.example.elvis.mypaintest.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/root"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin"  
  10.     tools:context=".MainActivity">  
  11.   
  12.     <com.example.elvis.mypaintest.MyButton  
  13.         android:id="@+id/myBt"  
  14.         android:text="button"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="wrap_content"  
  17.         android:layout_centerHorizontal="true"  
  18.         android:layout_marginTop="193dp"  
  19.         android:gravity="center_horizontal" />  
  20.   
  21. </com.example.elvis.mypaintest.MyViewGroup></span>  

此时运行,点击button按钮看到对应的LOG

情形一

这个属于最基本流程,对应情形一,执行流程是首先由activity捕获到ACTION_DWON事件,然后调用activity的dispatchTouchEvent,接着绕开activity的onTouchEvent直接将事件传递给MyViewGroup,由于它也未消耗该事件,因此也绕开调用onTouchEvent,直到MyButton的dispatchTouchEvent,在之后调用该控件的onTouchEvent,ACTION_UP事件也是一样的流程。

情形二:

将MyGroup的dispatchTouchEvent返回为 true,表示消耗了该事件,此时绕开了mybutton直接传给了MainActivity',ACTION_UP也是一样的流程

4、总结

【事件分发】:public boolean dispatchTouchEvent(MotionEvent ev)
  Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:

    * 如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
    *  如果 return false,事件分发分为两种情况:

    1. 如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
    2. 如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的  onTouchEvent 进行消费。

  如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。

【事件拦截】:public boolean onInterceptTouchEvent(MotionEvent ev)
  在外层 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 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:

    * 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
    * 如果返回了 true 则会接收并消费该事件。
    * 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。

转载:http://blog.csdn.net/xsf50717/article/details/49230179

时间: 2024-09-08 11:26:22

认识一下Android 事件分发机制的相关文章

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的 朋友对View的事件分发已经有比较深刻的理解了. 还未阅读过的朋友,请先参考 Android事件分发机 制完全解析,带你从源码的角度彻底理解(上) . 那么今天我们将继续上次未完成的话题,从源码的 角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区 别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子

一篇文章彻底搞懂Android事件分发机制

本文讲的是一篇文章彻底搞懂Android事件分发机制,在android开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,需要我们深入的了解android事件响应机制才能解决,事件响应机制已经是android开发者必不可少的知识.面试找工作的时候也是面试官经常会问的一个问题. 涉及到事件响应的常用方法构成 用户在手指与屏幕接触过程中通过MotionEvent对象产生一系列事件,它有四种状态: MotionEvent.ACTION_DOW

Android事件分发机制

说在开头,之前项目中使用到了ListView和Button的组合,由于两者都有click事件,也意识到应该是Android的事件分发机制的原因.面试时也特意去恶补过,不过也是一知半解,此次因在项目中遇到该问题特意去详细了解一下. 引言 点击事件的分发机制由于主要发生在界面中,需要先了解Android系统的UI架构,如下图所示. 我们都知道Android程序的UI是由Activity这个组件构成的,而实际中是使用setContentView这个方法设置一个自定义布局的,这里的ContentView

Android事件分发机制(下) View的事件处理

综述 在上篇文章Android中的事件分发机制(上)--ViewGroup的事件分发中,对ViewGroup的事件分发进行了详细的分析.在文章的最后ViewGroup的dispatchTouchEvent方法调用dispatchTransformedTouchEvent方法成功将事件传递给ViewGroup的子View.并交由子View进行处理.那么现在就来分析一下子View接收到事件以后是如何处理的. View的事件处理 对于这里描述的View,它是ViewGroup的父类,并不包含任何的子元

Android事件分发机制源码和实例解析

1.事件分发过程的理解 1.1. 概述 1.2. 主要方法 1.3. 核心行为 1.4. 特殊情况 2.案例分析 2.1. 案例1:均不消费 down 事件 2.2. 案例2:View0 消费 down 事件 2.3. 案例3:ViewGroup2nd 消费 down 事件 3.down 事件分发图 1. 事件分发过程的理解 1.1. 概述 事件主要有 down(MotionEvent.ACTION_DOWN),move(MotionEvent.ACTION_MOVE),up(MotionEve

Android View 事件分发机制详解_Android

Android开发,触控无处不在.对于一些 不咋看源码的同学来说,多少对这块都会有一些疑惑.View事件的分发机制,不仅在做业务需求中会碰到这些问题,在一些面试笔试题中也常有人问,可谓是老生常谈了.我以前也看过很多人写的这方面的文章,不是说的太啰嗦就是太模糊,还有一些在细节上写的也有争议,故再次重新整理一下这块内容,十分钟让你搞明白View事件的分发机制. 说白了这些触控的事件分发机制就是弄清楚三个方法,dispatchTouchEvent(),OnInterceptTouchEvent(),o

Android View事件分发机制详解_Android

准备了一阵子,一直想写一篇事件分发的文章总结一下,这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等,这些子View的外层还有ViewGroup,如RelativeLayout,LinearLayout.作为一个开发者,我们会思考,当点击一个按钮,Android系统是怎样确定我点的就是按钮而不是TextView的?然后还正确的响应了按钮的点击事件.内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分发机制: 1. 通过setC

30分钟搞清楚Android Touch事件分发机制_Android

Touch事件分发中只有两个主角:ViewGroup和View.Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理. View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析. ViewGroup的相关事件有三个:onInterceptTouchEvent.dispatchTouchEvent.onTouchEvent.View的相关事件只有两个:

Android事件传递机制

实验环境 OS X 10.9 Eclipse(ADT) Android源码版本:API Level 19(Android 4.4) Android事件构成 在Android中,事件主要包括点按.长按.拖拽.滑动等,点按又包括单击和 双击,另外还包括单指操作和多指操作.所有这些都构成了Android中的事件响应 .总的来说,所有的事件都由如下三个部分作为基础: 按下(ACTION_DOWN) 移动(ACTION_MOVE) 抬起(ACTION_UP) 所有的操作事件首先必须执行的是按下操作(ACT