View的点击事件的分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而这个过程就是分发过程。

分发过程主要由以下3个方法共同完成:

  public boolean dispatchTouchEvent(MotionEvent event)

  用来进行事件的分发。如果事件能够传递给当前的View,那么此方法一定会被调用,返回结果受当前的View的onTouchEvent和下级View的dispatchTouchEvent方法的

影响,表示是否消耗当前事件。

  public boolean onInterceptTouchEvent(MotionEvent event)

  在上述方法方法的内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会再次被调用,返回结果表示是否拦截当前事件。

  public boolean onTouchEvent(MotionEvent event)

  在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

  三者关系用伪代码表示:

   public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if (onIterceptTouchEvent(ev)) {
             consume = onTouchEvent(ev);   
        } else {
             consume = child.dispatchTouchEvent(ev);
        }
        return comsume;
    }         

  通过上面的伪代码我们可以大致了解到点击事件的传递规则:

    对于一个ViewGroup来说,点击事件产生后,首先传递给它,这是它会调用dispatchTouchEvent,如果这个ViewGroup的onIterceptTouchEvent方法返回true就表示它要拦截当前事件,

  接着事件就由这个ViewGroup处理,即它的onTouchEvent方法就会被调用;如果这个ViewGroup的onIterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前时间就会传递

  给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件最终被处理。

    当一个view需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被调用。当onTouch返回false时,当前View的onTouch方法会被调用;如果返回

  true,那么onTouchEvent方法将不会被调用。在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。

   优先级: OnTouchListener > onTouchEvent > OnClickListener

  下面用一个实例证明验证上面的结论:

  本例整体布局为:

      

  对于ViewGroup重写了如下三个方法:

  

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupA dispatchTouchEvent:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyViewGroupA onTouchEvent:" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupA onInterceptTouchEvent:" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

View的写法

  public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyView_OnTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyView_dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

源码在后面会有提供!

点击View后的Log如下所示:

09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onInterceptTouchEvent:0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyView_dispatchTouchEvent0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyView_OnTouchEvent0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:0
09-20 23:40:10.246 1522-1522/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onTouchEvent:0

在没有处理的情况下,点击事件的流程与伪代码中的过程一致。

我们可以把整个过程看作:ViewGroupA(总经理) ---> ViewGroupB(部长)--->  View(你自己)

事件传递是从总经理到部长在到你,首先总经理先执行dispatchTouchEvent,在执行onIterceptTouchEvent。如果不拦截然后到部长执行,同样是这两个方法,

部长也不拦截,然后事情交给你,由于你是最后一个所以无需拦截。调用dispatchTouchEvent,在调用OnTouchEvent,你发现你解决不了,返回了一个false

值(默认是false)。然后交给部长,部长调用OnTouchEvent方法,发现也处理不了,然后交给总经理处理,总经理调用onTouchEvent方法。

修改ViewGroupB中的代码如下:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupB onInterceptTouchEvent:" + ev.getAction());
        return true;
    }

打印的Log为:

09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:0
09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:0
09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:0
09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onInterceptTouchEvent:0
09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:0
09-21 07:39:23.222 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onTouchEvent:0

这个过程可以描述为:部长拦截了该事件,进行了处理,所以不需要你了,处理完后发现无法解决,然后提交给总经理。

再次修改ViewGroupB代码:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupB onInterceptTouchEvent:" + ev.getAction());
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyViewGroupB onTouchEvent:" + event.getAction());
        return true;
    }

打印Log为:

09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:0
09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:0
09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:0
09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onInterceptTouchEvent:0
09-21 07:45:35.362 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:0
09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA dispatchTouchEvent:1
09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupA onInterceptTouchEvent:1
09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB dispatchTouchEvent:1
09-21 07:45:35.462 31696-31696/com.example.huangyichun.myviewtest D/hyc: MyViewGroupB onTouchEvent:1

这个过程可以描述为:部长拦截事件,且完美处理了,该事件就此结束。点击事件是一个事件序列包括:

