在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

深入解析Andoird应用开发中View的事件传递

2016-8-16 12:49| 发布者: zhangjf| 查看: 594| 评论: 0

摘要: 下面以点击某个view之后的事件传递为例子。 首先分析view里的dispatchTouchEvent()方法,它是点击view执行的第一个方法。 public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null ( ...

2016218174438007.jpg (602×339)

下面以点击某个view之后的事件传递为例子。
首先分析view里的dispatchTouchEvent()方法,它是点击view执行的第一个方法。

  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. }
复制代码

注意:里面包含两个回调函数 onTouch(),onTouchEvent();如果控件绑定了OnTouchListener,且该控件是enabled,那么就执行onTouch()方法,如果该方法返回true,则说明该触摸事件已经被OnTouchListener监听器消费掉了,不会再往下分发了;但是如果返回false,则说明未被消费,继续往下分发到该控件的onTouchEvent()去处理。

然后分析onTouchEvent()方法,进行进一步的触摸事件处理。

  1. if (((viewFlags & CLICKABLE) == CLICKABLE ||
  2. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  3. switch (event.getAction()) {
  4. case MotionEvent.ACTION_UP:
  5. .....
  6. performClick(); //响应点击事件
  7. break;
  8. case MotionEvent.ACTION_DOWN:
  9. ..... break;
  10. case MotionEvent.ACTION_CANCEL:
  11. ..... break;
  12. case MotionEvent.ACTION_MOVE:
  13. ..... break;
  14. }
  15. return true;
  16. }
  17. return false;
复制代码

如果该控件是clickable 、long_clickable的,那么就可以响应对应事件,响应完后返回true继续响应。比如点击事件,先响应ACTION_DOWN,然后break并返回true,然后手抬起,又从dispatchTouchEvent()分发下来,再响应ACTION_UP,里面会去performClick()响应点击事件。

响应点击事件

  1. public boolean performClick() {
  2. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  3. if (mOnClickListener != null) {
  4. playSoundEffect(SoundEffectConstants.CLICK);
  5. mOnClickListener.onClick(this);
  6. return true;
  7. }
  8. return false;
  9. }
复制代码

里面执行mOnClickListener.onClick(this);即回调绑定监听器的onClick()函数。

关键点:
onTouch和onTouchEvent的区别,又该如何使用?
答:
当view控件接受到触摸事件,如果控件绑定了onTouchListener监听器,而且该控件是enable,那么就去执行onTouch()方法,如果返回true,则已经把触摸事件消费掉,不再向下传递;如果返回false,那么继续调用onTouchEvent()事件。

Android的Touch事件传递到Activity顶层的DecorView(一个FrameLayout)之后,会通过ViewGroup一层层往视图树的上面传递,最终将事件传递给实际接收的View。下面给出一些重要的方法。

dispatchTouchEvent
事件传递到一个ViewGroup上面时,会调用dispatchTouchEvent。代码有删减

  1. public boolean dispatchTouchEvent(MotionEvent ev) {
  2. boolean handled = false;
  3. if (onFilterTouchEventForSecurity(ev)) {
  4. final int action = ev.getAction();
  5. final int actionMasked = action & MotionEvent.ACTION_MASK;
  6. // Attention 1 :在按下时候清除一些状态
  7. if (actionMasked == MotionEvent.ACTION_DOWN) {
  8. cancelAndClearTouchTargets(ev);
  9. //注意这个方法
  10. resetTouchState();
  11. }
  12. // Attention 2:检查是否需要拦截
  13. final boolean intercepted;
  14. //如果刚刚按下 或者 已经有子View来处理
  15. if (actionMasked == MotionEvent.ACTION_DOWN
  16. || mFirstTouchTarget != null) {
  17. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
  18. if (!disallowIntercept) {
  19. intercepted = onInterceptTouchEvent(ev);
  20. ev.setAction(action); // restore action in case it was changed
  21. } else {
  22. intercepted = false;
  23. }
  24. } else {
  25. // 不是一个动作序列的开始 同时也没有子View来处理,直接拦截
  26. intercepted = true;
  27. }
  28. //事件没有取消 同时没有被当前ViewGroup拦截,去找是否有子View接盘
  29. if (!canceled && !intercepted) {
  30. //如果这是一系列动作的开始 或者有一个新的Pointer按下 我们需要去找能够处理这个Pointer的子View
  31. if (actionMasked == MotionEvent.ACTION_DOWN
  32. || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
  33. || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  34. final int actionIndex = ev.getActionIndex(); // always 0 for down
  35. //上面说的触碰点32的限制就是这里导致
  36. final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
  37. : TouchTarget.ALL_POINTER_IDS;
  38. final int childrenCount = mChildrenCount;
  39. if (newTouchTarget == null && childrenCount != 0) {
  40. final float x = ev.getX(actionIndex);
  41. final float y = ev.getY(actionIndex);
  42. //对当前ViewGroup的所有子View进行排序,在上层的放在开始
  43. final ArrayList<View> preorderedList = buildOrderedChildList();
  44. final boolean customOrder = preorderedList == null
  45. && isChildrenDrawingOrderEnabled();
  46. final View[] children = mChildren;
  47. for (int i = childrenCount - 1; i >= 0; i--) {
  48. final int childIndex = customOrder
  49. ? getChildDrawingOrder(childrenCount, i) : i;
  50. final View child = (preorderedList == null)
  51. ? children[childIndex] : preorderedList.get(childIndex);
  52. // canViewReceivePointerEvents visible的View都可以接受事件
  53. // isTransformedTouchPointInView 计算是否落在点击区域上
  54. if (!canViewReceivePointerEvents(child)
  55. || !isTransformedTouchPointInView(x, y, child, null)) {
  56. ev.setTargetAccessibilityFocus(false);
  57. continue;
  58. }
  59. //能够处理这个Pointer的View是否已经处理之前的Pointer,那么把
  60. newTouchTarget = getTouchTarget(child);
  61. if (newTouchTarget != null) {
  62. // Child is already receiving touch within its bounds.
  63. // Give it the new pointer in addition to the ones it is handling.
  64. newTouchTarget.pointerIdBits |= idBitsToAssign;
  65. break;
  66. } }
  67. //Attention 3 : 直接发给子View
  68. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  69. // Child wants to receive touch within its bounds.
  70. mLastTouchDownTime = ev.getDownTime();
  71. if (preorderedList != null) {
  72. // childIndex points into presorted list, find original index
  73. for (int j = 0; j < childrenCount; j++) {
  74. if (children[childIndex] == mChildren[j]) {
  75. mLastTouchDownIndex = j;
  76. break;
  77. }
  78. }
  79. } else {
  80. mLastTouchDownIndex = childIndex;
  81. }
  82. mLastTouchDownX = ev.getX();
  83. mLastTouchDownY = ev.getY();
  84. newTouchTarget = addTouchTarget(child, idBitsToAssign);
  85. alreadyDispatchedToNewTouchTarget = true;
  86. break;
  87. }
  88. }
  89. }
  90. }
  91. }
  92. // 前面已经找到了接收事件的子View,如果为NULL,表示没有子View来接手,当前ViewGroup需要来处理
  93. if (mFirstTouchTarget == null) {
  94. // ViewGroup处理
  95. handled = dispatchTransformedTouchEvent(ev, canceled, null,
  96. TouchTarget.ALL_POINTER_IDS);
  97. } else {
  98. if(alreadyDispatchedToNewTouchTarget) {
  99. //ignore some code
  100. if (dispatchTransformedTouchEvent(ev, cancelChild,
  101. target.child, target.pointerIdBits)) {
  102. handled = true;
  103. }
  104. }
  105. }
  106. return handled;
  107. }
