内容简介:从当一个点击操作发生时,最先传递给 当前 Activity,由 Activity 的 dispatchTouchEvent 方法分发。我们看下 Activity 的 dispatchTouchEvent 的具体源码:继续查看 window 的 superDispatchTouchEvent 源码,window 是一个抽象类,有一个唯一的实现类 PhoneWindow(这一点可以在 window 的类注释中查看到),那么我们就找 PhoneWindow 的 superDispatchTouchEvent 方法
Activity ---> ViewGroup流程分析
从 View的事件分发(一)分发流程 文章得知,从用户点击屏幕到屏幕中的控件响应操作的大致流程是 Activity-> ViewGroup-> View。那么 Activity 是怎么把事件传递给我们在 xml 中写的根视图呢?就是说 Activity-> ViewGroup 具体是一个怎样的流程?
当一个点击操作发生时,最先传递给 当前 Activity,由 Activity 的 dispatchTouchEvent 方法分发。我们看下 Activity 的 dispatchTouchEvent 的具体源码:
//Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//调用 window 的 superDispatchTouchEvent 方法进行事件的分发
//如果返回 true,就代表事件被消费, Activity 的 dispatchTouchEvent 就直接返回 true,不再往下执行。
//如果返回 false,就代表事件没有被消费,就调用 Activity 的 onTouchEvent 方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
继续查看 window 的 superDispatchTouchEvent 源码,window 是一个抽象类,有一个唯一的实现类 PhoneWindow(这一点可以在 window 的类注释中查看到),那么我们就找 PhoneWindow 的 superDispatchTouchEvent 方法。
//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor 是 DecorView
//这里调用 DecorView 的 superDispatchTouchEvent 方法
return mDecor.superDispatchTouchEvent(event);
}
复制代码
继续查看 DecorView 的 superDispatchTouchEvent 方法
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
//调用父类的 dispatchTouchEvent 方法进行分发.
return super.dispatchTouchEvent(event);
}
复制代码
我们知道可以通过 ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0); 来找到 Activity 所设置的 View,PhoneWindow 中调用的 mDecor 就是 getWindow().getDecorView() 获取的 DecorView,我们通过 Activity 所设置的 View 就是它的子 View。DecorView 是继承的 FrameLayout,当然 FrameLayout 是没有重写 dispatchTouchEvent 方法的,所以最终就是通过 FrameLayout 的父类 ViewGroup 的 dispatchTouchEvent 方法来将事件分发给我们开发的页面。
总结
完整的事件分发流程: Activity ---> PhoneWindow ---> DecorView ---> ViewGroup ---> ··· --->View
ViewGroup ---> View流程分析
在分析 ViewGroup 的 dispatchTouchEvent 方法之前,先分析一个关键的类: TouchTarget 。
TouchTarget是 ViewGroup 中的一个静态内部类。记录了响应 Touch 事件 View 的链表。在 ViewGroup 中,有一个成员变量 private TouchTarget mFirstTouchTarget ,这个变量在 dispatchTouchEvent 分发 Touch 事件中起了重要作用。
private static final class TouchTarget {
//链表的最大长度
private static final int MAX_RECYCLED = 32;
//用于控制同步的锁锁
private static final Object sRecycleLock = new Object[0];
//内部用于复用的表头
private static TouchTarget sRecycleBin;
//可复用的实例链表的长度
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// The touched child view.
//记录响应 Touch 事件的 View
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
// 对目标捕获的所有指针的指针id的组合位掩码,和多点触控有关。
public int pointerIdBits;
// The next target in the target list.
//链表中的下一个记录触摸 View 的 TouchTarget 对象
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
//应用中,首次被触摸的时候 sRecycleBin 是为 null 的。创建一个新对象
target = new TouchTarget();
} else {
//将链表的表首的对象赋值为 target,sRecycleBin 指向下一个对象等待下一次复用。
target = sRecycleBin;
//表头 sRecycleBin 往下移一位,指向 next 对象。
sRecycleBin = target.next;
//可复用的实例链表的长度减一
sRecycledCount--;
//将 target 的 next 赋值为 null
target.next = null;
}
}
//记录响应 Touch 事件的 View。
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
//回收 TouchTarget 对象,后面复用
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
//复用链表的长度最大不超过 MAX_RECYCLED
if (sRecycledCount < MAX_RECYCLED) {
//在链表的最前面加一个可复用的对象作为表头。sRecycleBin 指向这个对象。
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
复制代码
mFirstTouchTarget 是通过 TouchTarget.obtain 方法赋值的(这个后面会说),应用首次被触摸的时候是通过 new 一个对象来赋值的,后面触摸的时候,是复用了链表首部对象。最后通过 recycle() 方法用来回收自己,用来以后复用。
首先看 ViewGroup 的 dispatchTouchEvent 方法:
分析之前,我们先确定两个疑问:
- 问题一:ViewGroup 是怎么分发给子 View 的?
- 问题二:关于 requestDisallowInterceptTouchEvent
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) {
//取消并清除触摸目标,里面将 mFirstTouchTarget 置为 null。
// mFirstTouchTarget 是记录响应触摸事件 View 的链表。
cancelAndClearTouchTargets(ev);
//如果是 DOWN 事件,就重置触摸状态。
//将 mGroupFlags 重置 FLAG_DISALLOW_INTERCEPT 标志位,这个是和 requestDisallowInterceptTouchEvent 相关的,后面再进行解释。
resetTouchState();
//这两个方法内部都调用了 clearTouchTargets() 方法,回收 mFirstTouchTarget 指向的链表上的对象,将 mFirstTouchTarget 置为null。
}
①------------
//检查是否拦截
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//是否不允许拦截
//(requestDisallowInterceptTouchEvent 就是通过给 mGroupFlags 添加FLAG_DISALLOW_INTERCEPT 标志位来告诉父控件不要拦截的)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//如果允许拦截,那就调用 onInterceptTouchEvent 方法查询是否拦截,记录返回值
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//如果不允许拦截,直接将 intercepted 赋值为 false
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//如果不是DOWN 事件,并且 mFirstTouchTarget 为 null,这个 ViewGroup 就继续拦截事件
//如果能够执行到这里,说明 DOWN 后面的事件传递到了这里,
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.
//是否自己被拦截,或者传递的是 CANCEL 事件
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;
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 事件。
//(ACTION_POINTER_DOWN 是一个手指按下的后,另一个手指也按下传递的 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;
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.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
②------------
//遍历子 View
//注意 这里是倒序遍历的,先看最上面的 View 是否响应操作事件,其实这也是符合我们的直觉的.
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;
}
③------------
//查看子 View 是否在当前点击区域,如果不在就跳过这次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//判断这个 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);
④------------
//这里调用了 dispatchTransformedTouchEvent 方法,
//这个方法里面调用子 View 的 dispatchTouchEvent。(后面说明)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//如果 dispatchTransformedTouchEvent 返回了 true,就代表这个 child 消费了这个事件
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();
⑤------------
//调用 addTouchTarget 方法,为 mFirstTouchTarget 进行赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//没有找到响应控件的 View,但是 mFirstTouchTarget 不为null(之前存在响应事件的 View)
//例如:一个手指按在一个按钮上,另个一手指按在其他控件上(没有消费事件)
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;
//遍历链表,将 newTouchTarget 指向链表的尾部的对象。
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
⑥------------
//如果 mFirstTouchTarget 为null,没有子 View 消费事件
//就通过 dispatchTransformedTouchEvent 调用自身的 onTouchEvent.
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 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) {
//DOWN 事件传递过来的时候,是走到这里的。这一点在前面的流程中有体现。
handled = true;
} else {
// target.child 是否被设置了 CANCEL 事件,或者传递过来的是否是 CANCEL 事件。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
⑦------------ //对于非 DOWN 事件,也是在这里调用 dispatchTransformedTouchEvent 方法进行分发。
/*
*如果父控件拦截事件 intercepted 为true,则 cancelChild 为 true。
*在调用 dispatchTransformedTouchEvent 进行分发事件的时候,传递了一个 CANCEL 事件给子 View(后面说明)
*/
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
⑧------------
//如果表头被回收, mFirstTouchTarget 指向下一个对象。
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) {
// UP 事件的时候,重置触摸状态
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;
}
---------------------------------------------------------------------
//ViewGroup 类
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
//创建一个 TouchTarget 对象
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
//将 mFirstTouchTarget 赋值给 target 的 next。第一次赋值的话,mFirstTouchTarget肯定是 null了。
target.next = mFirstTouchTarget ;
//mFirstTouchTarget 赋值为 target
mFirstTouchTarget = target;
return target;
}
---------------------------------------------------------------------
//ViewGroup 类
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
//遍历链表,找到该 child 所在的 TouchTarget 对象。
if (target.child == child) {
return target;
}
}
return null;
}
---------------------------------------------------------------------
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
//遍历链表,依次调用链表上TouchTarget对象的 recycle 方法。
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
//将 mFirstTouchTarget 置为null。
mFirstTouchTarget = null;
}
}
复制代码
dispatchTransformedTouchEvent 方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//cancel为 true,或者传递的是一个 CANCEL 事件,设置 cancel 事件
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//如果 child 为空,就调用父类(View)的 dispatchTouchEvent 方法来分发
//(里面调用了 onTouchEvent 方法,后面进行分析)
handled = super.dispatchTouchEvent(event);
} else {
//child 不为 null,就调用子类的分发
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 (newPointerIdBits == 0) {
return false;
}
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);
//将事件分发给子 View
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;
}
复制代码
-
问题一:ViewGroup 是怎么分发给子 View 的?
DOWN 事件:通过上述源码分析,ViewGroup 在进行事件分发的时候,先在 DOWN 事件的时候重置一些状态,然后在 代码① 处进行询问是否进行拦截,因为首次传入的事件是 ACTION_DOWN 事件,并且 mFirstTouchTarget 被重置为 null,所以就调用 onInterceptTouchEvent 询问是否拦截。如果 不拦截 就执行到 代码② ,进行遍历子 View,寻找在点击区域的子类( 代码③ ) ,然后通过( 代码④ ) dispatchTransformedTouchEvent 方法调用该 child 的 dispatchTouchEvent 方法进行分发。如此事件就分发到了子 View。
此时,如果子 View 消费了 DOWN 事件( child 的 dispatchTouchEvent 返回 true ---> dispatchTransformedTouchEvent 方法返回 true)。就执行到了 代码⑤ ,通过 addTouchTarget 将该 View 绑定到 TouchTarget 链表中,并且是在表头位置,并且将 newTouchTarget 和 mFristTouchTarget 都赋值指向表头。如果不消费,就进行下一次循环,寻找消费该事件的子 View。
那么,如果 ViewGroup 进行事件 拦截 呢?事件分发该怎么执行?分为两种情况:1. 直接在 DOWN 事件的时候进行拦截;2. 拦截 MOVE、UP 事件。
- 如果直接拦截 DOWN 事件,mFirstTouchTarget 为 null,intercepted 为 true。那么直接执行到 代码⑥ ,通过 dispatchTransformedTouchEvent 源码可知,直接执行了super.dispatchTouchEvent(event)。View (ViewGroup 父类是 View)的 dispatchTouchEvent 方法内部调用了 onTouchEvent 方法,从而事件传递到了 ViewGroup 的 onTouchEvent 方法。
- MOVE、UP 事件到来的时候开始拦截,此时会产生 CANCEL 事件。具体分析在 延伸知识一:CANCEL 事件产生的内部实现。 。
MOVE、UP 事件:MOVE、UP 事件能够传递过来,那就说明有子 View 消费了 DOWN 事件。mFristTouchTarget 不为 null。代码执行到 代码⑦ ,通过 dispatchTransformedTouchEvent 方法将事件分发给消费了 DOWN 事件的子 View。
-
延伸知识一:CANCEL 事件产生的内部实现。
CANCEL 事件产生的条件就是,子 View 消费了 DOWN 事件,在 MOVE、UP 事件中,父View 拦截了事件。在 View的事件分发(一)分发流程 中的 ==CANCEL 事件模拟==中看到的日志是: 在第一个 MOVE 事件中,子 View 收到了 CANCEL 事件,第二个 MOVE 事件的时候,ViewGroup 的 onTouchEvent 才接收收到事件。 那么我们分析一下流程:
由于子 View 消费了 DOWN 事件,所以 mFirstTouchTarget 不为 null 。在第一个 MOVE 事件来临的时候,代码执行到了 代码① ,此时 onInterceptTouchEvent 返回 true,所以 intercepted 被赋值为 true。然后代码执行到了 代码⑦ 。intercepted 为true, cancelChild 自然也就为 true。通过 dispatchTransformedTouchEvent 源码可知,cancel 为 true 时,就设置了一个 CANCEL 事件给 child。在 代码⑧ 中,由于此时是单点触摸,mFirstTouchTarget 指向的链表只有绑定这个 View 的TouchTarget对象,next 为 null,所以 mFirstTouchTarget 赋值为 null。如此,child 在父控件拦截的时候就收到了一个 CANCEL 事件。
第二个 MOVE 事件:由于此时 mFirstTouchTarget 为null,就直接调用了 代码⑥ ,通过 dispatchTransformedTouchEvent 调用了自身的 onTouchEvent 方法。
也可以看出,一旦 ViewGroup 拦截之后, mFirstTouchTarget 重置为 null,后续事件来的时候直接将 intercepted 设置为 true,而不再调用 onInterceptTouchEvent 方法。
-
问题二:关于 requestDisallowInterceptTouchEvent
requestDisallowInterceptTouchEvent 本身是 ViewParent 中的方法,ViewParent 是一个接口,ViewGroup 实现了它。我们子类 View 中通过调用
getParent().requestDisallowInterceptTouchEvent(true);来达到请求父控件不拦截。//ViewGroup 类 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } //给 mGroupFlags 设置标识。 if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } } 复制代码在 代码块① 中,可以看到先通过
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;来进行判断子类 View 是否设置了不允许拦截标识,然后再对自身是否拦截进行判断。如果不允许拦截将 intercepted 设置为 false。在 DOWN 事件中,ViewGroup 会先重置 mGroupFlags 标识。所以并不是说子 View 在请求一次不拦截后,父控件就永远不拦截。
private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); //重置 mGroupFlags mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; } 复制代码
View 的 dispatchTouchEvent 方法
//View 类
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果设置了 mOnTouchListener ,并且 onTouch 返回 true
//就直接 返回结果 true
result = true;
}
//否则,调用 View 的 onTouchEvent 方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
复制代码
View 的 onTouchEvent 方法。
public boolean onTouchEvent(MotionEvent event) {
//------代码省略-------
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//一个不可用的 View 仍然是可以消费这个事件的,只是没有响应而已。
//根据 clickable 的值来决定是否消费这个事件。
return clickable;
}
//是否设置了触摸代理 (如果尝试扩大 View 的 touch 范围,可以使用触摸代理)
if (mTouchDelegate != null) {
//如果设置了触摸代理,就将事件 event 传递个触摸代理的 onTouchEvent 方法来处理。
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果 clickable 为 true ,就执行下面的代码,并返回 true 消费事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//------代码省略-------
break;
case MotionEvent.ACTION_DOWN:
//------代码省略-------
break;
case MotionEvent.ACTION_CANCEL:
//------代码省略-------
break;
case MotionEvent.ACTION_MOVE:
//------代码省略-------
break;
}
return true;
}
return false;
}
复制代码
可以看出,View 的 dispatchTouchEvent 中,首先进行判断是否设置了 mOnTouchListener ,如果设置了 mOnTouchListener 并且 mOnTouchListener.onTouch 返回 true,则 View 的 dispatchTouchEvent 方法直接返回 true, 不会调用 View 的 onTouchEvent 方法。否则调用 View 的 onTouchEvent 方法。
在 View 的 onTouchEvent 方法中,我们可以看到如果设置了点击事件和长按事件,那么 onTouchEvent 一定会返回为 true了,也就是消费了事件。最终,事件走到这里就算结束了。
由于 OnLongClickListener 和 OnClickListener 的回调在 onTouchEvent 方法中调用,所以 当我们设置了 setOnTouchListener 并且 onTouch 返回 true 的时候,setOnLongClickListener 和 setOnClickListener 是不会被回调的。
View 的 TouchEvent 详细流程: View的事件分发(三)源码分析
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Android事件分发源码归纳
- View的事件分发(三)源码分析
- Android事件分发机制[-View-] 源码级
- 基于源码分析 Android View 事件分发机制
- Laravel Queue——消息队列任务与分发源码剖析
- Android读书笔记--从源码角度剖析View事件分发机制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
XForms Essentials
Micah Dubinko / O'Reilly Media, Inc. / 2003-08-27 / USD 29.95
The use of forms on the Web is so commonplace that most user interactions involve some type of form. XForms - a combination of XML and forms - offers a powerful alternative to HTML-based forms. By pro......一起来看看 《XForms Essentials》 这本书的介绍吧!
Markdown 在线编辑器
Markdown 在线编辑器
RGB HSV 转换
RGB HSV 互转工具