MotionEvent.ACTION_DOWN 和 MotionEvent.ACTION_UP两个事件。由于ViewGroupB已经拦截过了,之后的时间只要经理没有拦截,都交给部长处理。

 关于事件传递机制,这里给出一些结论,根据这些结论可以更好的理解整个传递机制,如下所示:

  (1)同一个事件序列式指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中产生的一系列事件,这个事件序列以down事件开始,

  中间含有数量不定的move事件,最终以up事件结束。

  (2)正常情况下,一个事件序列只能被一个view拦截且消耗。这一条的原因可以参考(3),因为一旦一个元素拦截了某次事件,那么同一个事件序列内的所有时间都会直接

  交给它处理,因此同一个事件序列中的事件不能分别由两个view同时处理,但是通过特殊手段可以做到,比如一个view将本该自己处理的事件通过onTouchEvent强行传递

  给其他view处理。

  (3)某个view一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能传递给它的话),并且它的onIterceptTouchEvent不会再被调用。这条也很好理解,

  就是说当一个view决定拦截一个事件后,那么系统会把同一个时间序列内的其他方法都直接交给它来处理,因此就不用在调用这个view的onIterceptTouchEvent去询问它

  是否要拦截了。

  (4)某个view一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将

  重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个view处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来

  处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事件交给这个程序员做了,二者是类似的道理。

  (5)如果view不消除除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终

  这些消失的点击事件会传递给Activity。

  (6)ViewGroup默认不拦截是任何事件。android源码中ViewGroup的onIterceptTouchEvent方法默认返回false.

  (7)View没有onIterceptTouchEvent方法,一旦有点击事件传递给他,那么它的onTouchEvent方法就会被调用。

  (8)View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。view的longClickable属性默认都为false,clickable

  属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。

  (9)view的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true.

  (10)onClick会发生的前提是当前view是可点击的,而且它收到了down和up事件

  (11)事件传递过程是由外向内,即事件总是先传递给父元素,然后再由父元素分发给子view,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的时间分发

  过程,但是ACTION_DOWN事件除外。

源码:

