█13★710097773█ 电微信同号 █供卵试管婴儿██代孕选性别生男孩 ██试管包出生██代孕男孩██代孕包出生███代孕选性别██试管婴儿███代孕生男孩█████试管婴儿代孕生男孩███供卵试管婴儿代孕███
预备知识
MotionEvent
在Android设备中,触摸事件主要包括点按、长按、拖拽、滑动等,点按又包括单击和双击,另外还包括单指操作和多指操作等。一个最简单的用户触摸事件一般经过以下几个流程:
- 手指按下
- 手指滑动
- 手指抬起
Android把这些事件的每一步抽象为MotionEvent
这一概念,MotionEvent包含了触摸的坐标位置,点按的数量(手指的数量),时间点等信息,用于描述用户当前的具体动作,常见的MotionEvent有下面几种类型:
ACTION_DOWN
ACTION_UP
ACTION_MOVE
ACTION_CANCEL
其中,ACTION_DOWN
、ACTION_MOVE
、ACTION_UP
就分别对应于上面的手指按下、手指滑动、手指抬起操作,即一个最简单的用户操作包含了一个ACTION_DOWN
事件,若干个ACTION_MOVE
事件和一个ACTION_UP
事件。
几个方法
事件分发过程中,涉及的主要方法有以下几个:
dispatchTouchEvent
: 用于事件的分发,所有的事件都要通过此方法进行分发,决定是自己对事件进行消费还是交由子View处理onTouchEvent
: 主要用于事件的处理,返回true表示消费当前事件onInterceptTouchEvent
: 是ViewGroup
中独有的方法,若返回true
表示拦截当前事件,交由自己的onTouchEvent()
进行处理,返回false
表示不拦截
我们的源码分析也主要围绕这几个方法展开。
源码分析
Activity
我们从Activity的dispatchTouchEvent
方法作为入口进行分析:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
这个方法首先会判断当前触摸事件的类型,如果是ACTION_DOWN
事件,会触发onUserInteraction
方法。根据文档注释,当有任意一个按键、触屏或者轨迹球事件发生时,栈顶Activity的onUserInteraction
会被触发。如果我们需要知道用户是不是正在和设备交互,可以在子类中重写这个方法,去获取通知(比如取消屏保这个场景)。
然后是调用Activity内部mWindow
的superDispatchTouchEvent
方法,mWindow
其实是PhoneWindow的
实例,我们看看这个方法做了什么:
public class PhoneWindow extends Window implements MenuBuilder.Callback { ... @Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ... public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } ... } }
原来PhoneWindow内部调用了DecorView的同名方法,而DecorView其实是FrameLayout的子类,FrameLayout并没有重写dispatchTouchEvent方法,所以事件开始交由ViewGroup的dispatchTouchEvent开始分发了,这个方法将在下一节分析。
我们回到Activity的dispatchTouchEvent
方法,注意当getWindow().superDispatchTouchEvent(ev)
这一语句返回false时,即事件没有被任何子View消费时,最终会执行Activity的onTouchEvent
:
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
小结:
事件从Activity的dispatchTouchEvent开始,经由DecorView开始向下传递,交由子View处理,若事件未被任何Activity的子View处理,将由Activity自己处理。
ViewGroup
由上节分析可知,事件来到DecorView后,经过层层调用,来到了ViewGroup的dispatchTouchEvent方法中:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); ... // 先检验事件是否需要被ViewGroup拦截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 校验是否给mGroupFlags设置了FLAG_DISALLOW_INTERCEPT标志位 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 走onInterceptTouchEvent判断是否拦截事件 intercepted = onInterceptTouchEvent(ev); } else { intercepted = false; } } else { intercepted = true; } ... final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; if (!canceled && !intercepted) { // 注意ACTION_DOWN等事件才会走遍历所有子View的流程 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { ... // 开始遍历所有子View开始逐个分发事件 final int childrenCount = mChildrenCount; if (childrenCount != 0) { for (int i = childrenCount - 1; i >= 0; i--) { // 判断触摸点是否在这个View的内部 final View child = children[i]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } ... // 事件被子View消费,退出循环,不再继续分发给其他子View if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... // addTouchTarget内部将mFirstTouchTarget设置为child,即不为null newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } } // 事件未被任何子View消费,自己处理 if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 将MotionEvent.ACTION_DOWN后续事件分发给mFirstTouchTarget指向的View 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; } ... } 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); } } return handled; } private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } } private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; ... // 注意传参child为null时,调用的是自己的dispatchTouchEvent if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(transformedEvent); } return handled; } public boolean onInterceptTouchEvent(MotionEvent ev) { // 默认不拦截事件 return false; }
这个方法比较长,只要把握住主要脉络,修枝剪叶后还是非常清晰的:
(1) 判断事件是够需要被ViewGroup拦截
首先会根据mGroupFlags
判断是否可以执行onInterceptTouchEvent
方法,它的值可以通过requestDisallowInterceptTouchEvent
方法设置:
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { // 层层向上传递,告知所有父View不拦截事件 mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } }
所以我们在处理某些滑动冲突场景时,可以从子View中调用父View的requestDisallowInterceptTouchEvent
方法,阻止父View拦截事件。
如果view没有设置FLAG_DISALLOW_INTERCEPT
,就可以进入onInterceptTouchEvent方法,判断是否应该被自己拦截,
ViewGroup的onInterceptTouchEvent直接返回了false,即默认是不拦截事件的,ViewGroup的子类可以重写这个方法,内部判断拦截逻辑。
注意:只有当事件类型是ACTION_DOWN
或者mFirstTouchTarget不为空时,才会走是否需要拦截事件这一判断,如果事件是ACTION_DOWN
的后续事件(如ACTION_MOVE
、ACTION_UP
等),且在传递ACTION_DOWN
事件过程中没有找到目标子View时,事件将会直接被拦截,交给ViewGroup自己处理。mFirstTouchTarget的赋值会在下一节提到。
(2) 遍历所有子View,逐个分发事件:
执行遍历分发的条件是:当前事件是ACTION_DOWN
、ACTION_POINTER_DOWN
或者ACTION_HOVER_MOVE
三种类型中的一个(后两种用的比较少,暂且忽略)。所以,如果事件是ACTION_DOWN
的后续事件,如ACTION_UP
事件,将不会进入遍历流程!
进入遍历流程后,拿到一个子View,首先会判断触摸点是不是在子View范围内,如果不是直接跳过该子View;
否则通过dispatchTransformedTouchEvent
方法,间接调用child.dispatchTouchEvent
达到传递的目的;
如果dispatchTransformedTouchEvent
返回true,即事件被子View消费,就会把mFirstTouchTarget设置为child,即不为null,并将alreadyDispatchedToNewTouchTarget设置为true,然后跳出循环,事件不再继续传递给其他子View。
可以理解为,这一步的主要作用是,在事件的开始,即传递ACTION_DOWN
事件过程中,找到一个需要消费事件的子View,我们可以称之为目标子View
,执行第一次事件传递,并把mFirstTouchTarget设置为这个目标子View
(3) 将事件交给ViewGroup自己或者目标子View处理
经过上面一步后,如果mFirstTouchTarget仍然为空,说明没有任何一个子View消费事件,将同样会调用dispatchTransformedTouchEvent,但此时这个方法的View child
参数为null,所以调用的其实是super.dispatchTouchEvent(event)
,即事件交给ViewGroup自己处理。ViewGroup是View的子View,所以事件将会使用View的dispatchTouchEvent(event)方法判断是否消费事件。
反之,如果mFirstTouchTarget不为null,说明上一次事件传递时,找到了需要处理事件的目标子View,此时,ACTION_DOWN
的后续事件,如ACTION_UP
等事件,都会传递至mFirstTouchTarget中保存的目标子View中。这里面还有一个小细节,如果在上一节遍历过程中已经把本次事件传递给子View,alreadyDispatchedToNewTouchTarget的值会被设置为true,代码会判断alreadyDispatchedToNewTouchTarget的值,避免做重复分发。
小结: