很多其它内容请參照我的个人网站: http://stackvoid.com/

网上非常多关于Android事件分发机制的解释,大多数描写叙述的都不够清晰,没有吧来龙去脉搞清晰,本文将带你从Touch事件产生到Touch事件被消费这一全过程作全面的剖析。

产生Touch事件

这部分牵扯到硬件和Linux内核部分;我们简单讲述一下这部分内容,假设有兴趣的话能够參考这篇文章

传递Touch事件

触摸事件是由Linux内核的一个Input子系统来管理的(InputManager),Linux子系统会在/dev/input/ 这个路径下创建硬件输入设备节点(这里的硬件设备就是我们的触摸屏了)。当手指触动触摸屏时,硬件设备通过设备节点像内核(事实上是InputManager管理)报告事件,InputManager 经过处理将此事件传给
Android系统的一个系统Service:WindowManagerService 。

WindowManagerService调用dispatchPointer()从存放WindowState的z-order顺序列表中找到能接收当前touch事件的
WindowState,通过IWindow代理将此消息发送到IWindow服务端(IWindow.Stub子类),这个IWindow.Stub属于ViewRoot(这个类继承Handler,主要用于连接PhoneWindow和WindowManagerService),所以事件就传到了ViewRoot.dispatchPointer()中.

我们来看一下ViewRoot的dispatchPointer方法:

1 public void dispatchPointer(MotionEvent event, long eventTime,
2 boolean callWhenDone) {
3 Message msg = obtainMessage(DISPATCH_POINTER);
4 msg.obj = event;
5 msg.arg1 = callWhenDone ? 1 : 0;
6 sendMessageAtTime(msg, eventTime);
7 }

dispatchPointer方法就是把这个事件封装成Message发送出去,在ViewRoot Handler的handleMessage中被处理,其调用了mView.dispatchTouchEvent方法(mView是一个PhoneWindow.DecorView对象),PhoneWindow.DecorView继承FrameLayout(FrameLayout继承ViewGroup,ViewGroup继承自View),DecorView里的dispatchTouchEvent方法例如以下. 这里的Callback的cb事实上就是Activity的attach()方法里的设置回调。

 1 //in file PhoneWindow.java
2 public boolean dispatchTouchEvent(MotionEvent ev) {
3 final Callback cb = getCallback();
4 if (mEnableFaceDetection) {
5 int pointCount = ev.getPointerCount();
6
7 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
8 case MotionEvent.ACTION_POINTER_DOWN:
9
10 .......
11
12 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
13 : super.dispatchTouchEvent(ev);
14 }
15 //in file Activity.java -> attach()
16 mFragments.attachActivity(this, mContainer, null);
17 mWindow = PolicyManager.makeNewWindow(this);
18 mWindow.setCallback(this);//设置回调

也就是说,正常情形下,当前的Activity就是这里的cb,即调用了Activity的dispatchTouchEvent方法。

以下来分析一下从Activity到各个子View的事件传递和处理过程。

首先先分析Activity的dispatchTouchEvent方法。

1 public boolean dispatchTouchEvent(MotionEvent ev) {
2 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3 onUserInteraction();
4 }
5 if (getWindow().superDispatchTouchEvent(ev)) {
6 return true;
7 }
8 return onTouchEvent(ev);
9 }

onUserInteraction() 是一个空方法,开发人员能够依据自己的需求覆写这种方法(这种方法在一个Touch事件的周期肯定会调用到的)。假设推断成立返回True,当前事件就不在传播下去了。 superDispatchTouchEvent(ev) 这种方法做了什么呢? getWindow().superDispatchTouchEvent(ev) 也就是调用了PhoneWindow.superDispatchTouchEvent 方法,而这种方法返回的是
mDecor.superDispatchTouchEvent(event),在内部类 DecorView(上文中的mDecor) 的superDispatchTouchEvent 中调用super.dispatchTouchEvent(event),而DecorView继承自ViewGroup(通过FrameLayout,FrameLayout没有dispatchTouchEvent),终于调用的是ViewGroup的dispatchTouchEvent方法。

小结一下。Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通过 CallBack 调用了 Activity 的 dispatchTouchEvent 方法,在 Activity 这里,我们能够重写 Activity 的dispatchTouchEvent 方法阻断 touch事件的传播。接着在Activity里的dispatchTouchEvent 方法里,事件又再次传递到DecorView,DecorView通过调用父类(ViewGroup)的dispatchTouchEvent
将事件传给父类处理,也就是我们以下要分析的方法,这才进入网上大部分文章解说的touch事件传递流程。

