首先,我们需要 知道什么是ViewGroup,它和普通的View有什么区别?

  ViewGroup就是一组View的集合,它包含很多子View和ViewGroup,是Android 所有布局的父类或间接父类.

  但ViewGroup也是一个View,只不过比起View,它可以包含子View和定义布局参数的功能.

  现在,通过一个Demo演示Android中ViewGroup的事件分发机制.

  首先我们来自定义一个布局,命名为MyLayout,继承自LinearLayout,如下 所示:

 

  1.   publicclassMyLayoutextendsLinearLayout{
  2.       publicMyLayout(Context context,AttributeSet attrs){
  3.       super(context, attrs);
  4.       }
  5.   }
 

  然后,打开主布局文件activity_main.xml,在其中加入我们自定义的布局:

  1.  xmlns:tools="http://schemas.android.com/tools"
  2.   android:id="@+id/my_layout"
  3.   android:layout_width="match_parent"
  4.   android:layout_height="match_parent"
  5.   android:orientation="vertical" >
  6.   
  7.   android:id="@+id/button1"
  8.   android:layout_width="match_parent"
  9.   android:layout_height="wrap_content"
  10.   android:text="Button1" />
  11.   
  12.   android:id="@+id/button2"
  13.   android:layout_width="match_parent"
  14.   android:layout_height="wrap_content"
  15.   android:text="Button2" />
 

  可以看到,我们在MyLayout中添加了两个按钮,接着在MainActivity中为这两个按钮和MyLayout都注册了监听事件:

 

  1.  myLayout.setOnTouchListener(newOnTouchListener(){
  2.   @Override
  3.       publicboolean onTouch(View v,MotionEvent event){
  4.           Log.d("TAG","myLayout on touch");
  5.       returnfalse;
  6.       }
  7.   });
  8.   button1.setOnClickListener(newOnClickListener(){
  9.   @Override
  10.       publicvoid onClick(View v){
  11.           Log.d("TAG","You clicked button1");
  12.       }
  13.   });
  14.   button2.setOnClickListener(newOnClickListener(){
  15.   @Override
  16.       publicvoid onClick(View v){
  17.           Log.d("TAG","You clicked button2");
  18.       }
  19.   });
 

  我们在MyLayout的onTouch方法,和Button1、Button2的onClick方法中都打印了一句话。现在运行一下项目,效果图如下所示:

  

  分别点击一下Button1、Button2和空白区域,打印结果如下所示:

  

  当你点击按钮时,MyLayout的Touch事件并不会被触发,而在点击除按钮之外的空白部分,却能触发MyLayot的Touch事件.

  难道事件是从View传递到ViewGroup中的?现在下结论还太早.

  查阅文档,发现ViewGroup中还有一个onInterceptTouchEvent()方法,

  1.   publicboolean onInterceptTouchEvent(MotionEvent ev){
  2.    returnfalse;
  3.   }
 

  看Intercept,知道这应该是一个拦截器,拦截Touch事件的.但默认确实对所有的Touch都不拦截.

  在MyLayout中重写一下这个方法,然后返回一个true.

  

  1.  publicclassMyLayoutextendsLinearLayout{
  2.   publicMyLayout(Context context,AttributeSet attrs){
  3.    super(context, attrs);
  4.   }
  5.   @Override
  6.   publicboolean onInterceptTouchEvent(MotionEvent ev){
  7.    returntrue;
  8.    }
  9.   }
 

 

  现在再次运行项目,然后分别Button1、Button2和空白区域,打印结果如下所示:

  

  你会发现,不管点击在哪里,永远都只会触发MyLayout的Touch事件.按钮的点击事件被完全屏蔽掉了.

  所以得出结论:Touch事件会先分发到ViewGroup中,再传递到View中.

  上篇有个结论,触摸任何控件,都会调用dispatchTouchEvent()方法.其实不太准确.

  实际情况是,当点击了某个控件,首先会调用所在布局ViewGroup的dispatchTouchEvent(),在方法里,找到子View,调用子VIew的dispatchTouchEvent.

  当点击了MyLayout时,就会调用MyLayout的dispatchTouchEvent方法,但是MyLayout并没有这个方法,直到父类Linearlayout的父类ViewGroup中才有.

  

  下面是ViewGroup中dispatchTouchEvent()的源码:

  

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. final int action = ev.getAction();
  3. final float xf = ev.getX();
  4. final float yf = ev.getY();
  5. final float scrolledXFloat = xf + mScrollX;
  6. final float scrolledYFloat = yf + mScrollY;
  7. final Rect frame = mTempRect;
  8. boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  9. if (action == MotionEvent.ACTION_DOWN) {
  10. if (mMotionTarget != null) {
  11. mMotionTarget = null;
  12. }
  13. if (disallowIntercept || !onInterceptTouchEvent(ev)) {
  14. ev.setAction(MotionEvent.ACTION_DOWN);
  15. final int scrolledXInt = (int) scrolledXFloat;
  16. final int scrolledYInt = (int) scrolledYFloat;
  17. final View[] children = mChildren;
  18. final int count = mChildrenCount;
  19. for (int i = count - 1; i >= 0; i--) {
  20. final View child = children[i];
  21. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
  22. || child.getAnimation() != null) {
  23. child.getHitRect(frame);
  24. if (frame.contains(scrolledXInt, scrolledYInt)) {
  25. final float xc = scrolledXFloat - child.mLeft;
  26. final float yc = scrolledYFloat - child.mTop;
  27. ev.setLocation(xc, yc);
  28. child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  29. if (child.dispatchTouchEvent(ev))  {
  30. mMotionTarget = child;
  31. return true;
  32. }
  33. }
  34. }
  35. }
  36. }
  37. }
  38. boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
  39. (action == MotionEvent.ACTION_CANCEL);
  40. if (isUpOrCancel) {
  41. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  42. }
  43. final View target = mMotionTarget;
  44. if (target == null) {
  45. ev.setLocation(xf, yf);
  46. if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
  47. ev.setAction(MotionEvent.ACTION_CANCEL);
  48. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  49. }
  50. return super.dispatchTouchEvent(ev);
  51. }
  52. if (!disallowIntercept && onInterceptTouchEvent(ev)) {
  53. final float xc = scrolledXFloat - (float) target.mLeft;
  54. final float yc = scrolledYFloat - (float) target.mTop;
  55. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  56. ev.setAction(MotionEvent.ACTION_CANCEL);
  57. ev.setLocation(xc, yc);
  58. if (!target.dispatchTouchEvent(ev)) {
  59. }
  60. mMotionTarget = null;
  61. return true;
  62. }
  63. if (isUpOrCancel) {
  64. mMotionTarget = null;
  65. }
  66. final float xc = scrolledXFloat - (float) target.mLeft;
  67. final float yc = scrolledYFloat - (float) target.mTop;
  68. ev.setLocation(xc, yc);
  69. if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
  70. ev.setAction(MotionEvent.ACTION_CANCEL);
  71. target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  72. mMotionTarget = null;
  73. }
  74. return target.dispatchTouchEvent(ev);
  75. }

  

  在第13行中,对onInterceptTouchEvent取反,因此如果在onInterceptTouchEvent中返回了false,会让表达式成立.

  结合之前的例子,在MyLayout重写了onInterceptTouchEvent,返回true,结果导致所有的按钮事件都被屏蔽.所以得出结论,按钮的点击处理事件,都是在13行中内部进行的.

  在上篇中,得出结论,调用View的dispatchTouchEvent方法都是有返回值的.如果一个控件是可以点击的,那么dispatchTouchEvent的返回值必定是true.

  因此如果按钮的点击事件得到执行,就会将MyLayout的Touch事件拦截掉.

  如果不是点击的按钮,而是空白区域,则31行一定不会返回true了,而是执行50行的return super.dispatchTouchEvent(ev);  这句代码会调用View中的dispatchTouchEvent方法.因为ViewGroup的父类就是View.因此MyLayout的Touch方法会得到执行.

  看一下整个ViewGroup事件分发过程的流程图:

  

  1. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。

  2. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。

  3. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。

