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. Spring错误之org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'bookService' is expected to be of type 'pw.fengya.tx.BookService' but was actually of type 'com.sun.proxy.$Proxy1

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cas ...

  2. Java中next()和nextLine()

    next()读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键.Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键 ...

  3. Python3 数字(Number)

    Python 解释器可以作为一个简单的计算器:您可以在解释器里输入一个表达式,它将输出表达式的值. 表达式的语法很直白: +, -, * 和/ 和在许多其它语言(如Pascal或C)里一样:括号可以用 ...

  4. 开源一个自己造的轮子:基于图的任务流引擎GraphScheduleEngine

    GraphScheduleEngine是什么: GraphScheduleEngine是一个基于DAG图的任务流引擎,不同语言编写.运行于不同机器上的模块.程序,均可以通过订阅GraphSchedul ...

  5. TV Metro界面(仿泰捷视频TV版)源码解析

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52822499 前言:上一篇介绍了 ...

  6. 微信小程序基本组件概述

    为了更好的理解微信小程序,本文90%文字描述来源于官网的介绍.官网原链接https://mp.weixin.qq.com/debug/wxadoc/dev/component/?t=20161222 ...

  7. 代理IP爬取,计算,发放自动化系统

    IoC Python端 MySQL端 PHP端 怎么使用 这学期有一门课叫<物联网与云计算>,于是我就做了一个大作业,实现的是对代理IP的爬取,计算推荐,发放给用户等任务的的自动化系统.由 ...

  8. Oracle 11g客户端及PLSQL Developer配置|Instant Client Setup-64位|OraClientLite11g_x86

    转载自:http://blog.csdn.net/xiaoyw71/article/details/45311589 Oracle 11g客户端 资源 下载资源,直接解压进行配置 Oracle官方资源 ...

  9. Swift函数柯里化(Currying)简谈

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 下面简单说说Swift语言中的函数柯里化.简单的说就是把接收多 ...

  10. 基于OpenCV 的美颜相机推送直播流

    程序流程: 1.图像采集 先从opencv(2.4.10版本)采集回来摄像头的图像,是一帧一帧的 每一帧图像是一个矩阵,opencv中的mat 数据结构. 2.人脸的美化 人脸美化,我们用的皮肤检测, ...