解惑requestDisallowInterceptTouchE

作者: HitenDev | 来源:发表于2016-12-01 21:44 被阅读1256次

最近在看官方控件源码时,无意间看到某些代码,让我想起有很多用requestDisallowInterceptTouchEvent来解决ScrollView和ViewPager冲突的例子,包括任玉刚写的《Android开发艺术探索》一书也提到这种方式,但是关于requestDisallowInterceptTouchEvent,你真的了解了吗?

这是网上写的最多的用requestDisallowInterceptTouchEvent解决ScroolView相关的滑动冲突例子,确实是正确姿势;

publicbooleanonTouchEvent(MotionEventevent){
switch(event.getAction()){
caseMotionEvent.ACTION_MOVE:
parent.requestDisallowInterceptTouchEvent(true);
break;
caseMotionEvent.ACTION_UP:
caseMotionEvent.ACTION_CANCEL:
parent.requestDisallowInterceptTouchEvent(false);
break;
}
}

疑问一.

parent.requestDisallowInterceptTouchEvent的调用为什么要写在onTouchEvent方法中,而不是构造方法或生命周期方法?

看一眼ViewGroup中关于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) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

官方是用mGroupFlags 和FLAG_DISALLOW_INTERCEPT按位运算,最终得到(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0这一bool值来标识是否拦截
现在再看看dispatchTouchEvent方法是怎样处理拦截事件的;
dispatchTouchEvent部分源码:


 // 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.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
  // Check for interception.
            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); // 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;
            }

dispatchTouchEvent每次接受到点击事件时,会初始化触摸状态,然后判断disallowIntercept是否为true,如果为true,不执行onInterceptTouchEvent,下面再看看resetTouchState()方法是如何实现的:

/**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

resetTouchState()会重置mGroupFlags标识,看到这句代码没:mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;这句代码会重置mGroupFlags对FLAG_DISALLOW_INTERCEPT的状态,你可以理解成把disallowIntercept置为false,所以得到结论,在dispatchTouchEvent中,每次触发按下事件时,disallowIntercept置为false,所以就解释了为什么在子view中的构造方法或生命周期方法调用parent.requestDisallowInterceptTouchEvent会失效;

额外知识
为什么(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0能标识一个指定的boolean类型,这就是考验你计算机基础是时候了,二进制知识和几个按位操作符 & | ~ ,你还记得吗?

mGroupFlags是一个16进制整形,可以标识很多状态,每一位标识一个状态
在ViewGroup中FLAG_DISALLOW_INTERCEPT的值为0x80000,化成二进制,就是1000000000000000000
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT操作得到的是 ??...(1/0)???????????????????,就是说mGroupFlags化成二进制的第20位标识的是DISALLOW_INTERCEPT,这样运算的好处是,不影响mGroupFlags其他位的数值
(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0这个运算,唯一有效的就是第20位,因为其他位都是0,&的结果还是0,没有意义,所以第20位为0时,运算结果为false,第20位为1时,运算结果为true

疑问二.

** 既然parent已经做了拦截,事件又是如何传递到child view的onTouchEvent方法中的?**

这样疑问可能有很多同学也纳闷过,无奈网上没人回答,其实翻看ScrollView源码就能看明白了,现在我们只有自己看ScrollView源码了;

onInterceptTouchEvent方法中ACTION_DOWN处理:

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {
  ...此处省略很多代码
    case MotionEvent.ACTION_DOWN: {
                final int y = (int) ev.getY();
                if (!inChild((int) ev.getX(), (int) y)) {
                    mIsBeingDragged = false;
                    recycleVelocityTracker();
                    break;
                }

                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionY = y;
                mActivePointerId = ev.getPointerId(0);

                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                /*
                 * If being flinged and user touches the screen, initiate drag;
                 * otherwise don't. mScroller.isFinished should be false when
                 * being flinged. We need to call computeScrollOffset() first so that
                 * isFinished() is correct.
                */
                mScroller.computeScrollOffset();
                mIsBeingDragged = !mScroller.isFinished();
                if (mIsBeingDragged && mScrollStrictSpan == null) {
                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                }
                startNestedScroll(SCROLL_AXIS_VERTICAL);
                break;
            }
   ...此处省略很多代码
  /*    * The only time we want to intercept motion events is if we are in the    * drag mode.    */   
        return mIsBeingDragged;
}

