文章目录

  • 1. WindowManagerService
  • 2. Activity和Window
    • 总结
  • 3. ViewGroup
    • ViewGroup总结
  • 4. View
  • 参考材料

1. WindowManagerService

WindowManagerService是一个独立的进程,拥有自己的main方法。它的内部有多个List用于存放各种各样状态的Activity。

WindowManagerService接收到屏幕的点击事件后,就会分发给其内部正在显示的activity,这个就是activity的点击事件被分发的基本原理。

总之Acitivity的dispatchTouchEvent就是这样被调用的,而MotionEvent也是WindowManagerService生成的。

2. Activity和Window

事件分发机制的入口是Acitivity类的dispatchTouchEvent,他会调用内部的mWindow来继续分发事件。

//Acitivity的
public class Activity{@UnsupportedAppUsageprivate Window mWindow;public boolean dispatchTouchEvent(MotionEvent ev) {// 不重要,忽略if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}// 不要关注别的,主要关注这行代码if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);}public Window getWindow() {return mWindow;}
}

那这个Window又是什么呢?简单介绍一下。Window本身是一个抽象类,他的实例是phoneWindow对象,说白了就是每个Activity都会持有一个phoneWindow对象。

public abstract class Window {}
public class PhoneWindow extends Window implements MenuBuilder.Callback {}

注意,Window是一个抽象类,而PhoneWindow继承了Window,所以其实Window类并没有多少View相关的方法(因为没有继承View类)。

但是PhoneWindow内部有一个DecorView。DecorView中有两个View,TitleView和ContentView。TtitleView就是我们平时一直隐藏的actionBar,而ContentView就是我们平时写的xml文件,还记不记得我们每个Activity都一定会写的setContentView,实际上设置的就是DecorView的contentView。

最后再看一下事件从Activity分发到PhoneWindow后又做了些什么

public class PhoneWindow extends Window implements MenuBuilder.Callback {private DecorView mDecor;@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);}
}

传到了DecorView,再看看DecorView中做了什么

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {final Window.Callback cb = mWindow.getCallback();// 源码中是一行长代码,为了方便阅读我把它分开了// 这部分是有关Window的回调,暂时不关注if(cb != null && !mWindow.isDestroyed() && mFeatureId < 0)return cb.dispatchTouchEvent(ev);// 重点关注这行return super.dispatchTouEvent(ev);}
}

最终是调用了父类的dispatchTouchEvent,我们都知道FrameLayout本身就是ViewGroup的一种,接下来就要看View和ViewGroup的事件是如何分发的。

总结

  1. Activity内部持有一个PhoneWindow,而PhoneWindow又持有一个DecorView,DevorView其实就是ActionBar加上我们的ContentView。
  2. 事件分发流程是Activity-> PhoneWindow-> DecorView。
  3. DecorView本质是FrameLayout,即ViewGroup。

3. ViewGroup

刚才也说了,DecorView的本质是一个ViewGroup,这意味着事件分发本质上只有三个类参与,Activity,ViewGroup和View。

事件分发,只有存在可以分发的对象,才能把事件分发出去,View本身就是一个控件,已经是一个个体了,所以无法执行分发的这个操作。
ViewGroup是控件组,他的内部有子控件,存在分发事件的对象,所以ViewGroup可以执行分发的这个行为。

分发主要是通过dispatchTouchEvent这个方法,先看一下:

public boolean dispatchTouchEvent(MotionEvent ev) {boolean handled = false; // 这个变量表示事件是否被处理final int actionMasked = action & MotionEvent.ACTION_MASK; // 表示事件的类型......return handled;
}

顺便介绍一下事件的不同类型:

MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
MotionEvent.ACTION_UP 抬起View(与DOWN对应)
MotionEvent.ACTION_MOVE 滑动View
MotionEvent.ACTION_CANCEL 结束事件(非人为原因)

每当我们开始分发事件的时候,先要判断事件是否被拦截,是否拦截主要根据onInterceptTouchEvent方法和disallowIntercept共同决定。如果不拦截事件,就开始分发事件内容。

public boolean dispatchTouchEvent(MotionEvent ev) {// 省略关联性不高的代码,只看最关键的部分final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 这个变量表示是否禁用了拦截功能if (!disallowIntercept) { // 如果没有启用禁用拦截功能intercepted = onInterceptTouchEvent(ev); // 通过该方法判断是否拦截事件ev.setAction(action); } else { // 如果启用了禁用拦截功能,就不会拦截该点击事件intercepted = false;}} else { intercepted = true;}if (!canceled && !intercepted) {.....分发点击事件......}......其他代码......
}/*** 这个方法,返回true的条件太多,一般可以看成返回false* 也就是说,一般情况下,ViewGroup不拦截事件* 如果用户有特别需求,需要拦截事件,就重写该方法,然后返回true即可。
**/
public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false;
}

事件分发的本质分为两种,一种是当前ViewGroup中,有可以被分发事件的子控件,事件就分发给它;另一种是事件无法被任何子控件接收,ViewGroup就自己处理事件。


