前言
最近在重新系统行学习下View的事件分发机制,记录下相关内容
- 在 自定义View或ViewGroup的触摸反馈时,一般只用重写 onTouchEvent、onIntercpetEvent(ViewGroup独有) 方法
- 对于View点击事件,在父View不主动拦截时,最底层的View具有最高的优先级去捕捉事件,根据其是否接收,依次往上层传递。 在同级View 出现重叠时,依照那个在最上方那个具有更高的优先级去捕捉事件。
- 一个View 是否确定捕捉事件,主要是看dispatchTouchEvent是否返回true。但是dispatchTouchEvent的返回值基本也是被 onTounchEvent、onIntercpetEvent 两个方法所决定。
- View 可通过setTouchEventListener方法,在外部自定义 onTouchEvent方法的实现
onTouchEvent的触发
class View {
//其实就是处理了 点击、长按、右键点击等
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//判断是否可点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//当View 禁用时,虽然不会处理事件,但是也要根据clickable判断是否消费该事件,当clickable为true时,仍然消费该事件,父View无法捕捉该事件
if ((viewFlags & ENABLED_MASK) == DISABLED
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
// 以前的屏幕可能不够精确,因此扩大点击范围
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 是否可点击,即使不可点击,但是如果设置了 tooltipText属性(8.0开始有该属性),在长按时会弹出,tooltipText用于解释该控件的作用
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//处理TOOLTIP,延时1500ms后消失
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
//不支持点击,则移出各种Tap 、长按监听状态
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
//如果是按下或预按下状态(预按下状态会在滚动时(或达到按下时)被移出)
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
//当View是可以获取焦点时,且没有获取到焦点时,让其获取到焦点
//比如电视按钮,在点击ok之前,能知道当前是那个按钮,因为那个按钮获取了焦点,且被一个框子框住了
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
//如果是预按下状态,则设置为按下状态,并且后面会有个延时的抬起操作,以此让用户看到被点击的效果,延时时间为64ms
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.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
//判断点击源是否手触碰屏幕。如果是的话tooltipText的弹出将会离手指远点
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
//不可点击时,根据长按判断是否显示 tooltipText
if (!clickable) {
//该方法是通过handler.postDelay来判断的
// 如果追朔 ViewConfiguration.getLongPressTimeout源码可以发现,设备的长按触发时间是由Settings.Secure.LONG_PRESS_TIMEOUT属性决定的,一般该属性为空的情况下,默认值为500ms
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
//检测是否是鼠标的右键点击,如果是则该事件被消费,显示上下文菜单
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
//isInScrollingContainer 通过递归判断是否在一个可滚动的View中
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
//在滑动区间
if (isInScrollingContainer) {
// View被按下一般会立即变色,来标志按下了。
// 而PFLAG_PREPRESSED 表示当前为预按下状态, 此时还不会变颜色
// 什么是预按下状态?当前我按下了,但是我有可能会滑动,所以我需要等一段时间,如果过了时间没触发滑动,那View就显示按下状态
//如此给用户一种系统知道其是按下还是滑动的感觉
// 这是Android的设计理念,但是ios则不同,ios的控件在触碰到的瞬间就会被标志在Tap状态了,变颜色,直到开始滑动的时候消失
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
//CheckForTap 是一个 Runable,里面做了置空预按压状态并标注为按下状态 mPrivateFlags &= ~PFLAG_PREPRESSED;setPress(true)
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 不在滑动区间中,就将其Press状态设置为true.
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
// 移除各种状态
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
// 用于通知手指滑动了
// 触摸显示波纹效果(Android 5.0开始),波纹中心随手的移动而改变
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
// 当手出界,就认为所有操作都取消
// window的规则则即使你出界了,但是只要回来的时候还在界面内则事件出发
// 触摸点是否移出了view+slop扩展出的范围,slop的存在是为了保证在按下后轻微移出点击区域的情况下能正常判断点击
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
//移除预按下状态,移除长按状态
removeTapCallback();
removeLongPressCallback();
//如果已经是按下状态了,则重置为未按下状态
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
// 从android10 开始,增加了deepPress判断,即如果用户是用力按的情况,则直接触发长按状态
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isInScrollingContainer() {
ViewParent p = getParent();
while (p != null && p instanceof ViewGroup) {
// 只要父View是 ViewGroup且 shouldDelayChildPressedState()属性是true,就表示在可滑动View中
//shouldDelayChildPressedState()函数默认为true,如果是不可滑动的ViewGroup需要重写为false,否则会有大约100ms的延迟显示按压状态
if (((ViewGroup) p).shouldDelayChildPressedState()) {
return true;
}
p = p.getParent();
}
return false;
}
}
onInterceptTouchEvent 父View(滑动View等) 抢夺子View的事件
子View 之间,谁获得了触摸事件的所有权,谁就一直获得。但是对于父View、子View之间。即使子View获得了事件的所有权,也可能被父View夺去。
onTnterceptTouchEvent方法只有ViewGroup才有。
在正常的流程中,是父View 的onInterceptTouchEvent 方法判断是否拦截事件,在其不拦截时,才会将事件给到子View的onTouchEvent方法。
父View的 onInterceptTouchEvent
在一组事件过程中每个事件来临时都会被调用,但一旦如果返回了Ture,便表示该组事件(直到ACTION_UP结束)被父View捕捉了,将会马上调用自身的onTouchEent处理被捕获事件,且在该组事件结束前不再会调用onInterceptTouchEvent,该组事件被父View持有到事件结束。
如果后面可能要拦截事件,应该在 onInterceptTouchEvent
中记录之前的事件,做好拦截之后工作的准备。
dispatchTouchEvent
一个View 是否拦截事件,onTouchEvent和onIntercepTouchEvent 其实都不是重点,重点其实是dispatchTouchEvent,只有dispatchTouchEvent返回true才表示事件被拦截了。
一般而言dispatchTouchEvent
是不需要被重写的。遵循Android 本身流程定义即可。
View::dispatchTouchEvent
和 ViewGroup::dispatchTouchEvent
具有很大不同。
View::dispatchTouchEvent
class View {
// 在View::dispatchTouhEvent 中核心流程其实只有 onTounchEvent 的调用
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
}
ViewGroup::dispatchTouchEvent
ViewGroup::dispatchTouchEvent
的整体逻辑其实如下
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result;
if(interceptTouchEvent()) {
result = onTouchEvent();//实际应该是调用 super.dispatchTouchEvent
} else {
result = 子View 的dispatchTouchEvent();// 不确定是那个View,因为子View有可能是重叠的
}
}
ViewGroup {
@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;
//每次一个 ACTIN_DOWN 事件都是新的一组事件的开始
//此时清除之前的各种状态
// 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.
//清除所有的touch对象,确保没有子View处于被按下状态
cancelAndClearTouchTargets(ev);
//清除 触摸状态
resetTouchState();
}
// Check for interception.
final boolean intercepted;
// 检查是否拦截事件
// mFirstTouchTarget 是一堆或一个 TouchTarget的链表集合的头节点。TouchTarget指的是被按压被触摸的View链表
// 当事件为 ACTION_DOWN的时候要给子View去捕获事件的机会,
// 而mFirstTouchTarget不为空表示已有View层捕获了事件,之后根据 mGroupFlags 或 onInterceptTouchEvent来决定是否将事件给子View
// 显然如果没有子View被按下过,就不需要去考虑是否需要拦截,而ACTION_DOWN 事件则是例外,需要根据条件看子View是否会消费该事件
//mFirstTouchTarget 在多点触碰时可能就存在一个父View下有多个子View同时被触碰
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.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility 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;
// split 表示虽然非第一个按下的手指,但是触发的View与第一个按下的View不是一个,则认为也是一个新的ACTION_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 =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : 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--) {
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 (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
// 如果已经有已经正在接收事件的View,则将该事件并入
// 对于View来说,一个父View 可以有多个View正在同时被触碰,一个View也可以同时被多个手指触碰
// 此处将多个手指事件合并给一个View
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);
// 如果找到正在接受该手指事件的View,则查找是否有新的子View 可以接收到该事件,如果有,则将其加入事件TouchTarget链表中,并且dispatchTransformedTouchEvent会对event相对坐标信息、ACTION等做一个转换。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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();
}
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;
}
}
}
// 任何子View都不接受事件,则 View自身进行处理,最终调用到onTounchEvent
// 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 {
//查找需要接收该事件的TouchView,转换后进行分派
// 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 void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//清除子View禁止父View夺取事件的标志位
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
/*
用于请求父ViewGroup不要拦截事件。
作用举例:这函数的作用主要是解决左右滑动冲突。如在一个ViewPager2中的其中一个Pager中有一个ScroolView,一开始用户是向上
滑动,但是滑动过程向水平方向滑动了,触发ViewPager的左右滑动,此时ViewPager的onInterceptEvent会触发返回ture,拦截,从而导致
之后变成左右滑动,影响用户体验。而只要子ViewGroup在ACTION_DOWN之后调用了 requestDisallowInterceptTouchEvent,即可请求父View不再拦截,此时就只会继续上下滑动了。 但是当下组ACTION_DOWN事件来临之后该 FLAG_DISALLOW_INTERCEPT会被在dispacthTouchEvent中调用 resetTouchState函数清除,否则水平方向滑动将无法被父View捕捉
*/
@Override
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) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
}