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控件)

<span style="font-size:18px;">/**
 * Created by ELVIS on 2015/10/18.
 *
 */
public class MyViewGroup extends RelativeLayout{
    private final  static String TAG = "MyViewGroup";
    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_UP");
                break;
        }
 //       return super.dispatchTouchEvent(ev);
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_UP");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_UP");
                break;
        }
        return super.onTouchEvent(ev);
    }

}</span>

MyBotton.java

/**
 * Created by ELVIS on 2015/10/18.
 * 自定义测试BUtton
 */
public class MyButton extends Button {
    public static  final String TAG = "MyButton";

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyButton(Context context) {
        super(context);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG, "MyButton dispatchTouchEvent--ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"MyButton dispatchTouchEvent--ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"MyButton dispatchTouchEvent--ACTION_UP");
                break;
        }
        return super.dispatchTouchEvent(ev);
       // return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                Log.i(TAG,"MyButton onTouchEvent--ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(TAG,"MyButton onTouchEvent--ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.i(TAG,"MyButton onTouchEvent--ACTION_UP");
                break;
        }

        return super.onTouchEvent(ev);
        //return true;
    }

}

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

<span style="font-size:18px;"><com.example.elvis.mypaintest.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <com.example.elvis.mypaintest.MyButton
        android:id="@+id/myBt"
        android:text="button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="193dp"
        android:gravity="center_horizontal" />

</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 时相同。

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

  1. Android事件分发机制(下)

    这篇文章继续讨论Android事件分发机制,首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子 ...

  2. Android事件分发机制(上)

    Android事件分发机制这个问题不止一个人问过我,每次我的回答都显得模拟两可,是因为自己一直对这个没有很好的理解,趁现在比较闲对这个做一点总结 举个例子: 你当前有一个非常简单的项目,只有一个Act ...

  3. android事件分发机制

    android事件分发机制,给控件设置ontouch监听事件,当ontouch返回true时,他就不会走onTouchEvent方法,要想走onTouchEvent方法只需要返回ontouch返回fa ...

  4. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  5. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

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

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  7. 【自己定义控件】android事件分发机制

    自己定义Viewgrou中我们或许会常常碰到这种情况,2个子控件的事件冲突导致滑动没实用了.滑动反应非常慢,点击没用了,要划非常多次才移动一点点等等.或许我们第一反应就是百度,google去搜索下答案 ...

  8. Android 事件分发机制具体解释

    很多其它内容请參照我的个人网站: http://stackvoid.com/ 网上非常多关于Android事件分发机制的解释,大多数描写叙述的都不够清晰,没有吧来龙去脉搞清晰,本文将带你从Touch事 ...

  9. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  10. 【朝花夕拾】Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/10998855.html]谢谢! 在自定义View中,经常需要处理Android事件分发的问题, ...

随机推荐

  1. ACM 畅通工程2

    Problem Description 省政府"畅通工程"的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).经过调查评估,得到 ...

  2. PHP 5 MySQLi 函数

    在 PHP 中使用 MySQLi 函数需要注意的是:你需要添加对 MySQLi 扩展的支持. PHP MySQLi 简介 PHP MySQLi = PHP MySQL Improved! MySQLi ...

  3. Bootstrap3 排版-改变大小写

    通过这几个类可以改变文本的大小写. <p class="text-lowercase">Lowercased text.</p> <p class=& ...

  4. move_uploaded_file的failed to open stream错误处理

    PHP的基本语法学习的差不多了,现在开始学习PHP的文件上传功能实现了.功能中使用到了move_uploaded_file方法,运行时报错: failed to open stream. 经过查资料, ...

  5. ObjectOutputStream 和 ObjectInputStream的使用

    一.看一下API文档 ObjectOutputStream : ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream.可以使用 ObjectInp ...

  6. Android样式(style)和主题(theme)

    样式和主题 样式是指为 View 或窗口指定外观和格式的属性集合.样式可以指定高度.填充.字体颜色.字号.背景色等许多属性. 样式是在与指定布局的 XML 不同的 XML 资源中进行定义. Andro ...

  7. 去除CSDN 博客页广告的历程

    第一招 方式1 方式2 第二招 第三招 素材准备 必备知识 代码部分 测试 总结 作为CSDN的忠实用户,我觉得它挺不错的.美中不足的是广告,虽然相比于微博啊,开源中国啊这些博客站点,它的广告已经算是 ...

  8. 【伯乐在线】FACEBOOK产品设计总监:设计APP时的14个必考题

    最近看到Facebook产品设计总监Julie Zhuo拷问产品的14个问题,非常靠谱.其中有3个问题堪称致命拷问: 1.使用前:这款产品是如何吸引到你的注意力的?2.开始使用:使用这款产品是轻松愉悦 ...

  9. Activity平移动画

    Activity平移动画 效果图 添加动画文件 在res下添加anim文件夹,在anim下添加几个动画文件,分别是进入和退出的动画时间和移动距离,属性很简单,一看就懂,不磨叽了. tran_next_ ...

  10. Vibrator控制手机震动

    Vibrator控制手机震动 效果图 源码 下载地址(Android Studio工程):http://download.csdn.net/detail/q4878802/9049755 添加权限 & ...