最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘记了,其实网上关于Touch事件的传递的文章真的很多,但是很少有系统性的,都是写了一个简单的demo运行了一下,对于我们了解Android Touch事件基本上没有任何帮助。

今天我打算从源码的角度来分析一下Touch事件的传递机制。在了解Touch事件之前,最好了解下Android中窗口的创建过程,这样对于Android窗口的整体结构和事件的传递过程会了解更深。

我就把事件的始点定在PhoneWindow中的DecorView吧,至于是谁把事件传递给DecorView的我们先不用去关心它。(如果想深入研究,请阅读我的另外一篇文章Android中按键事件传递机制)我们只需要知道它的上家是通过dispatchTouchEvent方法将事件分发给DecorView就行了,我进入到该方法瞧瞧究竟。

在阅读之前最好阅读Android窗口创建过程

  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent ev)
  3. {
  4. //该Callback就是该DecorView附属的Activity,可以看我的另外一篇文章《Android中窗口的创建过程》
  5. final Callback cb = getCallback();
  6. //如果cb!=null && mFeatureId<0 就执行Activity中的dispatchTouchEvent方法,对于应用程序窗口 <span >         </span>    //这两个条件一般是满足的
  7. return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
  8. .dispatchTouchEvent(ev);
  9. }

在DecorView中事件通过dispatchTouchEvent方法被分发到了Activity中,相信Activity对于每个Android开发者都不会陌生吧,那我们就进入Activity的dispatchTouchEvent方法中。

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  3. onUserInteraction();
  4. }
  5. //getWindow返回什么?如果阅读过我的《Android中窗口创建过程》的都知道就是PhoneWindow,如果PhoneWindow中的superDispatchTouchEvent方法返回了true,
  6. //那么该Touch事件就被PhoneWindow给消费掉了,不会再继续传递,如果返回false,那么就会执行Activity的onTouchEvent方法
  7. if (getWindow().superDispatchTouchEvent(ev)) {
  8. return true;
  9. }
  10. return onTouchEvent(ev);
  11. }

进入PhoneWindow中的superDispatchTouchEvent方法:

  1. @Override
  2. public boolean superDispatchTouchEvent(MotionEvent event) {
  3. //mDecor是一个DecorView类型变量
  4. return mDecor.superDispatchTouchEvent(event);
  5. }

进入DecorView中的superDispatchTouchEvent方法:

  1. public boolean superDispatchTouchEvent(MotionEvent event) {
  2. //直接调用父类的dispatchTouchEvent方法
  3. return super.dispatchTouchEvent(event);
  4. }

