RecyclerView擴(kuò)展(五) - ViewPager2的源碼分析

??ViewPager2是Google爸爸在幾個(gè)月前推出來(lái)的新控件,此控件的目的就是為了替代傳統(tǒng)的ViewPager控件。至于為什么要淘汰ViewPager,我想就不用解釋這其中的原因吧,ViewPager歷來(lái)最大的詬病就是不會(huì)復(fù)用View(其實(shí)我對(duì)ViewPager的原理了解的不多,各位大佬就當(dāng)我信口雌黃吧????。)。而ViewPager2內(nèi)部是通過(guò)RecyclerView來(lái)實(shí)現(xiàn)的,性能當(dāng)然不容置疑。還有最重要的一點(diǎn),ViewPager2幾乎復(fù)制了ViewPager所有的API,所以,ViewPager2在使用上幾乎跟ViewPage完全一樣。
??本文打算從源碼角度入手,詳細(xì)的分析ViewPager2的實(shí)現(xiàn)原理。其實(shí)早在RecyclerView 源碼分析(七) - 自定義LayoutManager及其相關(guān)組件的源碼分析文章中,我在分析SnapHelper源碼時(shí),在文章里面簡(jiǎn)單的說(shuō)了一句。而此文算是兌現(xiàn)當(dāng)初的一個(gè)承諾,看看怎么通過(guò)RecyclerView + SnapHelper的方式來(lái)實(shí)現(xiàn)一個(gè)ViewPager。
??需要注意的是:目前ViewPager2還不太穩(wěn)定,所以請(qǐng)謹(jǐn)慎使用到生產(chǎn)環(huán)境中。
??在閱讀本文之前,建議大家先了解SnapHelper的原理,本文參考文章:

  1. RecyclerView 源碼分析(七) - 自定義LayoutManager及其相關(guān)組件的源碼分析

??注意,本文ViewPager2版本均為1.0.0-alpha04

1. 概述

??我在閱讀ViewPager2的源碼之前,思考過(guò)一個(gè)問(wèn)題,到底應(yīng)不應(yīng)該看看ViewPager2的源碼嗎?其實(shí)從簡(jiǎn)單的方面來(lái)說(shuō),真的沒(méi)必要去閱讀它的源碼,熟悉RecyclerView的同學(xué),ViewPager2內(nèi)部肯定是使用SnapHelper實(shí)現(xiàn)。所以,我們閱讀ViewPager2的源碼到底是為了什么?就是因?yàn)殚e的蛋疼,然后寫(xiě)出來(lái)裝逼嗎?我想肯定不是,我總結(jié)如下幾點(diǎn):

  1. 了解ViewPager2是怎么將RecyclerView的滑動(dòng)事件轉(zhuǎn)變?yōu)?code>ViewPager的頁(yè)面滑動(dòng)事件。
  2. 了解怎么使用RecyclerView來(lái)加載Fragment。

??這其中,我覺(jué)得第2點(diǎn)非常的重要,為什么重要呢?RecyclerView加載Fragment這里涉及到細(xì)節(jié)非常的多,因?yàn)镕ragment本身有生命周期,所以我們?nèi)绾瓮ㄟ^(guò)Adapter來(lái)有效維護(hù)Fragment的生命周期,這本身就是一種挑戰(zhàn)。
??本文打算從如下幾個(gè)方面來(lái)介紹:

  1. PagerSnapHelper的源碼分析,主要是了解它內(nèi)部的原理,是如何實(shí)現(xiàn)ViewPager的效果。
  2. 各種組件的分析,包括ScrollEventAdapterPageTransformerAdapter。
  3. FragmentStateAdapter的源碼分析,主要是了解Adapter是怎么加載Fragment的。

??接下來(lái),我們正式來(lái)分析ViewPager2的源碼分析。

2. ViewPager2的基本結(jié)構(gòu)

??在分析ViewPager2源碼之前,我們先來(lái)看看ViewPager的內(nèi)部結(jié)構(gòu),了解一下ViewPager2是怎么實(shí)現(xiàn)的。
??從ViewPager2的源碼中我們知道,ViewPager2繼承于ViewGroup,其內(nèi)部包含有一個(gè)RecyclerView控件,其他部分都是圍繞著這個(gè)RecyclerView來(lái)實(shí)現(xiàn)的。總之,ViewPager2是以一個(gè)組合的方式來(lái)實(shí)現(xiàn)的。
??這其中,ScrollEventAdapter的作用是將RecyclerView.OnScrollListener事件轉(zhuǎn)變?yōu)?code>ViewPager2.OnPageChangeCallback事件;FakeDrag的作用是用來(lái)實(shí)現(xiàn)模擬拖動(dòng)的效果;PageTransformerAdapter的作用是將頁(yè)面的滑動(dòng)事件轉(zhuǎn)變?yōu)楸嚷首兓热缯f(shuō),一個(gè)頁(yè)面從左到右滑動(dòng),變化規(guī)則是從0~1,關(guān)于這個(gè)組件,我相信熟悉ViewPager2的同學(xué)都應(yīng)該都知道。
??最后就是最重要的東西--FragmentStateAdapter,這個(gè)Adapter在為了加載Fragment,花費(fèi)了很多的功夫,為我們想要使用Adapter加載Fragment提供了非常權(quán)威的參考。