ScrollView中定义了一个属性mIsBeingDragged,而onInterceptTouchEvent的返回值就是onInterceptTouchEvent,就意味着mIsBeingDragged为ture时,拦截事件,为false时,不拦截;

onInterceptTouchEvent方法中ACTION_MOVE处理:

 switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
                    break;
                }

                final int pointerIndex = ev.findPointerIndex(activePointerId);
                if (pointerIndex == -1) {
                    Log.e(TAG, "Invalid pointerId=" + activePointerId
                            + " in onInterceptTouchEvent");
                    break;
                }

                final int y = (int) ev.getY(pointerIndex);
                final int yDiff = Math.abs(y - mLastMotionY);
                if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
                    mIsBeingDragged = true;
                    mLastMotionY = y;
                    initVelocityTrackerIfNotExists();
                    mVelocityTracker.addMovement(ev);
                    mNestedYOffset = 0;
                    if (mScrollStrictSpan == null) {
                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                    }
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            }

可以看出,只有在满足条件yDiff>mTouchSlop时,才可以执行 mIsBeingDragged = true;这就说明,触摸事件不是一下子就拦截掉,在yDiff<=mTouchSlop这一段时机下, 子View是可以得到触摸事件的,这就解释了为什么在子View的onTouchEvent方法中,可以执行到parent.requestDisallowInterceptTouchEvent()这句代码;

其实这个谷歌完全可以在requestDisallowInterceptTouchEvent注释上写明白,结果并没有,带着疑惑,也促进了我们就看源码的习惯,嗯嗯!!!

总结

除了ScrollView,其实在很多Android原生的滑动布局的onInterceptTouchEvent都是这样处理拦截的,比如SwipeRefreshLayout,当然,自己想写一个滑动布局,大致也是参考这些,这样的写法可以说是约定成俗的,只是细心的你能不能察觉这一切了

相关文章

  • 解惑requestDisallowInterceptTouchE

    最近在看官方控件源码时,无意间看到某些代码,让我想起有很多用requestDisallowInterceptTou...

  • 解惑

    今天在听音频的时候,听到老师说你所看到的,要么是你想要的,要么是你所厌恶的。突然有点想明白一件事了。 ...

  • 解惑

    我是一个刚步入社会的菜鸟,我有很多迷惑,也有很多委屈,更多的是迷茫。一天天看着自己消沉,内心是烦躁的。但是我除了烦...

  • 解惑

    当自己静下来时总会想,为什么什么事都没做,但时间就是不够用呢?而且每当年长一岁,这种感觉也越强烈。现在一个人...

  • 解惑

    上周的作文我提出焦虑,因为学习时间不够的原因,让我产生了极大的焦虑。凑巧的事,上周Claire战友的作业跟我的...

  • 解惑

    最近在学驾驶,不喜欢但也得学了,星期六在驾校上堂安全课,看着那些事故场面,让我不由想到佛陀的四法印其中一个:诸形无...

  • 解惑

    辞别墨客寻花去,却迎山风不解怀。 儿童急走无觅处,黛擎黄蝶有片海。 悟得道中佛自在,方知万物一脉来。 是日辗转千余...

  • 解惑:

    世人被财所迷眼,钱是魔鬼众生缠。 应求温饱争自在,不可丧志争苟全! 一日三餐灌满肠,梦不心惊睡的安。 龟虽长寿终化...

  • 解惑

    今天,我有幸参加了杨主任工作室安排的关于“教育写作”的培训活动,张文质老师分享了如何破除写作的恐惧,而钟杰老...

  • 解惑

    因为华大铜屋顶的事,心情波动大,情绪不稳。既有对公司的抱怨,也有对自己无力摆脱此困境的焦虑。 细想,我...

网友评论

  • captain991:"ScrollView中定义了一个属性mIsBeingDragged,而onInterceptTouchEvent的返回值就是onInterceptTouchEvent"
    这句话写错了吧,是“而onInterceptTouchEvent的返回值就是mIsBeingDragged”吧

本文标题:解惑requestDisallowInterceptTouchE

本文链接:https://www.haomeiwen.com/subject/xuxfmttx.html