复制代码

上面代码中的Attention在后面部分将会涉及,重点注意。

这里需要指出一点的是,一系列动作中的不同Pointer可以分配给不同的View去响应。ViewGroup会维护一个PointerId和处理View的列表TouchTarget,一个TouchTarget代表一个可以处理Pointer的子View,当然一个View可以处理多个Pointer,比如两根手指都在某一个子View区域。TouchTarget内部使用一个int来存储它能处理的PointerId,一个int32位,这也就是上层为啥最多只能允许同时最多32点触碰。

看一下Attention 3 处的代码,我们经常说view的dispatchTouchEvent如果返回false,那么它就不能系列动作后面的动作,这是为啥呢?因为Attention 3处如果返回false,那么它不会被记录到TouchTarget中,ViewGroup认为你没有能力处理这个事件。

这里可以看到,ViewGroup真正处理事件是在dispatchTransformedTouchEvent里面,跟进去看看:

  1. dispatchTransformedTouchEvent
  2. private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
  3. View child, int desiredPointerIdBits) {
  4. //没有子类处理,那么交给viewgroup处理
  5. if (child == null) {
  6. handled = super.dispatchTouchEvent(transformedEvent);
  7. } else {
  8. final float offsetX = mScrollX - child.mLeft;
  9. final float offsetY = mScrollY - child.mTop;
  10. transformedEvent.offsetLocation(offsetX, offsetY);
  11. if (! child.hasIdentityMatrix()) {
  12. transformedEvent.transform(child.getInverseMatrix());
  13. }
  14. handled = child.dispatchTouchEvent(transformedEvent);
  15. }
  16. return handled;
  17. }
复制代码

可以看到这里不管怎么样,都会调用View的dispatchTouchEvent,这是真正处理这一次点击事件的地方。

  1. dispatchTouchEvent
  2. public boolean dispatchTouchEvent(MotionEvent event) {
  3. if (onFilterTouchEventForSecurity(event)) {
  4. //先走View的onTouch事件,如果onTouch返回True
  5. ListenerInfo li = mListenerInfo;
  6. if (li != null && li.mOnTouchListener != null
  7. && (mViewFlags & ENABLED_MASK) == ENABLED
  8. && li.mOnTouchListener.onTouch(this, event)) {
  9. result = true;
  10. }
  11. if (!result && onTouchEvent(event)) {
  12. result = true;
  13. }
  14. }
  15. return result;
  16. }
复制代码

我们给View设置的onTouch事件处在一个较高的优先级,如果onTouch执行返回true,那么就不会去走view的onTouchEvent,而我们一些点击事件都是在onTouchEvent中处理的,这也是为什么onTouch中返回true,view的点击相关事件不会被处理。

小小总结一下这个流程
ViewGroup在接受到上级传下来的事件时,如果是一系列Touch事件的开始(ACTION_DOWN),ViewGroup会先看看自己需不需要拦截这个事件(onInterceptTouchEvent,ViewGroup的默认实现直接返回false表示不拦截),接着ViewGroup遍历自己所有的View。找到当前点击的那个View,马上调用目标View的dispatchTouchEvent。如果目标View的dispatchTouchEvent返回false,那么认为目标View只是在那个位置而已,它并不想接受这个事件,只想安安静静的做一个View(我静静地看着你们装*)。此时,ViewGroup还会去走一下自己dispatchTouchEvent,Done!

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-5-6 15:39

Copyright 2015-2025 djqfx

返回顶部