3. ViewPager2的基本分析

??從這里開(kāi)始,我們正式開(kāi)始分析源碼。我們先來(lái)看看ViewPager2的基本源碼,重點(diǎn)在initialize方法里面:

    private void initialize(Context context, AttributeSet attrs) {
        // 初始化RecyclerView
        mRecyclerView = new RecyclerViewImpl(context);
        mRecyclerView.setId(ViewCompat.generateViewId());
        // 初始化LayoutManager
        mLayoutManager = new LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);
        setOrientation(context, attrs);

        mRecyclerView.setLayoutParams(
                new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());

        // 創(chuàng)建滑動(dòng)事件轉(zhuǎn)換器的對(duì)象
        mScrollEventAdapter = new ScrollEventAdapter(mLayoutManager);
        // 創(chuàng)建模擬拖動(dòng)事件的對(duì)象
        mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
        // 創(chuàng)建PagerSnapHelper對(duì)象,用來(lái)實(shí)現(xiàn)頁(yè)面切換的基本效果
        mPagerSnapHelper = new PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
    
        mRecyclerView.addOnScrollListener(mScrollEventAdapter);
        // ······
    }

??在initialize方法里面,主要初始化RecyclerView的基本配置和基本組件。在這個(gè)方面,做了兩件比較重要的事情:1. 給RecyclerView設(shè)置了滑動(dòng)監(jiān)聽(tīng)事件,涉及到的組件是ScrollEventAdapter,后面的基本功能都需要這個(gè)組件的支持;2. 設(shè)置了PagerSnapHelper,目的是實(shí)現(xiàn)切面切換的效果。
??我們對(duì)ViewPager2有了基本的了解之后,現(xiàn)在就來(lái)對(duì)各個(gè)組件進(jìn)行詳細(xì)的分析。

4. PagerSnapHelper

??在 RecyclerView 源碼分析(七) - 自定義LayoutManager及其相關(guān)組件的源碼分析文章里面,我已經(jīng)簡(jiǎn)單分析過(guò)SnapHelper。我們知道SnapHelper最重要的三個(gè)方法是:calculateDistanceToFinalSnapfindSnapViewfindTargetSnapPosition。
??為了更好區(qū)分這三個(gè)方法的不同點(diǎn),我以一個(gè)非常常用的場(chǎng)景來(lái)描述這三個(gè)方法的調(diào)用,分別分為如下三個(gè)階段:

  1. 假設(shè)手指在快速滑動(dòng)一個(gè)RecyclerView,在手指離開(kāi)屏幕之前,如上的三個(gè)方法都不會(huì)被調(diào)用。
  2. 而此時(shí)如果手指如果手指離開(kāi)了屏幕,接下來(lái)就是Fling事件來(lái)滑動(dòng)RecyclerView,在Fling事件觸發(fā)之際,findTargetSnapPosition方法會(huì)被調(diào)用,此方法的作用就是用來(lái)計(jì)算Fling事件能滑動(dòng)到位置。
  3. 當(dāng)Fling事件結(jié)束之際,RecyclerView會(huì)回調(diào)SnapHelper內(nèi)部OnScrollListener接口的onScrollStateChanged方法。此時(shí)RecyclerView的滑動(dòng)狀態(tài)為RecyclerView.SCROLL_STATE_IDLE,所以就會(huì)分別調(diào)用findSnapView方法來(lái)找到需要顯示在RecyclerView的最前面的View。找到目標(biāo)View之后,就會(huì)調(diào)用calculateDistanceToFinalSnap方法來(lái)計(jì)算需要滑動(dòng)的距離,然后調(diào)動(dòng)RecyclerView相關(guān)方法進(jìn)行滑動(dòng)。

??正常來(lái)說(shuō),當(dāng)RecyclerView在Fling時(shí),如果想要不去攔截Fling時(shí)間,想讓RecyclerView開(kāi)心的Fling,可以直接在findTargetSnapPosition方法返回RecyclerView.NO_POSITION即可,從而將Fling事件交給RecyclerView,或者我們可以在findTargetSnapPosition方法來(lái)計(jì)算滑動(dòng)的最終位置,然后通過(guò)SmoothScroller來(lái)實(shí)現(xiàn)滑動(dòng)。
??但是,我們知道PagerSnapHelper不支持Fling事件,所以在PagerSnapHelper內(nèi)部,必須實(shí)現(xiàn)findTargetSnapPosition方法,從而避免RecyclerViewFling。

