ViewPager嵌套下的RecycleView滑動(dòng)沖突

起因

主頁的某一個(gè)Tab有是一個(gè)類似與新聞客戶端的多tab頁面世澜,如
QQ圖片20180724175629.png

在用戶要滑動(dòng)的Banner活動(dòng)圖的時(shí)候播演,時(shí)而會(huì)觸發(fā)切換tab沧烈,時(shí)而會(huì)觸發(fā)切換banner導(dǎo)致操作上的一種不適感

現(xiàn)象大致分析

  • 即使不看源碼暗赶,憑感覺也可以知道肯定是ViewPager的在處理Move行為的時(shí)候沒有判斷好(因?yàn)閐own的時(shí)候肯定是不知道讓哪一個(gè)組件來處理的)

需要達(dá)到的效果

  • 在Banner處滑動(dòng)的時(shí)候彤避,交由Banner來處理
  • 在Banner下方列表處上下滑動(dòng)交由該tab下的recycleview來處理(也就是不需要任何改動(dòng))
  • 在Banner下方列表處左右滑動(dòng)交由ViewPager來處理

源碼分析

我們直接來看ViewPager的onInterceptTouchEvent方法,畢竟是這個(gè)方法來處理事件的攔截與否

   switch (action) {
            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);
                final float x = ev.getX(pointerIndex);
                final float dx = x - mLastMotionX;
                final float xDiff = Math.abs(dx);
                final float y = ev.getY(pointerIndex);
                final float yDiff = Math.abs(y - mInitialMotionY);
                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);

                if (dx != 0 && !isGutterDrag(mLastMotionX, dx)
                        && canScroll(this, false, (int) dx, (int) x, (int) y)) {
                    // Nested view has scrollable area under this point. Let it be handled there.
                    mLastMotionX = x;
                    mLastMotionY = y;
                    mIsUnableToDrag = true;
                    return false;
                }
                if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                    if (DEBUG) Log.v(TAG, "Starting drag!");
                    mIsBeingDragged = true;
                    requestParentDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                    mLastMotionX = dx > 0
                            ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop;
                    mLastMotionY = y;
                    setScrollingCacheEnabled(true);
                } else if (yDiff > mTouchSlop) {
                    // The finger has moved enough in the vertical
                    // direction to be counted as a drag...  abort
                    // any attempt to drag horizontally, to work correctly
                    // with children that have scrolling containers.
                    if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                    mIsUnableToDrag = true;
                }
                if (mIsBeingDragged) {
                    // Scroll to follow the motion event
                    if (performDrag(x)) {
                        ViewCompat.postInvalidateOnAnimation(this);
                    }
                }
                break;
            }

            case MotionEvent.ACTION_DOWN: {
                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionX = mInitialMotionX = ev.getX();
                mLastMotionY = mInitialMotionY = ev.getY();
                mActivePointerId = ev.getPointerId(0);
                mIsUnableToDrag = false;

                mIsScrollStarted = true;
                mScroller.computeScrollOffset();
                if (mScrollState == SCROLL_STATE_SETTLING
                        && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
                    // Let the user 'catch' the pager as it animates.
                    mScroller.abortAnimation();
                    mPopulatePending = false;
                    populate();
                    mIsBeingDragged = true;
                    requestParentDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                } else {
                    completeScroll(false);
                    mIsBeingDragged = false;
                }

                if (DEBUG) {
                    Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
                            + " mIsBeingDragged=" + mIsBeingDragged
                            + "mIsUnableToDrag=" + mIsUnableToDrag);
                }
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        /*
         * The only time we want to intercept motion events is if we are in the
         * drag mode.
         */
        return mIsBeingDragged;
    }

返回值為mIsBeingDragged麦乞,并且上文說了只需要看Move的行為蕴茴,那么可以得知我們只需要看ViewPager什么時(shí)候返回了false(也就是你不要攔我啊,我要這個(gè)行為給我的孩子)