package com.example.huangyichun.myviewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class MyViewGroupA extends LinearLayout {
    private static final String TAG = "hyc";
    public MyViewGroupA(Context context) {
        super(context);
    }

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

    public MyViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupA dispatchTouchEvent:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyViewGroupA onTouchEvent:" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupA onInterceptTouchEvent:" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }
}
package com.example.huangyichun.myviewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class MyViewGroupB extends LinearLayout {
    private static final String TAG = "hyc";
    public MyViewGroupB(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    public MyViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupB onInterceptTouchEvent:" + ev.getAction());
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyViewGroupB onTouchEvent:" + event.getAction());
        return true;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(TAG, "MyViewGroupB dispatchTouchEvent:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }
}
package com.example.huangyichun.myviewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View {

    private static final String TAG = "hyc";

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

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

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyView_OnTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d(TAG, "MyView_dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }

}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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="com.example.huangyichun.myviewtest.MainActivity">

    <com.example.huangyichun.myviewtest.MyViewGroupA
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@color/colorPrimaryDark">
        <com.example.huangyichun.myviewtest.MyViewGroupB
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="@android:color/holo_green_dark">
            <com.example.huangyichun.myviewtest.MyView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:background="@android:color/holo_red_dark"/>
        </com.example.huangyichun.myviewtest.MyViewGroupB>
    </com.example.huangyichun.myviewtest.MyViewGroupA>

</RelativeLayout>

  

android开发艺术探索读书笔记之-------view的事件分发机制的更多相关文章

  1. Android开发艺术探索读书笔记——01 Activity的生命周期

    http://www.cnblogs.com/csonezp/p/5121142.html 新买了一本书,<Android开发艺术探索>.这本书算是一本进阶书籍,适合有一定安卓开发基础,做 ...

  2. Android开发艺术探索读书笔记——进程间通信

    1. 多进程使用场景 1) 应用某些模块由于特殊需求须要执行在单独进程中. 如消息推送,使消息推送进程与应用进程能单独存活,消息推送进程不会由于应用程序进程crash而受影响. 2) 为加大一个应用可 ...

  3. Android开发艺术探索(三)——View的事件体系

    一.View基础知识 主要介绍内容有:View的位置参数.MotionEvent和TouchSlope对象.VelocityTracker.GestureDetector和Scroller对象 1.什 ...

  4. Android开发艺术探索学习笔记(三)

    第三章  View的事件体系 3.1 View基础知识 3.1.1 什么是view View 是Android中所有控件的基类,是一种界面层的控件的一种抽象,它代表了一个控件. 3.1.2 View的 ...

  5. Android开发艺术探索学习笔记(六)

    第六章 Android的Drawable  Drawable的优点:使用简单,比自定义view的成本要低:非图片类型的Drawable占用空间小,有利于减小APK安装包的大小. 6.1Drawable ...

  6. Android开发艺术探索学习笔记(四)

    第四章 View的工作原理 4.1初识ViewRoot和DecorView ViewRoot是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成 ...

  7. Android开发艺术探索学习笔记(十一)

    第十一章  Android的线程和线程池 从用途上来说,线程分为子线程和主线程,主线程主要处理和界面相关的事情,而子线程往往用于执行耗时的操作.AsyncTask,IntentService,Hand ...

  8. Android开发艺术探索学习笔记(十)

    第十章  Android的消息机制 面试中经常会被问到的一个问题:handler是如何在子线程和主线程中进行消息的传递的,这个问题通过了解Android的消息机制可以得到一个准确的答案. Androi ...

  9. Android开发艺术探索学习笔记(一)

    第一章 Activity的生命周期和启动模式 1.1Activity的生命周期全面解析 1.1.1典型情况下的生命周期分析 (1)在两个Activity进行切换时,当前的Activity的onPaus ...

随机推荐

  1. Flex 数据绑定

    Flex 数据绑定 <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:f ...

  2. Linux系统监控命令

    1. uptime: 负载监控,w命令更好地显示当前登录用户数的信息 2.top: 相当于uptime,提供了负载平均值的快照 (1)第一行: up 48days: 系统运行时间 2 users: 当 ...

  3. 用虚拟 router 连通 subnet - 每天5分钟玩转 OpenStack(141)

    Neutron Routing 服务提供跨 subnet 互联互通的能力.例如前面我们搭建了实验环境: cirros-vm1      172.16.100.3        vlan100 cirr ...

  4. 数据流程redux学习(一)

    思考题: react+redux开发这么一个原型,要怎么开发? 整个redux流程的逻辑非常清晰,数据流是单向循环的,就像一个生产的流水线: store(存放状态) -> Container(显 ...

  5. js事件对象

    哎,事件也有对象呦.程序员一直在讲对象对象,那么,过年是不是该带个对象回家呢?好了,既然事件有对象,承认这个事实吧!哈哈,那么,其就有属性,接下来,就放个 例子来讲讲,顺便把常用的属性也添加进去.. ...

  6. (三)Lua脚本语言入门

    又要找工作了,变的忧虑了,唯有学习才让内心变得踏实,今天玩了一下午的王者荣耀,正事都忘了...... 如果认为所谓的毅力是每分每秒的"艰苦忍耐"式的奋斗,那这是一种很不足的心理状态 ...

  7. 新生命组件XAgent使用心得

    1.简单介绍 XAgent为大石头带领下的新生命团队自己开发的一个.Net下的常用的Windows服务管理组件利器,通过在控制台中简单的输入1,2,3,4,5等数字可以实现一步安装.卸载Windows ...

  8. Professional C# 6 and .NET Core 1.0 - 38 Entity Framework Core

    本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处:Professional C# 6 and .NET Core 1.0 - 38 Entity Framework ...

  9. 爬虫之爬取网贷之家在档P2P平台基本数据并存入数据库

    python 版本 :3.5.2 Jupyter Notebook 使用库: reuqests (For human) json (用来加载JSON数据) datetime (用来记录抓取所花时间,也 ...

  10. 【easyui】之treegrid的分页

    easyui官网给的treegrid的分页是相当的复杂,我们来简化一下! 首先treegrid 分页和 datagrid一样需要设置一系列参数! 如下: depTreeGrid=$("#de ...