(1). findTargetSnapPosition方法

??熟悉PagerSnapHelper的基本知識(shí)之后,現(xiàn)在我們來(lái)重點(diǎn)分析這三個(gè)方法,我們先來(lái)看看findTargetSnapPosition方法,看看它是怎么阻止RecyclerView的Fling事件。

    @Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY) {
        // ······
        // 找到與當(dāng)前View相鄰的View,包括左相鄰和右響鈴,并且計(jì)算滑動(dòng)的距離
        for (int i = 0; i < childCount; i++) {
            final View child = layoutManager.getChildAt(i);
            if (child == null) {
                continue;
            }
            final int distance = distanceToCenter(layoutManager, child, orientationHelper);

            if (distance <= 0 && distance > distanceBefore) {
                // Child is before the center and closer then the previous best
                distanceBefore = distance;
                closestChildBeforeCenter = child;
            }
            if (distance >= 0 && distance < distanceAfter) {
                // Child is after the center and closer then the previous best
                distanceAfter = distance;
                closestChildAfterCenter = child;
            }
        }

        // 根據(jù)滑動(dòng)的方向來(lái)返回的相應(yīng)位置
        final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);
        if (forwardDirection && closestChildAfterCenter != null) {
            return layoutManager.getPosition(closestChildAfterCenter);
        } else if (!forwardDirection && closestChildBeforeCenter != null) {
            return layoutManager.getPosition(closestChildBeforeCenter);
        }

        // 兜底計(jì)算
        View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter;
        if (visibleView == null) {
            return RecyclerView.NO_POSITION;
        }
        int visiblePosition = layoutManager.getPosition(visibleView);
        int snapToPosition = visiblePosition
                + (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1);

        if (snapToPosition < 0 || snapToPosition >= itemCount) {
            return RecyclerView.NO_POSITION;
        }
        return snapToPosition;
    }

??從上面的代碼中,我們可以非常容易得到一個(gè)信息,為了阻止RecyclerView的Fling事件,findTargetSnapPosition方法直接返回當(dāng)前ItemView的上一個(gè)ItemView或者下一個(gè)ItemView的位置。所以PagerSnapHelperfindTargetSnapPosition方法還是非常簡(jiǎn)單的。
??那么findTargetSnapPosition方法是怎么阻止Fling事件的觸發(fā)呢?首先得保證findTargetSnapPosition方法返回的值不為RecyclerView.NO_POSITION,然后我們來(lái)看看SnapHelpersnapFromFling方法:

    private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY) {
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return false;
        }

        RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
        if (smoothScroller == null) {
            return false;
        }

        int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
        if (targetPosition == RecyclerView.NO_POSITION) {
            return false;
        }

        smoothScroller.setTargetPosition(targetPosition);
        layoutManager.startSmoothScroll(smoothScroller);
        return true;
    }

??從snapFromFling方法中我們知道,只要findTargetSnapPosition方法返回不為RecyclerView.NO_POSITION,那么接下來(lái)的滑動(dòng)事件會(huì)交給SmoothScroller去處理,所以RecyclerView最終滑到的位置為當(dāng)前位置的上一個(gè)或者下一個(gè),不會(huì)產(chǎn)生Fling的效果。

(2). findSnapView方法

??當(dāng)RecyclerView滑動(dòng)完畢之后,此時(shí)會(huì)先調(diào)用findSnapView方法獲取來(lái)最終位置的ItemView。當(dāng)RecyclerView觸發(fā)Fling事件時(shí),才會(huì)觸發(fā)findTargetSnapPosition方法,從而保證RecyclerView滑動(dòng)到正確位置;那么當(dāng)RecyclerView沒(méi)有觸發(fā)Fling事件,怎么保證RecyclerView滑動(dòng)到正確位置呢?當(dāng)然是findSnapView方法和calculateDistanceToFinalSnap方法,這倆方法還有一個(gè)目的就是,如果Fling沒(méi)有滑動(dòng)正確位置,這倆方法可以做一個(gè)兜底操作:

    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        if (layoutManager.canScrollVertically()) {
            return findCenterView(layoutManager, getVerticalHelper(layoutManager));
        } else if (layoutManager.canScrollHorizontally()) {
            return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
        }
        return null;
    }