为什么要从 PhoneWindow.DecorView 中 传到 Activity,然后在传回 PhoneWindow.DecorView 中呢? 主要是为了方便在Activity中通过控制dispatchTouchEvent 来控制当前Activity 事件的分发, 下一篇关于数据埋点文章就应用了这个机制

OK,我们要重点分析的就是ViewGroup中的dispatchTouchEvent方法。

  1 @Override
2 public boolean dispatchTouchEvent(MotionEvent ev) {
3 //......
4
5 // Check for interception.
6 final boolean intercepted;//是否被拦截
7 if (actionMasked == MotionEvent.ACTION_DOWN
8 || mFirstTouchTarget != null) {//Touch按下事件
9 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
10 if (!disallowIntercept) {
11 intercepted = onInterceptTouchEvent(ev);//推断消息是否须要被viewGroup拦截,这种方法我们能够覆写,
12 //覆写生效的前提是 disallowIntercept 为FALSE,否则写了也没用
13 ev.setAction(action); // restore action in case it was changed
14 } else {//不同意拦截
15 intercepted = false;
16 }
17 } else {
18 // There are no touch targets and this action is not an initial down
19 // so this view group continues to intercept touches.
20 //这个操作不是一開始down事件,我们把它置为TRUE,拦截之
21 intercepted = true;
22 }
23
24 // Check for cancelation.
25 final boolean canceled = resetCancelNextUpFlag(this)
26 || actionMasked == MotionEvent.ACTION_CANCEL;
27
28 //.........
29
30 final int childrenCount = mChildrenCount;//ViewGroup中子View的个数
31 if (newTouchTarget == null && childrenCount != 0) {
32 final float x = ev.getX(actionIndex);//获取坐标,用来比对
33 final float y = ev.getY(actionIndex);
34 // Find a child that can receive the event.
35 // Scan children from front to back.
36 final View[] children = mChildren;//获取viewgroup全部的子view
37
38 final boolean customOrder = isChildrenDrawingOrderEnabled();//子View的绘制顺序
39 ////从高到低遍历全部子View,找到能处理touch事件的child View
40 for (int i = childrenCount - 1; i >= 0; i--) {
41 final int childIndex = customOrder ?
42 getChildDrawingOrder(childrenCount, i) : i;//依据Order获取子view
43 final View child = children[childIndex];
44 //推断是不是我们须要的View
45 if (!canViewReceivePointerEvents(child)
46 || !isTransformedTouchPointInView(x, y, child, null)) {
47 continue;
48 }
49
50 newTouchTarget = getTouchTarget(child);//从链表里找子view
51 if (newTouchTarget != null) {//找到子view
52 // Child is already receiving touch within its bounds.
53 // Give it the new pointer in addition to the ones it is handling.
54 //已经找到,循环结束,目标就是newTouchTarget
55 newTouchTarget.pointerIdBits |= idBitsToAssign;
56 break;
57 }
58
59 //.......
60 }
61 }
62 }
63 }
64
65 // Dispatch to touch targets.
66 if (mFirstTouchTarget == null) {
67 // No touch targets so treat this as an ordinary view.
68 /*dispatchTransformedTouchEvent方法中,假设child是null,那么就调用super.dispatchTouchEvent,
69 *也就是ViewGroup的父类View的dispatchTouchEvent(假设我们在前面拦截了touch事件,那么就会这样处理),
70 *假设不是null,则调用child.dispatchTouchEvent。
71 **/
72 handled = dispatchTransformedTouchEvent(ev, canceled, null,
73 TouchTarget.ALL_POINTER_IDS);
74 } else {
75 //....
76 }
77 //........
78 return handled;
79 }
80 /**
81 * Transforms a motion event into the coordinate space of a particular child view,
82 * filters out irrelevant pointer ids, and overrides its action if necessary.
83 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
84 */
85 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
86 View child, int desiredPointerIdBits) {
87 final boolean handled;
88
89 //……
90
91 // Perform any necessary transformations and dispatch.
92 if (child == null) {
93 handled = super.dispatchTouchEvent(transformedEvent);
94 } else {
95 //……
96 handled = child.dispatchTouchEvent(transformedEvent);
97 }
98
99 // Done.
100 //……
101 return handled;
102 }

我们来总结一下 ViewGroup 的 dispatchTouchEvent 的调用过程。

  1. 首先推断此 MotionEvent 是否能被拦截,假设是的话,能调用我们覆写 onInterceptTouchEvent来处理拦截到的事件;假设此方法返回TRUE,表示须要拦截,那么事件到此为止,就不会传递到子View中去。这里要注意,onInterceptTouchEvent 方法默认是返回FALSE。
  2. 若没有拦截此Event,首先找到此ViewGroup中全部的子View,通过方法 canViewReceivePointerEvents和isTransformedTouchPointInView,对每一个子View通过坐标(Event事件坐标和子View坐标比对)计算,找到坐标匹配的View。
  3. 调用dispatchTransformedTouchEvent方法,处理Event事件。
 1 //ViewGroup.java dispatchTransformedTouchEvent方法截取
