一、事件分发
来源:互联网 发布:怎么申请淘宝直播 编辑:程序博客网 时间:2024/05/22 16:21
有一段时间没更博客了,想象我写博客的初衷主要有2个原因:
- 如果有人看到了,从中找到一点能帮助到他的东西,这样最好不过了。
- 写给自己看。我相信整理一遍写一遍对自己的理解和认知有很大的帮助。
最近闭关修炼,哈哈哈,说的有点魔幻。主要最近对安卓的看法有了一点新的看法,看了一个公众号的推送,有个人将安卓的学习和进阶分为了5个部分:
- UI
- 性能
- NDk
- 架构
- 其他
这样分是基于项目而言的,不同类型不同定位的项目这5个部分所占的比重也是不一样的。比如有些项目就要UI好看,用户看你UI好看就会极大的提升兴趣。
当然我最近学习这些东西不是因为看了这个公众号然后就去学UI相关的东西,而是我在学的过程中发现了这个公众号,我就觉得这样分也比较合理,就顺带提了一下。
回归主题:今天我要说的是我学习的第一个东西:
事件分发
之前一直学习这块,但是也是含含糊糊,这次静下心来好好的梳理了一遍,算是掌握了7-8分吧,但还有几个问题没弄明白,百度了也没百到,问了大神也没得到我想要的答案,然后就不了了之了。这些问题我记在心中,我相信在不远的将来肯定会遇到我的师傅,他肯定会为我传道解惑。
好了下面开始我的表演:
我之前只知道事件分发是从父view传递到子view,但是我不知道起点是那里。最近看了一篇博客,链接忘记了,我感到很羞耻,其实以我的水平我应该能想到是Activity,但是之前我没想到,但我现在知道了,哈哈哈。
为什么说是Activity呢,因为Activity是负责与用户交互的啊,那么我的手势操作不应该被Activity所捕捉然后进行下一步吗?
那么Activity是怎么捕捉到我们的手势的呢?这个我不知道,跟硬件或底层实现有关了,我只知道最终会从来到Activity的dispatchTouchEvent()方法:
1. public boolean dispatchTouchEvent(MotionEvent ev) { 2. 3. //如果是按下状态就调用onUserInteraction()方法,onUserInteraction()方法 4. //是个空的方法, 我们直接跳过这里看下面的实现 5. if (ev.getAction() == MotionEvent.ACTION_DOWN) { 6. onUserInteraction(); 7. } 8. 9. if (getWindow().superDispatchTouchEvent(ev)) { 10. return true; 11. } 12. 13. //getWindow().superDispatchTouchEvent(ev)返回false,这个事件就交给Activity 14. //来处理, Activity的onTouchEvent()方法直接返回了false 15. return onTouchEvent(ev); 16. }
根据注释我们可以看到,第9行如果返回true就说明有view能处理我们的事件,那么就不会调用Activity的onTouchEvent()方法;如果第9行返回false,那么就说明没有view能响应我们的事件,那么事件就交给Activity处理。
我们进入第9行的getWindow().superDispatchTouchEvent(ev)方法内部:
它是调用Window类的superDispatchTouchEvent(ev)方法,但是我们进入Window类发现这个方法是个抽象方法,而Window也是一个抽象类。Window就是我们的窗口,就是最顶层的一个承载窗体view的东西。他只有一个子类PhoneWindow,我们直接进入PhoneWindow类的superDispatchTouchEvent(ev)方法:
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
mDecor是什么呢?他是DecorView的实例,这个DecorView是一个继承FrameLayout的的ViewGroup,在之前的版本中,它是作为PhoneWindow的内部类的形式出现,在之后的版本中它被独立出来作为一个单独的类。他的作用就是存放我们在Activity中设置的布局View,也就是说我们在Activity中通过setContentView(int resid)方法设置的布局,最终会绑定到DecorView上。看一张图:
图中DecorView中还有一个LinearLayout,因为要存放2个FrameLayout:一个是顶部的ActionBar,一个是我们给activity设置的布局。
好了,我们继续看DecorView类中的superDispatchTouchEvent(event)方法:
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
调用的是父类的dispatchTouchEvent(event),我们知道他是继承FrameLayout的,所以我们去ViewGroup中看dispatchTouchEvent(event)方法:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. //预处理下 if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. //检查是否拦截事件 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); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. //cancle检查 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; // 1、这个if是在没有拦截也没有取消事件时执行的 if (!canceled && !intercepted) { // If the event is targeting accessiiblity focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; //从down开始 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. //移除之前点击的这个点的位置的一些操作 removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; //拿到child if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. //这句话的意思就是根据用户的触摸位置找到相应的子view,添加进集合中,然后根据子view的count数反向遍历,为什么要反向遍历,因为view是一层一层覆盖的啊(比如Relativelayout)。然后我们找到最上层的子view,让他去相应事件 final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //这个newTouchTarget就是在链表里找到的接收事件的view对应的newTouchTarget ,这个newTouchTarget 应该包含了一些信息吧,如果不=null,说明这个view已经接收到事件了 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //这个if方法里面就是去调用该子view的dispatchtouchevent方法,下面会贴源码继续分析 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. //如果这个子view的dispatchtouchevent返回true ,应该这一套下来有一个不管是view还是ViewGroup能处理事件才会返回true mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } //没找到接收事件的child if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } //2、接下来的if else逻辑是当拦截了或者是cancle事件时执行。 // Dispatch to touch targets. if (mFirstTouchTarget == null) { //拦截了,就自己去处理 // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //这里应该是cancle事件吧 // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
新版API(我这是API26)做了一些改动,有些东西提出去做了,有些东西整合进来了。分析起来有点吃力,之前看的是老版本的API,简单一些,但主体都差不多。
总结一下:对于ViewGroup的dispatchTouchEvent方法:
首先看down是否拦截,如果没拦截,根据我们点击的位置找到子view,然后调用它的dispatchTouchEvent方法,如果这个子view的dispatchTouchEvent方法返回true,那么ViewGroup也返回true。如果子viewdispatchTouchEvent返回false,代表不能处理事件,那么对于ViewGroup而言就要自己处理了,他会先判断是是cancle还是up事件,如果是cancle或者up得话,就将拦截事件的标志置为默认的false(可以理解当我们设置了拦截事件后,我们抬起手指或取消touch的时候会将这个标志置为false。所以说disallowIntercept在我们每次down的时候都是false。)。然后因为ViewGroup自己处理,就要调用ViewGroup的super.dispatchtouchevent方法,如果是up或者cancle,那么就传入up或cancle事件,并返回。下面还有,下面的逻辑down事件不会走,move和up可能会走到这里,如果move或up拦截了事件的话,那么就将cancle事件交给子view,ViewGroup返回true,表示消费了事件。
如果调用,那么最终会调用到我们想要点击的子view,执行他的dispatchtouchevent方法。然后根据返回情况向上返回,就是ViewGroup的dispatchtouchevent返回值是根据其子view的dispatchtouchevent返回值而定的。
有一张图:
view的dispatchtouchevent方法:
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
如果view设置了touchlistener,enable,而且ontouch方法返回了true的话,就不会执行ontouchevent了,那么点击事件就无法执行了。否则就执行ontouchevent方法。
我们看onTouchEvent方法:
1. public boolean onTouchEvent(MotionEvent event) { 2. final int viewFlags = mViewFlags; 3. 4. if ((viewFlags & ENABLED_MASK) == DISABLED) { 5. return (((viewFlags & CLICKABLE) == CLICKABLE || 6. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 7. } 8. 9. //如果设置了Touch代理,就交给代理来处理,mTouchDelegate默认是null 10. if (mTouchDelegate != null) { 11. if (mTouchDelegate.onTouchEvent(event)) { 12. return true; 13. } 14. } 15. 16. //如果View是clickable或者longClickable的onTouchEvent就返回true, 否则返回false 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. boolean focusTaken = false; 24. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 25. focusTaken = requestFocus(); 26. } 27. 28. if (!mHasPerformedLongPress) { 29. removeLongPressCallback(); 30. 31. if (!focusTaken) { 32. if (mPerformClick == null) { 33. mPerformClick = new PerformClick(); 34. } 35. if (!post(mPerformClick)) { 36. performClick(); 37. } 38. } 39. } 40. 41. if (mUnsetPressedState == null) { 42. mUnsetPressedState = new UnsetPressedState(); 43. } 44. 45. if (prepressed) { 46. mPrivateFlags |= PRESSED; 47. refreshDrawableState(); 48. postDelayed(mUnsetPressedState, 49. ViewConfiguration.getPressedStateDuration()); 50. } else if (!post(mUnsetPressedState)) { 51. mUnsetPressedState.run(); 52. } 53. removeTapCallback(); 54. } 55. break; 56. 57. case MotionEvent.ACTION_DOWN: 58. if (mPendingCheckForTap == null) { 59. mPendingCheckForTap = new CheckForTap(); 60. } 61. mPrivateFlags |= PREPRESSED; 62. mHasPerformedLongPress = false; 63. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 64. break; 65. 66. case MotionEvent.ACTION_CANCEL: 67. mPrivateFlags &= ~PRESSED; 68. refreshDrawableState(); 69. removeTapCallback(); 70. break; 71. 72. case MotionEvent.ACTION_MOVE: 73. final int x = (int) event.getX(); 74. final int y = (int) event.getY(); 75. 76. //当手指在View上面滑动超过View的边界, 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. removeLongPressCallback(); 84. 85. mPrivateFlags &= ~PRESSED; 86. refreshDrawableState(); 87. } 88. } 89. break; 90. } 91. return true; 92. } 93. 94. return false; 95. }
这个就是处理点击事件了,长点击在down里面执行,短点击在up里执行。
down里面会发送一个post延迟消息,用来判断是否是长点击事件即是否达到系统设置的长点击事件时间,而且要设置了onLongClickListener的话,达到事件后就去执行长点击事件,如果onLongClick返回了true,那么会有一个标志mHasPerformedLongPress被置为true,相当于表示事件被消费了。在up事件中if会对这个标志取反,如果被消费了,那么就不会进入if里面,那么点击事件不会得到执行了。但是up这个时间还是会执行的,只不过不执行点击事件了。
事件分发应该就是这样大致的流程,有几点注意:
- 我们重写拦截事件的方法去拦截事件,默认拦截的是down事件。如果想拦截其他事件,那么就需要在方法里面对ev进行判断然后进行拦截操作。
- 如果在ViewGroup中对down拦截了,那么就会调用其的super.dispatchtouchevent方法,也就是view的,但是ViewGroup是没有设置ontouchlistener的,而且默认不能点击,所以根据情况自己去做相应的处理,默认不做处理的话就直接返回false了。
- 如果对move或者up事件进行拦截的话,子view得不到这些事件,点击事件无法执行,但是长点击事件是可以执行的,因为长点击是在down里面做的,这种情况ViewGroup就会将cancle交给子view,并直接返回true。
就到这里为止吧,以后项目中遇到什么问题再来补充,学习的过程也是一个不断纠正错误的过程。
下一篇我要写从启动Activity到用户可以点击view的这个过程发生了什么。
- Android 事件分发一
- 事件分发机制(一)
- Android事件分发<一>
- 一、事件分发
- [cocos2dx]事件分发机制(一)
- android事件分发(一)
- Android事件分发分析(一)
- 事件分发、拦截、消费(一)
- Android事件分发机制(一)
- android 事件分发机制一
- android viewGroup事件分发一
- Android事件的分发(一)
- Android事件分发 (一)
- android事件分发机制一
- 事件分发机制(一)
- Android事件分发机制一
- View的事件分发机制一:事件分发概述
- 笔记:事件分发机制(一):View的事件分发
- 子网掩码
- 一些js语法和html5画布的内容以及一点点网页内容(week7学习)
- python -- 机器人行走
- NiosII开发常见问题(转)
- 4. 自动售票机例子
- 一、事件分发
- Andrew Ng机器学习笔记ex8 异常检测和推荐系统
- leetcode 458. Poor Pigs
- jQuery 的正则表达式验证
- 什么是跨域?如何解决
- python -- 机器人行走步数问题
- NEUQACM OJ 1590
- 洛谷 P1251 餐巾计划问题
- 中文乱码问题和拦截器