尊重原创:http://blog.csdn.net/yuanzeyao/article/details/37961997

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

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

这种方法也是相当的复杂啊,可是我们没有必要每一行都看,我们仅仅须要挑重点看就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 Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘 ...

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

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

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

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

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

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

  6. Android touch 事件传递机制

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

  7. Android Touch事件传递机制通俗讲解

    在讲正题之前我们讲一段有关任务传递的小故事,抛砖迎玉下: 话说一家软件公司,来一个任务,分派给了开发经理去完成: 开发经理拿到,看了一下,感觉好简单,于是 开发经理:分派给了开发组长 开发组长:分派给 ...

  8. Android Touch事件传递机制引发的血案

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38942135 关于Android Touch事件传递机制我之前也写过两篇文章,自觉得对Tou ...

  9. Android Touch事件传递机制 一: OnTouch,OnItemClick(监听器),dispatchTouchEvent(伪生命周期)

      ViewGroup View  Activity dispatchTouchEvent 有 有 有 onInterceptTouchEvent 有 无 无 onTouchEvent 有 有 有 例 ...

随机推荐

  1. video标签 拖动 转自w3school

    调整视频大小 播放 暂停 用js实现 详细参见http://www.w3school.com.cn/tiy/t.asp?f=html5_video_dom 图片的拖动详见http://www.w3sc ...

  2. 浅谈Struts2(四)

    一.Struts2的拦截器(Intercept) 作用:把多个Action中的共有代码,提取至拦截器,从而减少Action中的冗余代码. 1.Action拦截器 a.编写interceptor类 pu ...

  3. 五毛的cocos2d-x学习笔记07-计时器、数据读写、文件读写

    调度器: 定时任务是通过调度器实现的.cocos2d-x推荐用调度器而不是其他方法实现定时任务.Node类都知道如何调度和取消调度事件. 有3种调度器: 默认调度器:schedulerUpdate() ...

  4. [LeetCode]题解(python):132-Palindrome Partitioning II

    题目来源: https://leetcode.com/problems/palindrome-partitioning-ii/ 题意分析: 给定一个s,可以将s拆成若干个回文子字符串之和,如果拆成了m ...

  5. C# Regex ignoring non-capturing group

    E.g I want match the keword "3398" after "red" from the string "This is red ...

  6. SIGAR - System Information Gatherer And Reporter

    https://support.hyperic.com/display/SIGAR/Home 收藏一篇: http://www.cnitblog.com/houcy/archive/2012/11/2 ...

  7. ThinkPHP 3.1.2 视图-1

    一.模板的使用 (重点) a.规则 模板文件夹下[TPL]/[分组文件夹/][模板主题文件夹/]和模块名同名的文件夹[Index]/和方法名同名的文件 [index].html(.tpl) 更换模板文 ...

  8. Balanced Binary Tree --Leetcode C++

    递归 左子树是否为平衡二叉树 右子树是否为平衡二叉树 左右子树高度差是否大于1,大于1,返回false 否则判断左右子树 最简单的理解方法就是如下的思路: class Solution { publi ...

  9. Node.mongoose

    简介 mongodb是一款面向文档的数据库,不是关系型数据库,新手熟悉mysql.sqlserver等数据库的人可能入手稍微困难些,需要转换一下思想,可以不需要有固定的存储模式,以文档模型为存储内容相 ...

  10. Python+Django+SAE系列教程9-----Django的视图和URL

    第三.四.五章介绍的就是Django中MVC的视图.模板.模型了. 首先来看视图(view),在用Django生成的站点目录中,创建一个view.py文件,这个文件開始是空的.然后我们输入下面内容: ...