2 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
3 event.setAction(MotionEvent.ACTION_CANCEL);
4 if (child == null) {//Event事件被截获,调用父类View的dispatchTouchEvent方法
5 handled = super.dispatchTouchEvent(event);
6 } else {
7 handled = child.dispatchTouchEvent(event);//调用子View的dispatchTouchEvent方法
8 }
9 event.setAction(oldAction);
10 return handled;
11 }
  1. 如果这个子View是一个Button,会调用Button.dispatchTouchEvent 方法,Button和它的父类TextView都没有dispatchTouchEvent方法,仅仅能继续看父类View了,事实上终于调用的还是View.dispatchTouchEvent 方法。
  2. 我们继续分析View.dispatchTouchEvent 方法。mOnTouchListener 是OnTouchListener对象,由setOnTouchListener 方法设置;
 1 public boolean dispatchTouchEvent(MotionEvent event) {
2 if (mInputEventConsistencyVerifier != null) {
3 mInputEventConsistencyVerifier.onTouchEvent(event, 0);
4 }
5
6 if (onFilterTouchEventForSecurity(event)) {
7 //noinspection SimplifiableIfStatement
8 ListenerInfo li = mListenerInfo;//View 内部类,管理一些listener
9 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
10 && li.mOnTouchListener.onTouch(this, event)) {
11 return true;
12 }
13
14 if (onTouchEvent(event)) {
15 return true;
16 }
17 }
18
19 if (mInputEventConsistencyVerifier != null) {
20 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
21 }
22 return false;//没有消费掉,仅仅能返回false,让调用者来处理了。
23 }
24 /**
25 * Register a callback to be invoked when a touch event is sent to this view.
26 * @param l the touch listener to attach to this view
27 */
28 public void setOnTouchListener(OnTouchListener l) {
29 getListenerInfo().mOnTouchListener = l;
30 }

若当前ListenerInfo 方法初始化而且 li.mOnTouchListener 的值不为空且ENABLE掩码为Enable,那么调用mOnTouchListener(this,event)方法。boolean
onTouch(View v, MotionEvent event)
 这种方法是在View的内部接口 OnTouchListener中的,是一个空方法,须要用户自己来实现。拿一个Button来举例;我们覆写的onTouch()方法在这里被调用。