走到这里我们先暂停一下,会看一下DecorView类的dispatchTouchEvent方法,如果callBack不为空,那么调用CallBack的dispatchTouchEvent方法,否则调用super.dispatchTouchEvent方法,但是在CallBack不为空的条件下最中也是调用到了super.dispatchTouchEvent方法,那么它的super是哪个那,我们继续往下看:
通过源码我们可以看到DecorView是继承自FrameLayout。所以事件最终是传递到了FrameLayout的dispatchTouchEvent中,FrameLayout中的此方法是继承自ViewGroup的,我们直接到ViewGroup中查看此方法吧:

  1. @Override
  2. public boolean dispatchTouchEvent(MotionEvent ev) {
  3. final int action = ev.getAction();
  4. final float xf = ev.getX();
  5. final float yf = ev.getY();
  6. final float scrolledXFloat = xf + mScrollX;
  7. final float scrolledYFloat = yf + mScrollY;
  8. final Rect frame = mTempRect;
  9. //可以通过requestDisallowInterceptTouchEvent方法来设置该变量的值,通常是false
  10. boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  11. if (action == MotionEvent.ACTION_DOWN) {
  12. if (mMotionTarget != null) {
  13. // this is weird, we got a pen down, but we thought it was
  14. // already down!
  15. // XXX: We should probably send an ACTION_UP to the current
  16. // target.
  17. mMotionTarget = null;
  18. }
  19. // If we're disallowing intercept or if we're allowing and we didn't
  20. // intercept
  21. //onInterceptTouchEvent在默认情况下是返回false的,所以这里通常是可以进去的
  22. if (disallowIntercept || !onInterceptTouchEvent(ev)) {
  23. // reset this event's action (just to protect ourselves)
  24. ev.setAction(MotionEvent.ACTION_DOWN);
  25. // We know we want to dispatch the event down, find a child
  26. // who can handle it, start with the front-most child.
  27. final int scrolledXInt = (int) scrolledXFloat;
  28. final int scrolledYInt = (int) scrolledYFloat;
  29. final View[] children = mChildren;
  30. final int count = mChildrenCount;
  31. //遍历ViewGroup的孩子,如果触摸点在某一个子View中,则调用在子View的dispatchTouchEvent
  32. for (int i = count - 1; i >= 0; i--) {
  33. final View child = children[i];
  34. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
  35. || child.getAnimation() != null) {
  36. child.getHitRect(frame);
  37. if (frame.contains(scrolledXInt, scrolledYInt)) {
  38. // offset the event to the view's coordinate system
  39. final float xc = scrolledXFloat - child.mLeft;
  40. final float yc = scrolledYFloat - child.mTop;
  41. ev.setLocation(xc, yc);
  42. child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  43. //调用了某一个子View 的dispatchTouchEvent ,如果这个子View 的dispatchTouchEvent返回true,那么意味着这个事件
  44. //已经被这个子View消费了,不会继续传递
  45. if (child.dispatchTouchEvent(ev))  {
  46. // Event handled, we have a target now.
  47. mMotionTarget = child;
  48. return true;
  49. }
  50. // The event didn't get handled, try the next view.
  51. // Don't reset the event's location, it's not
  52. // necessary here.
  53. }
  54. }
  55. }
  56. }
  57. }
  58. boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
  59. (action == MotionEvent.ACTION_CANCEL);
  60. if (isUpOrCancel) {
  61. // Note, we've already copied the previous state to our local
  62. // variable, so this takes effect on the next event
  63. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  64. }
  65. // The event wasn't an ACTION_DOWN, dispatch it to our target if
  66. // we have one.
  67. final View target = mMotionTarget;
  68. //对于一个Action_down事件,如果走到了这里,说明所有的子View 都没有消费掉这个事件,那么它就调用父类的
  69. //的dispatchTouchEvnet方法,ViewGroup的父类就是View
  70. if (target == null) {
  71. // We don't have a target, this means we're handling the
  72. // event as a regular view.
  73. ev.setLocation(xf, yf);
  74. if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
  75. ev.setAction(MotionEvent.ACTION_CANCEL);
  76. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  77. }
  78. return super.dispatchTouchEvent(ev);
  79. }
  80. // if have a target, see if we're allowed to and want to intercept its
  81. // events
  82. if (!disallowIntercept && onInterceptTouchEvent(ev)) {
  83. final float xc = scrolledXFloat - (float) target.mLeft;
  84. final float yc = scrolledYFloat - (float) target.mTop;
  85. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  86. ev.setAction(MotionEvent.ACTION_CANCEL);
  87. ev.setLocation(xc, yc);
  88. if (!target.dispatchTouchEvent(ev)) {
  89. // target didn't handle ACTION_CANCEL. not much we can do
  90. // but they should have.
  91. }
  92. // clear the target
  93. mMotionTarget = null;
  94. // Don't dispatch this event to our own view, because we already
  95. // saw it when intercepting; we just want to give the following
  96. // event to the normal onTouchEvent().
  97. return true;
  98. }
  99. if (isUpOrCancel) {
  100. mMotionTarget = null;
  101. }
  102. // finally offset the event to the target's coordinate system and
  103. // dispatch the event.
  104. final float xc = scrolledXFloat - (float) target.mLeft;
  105. final float yc = scrolledYFloat - (float) target.mTop;
  106. ev.setLocation(xc, yc);
  107. if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
  108. ev.setAction(MotionEvent.ACTION_CANCEL);
  109. target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
  110. mMotionTarget = null;
  111. }
  112. return target.dispatchTouchEvent(ev);
  113. }

刚才在看ViewGroup的dispatchTouchEvent方法时,我们看到了一个方法onInterceptTouchEvent,这个方法是干什么的呢,我们先看看他都干了什么吧

  1. public boolean onInterceptTouchEvent(MotionEvent ev) {
  2. return false;
  3. }

发现里面就是返回了一个false, 通过方法名字我们就可以知道该方法的作用,是否阻止TouchEvent的传递,默认是false 也就是不会阻止。

现在总结一下ViewGroup的dispatchTouchEvnet的逻辑 ,毕竟这个方法有些复杂:
1、如果disallowIntercept|| !onInterceptTouchEvent(),那么事件才可以继续传递下去,否则直接调用该ViewGroup的父类的dispatchTouchEvent,也就是View的dispatchTouchEvent.
2、依次遍历ViewGroup的所有子View,将事件传递个子View,如果某一个子View处理了该事件,并且返回true,那么事件结束,停止传递
3、如果所有的子View没有消费掉这个事件,那么就调用View的dispatchTouchEvent

对于任何一款Android应用,展现给用户最上面的通常就是一个View,如Button,ImageView等等,也就是说一些触摸事件最终都是传递给了这个控件,如果控件消费了这些事件,那么就停止传递了,如果没有消费,那么就交给控件所属ViewGroup的onTouchEvnet处理,我们就看看View的dispatchTouchEvent方法吧

  1. public boolean dispatchTouchEvent(MotionEvent event) {
  2. if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
  3. mOnTouchListener.onTouch(this, event)) {
  4. return true;
  5. }
  6. return onTouchEvent(event);
  7. }