??在findSnapView內(nèi)部,調(diào)用findCenterView方法,我們先來(lái)看看findCenterView方法的代碼:

    private View findCenterView(RecyclerView.LayoutManager layoutManager,
            OrientationHelper helper) {
        int childCount = layoutManager.getChildCount();
        if (childCount == 0) {
            return null;
        }

        View closestChild = null;
        final int center;
        if (layoutManager.getClipToPadding()) {
            center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
        } else {
            center = helper.getEnd() / 2;
        }
        int absClosest = Integer.MAX_VALUE;

        for (int i = 0; i < childCount; i++) {
            final View child = layoutManager.getChildAt(i);
            int childCenter = helper.getDecoratedStart(child)
                    + (helper.getDecoratedMeasurement(child) / 2);
            int absDistance = Math.abs(childCenter - center);

            /* if child center is closer than previous closest, set it as closest  */
            if (absDistance < absClosest) {
                absClosest = absDistance;
                closestChild = child;
            }
        }
        return closestChild;
    }

??findCenterView方法還是比較長(zhǎng),但是表示的意思非常簡(jiǎn)單,就是找到當(dāng)前中心距離屏幕中心最近的ItemView。這個(gè)怎么來(lái)理解呢?比如說(shuō),我們手指在滑動(dòng)一個(gè)頁(yè)面,滑動(dòng)到一定距離時(shí)就松開(kāi)了,此時(shí)屏幕當(dāng)中有兩個(gè)頁(yè)面,那么ViewPager2應(yīng)該滑動(dòng)到哪一個(gè)頁(yè)面呢?當(dāng)然是距離屏幕中心最近的頁(yè)面。findCenterView方法的作用便是如此。

(3). calculateDistanceToFinalSnap方法

??找到需要滑到的ItemView,此時(shí)就應(yīng)該調(diào)用calculateDistanceToFinalSnap方法來(lái)計(jì)算,此時(shí)RecyclerView還需要滑動(dòng)多少距離才能達(dá)到正確位置:

    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
            @NonNull View targetView) {
        int[] out = new int[2];
        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToCenter(layoutManager, targetView,
                    getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }

        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToCenter(layoutManager, targetView,
                    getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }

??calculateDistanceToFinalSnap表達(dá)的意思非常簡(jiǎn)單,就是計(jì)算RecyclerView需要滑動(dòng)的距離,主要通過(guò)distanceToCenter方法來(lái)計(jì)算,具體細(xì)節(jié)我們就不討論,非常簡(jiǎn)單,有興趣的同學(xué)可以去看看。

??我們從整體上了解了PagerSnapHelper的源碼,應(yīng)該非常容易的知道,為什么PagerSnapHelper可以實(shí)現(xiàn)頁(yè)面切換的效果。我來(lái)簡(jiǎn)單的總結(jié)一下:

  1. 首先阻止RecyclerView的Fling事件,阻止的方式就是重寫(xiě)findTargetSnapPosition方法,當(dāng)RecyclerView觸發(fā)了Fling事件之后,直接滑動(dòng)到下一個(gè)或者上一個(gè)。
  2. 如果RecyclerView沒(méi)有觸發(fā)Fling事件,或者Fling階段未能滑動(dòng)到正確位置,此時(shí)需要findSnapView方法和calculateDistanceToFinalSnap來(lái)保證滑動(dòng)到正確的頁(yè)面。

5. ScrollEventAdapter

??分析完PagerSnaHelper之后,我們來(lái)看看ScrollEventAdapter。前面我們已經(jīng)說(shuō)過(guò)了,ScrollEventAdapter的作用將RecyclerView的滑動(dòng)事件轉(zhuǎn)為ViewPager2的頁(yè)面滑動(dòng)事件。
??在分析源碼之前,我們先來(lái)看看幾個(gè)狀態(tài):

名稱(chēng) 含義
STATE_IDLE 表示當(dāng)前ViewPager2處于停止?fàn)顟B(tài)
STATE_IN_PROGRESS_MANUAL_DRAG 表示當(dāng)前ViewPager2處于手指拖動(dòng)狀態(tài)
STATE_IN_PROGRESS_SMOOTH_SCROLL 表示當(dāng)前ViewPager2處于緩慢滑動(dòng)的狀態(tài)。這個(gè)狀態(tài)只在調(diào)用了ViewPager2setCurrentItem方法才有可能出現(xiàn)。
STATE_IN_PROGRESS_IMMEDIATE_SCROLL 表示當(dāng)前ViewPager2處于迅速滑動(dòng)的狀態(tài)。這個(gè)狀態(tài)只在調(diào)用了ViewPager2setCurrentItem方法才有可能出現(xiàn)。
STATE_IN_PROGRESS_FAKE_DRAG 表示當(dāng)前ViewPager2未使用手指滑動(dòng),而是通過(guò)FakerDrag實(shí)現(xiàn)的。