public boolean dispatchTouchEvent(MotionEvent ev) {......省略前面那一段判断intercepted的代码......if (!canceled && !intercepted) {......这里会省略一些我认为不重要的代码......final int childrenCount = mChildrenCount;if (childrenCount != 0) {final View[] children = mChildren;// 注意,这个地方是倒序获取子Viewfor (int i = childrenCount - 1; i >= 0; i--) {// 下面这两行,不要去管他具体是怎么实现的,你就知道他是获取子View的下标和实例就行了final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);// 第一个是判断child是否能接收事件,第二个是判断点击事件是否在子View的范围内,两个条件不满足任意一个,就跳过该子View           // 这里不要去管这两个方法是怎么实现的,我们根据方法名能知道方法的作用就好了if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) continue;// 分发转换为点击事件if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// 此处和源码部分不符,我有意省略了TouchTarget相关的内容,我认为关联性不强,但是逻辑应该是对的handled = true;}}}}if (mFirstTouchTarget == null) {// 分发转为点击事件,但是这里子View传的是空handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);}return handled;
}// 重点关注第三个参数,View child
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {final boolean handled;final int oldAction = event.getAction();if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {// 当传入的child为空时,该View就会自己处理这个事件handled = super.dispatchTouchEvent(event);} else {// 否则就让子View来处理这个事件handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}.......省略一大段.......
}

最后一段的内容是这样,当我们的事件没有被拦截时,就会遍历这个ViewGroup所持有的子View,判断到某个View如果在事件点击的范围内时,就调用dispatchTransformedTouchEvent这个方法,将该子View作为参数传进去。

这里要注意,对于遍历的方式,ViewGroup是倒着遍历他的子View集合的,原因是。每当我们往ViewGroup中添加View时,后添加的View总是会显示在屏幕的上方从而盖住先添加的view,而用户点击到这个地方时,他肯定是想点击他所看到的控件,也就是后添加的View,所以这里需要用倒着的顺序遍历。

如果遍历完所有的子View,还是没能处理这个点击事件(比如说这个事件点击到了该ViewGroup的空白地点,即上图中白色部分),还是调用dispatchTransformedTouchEvent这个方法,此时child传空

再来看看dispatchTransformedTouchEvent这个方法,当我们传入的child为空时,就会调用super.dispatchTouchEvent(event);
即ViewGroup的父类View的dispatchTouchEvent;而传入的child不为空时,就调用child的dispatchTouchEvent(event);

ViewGroup总结

最后总结一下ViewGroup的事件分发流程

  1. dispatchTouchEvent:判断该事件是否被拦截,如果被拦截,由该ViewGroup自己处理事件
  2. dispatchTouchEvent:没有被拦截,倒序遍历所有的子View,选择处于点击范围内的子View,让他处理该事件
  3. dispatchTouchEvent:如果所有子View都不能处理该事件,由该ViewGroup自己处理事件
  4. dispatchTransformedTouchEvent:处理事件时调用的方法,如果child变量传null,则调用该ViewGroup父类View的dispatchTouchEvent(即自己处理点击事件);child不为空,调用child的dispatchTouchEvent(即子View处理点击事件)。
  5. onInterceptTouchEvent:判断是否拦截事件,一般返回false。如果有特殊需要重写该方法。

4. View

最后看看View在事件分发过程中做了什么,按照道理来说,只有持有子控件的ViewGroup,才能将事件分发给别人。View本身是一个单独的控件,并不存在可以分发事件的对象。

从刚刚ViewGroup的源码中,我们知道了View的事件分发也是从dispatchTouchEvent开始。

public boolean dispatchTouchEvent(MotionEvent event) {boolean result = false;// 这个条件你就默认他为true就行了if (onFilterTouchEventForSecurity(event)) {ListenerInfo li = mListenerInfo;// 注意看li.mOnTouchListener.onTouch(this, event))这个条件,他会调用onTouch方法,这个方法是一个接口// 我们如果有设置touch监听事件的话,就会调用这个方法if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}// 如果没有TouchListener监听事件或者onTouch没有返回true时就执行onTouchEvent(event)if (!result && onTouchEvent(event)) {result = true;}}return result;
}

dispatchTouchEvent主要做了两件事,他会先检测该View有没有设置TouchListener接口和onTouch方法,如果有就执行。

然后如果TouchListener.onTouch方法返回false,就执行onTouchEvent方法。

public boolean onTouchEvent(MotionEvent event) {final int action = event.getAction();// 看变量名就知道,是检测是否可以点击的变量final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:......performClickInternal();......break;case MotionEvent.ACTION_DOWN:.......break;case MotionEvent.ACTION_CANCEL:......break;case MotionEvent.ACTION_MOVE:.......break;}return true;}return false;}
}

onTouchEvent就是处理点击事件,如果该事件可以点击就一定返回true,否则一定返回false。

然后就是根据不同的事件类型进行处理,最后单独列出performClickInternal()这个方法,这个方法最终会调用到OnClickLisener.onClick这个方法,这就表示。我们平时的单击控件,实际上是在手指抬起来之后,才处理的。

参考材料

Android事件分发机制详解:史上最全面、最易懂 - 简书
https://www.jianshu.com/p/38015afcdb58
Activity 与 Window、PhoneWindow、DecorView 之间的关系详解_Chin_Style的博客-CSDN博客_phonewindow
https://blog.csdn.net/weixin_41101173/article/details/79685305
码牛学院VIP课程 2020-7-12 事件分发机制

Android的View事件分发机制原理相关推荐

  1. 安卓自定义View进阶-事件分发机制原理【转自 app架构师 微信公众号】

    注意:本文中所有源码分析部分均基于 API23(Android 6.0) 版本,由于安卓系统源码改变很多,可能与之前版本有所不同,但基本流程都是一致的. 为什么要有事件分发机制? 安卓上面的View是 ...

  2. 安卓自定义View进阶-事件分发机制原理

    之前讲解了很多与View绘图相关的知识,你可以在 安卓自定义View教程目录 中查看到这些文章,如果你理解了这些文章,那么至少2D绘图部分不是难题了,大部分的需求都能满足,但是关于View还有很多知识 ...

  3. 【Android View事件分发机制】关于拦截事件的注意点

    在父容器拦截事件时,为什么不能拦截DOWN事件呢? 先看看源码: 回顾一下事件分发机制原理,当事件来了之后,如果父容器不拦截,则会询问其child view ,当某child view 有事件需求,父 ...

  4. Android面试老生常谈的 View 事件分发机制,看这一篇就够了

    本文首发我的微信公众号:徐公,想成为一名优秀的 Android 开发者,需要一份完备的 知识体系,在这里,让我们一起成长,变得更好~. 在 Android 开发当中,View 的事件分发机制是一块很重 ...

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

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

  6. 一文读懂Android View事件分发机制

    Android View 虽然不是四大组件,但其并不比四大组件的地位低.而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎.ScrollView嵌套RecyclerView(或者ListVi ...

  7. View事件分发机制

    一.概念 View可以说是Android中的第五大控件了,不管是Button还是TextView还是复杂的RelativeView,他们的共同基类都是View,View是界面层控件的一种抽象,View ...

  8. View事件分发机制(源码 API27)

    1.什么是事件分发机制 当用户触摸屏幕时,会产生一个touch事件,这个touch事件(motionEvent)传递到某个具体的view处理的整个过程 用户触摸屏幕会产生一个事件流(ACTION_DO ...

  9. View事件分发机制(源码分析篇)

    01.Android中事件分发顺序 1.1 事件分发的对象是谁 事件分发的对象是事件.注意,事件分发是向下传递的,也就是父到子的顺序. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产 ...

  10. 【Android View事件分发机制】原理

    事件体系中的几个基础类 MotionEvent 点击事件的封装. getX/Y 相当于当前View左上角的x,y坐标 getRawX/Y 相对于手机屏幕左上角的x,y坐标 GestureDetecto ...

最新文章

  1. 实现HttpHandlerFactory的方法
  2. 代码整洁之道----读书笔记
  3. php大并发 大流量 大存储解决方案
  4. Tengine+LUA+Nginx-GridFS+jemalloc编译安装
  5. b站在线解析_这款游戏被全B站所唾弃,每个月却依然有5000万玩家坚持在线?!...
  6. cla c 语言编译器,第九章 CLA_C2000_C_Compiler.pdf
  7. Java反射机制深入研究
  8. JS 中 Map 与 JSON 转换
  9. MySQL语句判断新老客_数栖云应用场景实践——老客召回(文字版教程)
  10. php抽奖实现-概率算法
  11. JavaScript 原生Ajax
  12. SEGGER调试利器RTT,替代串口,高速数据上传
  13. UltraCompare如何进行模块和线路模式合并?
  14. 财富、通胀与印钞——读《原则2 :应对变化中的世界秩序》(上)
  15. ICML 2020 | SCAFFOLD:联邦学习的随机控制平均
  16. 陈艾盐:春燕百集访谈节目第十九集
  17. html保留数据库文本格式,以html格式显示数据库中的格式化文本(FLASK应用程序)...
  18. 2021-02-05
  19. windows下域名解析及修改hosts文件不起作用的问题解决
  20. Linux 块子系统优化

热门文章

  1. php表格行数怎么设置,表格怎么排版
  2. 公众号获取token失败_关于微信公众号开发的Token验证失败该如何解决?
  3. 计算机无法连接远程桌面怎么回事,笔者教你远程桌面无法连接怎么办
  4. Fins源代码欧姆龙通讯 OneApiConnect
  5. 方差分析 交互效应和无交互效应
  6. 软件测试基础之功能性测试
  7. vs配置python环境_VS2017中安装Python开发环境[TZZ]
  8. 基于layui 下拉多选 三级联动省市区demo
  9. 【修电脑】EFI Network 0 for IPv4(XX-XX-XX-XX-XX)boot failed
  10. matplotlb之柱形图与盒形图