dispatchTouchEvent源码分析
来源:互联网 发布:战略岗位 知乎 编辑:程序博客网 时间:2024/05/16 17:24
简介
通过对dispatchTouchEvent事件分发的理解,了解android事件的处理机制
事件分发流程
首先先确认事件由系统传递给当前Activity,然后由Activity开始分发,主要的流程:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View
看一下Activity.dispatchTouchEvent()源码
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed.(如果这个事件被消耗,返回true) */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // 空方法 onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
源码中可以看到,在Activity dispatchTouchEvent中,调用了Window的superDispatchTouchEvent,如果getWindow().superDispatchTouchEvent(ev)返回true,则这个事件被消耗,否则调用Activity的onTouchEvent方法,看一下Window是如何分发事件的:
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
Window调用了mDecor的superDispatchTouchEvent。mDecor是什么???
// This is the top-level view of the window, containing the window decor.(这是窗口的顶层视图) private DecorView mDecor; private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } }
调用了FrameLayout 的dispatchTouchEvent,这里说一下DecorView 是什么,DecorView继承自FrameLayout它是整个界面的最外层的ViewGroup。 也就是说整个Activity的根布局外面还包了一层DecorView,我们手机的标题栏就是显示在DecorView中。至此,Touch事件就已经由Activity到了顶层的View。继续看一下DecorView 是怎么分发的:
/** * {@inheritDoc} */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } 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; if (actionMasked == MotionEvent.ACTION_DOWN) { // 第一部分 cancelAndClearTouchTargets(ev); resetTouchState(); } 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); } else { intercepted = false; } } else { intercepted = true; } if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 第二部分 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; 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; removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { //计算Touch事件的坐标 final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //判断哪个子View接收Touch事件 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); 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 = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { 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; } ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } if (mFirstTouchTarget == null) { // 第三部分(child view不能消耗事件,把自己当做view来消耗) handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { 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; } } 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 void cancelAndClearTouchTargets(MotionEvent event) { ... clearTouchTargets(); ... } private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } }
当ACTION_DOWN时进行初始化和还原操作。在cancelAndClearTouchTargets( )中将mFirstTouchTarget设置为null,且在resetTouchState()中重置Touch状态标识
第二部分:
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; }
检查是否需要ViewGroup拦截Touch事件
先看一下最外面的判断条件:
actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
ACTION_DOWN 很好理解,mFirstTouchTarget 是什么??mFirstTouchTarget 是TouchTarget类的对象,而TouchTarget是ViewGroup中的一个内部类,它封装了被触摸的View及这次触摸所对应的ID,mFirstTouchTarget 在后面还有分析,这里因为有用到,先说下FirstTouchTarget的作用
(1) mFirstTouchTarget!= null
表示ViewGroup没有拦截Touch事件并且子View消费了Touch
(2) mFirstTouchTarget == null
表示ViewGroup拦截了Touch事件或者虽然ViewGroup没有拦截Touch事件但是子View也没有消费Touch。总之,此时需要ViewGroup自身处理Touch事件
也就是说如果child view没有消费ACTION_DOWN ,之后的move和up就不会往下传递,会被parent view拦截下来。事件被拦截下来会做些什么,没有拦截又会做些什么??
我们先从intercepted = false 没有拦截来看一下:
if (!canceled && !intercepted) { ... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } .... private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } } ... private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }}
在这个步骤中只有找到了可以消费Touch事件的子View时mFirstTouchTarget才不为null;其余情况比如未找到可以接收Touch事件的子View或者子View不能消费Touch事件时mFirstTouchTarget仍为null
如果Touch事件没有被取消也没有被拦截,那么ViewGroup将类型为ACTION_DOWN的Touch事件分发给child View。
(1) child == null
ViewGroup虽然override了dispatchTouchEvent方法,但是ViewGroup的dispatchTouchEvent是没有super父类的,如果child == null,就把ViewGroup当做一个普通的View处理
(2) child != null
调用child.dispatchTouchEvent继续分发
intercepted = true 拦截
if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false);}...if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);}...
因为intercepted = true, if (!canceled && !intercepted) { … } 都不会执行,所以mFirstTouchTarget == null,child ==null ,把ViewGroup当做普通的View处理,(普通的View的dispatchTouchEvent就是调用onTouchEvent())
View的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } }}
如果onTouchEvent 返回false,那么mFirstTouchTarget就为null,就会把ViewGroup当做普通View调用dispatchTouchEvent(onTouchEvent),这也就是平时所说的如果child view消耗不掉这个事件,会返回给parent的onTouchEvent中消耗,parent view也消耗不了,继续往上传,直到传递到Activity的dispatchTouchEvent中,不过这时getWindow().superDispatchTouchEvent = false,会调用Activity的onTouchEvent。
总结
通俗语言总结一下,事件来的时候,Activity会询问Window,Window这个事件你能不能消耗,Window一看,你先等等,我去问问DecorView他能不能消耗,DecorView一看,onInterceptTouchEvent返回false啊,不让我拦截啊,遍历一下子View吧,问问他们能不能消耗,那个谁,事件按在你的身上了,你看看你能不能消耗,RelativeLayout一看,也没有让我拦截啊,我也得遍历看看这个事件发生在那个子View上面,那个TextView,事件在你身上,你能不能消耗了他。TextView一看,消耗不了啊,RelativeLayout一看TextView消耗不了啊,mFirstTouchTarget==null啊,得,我自己消耗吧,嗯!一看自己的onTouchEvent也消耗不了啊!那个DecorView事件我消耗不了,DecorView一看自己,我也消耗不了,继续往上传,那个Window啊。事件我消耗不了啊,Window再告诉Activity事件消耗不了啊。Activity还得我自己来啊。调用自己的onTouchEvent,还是消耗不了,算了,不要了。
伪代码
public boolean dispatchTouchEvent(MotionEvent ev) { boolean result = false; // 默认状态为没有消费过 if (!onInterceptTouchEvent(ev)) { // 如果没有拦截交给子View result = child.dispatchTouchEvent(ev); } if (!result) { // 如果事件没有被消费,询问自身onTouchEvent result = onTouchEvent(ev); } return result;}
如果子View连ACTION_DOWN都不能消耗掉 ,则mFirstTouchTarget == null,那么后续的ACTION_UP,ACTION_MOVE子View也不会再收到
- dispatchTouchEvent源码分析
- Android dispatchTouchEvent源码分析
- android关于dispatchTouchEvent和onTouchEvent的源码实验分析
- dispatchTouchEvent源码解析
- dispatchTouchEvent源码解析
- dispatchTouchEvent小分析
- Android viewGoup.dispatchTouchEvent(ev)源码
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- Android触摸屏事件派发机制详解与源码分析三(Activity篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- dispatchTouchEvent, interceptTouchEvent,onTouchEvent 详细分析
- Android onTouchEvent,dispatchTouchEvent,onInterceptTouchEvent分析
- dispatchTouchEvent
- dispatchTouchEvent
- dispatchTouchEvent
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程(一)
- Android View系统源码分析(三)—— 根View内部消息派发过程&ViewGroup.dispatchTouchEvent()
- Android View系统源码分析(四)—— 各种消息监测的基本实现方法&View.dispatchTouchEvent()
- 从源码角度带你分析 Android View 事件分发 dispatchTouchEvent,onTouch,onTouchEvent,onClick逻辑顺序过程
- 解决微信小程序switchTab后tab不刷新
- Objective-c 中@property中属性详解
- 设计模式:访问者模式-vistor
- Android中View的事件分发机制
- 索引问题总结
- dispatchTouchEvent源码分析
- 两个栈实现队列
- 《万物本源》--我的新书博客预售
- MATLAB基本运算(1)
- Scala中的类class apply使用
- Echarts 占用CPU高,导致浏览器卡顿问题。
- python 读取oracle的方法
- Python3包管理
- 关于RecyclerView的宽高调整