1 button.setOnTouchListener(new OnTouchListener() {
2 @Override
3 public boolean onTouch(View v, MotionEvent event) {
4 //实现自己的功能
5 return true;
6 }
7 });
  1. 若onTouch方法返回true,则表示被消费,不会继续传递下去;返回false,表示时间还没被消费,继续传递到 onTouchEvent 这种方法里。
 1 public boolean onTouchEvent(MotionEvent event) {
2 //……
3 if (((viewFlags & CLICKABLE) == CLICKABLE ||
4 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
5 switch (event.getAction()) {
6 case MotionEvent.ACTION_UP:
7 //……
8 //假设没有触发长按事件,手指动作是up,则运行performClick()方法
9 if (!mHasPerformedLongPress) {
10 // This is a tap, so remove the longpress check
11 removeLongPressCallback();
12
13 // Only perform take click actions if we were in the pressed state
14 if (!focusTaken) {
15 // Use a Runnable and post this rather than calling
16 // performClick directly. This lets other visual state
17 // of the view update before click actions start.
18 //这里推断并去运行单击事件
19 if (mPerformClick == null) {
20 mPerformClick = new PerformClick();
21 }
22 if (!post(mPerformClick)) {
23 performClick();
24 }
25 }
26 }
27
28 break;
29 case MotionEvent.ACTION_DOWN:
30 //……
31 //是否触发长按事件是在这里推断的,详细细节我就不贴出来了
32 checkForLongClick(0);
33 //……
34 break;
35 //……
36 }
37 return true;
38 }
39
40 return false;
41 }

若状态不是CLICKABLE,那么会直接跳过推断运行return false,这意味着兴许的touch事件不会再传递过来了。而大家注意看,仅仅要是CLICKABLE,那么不管case哪个节点,最后都是return true,这样就保证了兴许事件能够传递过来。 非常明显在onTouchEvent 方法里面,主要就是推断应该运行哪个操作,是长按还是单击,然后去运行相应的方法。我们看看假设是单击,运行的方法:

 1 public boolean performClick() {
2 //……
3
4 ListenerInfo li = mListenerInfo;
5 if (li != null && li.mOnClickListener != null) {
6 //播放点击音效
7 playSoundEffect(SoundEffectConstants.CLICK);
8 //运行onClick方法
9 li.mOnClickListener.onClick(this);
10 return true;
11 }
12
13 return false;
14 }

事实上就是调用了我们OnClickListener里面的onClick方法。所以说,当onTouch() 和 onClick()都存在时候,肯定是先运行onTouch,之后再运行onClick;假设onTouch 把事件截获直接return true,那么 onClick 方法就不会运行了。 到这里,整个touch事件的传递过程我们就分析完了。

Touch事件一般调用过程总结

用户点击屏幕产生Touch(包含DOWN、UP、MOVE,本文分析的是DOWN)事件 -> InputManager -> WindowManagerService.dispatchPointer() -> IWindow.Stub -> ViewRoot.dispatchPointer() -> PhoneWindow.DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent
-> PhoneWindow.DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent() -> ViewGroup.dispatchTransformedTouchEvent() -> 子View.dispatchTouchEvent() -> 子View.onTouch() -> 子View.onTouchEvent() -> 事件被消费结束

实用的參考

  1. Android
    FrameWork——Touch事件派发过程具体解释
  2. android的窗体机制分析------事件处理
  3. Android事件分发机制全然解析,带你从源代码的角度彻底理解(下)

Android 事件分发机制具体解释的更多相关文章

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

    转载注明出处:http://blog.csdn.net/xiaohanluo/article/details/52416141 1. 概述 Android日常研发时,与View接触占领相当多的时间.而 ...

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

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

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

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

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

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

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

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

  6. Android事件分发机制二:viewGroup与view对事件的处理

    前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...

  7. 本以为精通Android事件分发机制,没想到被面试官问懵了

    文章中出现的源码均基于8.0 前言 事件分发机制不仅仅是核心知识点更是难点,并且还是View的一大难题滑动冲突解决方法的理论基础,因此掌握好View的事件分发机制是十分重要的. 一.基本认识 1. 事 ...

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

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

  9. android事件分发机制

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

随机推荐

  1. 【习题 5-14 UVA - 1598】Exchange

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 各组数据之间有空行! 且最后一行后面没有空行! 然后就是用set来模拟就好. 删除的时候,不着急删除. 因为并不用时刻输出集合大小. ...

  2. Springboot + shiro 整合之Url拦截设置(转)

    shiro 整合到springboot 还是比较简单的,只需要新建一个spring-shiro.xml的配置文件: <span style="font-size:14px;" ...

  3. [TypeScript] Interface and Class

    When to use Interface and when to use Class. Let's see one example: export interface Lesson { course ...

  4. POJ 2785 4 Values whose Sum is 0 Hash!

    http://poj.org/problem?id=2785 题目大意: 给你四个数组a,b,c,d求满足a+b+c+d=0的个数 其中a,b,c,d可能高达2^28 思路: 嗯,没错,和上次的 HD ...

  5. spring mvc controller间跳转 重定向 传参(转)

    spring mvc controller间跳转 重定向 传参 url:http://zghbwjl.blog.163.com/blog/static/12033667220137795252845/ ...

  6. 使用Verdi理解RTL design

    使用Verdi理解RTL design 接触到一些RTL代码,在阅读与深入理解的过程中的一些思考记录 协议与设计框图 认真反复阅读理解相关协议与设计框图,一个design的设计文档中,设计框图展示了这 ...

  7. Asp 使用 Microsoft.XMLHTTP 抓取网页内容无乱码处理,并过滤须要的内容

    Asp 使用 Microsoft.XMLHTTP 抓取网页内容.并过滤须要的内容 Asp 使用 Microsoft.XMLHTTP 抓取网页内容无乱码处理,并过滤须要的内容 演示样例源代码: < ...

  8. AVR第5课:蜂鸣器

    下面是蜂鸣器的电路图. 代码:蜂鸣器代码. <span style="font-size:18px;">/* *info:buzzer *author:chenlu * ...

  9. JS学习十四天----server端运行JS代码

    server端运行JS代码 话说,当今不在client使用JS代码才是稀罕事.因为web应用的体验越来越丰富,client用JS实现的逻辑也越来越多,这造成的结果就是某些差点儿一致的逻辑须要在clie ...

  10. TreeMap、HashMap、ConcurrentSkipListMap之性能比较

    比较Java原生的 3种Map的效率. 1.  TreeMap 2.  HashMap 3.  ConcurrentSkipListMap 结果: 模拟150W以内海量数据的插入和查找,通过增加和查找 ...