??ScrollEventAdapter實(shí)現(xiàn)的是OnScrollListener接口,所以,我們的重點(diǎn)放在兩個(gè)實(shí)現(xiàn)方法里面。不過(guò)在正式這倆方法之前,我們先來(lái)了解幾個(gè)方法,方便后面的理解。

方法名 含義
dispatchStateChanged 將狀態(tài)改變的信息分發(fā)到OnPageChangeCallback監(jiān)聽(tīng)器,不過(guò)需要注意的是:當(dāng)ViewPager2處于停止?fàn)顟B(tài),同時(shí)調(diào)用了setCurrentItem方法來(lái)立即切換到某一個(gè)頁(yè)面(注意,不是緩慢的切換),不會(huì)回調(diào)OnPageChangeCallback的方法。
dispatchSelected 分發(fā)選中頁(yè)面的信息。
dispatchScrolled 分發(fā)頁(yè)面滑動(dòng)的相關(guān)信息。

??接下來(lái),我們將正式分析onScrollStateChangedonScrolled。

(1). onScrollStateChanged方法

??當(dāng)RecyclerView的滑動(dòng)狀態(tài)發(fā)生變化,這個(gè)方法就會(huì)被調(diào)用。這個(gè)方法主要分為3個(gè)階段,分別如下:

  1. 開(kāi)始拖動(dòng),會(huì)調(diào)用startDrag方法表示拖動(dòng)開(kāi)始。
  2. 拖動(dòng)手勢(shì)的釋放,此時(shí)ViewPager2會(huì)準(zhǔn)備滑動(dòng)到正確的位置。
  3. 滑動(dòng)結(jié)束,此時(shí)ScrollEventAdapter會(huì)調(diào)用相關(guān)的方法更新?tīng)顟B(tài)。
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        // 1. 開(kāi)始拖動(dòng)
        if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG
                && newState == RecyclerView.SCROLL_STATE_DRAGGING) {
            startDrag(false);
            return;
        }
        // 2. 拖動(dòng)手勢(shì)的釋放
        if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_SETTLING) {
            // Only go through the settling phase if the drag actually moved the page
            if (mScrollHappened) {
                dispatchStateChanged(SCROLL_STATE_SETTLING);
                // Determine target page and dispatch onPageSelected on next scroll event
                mDispatchSelected = true;
            }
            return;
        }
        // 3. 滑動(dòng)結(jié)束
        if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_IDLE) {
            boolean dispatchIdle = false;
            updateScrollEventValues();
            // 如果在拖動(dòng)期間為產(chǎn)生移動(dòng)距離
            if (!mScrollHappened) {
                if (mScrollValues.mPosition != RecyclerView.NO_POSITION) {
                    dispatchScrolled(mScrollValues.mPosition, 0f, 0);
                }
                dispatchIdle = true;
            } else if (mScrollValues.mOffsetPx == 0) {
                dispatchIdle = true;
                if (mDragStartPosition != mScrollValues.mPosition) {
                    dispatchSelected(mScrollValues.mPosition);
                }
            }
            if (dispatchIdle) {
                dispatchStateChanged(SCROLL_STATE_IDLE);
                resetState();
            }
        }
    }

??第1步和第2步我們非常的容易理解,至于第3步我們需要注意如下兩點(diǎn):

  1. dispatchStateChanged方法的調(diào)用時(shí)機(jī):1. 根本沒(méi)有滑動(dòng),也就是說(shuō),onScrolled方法沒(méi)有被調(diào)用;2. 滑動(dòng)過(guò),并且在上一次滑動(dòng)中最后一次調(diào)用onScrolled方法的時(shí)候會(huì)被調(diào)用。
  2. dispatchSelected方法的調(diào)用時(shí)機(jī):當(dāng)mOffsetPx為0時(shí)會(huì)被調(diào)用,mOffsetPx為0表示當(dāng)前ViewPager2根本未滑動(dòng)。

(2). onScrolled方法

??在分析這個(gè)方法之前,我們看一下這個(gè)方法的代碼:

    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        mScrollHappened = true;
        // 更新相關(guān)值
        updateScrollEventValues();

        if (mDispatchSelected) {
            // 拖動(dòng)手勢(shì)釋放,ViewPager2正在滑動(dòng)到正確的位置
            mDispatchSelected = false;
            boolean scrollingForward = dy > 0 || (dy == 0 && dx < 0 == isLayoutRTL());
            mTarget = scrollingForward && mScrollValues.mOffsetPx != 0
                    ? mScrollValues.mPosition + 1 : mScrollValues.mPosition;
            if (mDragStartPosition != mTarget) {
                dispatchSelected(mTarget);
            }
        } else if (mAdapterState == STATE_IDLE) {
            // 調(diào)用了setAdapter方法
            dispatchSelected(mScrollValues.mPosition);
        }

        dispatchScrolled(mScrollValues.mPosition, mScrollValues.mOffset, mScrollValues.mOffsetPx);

        // 因?yàn)檎{(diào)用了setCurrentItem(x, false)不會(huì)觸發(fā)IDLE狀態(tài)的產(chǎn)生,所以需要在這里
        // 調(diào)用dispatchStateChanged方法
        if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)
                && mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {
            dispatchStateChanged(SCROLL_STATE_IDLE);
            resetState();
        }
    }

