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的值,避免做重复分发。
小结:
dispatchTouchEvent方法首先判断事件是否需要被拦截,如果需要拦截会调用onInterceptTouchEvent
,若该方法返回true,事件由ViewGroup自己处理,不在继续传递。
若事件未被拦截,将先遍历找出一个目标子View,后续事件也将交由目标子View处理。
若没有目标子View,事件由ViewGroup自己处理。此外,如果一个子View没有消费
ACTION_DOWN
类型的事件,那么事件将会被另一个子View或者ViewGroup自己消费,之后的事件都只会传递给目标子View(mFirstTouchTarget)或者ViewGroup自身。简单来说,就是如果一个View没有消费ACTION_DOWN
事件,后续事件也不会传递进来。
View
现在回头看上一节的第2、3步,不管是对子View分发事件,还是将事件分发给ViewGroup自身,最后都殊途同归,调用到了View的dispatchTouchEvent
,这就是我们这一节分析的目标。
public boolean dispatchTouchEvent(MotionEvent event) {
…
if (onFilterTouchEventForSecurity(event)) {
// 判断事件是否先交给ouTouch方法处理
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
// onTouch未消费事件,传给onTouchEvent
if (onTouchEvent(event)) {
return true;
}
}
…
return false;
}
代码量不多,主要做了三件事:
- 若View设置了OnTouchListener,且处于enable状态时,会先调用mOnTouchListener的onTouch方法
- 若onTouch返回false,事件传递给
onTouchEvent
方法继续处理 - 若最后onTouchEvent也没有消费这个事件,将返回false,告知上层parent将事件给其他兄弟View
这样,我们的分析转到了View的onTouchEvent
方法:
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
// 如果一个View处于DISABLED状态,但是CLICKABLE或者LONG_CLICKABLE的话,这个View仍然能消费事件
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
…
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
*。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-dARXHXAL-1715387249028)]
[外链图片转存中…(img-rIvKsQZf-1715387249030)]
[外链图片转存中…(img-hrVDJddL-1715387249032)]
[外链图片转存中…(img-WQEW9rxR-1715387249034)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!