View的这个方法非常简单,首先判断mTouchListener是否为空,并且这个View是否Eneable,如果都满足,那么首先调用mOnTouchListener.onTouch方法,如果onTouch方法返回true,那么就是说这个View消费了该事件,直接返回true,如果onTouch返回false,那么就会调用onTouchEvnet方法,这个mOnTouchListener是什么?

  1. public void setOnTouchListener(OnTouchListener l) {
  2. mOnTouchListener = l;
  3. }

看了这个就明白了吧,就是我们通过setOnTouchListener赋值的,另外我们还需要注意一点就是这个onTouch是在onTouchEvent方法之前执行的哦。
最后我们就看看这个View的onTouchEvnet吧

  1. public boolean onTouchEvent(MotionEvent event) {
  2. final int viewFlags = mViewFlags;
  3. //(A)
  4. if ((viewFlags & ENABLED_MASK) == DISABLED) {
  5. // A disabled view that is clickable still consumes the touch
  6. // events, it just doesn't respond to them.
  7. return (((viewFlags & CLICKABLE) == CLICKABLE ||
  8. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  9. }
  10. if (mTouchDelegate != null) {
  11. if (mTouchDelegate.onTouchEvent(event)) {
  12. return true;
  13. }
  14. }
  15. //(B)
  16. if (((viewFlags & CLICKABLE) == CLICKABLE ||
  17. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  18. switch (event.getAction()) {
  19. case MotionEvent.ACTION_UP:
  20. boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
  21. if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
  22. // take focus if we don't have it already and we should in
  23. // touch mode.
  24. boolean focusTaken = false;
  25. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
  26. focusTaken = requestFocus();
  27. }
  28. if (!mHasPerformedLongPress) {
  29. // This is a tap, so remove the longpress check
  30. removeLongPressCallback();
  31. // Only perform take click actions if we were in the pressed state
  32. if (!focusTaken) {
  33. // Use a Runnable and post this rather than calling
  34. // performClick directly. This lets other visual state
  35. // of the view update before click actions start.
  36. if (mPerformClick == null) {
  37. mPerformClick = new PerformClick();
  38. }
  39. //(C)
  40. if (!post(mPerformClick)) {
  41. performClick();
  42. }
  43. }
  44. }
  45. if (mUnsetPressedState == null) {
  46. mUnsetPressedState = new UnsetPressedState();
  47. }
  48. if (prepressed) {
  49. mPrivateFlags |= PRESSED;
  50. refreshDrawableState();
  51. postDelayed(mUnsetPressedState,
  52. ViewConfiguration.getPressedStateDuration());
  53. } else if (!post(mUnsetPressedState)) {
  54. // If the post failed, unpress right now
  55. mUnsetPressedState.run();
  56. }
  57. removeTapCallback();
  58. }
  59. break;
  60. case MotionEvent.ACTION_DOWN:
  61. if (mPendingCheckForTap == null) {
  62. mPendingCheckForTap = new CheckForTap();
  63. }
  64. mPrivateFlags |= PREPRESSED;
  65. mHasPerformedLongPress = false;
  66. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  67. break;
  68. case MotionEvent.ACTION_CANCEL:
  69. mPrivateFlags &= ~PRESSED;
  70. refreshDrawableState();
  71. removeTapCallback();
  72. break;
  73. case MotionEvent.ACTION_MOVE:
  74. final int x = (int) event.getX();
  75. final int y = (int) event.getY();
  76. // Be lenient about moving outside of buttons
  77. int slop = mTouchSlop;
  78. if ((x < 0 - slop) || (x >= getWidth() + slop) ||
  79. (y < 0 - slop) || (y >= getHeight() + slop)) {
  80. // Outside button
  81. removeTapCallback();
  82. if ((mPrivateFlags & PRESSED) != 0) {
  83. // Remove any future long press/tap checks
  84. removeLongPressCallback();
  85. // Need to switch from pressed to not pressed
  86. mPrivateFlags &= ~PRESSED;
  87. refreshDrawableState();
  88. }
  89. }
  90. break;
  91. }
  92. //(D)
  93. return true;
  94. }
  95. return false;
  96. }

这个方法也是相当的复杂啊,但是我们没有必要每一行都看,我们只需要挑重点看就Ok了。
请细看我标了 A B C D的四个地方,在A处,如果该View是Disable的,那么只要该View是clickable或者longclickable的,那么这个事件就被该View消费掉了,返回true
在看B 和 D,两处,如果该View是clickable或者longclickable的,那么D出总是返回true,也就是说事件一直被消费,至于C处我主要是说明的是View的onClick事件是在ACTION_UP中触发的。

学习到这里,我又需要总结一下:
如果我们触摸的一个View是clickable或者longclickable的,那么这个事件肯定会被这个View消费掉(当然前提是你没有改写它所在ViewGroup的onInterceptTouchEvent方法,如果你改写此方法返回true,那么View是无法接收到这个事件的)

我们现在还要思考一个问题,如果这个View没有消费掉这个事件,这个事件最终抛向何方?
还记得前面我说过ViewGroup的dispatchTouchEvent方法吗,如果它的所有的子View没有处理掉该事件,那么调用的是父类View的dispatchTouchEvnet方法,从而执行到了该ViewGroup的onTouch和onTouchEvent方法。

那如果ViewGroup也没有处理该事件呢,这里就要分两种情况啦:
1、如果这个ViewGroup不是DecorView,也就是说他的父View就是一个普通的ViewGroup(如LinearLayout里面放置一个LinearLayout),那么和上面子View没有处理掉消息有点类似,调用父类的onTouch和onTouchEvent方法
2、如果这个ViewGroup就是DecorView,那么就调用到了Activity的onTouchEvnet方法(此时没有onTouch方法)。

今天就先写到这里吧,后面我回用一个简单的Demo和一个简单的滑动冲突问题在深入学习TouchEvnet事件的。如果哪里没有写清楚的 ,欢迎拍砖。。。

Android Touch事件传递机制详解 上的更多相关文章

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

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

  2. Android Touch事件传递机制详解

    Android开发的朋友经常处理各种触摸事件,然而在触摸事件的传递过程中主要用到三个方法:dispatchTouchEvent().onInterceptTouchEvent()和onTouchEve ...

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

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

  4. Android Touch事件传递机制具体解释 上

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/37961997 近期总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,曾经 ...

  5. Android Touch事件传递机制 二:单纯的(伪生命周期)

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  6. Android Touch事件传递机制 二:单纯的(伪生命周期) 这个清楚一点

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  7. Android Touch事件传递机制具体解释 下

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

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

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

  9. Android touch 事件传递机制

    前言: (1)在自定义view的时候经常会遇到事件拦截处理,比如在侧滑菜单的时候,我们希望在侧滑菜单里面有listview控件,但是我们希望既能左右滑动又能上下滑动,这个时候就需要对触摸的touch事 ...

随机推荐

  1. Normal synchronous FIFO mode 和 Show-ahead synchronous FIFO mode

    FIFO是先进先出,可以用fifo来处理跨时钟域的数据传输问题,用到的地方特别多,一定要搞会. 在学习调用fifo的IP核中发现有normal synchronous FIFO mode 和 Show ...

  2. springboot集成pagehelper插件

    1.在pom.xml中引入依赖 <dependency> <groupId>com.github.pagehelper</groupId> <artifact ...

  3. MessageBox:弹出窗口

    Ext.onReady(function () { Ext.MessageBox.alert("提示信息!","Hello World!"); }); Ext, ...

  4. Log4j官方文档翻译(四、如何在java中输出日志消息)

    我们已经创建来配置文件,本章详细的介绍下如何生成调试信息,并把他们转化成文本文件. 基本的例子 下面就是创建的一个基本的例子: log4j.properties的内容为: log = /usr/hom ...

  5. 【bzoj3879】SvT 后缀数组+倍增RMQ+单调栈

    题目描述 (我并不想告诉你题目名字是什么鬼) 有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n]. 现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示), ...

  6. ubuntu--基础环境瞎搞集合

    安装ubuntu系统后有很多东西需要自己瞎搞一下,这里把一些瞎搞的过程记录在这里,方便以后重新装系统后重新配置. 一.安装. 可以在windows下制作启动盘(软碟通),然后开机u盘启动即可安装,预留 ...

  7. [luogu1357] 花园 [dp+矩阵快速幂]

    题面: 传送门 思路: 把P形花圃记录为0,C形记录为1,那么一段花圃就可以状态压缩成一个整数 那么,我们可以有这样的状压dp: dp[i][S]表示前i个花圃,最后m个的状态为S的情况 如果这是一条 ...

  8. linux系统程序设计教程

    linux系统程序设计教程 第一章:生成一个Process(进程) 进程是什么?简单地说,进程就是在执行状态下的一个程序(包括CPU状态,所占内存的状态,等等) A进程生成了B进程,也就是说,A程序在 ...

  9. P1558 色板游戏 (线段树)

    题目链接 Solution 一个简单的 或 线段树.竟然坑了我一个小时... 因为颜色很小,所以把状态压起来. 然后每个节点上的数值代表当前颜色状态. 然后节点合并很简单,直接或起来. 需要注意一下的 ...

  10. [暑假集训--数论]poj1595 Prime Cuts

    A prime number is a counting number (1, 2, 3, ...) that is evenly divisible only by 1 and itself. In ...