??onScrolled方法里面主要做了兩件事:

  1. 調(diào)用updateScrollEventValues方法更新ScrollEventValues里面的值。
  2. 調(diào)用相關(guān)方法,更新?tīng)顟B(tài)。

??關(guān)于更新ScrollEventValues里面的值,具體的細(xì)節(jié)是非常的簡(jiǎn)單,這里就不解釋了。我簡(jiǎn)單的解釋一下幾個(gè)屬性的含義:

名稱(chēng) 含義
mPosition 從開(kāi)始滑動(dòng)到滑動(dòng)結(jié)束,一直記錄著當(dāng)前滑動(dòng)到的位置。
mOffset 從一個(gè)頁(yè)面滑動(dòng)到另一個(gè)頁(yè)面,記錄著滑動(dòng)的百分比。
mOffsetPx 記錄著從開(kāi)始滑動(dòng)的頁(yè)面與當(dāng)前狀態(tài)的滑動(dòng)。每次滑動(dòng)結(jié)束之后,會(huì)被重置。

??其實(shí)總的來(lái)說(shuō),ScrollEventAdapter的源碼是非常簡(jiǎn)單,這里稍微復(fù)雜的就是各種狀態(tài)的更新和相關(guān)的方法的回調(diào)。我來(lái)簡(jiǎn)單的總結(jié)一下:

  1. 當(dāng)調(diào)用ViewPager2setAdapter方法時(shí),此時(shí)應(yīng)該回調(diào)一次dispatchSelected方法。
  2. 當(dāng)調(diào)用setCurrentItem(x, false)方法,不會(huì)調(diào)用onScrollStateChanged方法,因而不會(huì)產(chǎn)生idle狀態(tài),因此,我們需要在onScrolled方法特殊處理(onScrolled方法會(huì)被調(diào)用)。
  3. 正常的拖動(dòng)和釋放,就是onScrollStateChanged方法和onScrolled方法的正常回調(diào)。

6. PageTransformerAdapter

??PageTransformerAdapter的作用將OnPageChangeCallback的事件轉(zhuǎn)換成為一種特殊的事件,什么特殊的事件呢?我以一個(gè)例子來(lái)解釋一下:

  1. 假設(shè)ViewPager2此時(shí)從A頁(yè)面滑動(dòng)到B頁(yè)面,并且是從右往左滑動(dòng),其中A頁(yè)面的變化范圍:[0,-1);B頁(yè)面的變化范圍:[1,0)。
  2. 假設(shè)ViewPager2此時(shí)從B頁(yè)面滑動(dòng)到A頁(yè)面,并且是從左往右滑動(dòng),其中A頁(yè)面的變化范圍:[-1,0);B頁(yè)面的變化范圍:[0,1)。

??熟悉ViewPager的同學(xué)應(yīng)該都知道,在ViewPager中也有這么一個(gè)東西。這里我們來(lái)看一下PageTransformerAdapter是怎么進(jìn)行轉(zhuǎn)換的。
??PageTransformerAdapter實(shí)現(xiàn)于OnPageChangeCallback接口,監(jiān)聽(tīng)的是ScrollEventAdapter的頁(yè)面滑動(dòng)事件,然后將頁(yè)面滑動(dòng)事件轉(zhuǎn)換成為上面特殊的事件,我們來(lái)看看具體的實(shí)現(xiàn),真正的實(shí)現(xiàn)在onPageScrolled方法里面:

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mPageTransformer == null) {
            return;
        }

        float transformOffset = -positionOffset;
        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
            View view = mLayoutManager.getChildAt(i);
            if (view == null) {
                throw new IllegalStateException(String.format(Locale.US,
                        "LayoutManager returned a null child at pos %d/%d while transforming pages",
                        i, mLayoutManager.getChildCount()));
            }
            int currPos = mLayoutManager.getPosition(view);
            float viewOffset = transformOffset + (currPos - position);
            mPageTransformer.transformPage(view, viewOffset);
        }
    }

??相信不用我解釋上面的代碼吧,大家應(yīng)該都能看懂是怎么實(shí)現(xiàn)的。

7. FragmentStateAdapter

??接下來(lái),我們將分析FragmentStateAdapter,看看它是加載Fragment的。在正式分析源碼之前,我們先來(lái)幾個(gè)成員變量。