僅需要看這一段

 if (dx != 0 && !isGutterDrag(mLastMotionX, dx)
                        && canScroll(this, false, (int) dx, (int) x, (int) y)) {
                    // Nested view has scrollable area under this point. Let it be handled there.
                    mLastMotionX = x;
                    mLastMotionY = y;
                    mIsUnableToDrag = true;
                    return false;
                }
  • dx!=0不需要關(guān)注姐直,必然符合
  • isGutterDrag表示是否是在ViewPager的縫隙處滑動(dòng)倦淀,不需要關(guān)注
  • 重點(diǎn)來了,就是這個(gè)canScroll方法
    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
        if (v instanceof ViewGroup) {
            final ViewGroup group = (ViewGroup) v;
            final int scrollX = v.getScrollX();
            final int scrollY = v.getScrollY();
            final int count = group.getChildCount();
            // Count backwards - let topmost views consume scroll distance first.
            for (int i = count - 1; i >= 0; i--) {
                // TODO: Add versioned support here for transformed views.
                // This will not work for transformed views in Honeycomb+
                final View child = group.getChildAt(i);
                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
                        && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
                        && canScroll(child, true, dx, x + scrollX - child.getLeft(),
                                y + scrollY - child.getTop())) {
                    return true;
                }
            }
        }

        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
    }

緊接著

  • 可以看到声畏,這個(gè)方法的目的就是在于遍歷子View撞叽,孫View等里是否有可以橫向滾動(dòng)的組件并且落點(diǎn)在組件內(nèi)部的
  • 關(guān)鍵方法ViewCompat.canScrollHorizontally姻成,按理來說RecycleView設(shè)置了橫向的LinearLayoutManager,應(yīng)該是沒問題才對(duì)
  • 進(jìn)到RecycleView源碼可知愿棋,并沒有重寫canScrollHorizontally科展,而是在合適的時(shí)候(比如onTouch)返回了mLayout.canScrollHorizontally()

處理方案

  • 方案一
    重寫RecycleView的canScrollHorizontally,直接返回為true

經(jīng)測(cè)試糠雨,有效

  • 方案二
    手動(dòng)處置ViewPager的onInterceptTouchEvent才睹,添加監(jiān)聽,在監(jiān)聽中判斷落點(diǎn)是否在Banner的組件里甘邀,并且在RecycleView重寫dispatchTouchEvent
   @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mMoveListener == null ? !noScroller && super.onInterceptTouchEvent(ev) : mMoveListener.onMoveTouchEvent(ev) ? super.onInterceptTouchEvent(ev) : false;
    }

    private OnMoveTouchListener mMoveListener;

    public void setOnMoveListener(OnMoveTouchListener l) {
        mMoveListener = l;
    }

    public interface OnMoveTouchListener {
        boolean onMoveTouchEvent(MotionEvent ev);
    }
 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getY() > getMeasuredHeight())
            return false;
        return super.dispatchTouchEvent(ev);
    }
  • 還沒完琅攘,還需要判斷當(dāng)前界面是否顯示
  @Override
    public boolean onMoveTouchEvent(MotionEvent ev) {
        boolean b = getFragmentVisible() ? !mModulePage.getBannerPager().dispatchTouchEvent(ev) : false;
         return b;
      }

總之非常的麻煩,效果也能達(dá)到松邪,還是方案一好坞琴,完結(jié)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逗抑,隨后出現(xiàn)的幾起案子置济,更是在濱河造成了極大的恐慌,老刑警劉巖锋八,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異护盈,居然都是意外死亡挟纱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門腐宋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來紊服,“玉大人,你說我怎么就攤上這事胸竞∑坂停” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵卫枝,是天一觀的道長(zhǎng)煎饼。 經(jīng)常有香客問我,道長(zhǎng)校赤,這世上最難降的妖魔是什么吆玖? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮马篮,結(jié)果婚禮上沾乘,老公的妹妹穿的比我還像新娘。我一直安慰自己浑测,他們只是感情好翅阵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般掷匠。 火紅的嫁衣襯著肌膚如雪滥崩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天槐雾,我揣著相機(jī)與錄音夭委,去河邊找鬼。 笑死募强,一個(gè)胖子當(dāng)著我的面吹牛株灸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播擎值,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼慌烧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了鸠儿?” 一聲冷哼從身側(cè)響起屹蚊,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎进每,沒想到半個(gè)月后汹粤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡田晚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年嘱兼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贤徒。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芹壕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出接奈,到底是詐尸還是另有隱情踢涌,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布序宦,位于F島的核電站睁壁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏挨厚。R本人自食惡果不足惜堡僻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疫剃。 院中可真熱鬧钉疫,春花似錦、人聲如沸巢价。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至城菊,卻和暖如春备燃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凌唬。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工并齐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人客税。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓况褪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親更耻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子测垛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容