Android事件分发机制详解(2)----分析ViewGruop的事件分发的更多相关文章

  1. Android事件分发机制详解(1)----探究View的事件分发

    探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...

  2. Android事件传递机制详解及最新源码分析——ViewGroup篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...

  3. Android事件分发机制详解

    事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...

  4. Android Touch事件传递机制详解 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  5. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

  6. Android开发——事件分发机制详解

    0. 前言   转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52566965 深入学习事件分发机制,是为了解决在Android开发中 ...

  7. IOS 触摸事件分发机制详解

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 作者:MelonTeam 前言 很多时候大家都不关心IOS触摸事件的分发机制的实现原理,当遇到以下几种情形的时候你很可能抓破头皮都找不到解决方案 ...

  8. 【Android面试查漏补缺】之事件分发机制详解

    前言 查漏补缺,查漏补缺,你不知道哪里漏了,怎么补缺呢?本文属于[Android面试查漏补缺]系列文章第一篇,持续更新中,感兴趣的朋友可以[关注+收藏]哦~ 本系列文章是对自己的前段时间面试经历的总结 ...

  9. Android View 事件分发机制详解

    想必很多android开发者都遇到过手势冲突的情况,我们一般都是通过内部拦截和外部拦截法解决此类问题.要想搞明白原理就必须了解View的分发机制.在此之前我们先来了解一下以下三个非常重要的方法: di ...