變量名稱(chēng) 變量類(lèi)型 含義
mFragments LongSparseArray<Fragment> key為itemId,value為Fragment。表示position與所放Fragment的對(duì)應(yīng)關(guān)系(itemId與position有對(duì)應(yīng)關(guān)系)
mSavedStates LongSparseArray<Fragment.SavedState> key為itemId,value為Fragment的狀態(tài)
mItemIdToViewHolder LongSparseArray<Integer> key為itemId, value為ItemView的id。

??接下來(lái),我們將分析在Adapter中比較重要的幾個(gè)方法:

  1. onCreateViewHolder
  2. onBindViewHolder
  3. onViewAttachedToWindow
  4. onViewRecycled
  5. onFailedToRecycleView

??如上5個(gè)方法都與Fragment加載息息相關(guān),我們一個(gè)一個(gè)的來(lái)看。

(1). onCreateViewHolder方法

??onCreateViewHolder方法主要?jiǎng)?chuàng)建ViewHolder,我們來(lái)簡(jiǎn)單看看怎么創(chuàng)建ViewHolder

    @NonNull
    @Override
    public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return FragmentViewHolder.create(parent);
    }

??其實(shí)就是調(diào)用了FragmentViewHolder的一個(gè)靜態(tài)方法,具體細(xì)節(jié)這里就不展示了。

(2). onBindViewHolder方法

??onBindViewHolder方法主要是將Fragment加載到ItemView上,但是由于ViewHolder會(huì)被復(fù)用,所以這里需要很多的條件。我們先來(lái)簡(jiǎn)單的看一下代碼:

    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
        final long itemId = holder.getItemId();
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        // 如果當(dāng)前ItemView已經(jīng)加載了Fragment,并且不是同一個(gè)Fragment
        // 那么就移除
        if (boundItemId != null && boundItemId != itemId) {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }

        mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
        // 保證對(duì)應(yīng)位置的Fragment已經(jīng)初始化,并且放在mFragments中
        ensureFragment(position);

        final FrameLayout container = holder.getContainer();
        // 特殊情況,當(dāng)RecyclerView讓ItemView保持在Window,
        // 但是不在視圖樹(shù)中。
        if (ViewCompat.isAttachedToWindow(container)) {
            if (container.getParent() != null) {
                throw new IllegalStateException("Design assumption violated.");
            }
            // 當(dāng)ItemView添加在到RecyclerView中才加載Fragment
            container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    if (container.getParent() != null) {
                        container.removeOnLayoutChangeListener(this);
                        // 加載Fragment
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }

        gcFragments();
    }

?? onBindViewHolder方法主要分為三步:

  1. 如果當(dāng)前ItemView上已經(jīng)加載了Fragment,并且不是同一個(gè)Fragment(ItemView被復(fù)用了),那么先移除掉ItemView上的Fragment。
  2. 初始化相關(guān)信息。
  3. 如果存在特殊情況,會(huì)走特殊情況。正常來(lái)說(shuō),都會(huì)經(jīng)過(guò)onAttachToWindow方法來(lái)對(duì)Fragment進(jìn)行加載。

?? 這其中,第三步是尤為重要的,不過(guò)這里,我們先分析它,待會(huì)詳細(xì)的解釋。

(3). onViewAttachedToWindow方法

??正常來(lái)說(shuō),ItemView都會(huì)在這個(gè)方法里面對(duì)Fragment進(jìn)行加載,我們來(lái)看看代碼:

    @Override
    public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
        placeFragmentInViewHolder(holder);
        gcFragments();
    }

??同樣的,調(diào)用了placeFragmentInViewHolder方法加載Fragment。

(4). onViewRecycled方法

??當(dāng)ViewHolder被回收到回收池中,onViewRecycled方法會(huì)被調(diào)用。而在onViewRecycled方法里面,自然是對(duì)Fragment的卸載。我們簡(jiǎn)單的看一下代碼:

    @Override
    public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
        if (boundItemId != null) {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }
    }

??有人在問(wèn),為什么要在onViewRecycled方法來(lái)對(duì)Fragment進(jìn)行卸載,而不在onViewDetachedFromWindow方法進(jìn)行卸載。
??我們先來(lái)分析下onViewRecycled方法,當(dāng)onViewRecycled方法被調(diào)用,表示當(dāng)前ViewHolder已經(jīng)徹底沒(méi)有用了,被放入回收池,等待后面被復(fù)用,此時(shí)存在的情況可能有:1.當(dāng)前ItemView手動(dòng)移除掉了;2. 當(dāng)前位置對(duì)應(yīng)的視圖已經(jīng)徹底不在屏幕中,被當(dāng)前屏幕中某些位置復(fù)用了。所以在onViewRecycled方法里面移除Fragment比較合適。
??那么為什么在onViewDetachedFromWindow方法里面不合適呢?因?yàn)槊慨?dāng)一個(gè)頁(yè)面被滑走,都會(huì)調(diào)用這個(gè)方法,如果對(duì)其Fragment進(jìn)行卸載,此時(shí)用戶又滑回來(lái),又要重新加載一次,這性能就下降了很多。
??onFailedToRecycleView方法與onViewRecycled方法操作差不多,這里就不過(guò)多分析了。