随机推荐

  1. MySQL游标(cursor) 定义及使用

    概念 游标(Cursor)它使用户可逐行访问由SQL Server返回的结果集. 使用游标(cursor)的一个主要的原因就是把集合操作转换成单个记录处理方式. 用SQL语言从数据库中检索数据后,结果 ...

  2. java基础必备单词讲解 day two

    variable 变量 count 统计 sum 总数 salary 薪水 Scanner 接收 import 导入 eclipse 日食 control 控制 shift 改变 alt 替换键 ha ...

  3. ES6初识-函数扩展

    默认值 function test(x,y='world'){ console.log('默认值'); } function test2(...arg){ for(let v of arg){ con ...

  4. Python 初始—(多级字典)

    字典中 嵌套字典 如同json 对象, data={ "msg":{ “xxx.com”:["a","b"] } } data.values ...

  5. 本地预览的vue项目,在githubpage静态展示

    本地项目github静态展示 前提 在本地npm run dev后能够在本地端口正常显示 githubpage为自己的静态页面 上线 config/index.js中设置assetsPublicPat ...

  6. Atlas实现mysql主从分离

     可以接受失败,无法接受放弃!加油! 一.介绍Atlas及架构图 Atlas源代码用C语言编写,它对于Web Server相当于是DB,相对于DB相当于是Client,如果把Atlas的逻辑放到Web ...

  7. cacti和nagios监控web平台搭建

    在linux的运维中对服务器的监控,时刻了解服务器的状态是确保服务能够正常允许的条件,linux的服务监控平台有很多, cacti 下面对cacti(仙人掌),一种比较流行的开源监控软件做安装配置 具 ...

  8. python flask学习第2天 URL中两种方式传参

    新创建项目   自己写个url映射到自定义的视图函数 在url中传递参数 app.py from flask import Flask app = Flask(__name__) @app.route ...

  9. [CodeForces238E]Meeting Her(图论+记忆化搜索)

    Description 题目链接:Codeforces Solution 因为路线随机,所以找出各路线最短路必须经过的点,在这个点必定能上车 直接floyd暴力找割点 然后不断用k条公交车路线来更新D ...

  10. Oozie是什么

    Apache Oozie Workflow Scheduler for Hadoop Oozie is a workflow scheduler system to manage Apache Had ...