(5). placeFragmentInViewHolder方法

??接下來(lái)我們來(lái)分析placeFragmentInViewHolder方法,看看怎么加載Fragment。整個(gè)PageTransformerAdapter的核心點(diǎn)就在這個(gè)方法里面。
??在加載Fragment之前,我們需要判斷幾個(gè)狀態(tài):

  1. Fragment是否添加到ItemView 中。
  2. Fragment的View是否已經(jīng)創(chuàng)建。
  3. Fragment的View 是否添加視圖樹(shù)中

??計(jì)算下來(lái),一共8種情況,我們來(lái)看看代碼:

 void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {

        // ······
        // 1.Fragment未添加到ItemView中,但是View已經(jīng)創(chuàng)建
        // 非法狀態(tài)
        if (!fragment.isAdded() && view != null) {
            throw new IllegalStateException("Design assumption violated.");
        }

        // 2.Fragment添加到ItemView中,但是View未創(chuàng)建
        // 先等待View創(chuàng)建完成,然后將View添加到Container。
        if (fragment.isAdded() && view == null) {
            scheduleViewAttach(fragment, container);
            return;
        }

        // 3.Fragment添加到ItemView中,同時(shí)View已經(jīng)創(chuàng)建完成并且添加到Container中
        // 需要保證View添加到正確的Container中。
        if (fragment.isAdded() && view.getParent() != null) {
            if (view.getParent() != container) {
                addViewToContainer(view, container);
            }
            return;
        }

        // 4.Fragment添加到ItemView中,同時(shí)View已經(jīng)創(chuàng)建完成但是未添加到Container中
        // 需要將View添加到Container中。
        if (fragment.isAdded()) {
            addViewToContainer(view, container);
            return;
        }

        // 5.Fragment未創(chuàng)建,View未創(chuàng)建、未添加
        if (!shouldDelayFragmentTransactions()) {
            scheduleViewAttach(fragment, container);
            mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).commitNow();
        } else {
            // 調(diào)用了第5步,但是Fragment還未真正創(chuàng)建
            if (mFragmentManager.isDestroyed()) {
                return; // nothing we can do
            }
            mLifecycle.addObserver(new GenericLifecycleObserver() {
                @Override
                public void onStateChanged(@NonNull LifecycleOwner source,
                        @NonNull Lifecycle.Event event) {
                    if (shouldDelayFragmentTransactions()) {
                        return;
                    }
                    source.getLifecycle().removeObserver(this);
                    if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }
    }

??如上便是加載Fragment所有流程,還是挺簡(jiǎn)單的,就是情況太多了。由于代碼中的注釋已經(jīng)詳細(xì)解釋了每一步的含義,所以這里就不再贅述了。

8. 總結(jié)

??其實(shí)ViewPager2本身的源碼是非常簡(jiǎn)單的,它的核心點(diǎn)就在各個(gè)組件當(dāng)中,所以本文就不對(duì)ViewPager2的內(nèi)部源碼進(jìn)行分析。到此為止,我們對(duì)ViewPager2的源碼分析完畢,在這里,我在做一個(gè)小小的總結(jié)。

  1. ViewPager2本身是一個(gè)ViewGroup,沒(méi)有特殊作用,只是用來(lái)裝一個(gè)RecyclerView
  2. PagerSnapHelper實(shí)現(xiàn)頁(yè)面切換效果的原因是calculateDistanceToFinalSnap阻止RecyclerView的Fling事件,直接讓它滑動(dòng)相鄰頁(yè)面;findSnapView方法和findTargetSnapPosition用來(lái)輔助滑動(dòng)到正確的位置。
  3. ScrollEventAdapter的作用將RecyclerView的滑動(dòng)事件轉(zhuǎn)換成為ViewPager2的頁(yè)面滑動(dòng)事件。
  4. PageTransformerAdapter的作用將普通的頁(yè)面滑動(dòng)事件轉(zhuǎn)換為特殊事件。
  5. FragmentStateAdapter完美實(shí)現(xiàn)了使用Adapter加載Fragment。在FragmentStateAdapter中,完美地考慮到ViewHolder的復(fù)用,F(xiàn)ragment加載和卸載。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容