Androidx 中的 ViewPager 與 ViewPager2

前言

第一篇文章Fragment 與 FragmentPagerAdapter
我應該算是詳細敘述了Fragment與PagerAdapter的一些知識點,但那時候公司還沒引進AndroidX的庫,最近發現AndroidX與Support庫下的PagerAdapter還是有些許區別,多個api都標上了@Deprecate

所以這篇文章第一部分會先分析一下二者的區別。

第二部分則講述ViewPager 與 Viewpager2的接口區別。

第三部為Viewpager2的解析。

一、AndroidX中FragmentPagerAdapter的變化

其中FragmentPagerAdapter和FragmentStatePagerAdapter的變化大同小異,就以FragmentPagerAdapter為例。

@Behavior

首先很明顯的一點就是FragmentPagerAdapter的構造函數多了一個參數!

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
    private @interface Behavior { }

  @Deprecated
  public FragmentPagerAdapter(@NonNull FragmentManager fm) {
        this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
  }
   / *
     * @param fm fragment manager that will interact with this adapter
     * @param behavior determines if only current fragments are in a resumed state
     */
    public FragmentPagerAdapter(@NonNull FragmentManager fm,  @Behavior int behavior) {
        mFragmentManager = fm;
        mBehavior = behavior;
    }

就是Behavior這個玩意,當其值為默認的BEHAVIOR_SET_USER_VISIBLE_HINT時,那么FragmentPagerAdapter的表現就會與原support庫下一致,當為BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT時,那事情就會有點意思了!會影響ViewPager下Fragment的生命周期。

懶加載

As we know (翻譯:眾所周知。知識點!),以前我們切換Fragment頁面時,Adapter會調用fragement.setUserVisibleHint(boolean isVisibleToUser)來標志ViewPager的當前顯示頁面。而我們用getUserVisibleHint()來判斷并執行懶加載。

就這幾年開發經驗來說,見過幾個項目不同同事的關于ViewPager中不同的懶加載的處理,大多數是不處理!在onViewCreated()的時候,把當前頁面以及兩側緩存的頁面的數據都做了請求~沒有一絲絲防備。。

當然這樣寫非常的簡單,也不會出錯,只是沒必要,畢竟不少情況下,用戶可能都不會去滑動ViewPager。所以作為一位碼農,還是要有點追求的,何況這個問題處理起來也不難。

懶加載的三個判斷:

  1. 是否為當前頁面(是否可見)
  2. 是否已經加載過了
  3. 視圖是否初始化完成(setUserVisibleHint()的調用在onCreateView之前!)

當然還可以加個是否強制刷新之類的~

    var hasLoad = false

    var isViewInitiated = false

    fun loadData() {
        if (userVisibleHint && !hasLoad && isViewInitiated) {
            // load
            hasLoad = true
        }
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        loadData()
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        isViewInitiated = true
        loadData()
   }
      override fun onDestroyView() {
        super.onDestroyView()
         isViewInitiated = false // 注意考慮View被銷毀,而Fragment對象還在
    }

現在世道變了,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT之下用的是Lifecycle

      public Object instantiateItem(@NonNull ViewGroup container, int position) {
       
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        ...
        if (fragment != mCurrentPrimaryItem) {
            fragment.setMenuVisibility(false);
            // 看這里
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
            } else {
                fragment.setUserVisibleHint(false);
            }
        }
        return fragment;
    }

    @Override
    public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
               // 看這里
                if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                    if (mCurTransaction == null) {
                        mCurTransaction = mFragmentManager.beginTransaction();
                    }
                    mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
                } else {
                    mCurrentPrimaryItem.setUserVisibleHint(false);
                }
            }
            fragment.setMenuVisibility(true);
            // 看這里
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
            } else {
                fragment.setUserVisibleHint(true);
            }

            mCurrentPrimaryItem = fragment;
        }
    }

注意兩個函數中的mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
可發現初始化Fragment時,生命周期最大狀態為STARTED
而setPrimaryItem()設置當前的頁面為RESUMED, 原先的頁面改為STARTED

STARTED這個值意味著什么呢?

  1. 新建的Fragment生命周期在執行onActivityCreated()之后會繼續執行onStrat() ,但不會執行onResume()!
  2. 而原先處于RESUME狀態的則會執行onPause().

可以參考 ==> Lifecycle.State
setMaxLifecycle對Fragment周期的具體影響。

相信這時候,你應該有了如何改寫懶加載的方法了!

    var hasLoad = false

    fun loadData() {
        if (hasLoad) {
            // load
            hasLoad = true
        }
    }

    override fun onResume() {
        super.onResume()
        loadData()
    }

一個變量搞定。
so easy, mama never mind my study.
押韻就好 Never mind the details

二、述ViewPager 與 Viewpager2的區別。

ViewPager2發布好幾個月咯(2019.11.20),這么香的控件不學習一波?
這一部分主要參考官網的文檔。
viewpager2 版本迭代
viewpager2遷移指南

1. ViewPager2新增功能

1.0.0 的主要功能

  • 對之前的 ViewPager 實現的改進:
    • RTL(從右向左)布局支持
     <androidx.viewpager2.widget.ViewPager2
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/pager"
         android:layoutDirection="rtl"
         />
    
    • 垂直方向支持
    android:orientation="vertical"
    
    • 可靠的 Fragment 支持(包括處理 Fragment 的數據集)
      運行時,notifyDatasetChanged()來更新UI,正確的展示。
    • 數據集更改動畫(包括 DiffUtil 支持)

2. 接口變更

雖然 ViewPager2 保持了大多的 ViewPager的API的一致。但是總有一些必須改變,容我娓娓道來。

  • FragmentStateAdapter << == FragmentStatePagerAdapter、FragmentPagerAdapter
    是的,不用糾結了,統一使用FragmentStateAdapter
  • RecyclerView.Adapter << == PagerAdapter
    你沒有看錯,就是RecycleView,還有熟悉的ViewHolder、onBindViewHodler()
  • FragmentStateAdapter#createFragment << == PagerAdapter#getItem
    事實證明,以前的方法名稱是過去出現問題的根源。使得不少新手在getItem()與instantiateItem()之間打交道時吃過虧。
  • getItemCount << == getCount
  • ViewPage2#registerOnPageChangeCallback << == addPageChangeListener
    用抽象類替代接口,可以不用一次性override三個接口方法了,想用哪個就重載哪個
class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}

另外若使用了TabLayout
那記得升級material至1.1.0

implementation "com.google.android.material:material:1.1.0"

然后使用TabLayoutMediator

class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
    }
    ...
}

emmm... mama nerver...

三、 ViewPager2的解析

?? 終于到了重中之重的第三部分, == 讓我來看下現在的時間,

剛好午夜12點。休息一下,玩會手機。明天再搬運代碼。

好的,又是一周過去了,真是令人懷念這又浪費的一周的時光。
進入正題。
去年有天晚上閑著沒事,我研究了一晚上的ViewPager源碼,最后得出結論,我真是閑得蛋疼。簡直毫無意義,各種view偏移量計算以及意義不明的變量,看得是真難受。

發現ViewPager2是RecycleView封裝的之后,來了興趣,于是又干起這無意義的事,研究ViewPager2的源碼。幸虧,收獲還是有的,源碼也很通俗易懂,算上注釋代碼量也就1607行,原來的一半都不到!
真正進入正題~

里面的內容比起ViewPager是極簡了。簡單說就是將RecycleView再封裝了一遍,然后協同FragmentStateAdapter將RecycleView的每個Item與Fragment綁定!

ending,happy~

別打臉,我繼續說就是了。

看下ViewPager2的初始化。

ViewPager2中除了初始化的代碼,其它值得關注的地方并不多。

    private void initialize(Context context, AttributeSet attrs) {
        mAccessibilityProvider = sFeatureEnhancedA11yEnabled
                ? new PageAwareAccessibilityProvider()
                : new BasicAccessibilityProvider();

        mRecyclerView = new RecyclerViewImpl(context);
        mRecyclerView.setId(ViewCompat.generateViewId());
        mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        mLayoutManager = new LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
        setOrientation(context, attrs);

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

        // Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
        // attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
        mScrollEventAdapter = new ScrollEventAdapter(this);
        // Create FakeDrag before attaching PagerSnapHelper, same reason as above
        mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
        mPagerSnapHelper = new PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
        // Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
        // don't want to respond on the events sent out during the attach process
        mRecyclerView.addOnScrollListener(mScrollEventAdapter);

        mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
        mScrollEventAdapter.setOnPageChangeCallback(mPageChangeEventDispatcher);

        // Callback that updates mCurrentItem after swipes. Also triggered in other cases, but in
        // all those cases mCurrentItem will only be overwritten with the same 
       // 刪除幾個設置各種事件監聽的代碼 
      ...

        attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
    }

其中mRecyclerView = new RecyclerViewImpl(context);mLayoutManager = new LinearLayoutManagerImpl(context); 屬于重新封裝的RecyclerView和LinearLayoutManager,是分別為了處理事件攔截ViewPager緩存頁面的問題。

事件攔截我們結合mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView); 來看。
FakeDragger模擬拖拽事件!通過設置 setUserInputEnabled(false)攔截用戶觸摸事件,那就得通過模擬拖拽來實現切換下一頁了。

class RecyclerViewImpl  {
        ...
        public boolean onTouchEvent(MotionEvent event) {
            // isUserInputEnabled == false 則 不執行super.onTouchEvent(event)
            return isUserInputEnabled() && super.onTouchEvent(event);
        }

        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            // isUserInputEnabled == false 結合上面,就是,不攔截也不消費。
            // 所有事件直接分配給子View,若子View不消費,直接還給父View
            return isUserInputEnabled() && super.onInterceptTouchEvent(ev);
        }
}

mPagerSnapHelper = new PagerSnapHelperImpl();,PagerSnapHelper,即RecycleView的官方輔助類,則是為使得RecycleView的每個Item默認大小都是MATCH_PARENT,即每滑動一個就是一頁,且不能快速滑動。

最終RecycleView成為ViewPager的子View

attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());

也就是這樣使得RecycleView有了ViewPager的特性!
真的不能再簡單了,去你的ViewPager源碼。
RecycleView的知識,推薦這篇
看完這篇,面試RecyclerView的時候再也不怕了

當然這里沒有涉及ViewPager2的具體api的使用,由于實踐并不多,所以,還是推薦篇文章吧:
學不動也要學!深入了解ViewPager2

FragmentStateAdapter與ViewPager2

當然這里還要繼續說下FragmentStateAdapter與ViewPager2是怎么玩的。而關鍵之處就是如何使ViewItem與Fragment綁定,使之擁有生命周期。

先看看熟悉這兩個方法:

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

    @Override
    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
        if (boundItemId != null && boundItemId != itemId) {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }

        mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
        ensureFragment(position);

        /** Special case when {@link RecyclerView} decides to keep the {@link container}
         * attached to the window, but not to the view hierarchy (i.e. parent is null) */
         // 這里不是很懂了。指的是特殊情況下:container已經AttcahedToWindow,但是container.getParent() == null,需要重新綁定下Fragment。啥時候會發生這情況呢?
        final FrameLayout container = holder.getContainer();
        if (ViewCompat.isAttachedToWindow(container)) {
            if (container.getParent() != null) {
                throw new IllegalStateException("Design assumption violated.");
            }
            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);
                        placeFragmentInViewHolder(holder);
                    }
                }
            });
        }

        gcFragments();
    }

大致順序解讀下這兩段代碼.

1. 普普通通的FragmentViewHolder,

就是一個普通的僅有一個FrameLayout的Holder。
等待Fragment的視圖在正確的時機被add進來。

/**
 * {@link ViewHolder} implementation for handling {@link Fragment}s. Used in
 * {@link FragmentStateAdapter}.
 */
public final class FragmentViewHolder extends ViewHolder {
    private FragmentViewHolder(@NonNull FrameLayout container) {
        super(container);
    }

    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }

    @NonNull FrameLayout getContainer() {
        return (FrameLayout) itemView;
    }
}
2. onBindViewHolder() 變量解析

開局就三個無知所謂的id,真叫人頭大

       final long itemId = holder.getItemId();
        final int viewHolderId = holder.getContainer().getId();
        final Long boundItemId = itemForViewHolder(viewHolderId);   // 1
        if (boundItemId != null && boundItemId != itemId)  {
            removeFragment(boundItemId);
            mItemIdToViewHolder.remove(boundItemId);
        }
        // 與注釋1結合,boundItemId要么為null,要么等于itemId
        mItemIdToViewHolder.put(itemId, viewHolderId) 
      

先看下itemId的值是什么。
由于FragmentStateAdapter初始化時就調用了setHasStableIds(true),使得holder.getItemId()返回的即為holder所在的位置。

class ViewHolder {
        public final long getItemId() {
            return mItemId;
        }
}
class RecycleView.Adapter:   
    public final void bindViewHolder(@NonNull VH holder, int position) {
            holder.mPosition = position;
            if (hasStableIds()) {
                holder.mItemId = getItemId(position); // 看我看我
            }
            ...
    }

class FragmentStateAdapter extend RecycleView.Adapter {
    public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
            @NonNull Lifecycle lifecycle) {
        mFragmentManager = fragmentManager;
        mLifecycle = lifecycle;
         // 注意使用的super。因為這里FragmentStateAdapter覆蓋了這個方法
         // 其實現是接直接拋出UnsupportedOperationException,并不想讓你設置為false
        super.setHasStableIds(true); // 看我
    }
    
    public long getItemId(int position) { // 看我
        return position;
    }
}
 

所以也就是說itemId = holder.getItemId() = adapter.getItemId() = position

而至于viewHolderId就是ViewHolder中的FrameLayout的viewId。跟定義在xml中的固定值不一樣了,這里每個ViewHolder的viewId都不相同。

container.setId(ViewCompat.generateViewId());

boundItemId則是 用于將 viewHolderId 保存在Adapter的LongSparseArray中所使用的Key值(位置),這個值在另外一個列表中同時也指向某個Fragment。實際上 保存的時候,最終值依然等于position

3. 確定相應的Fragment,ensureFragment()

onBindViewHolder中還會繼續執行這一步,根據緩存判斷是否需要創建新的Fragment,并將之前回收Fragment的時保留的狀態恢復回來。

    private void ensureFragment(int position) {
        long itemId = getItemId(position);
        if (!mFragments.containsKey(itemId)) {
            // TODO(133419201): check if a Fragment provided here is a new Fragment
            Fragment newFragment = createFragment(position);
            newFragment.setInitialSavedState(mSavedStates.get(itemId));
            mFragments.put(itemId, newFragment);  // itemId也對應著Fragment
        }
    }
4. ViewHolder與Fragment進行綁定與解除

除了上面onBindViewHolder中注釋提到的特殊情況,一般是在這里進行綁定:

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

placeFragmentInViewHolder中的邏輯判斷比較多,我就不貼代碼了

大概就是 Fragment是否已經添加入FragmentManager,View是否銷毀了.
否則執行:

class FragmentStateAdapter {
    placeFragmentInViewHolder() {

          mFragmentManager.beginTransaction()
                    .add(fragment, "f" + holder.getItemId())
                    .setMaxLifecycle(fragment, STARTED)  // 嘿嘿
                    .commitNow();
                    
          //然后等待Fragment的View創建完成后, 添加到holder的FrameLayout上:
          mFragmentManager.registerFragmentLifecycleCallbacks(
                new FragmentManager.FragmentLifecycleCallbacks() {
                    // TODO(b/141956012): Suppressed during upgrade to AGP 3.6.
                    @SuppressWarnings("ReferenceEquality")
                    @Override
                    public void onFragmentViewCreated(@NonNull FragmentManager fm,
                            @NonNull Fragment f, @NonNull View v,
                            @Nullable Bundle savedInstanceState) {
                        if (f == fragment) {
                            fm.unregisterFragmentLifecycleCallbacks(this);
                            // 這里
                            addViewToContainer(v, container);
                        }
                    }
                }, false);
        }
}

嘿,注意關鍵點!setMaxLifecycle(fragment, STARTED) 懶加載!
默認STARTED,跟ViewPager一樣。
滑動頁面時:

      public void onPageScrollStateChanged(int state) {
              updateFragmentMaxLifecycle(false);
       }

會將當前的Fragent設置為RESUME,所以說起來ViewPager2的懶加載就跟AndroidX中的ViewPager一樣咯。在onResume()中判斷下是否已經加載過了就好了。

最后在onViewRecycled時 回收Fragment,保留狀態。跟FragmentStatePagerAdapter很是相似了。

    @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);
        }
    }

到這里,就接近尾聲了。
突然想起還有個玩意,還是要提一下的。

5. 頁面緩存數量
    public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
        if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
            throw new IllegalArgumentException(
                    "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
        }
        mOffscreenPageLimit = limit;
        // Trigger layout so prefetch happens through getExtraLayoutSize()
        mRecyclerView.requestLayout();
    }

這里的OFFSCREEN_PAGE_LIMIT_DEFAULT默認值為-1,若調用該方法則不可設置小于1的情況。

上面我們提LinearLayoutManagerImp有設置頁面緩存的作用,就是這里了:

    @Override
    protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
            @NonNull int[] extraLayoutSpace) {
        int pageLimit = getOffscreenPageLimit();  // 看我看我
        if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
            // Only do custom prefetching of offscreen pages if requested
            super.calculateExtraLayoutSpace(state, extraLayoutSpace);
            return;
        }
        final int offscreenSpace = getPageSize() * pageLimit;
        extraLayoutSpace[0] = offscreenSpace;  // 左右兩邊額外布局
        extraLayoutSpace[1] = offscreenSpace;
    }

調用setOffscreenPageLimit使得RecycleView 預加載(繪制)了前后的頁面。當其值為默認的OFFSCREEN_PAGE_LIMIT_DEFAULT時,則會依舊按照RecycleView的默認緩存機制進行。

另外,我們上面提了Fragment在onViewRecycled()時被銷毀!注意,并不是onViewDetachedFromWindow, 也就是當前頁面移出視野,且也不在PageLimit的范圍內的時候,雖然View已經DetachedFromWindow,但并未執行onViewRecycled(),所以Fragment, 還是與這個View綁定的呢,畢竟,又滑回到這個頁面時,并不會執行onBindViewHolder。
一般再多滑兩頁,這個Fragment才會被銷毀。

這里引出一個思考,為什么不在onViewRecycled()對應的onBindViewHolder()中進行 ViewHolder與Fragment的綁定?
發現,在使用ViewPager2中,滑動頁面會預加載執行onBindViewHolder()。看這份log,當前使用的setOffscreenPageLimit(1)

//打開頁面
2020-03-07 18:19:12.298 I: createFragment 0
2020-03-07 18:19:12.299 I: onBindViewHolder 0
2020-03-07 18:19:12.300 I: onCreate 0
2020-03-07 18:19:12.300 I: onStart 0
2020-03-07 18:19:12.301 I: onResume 0
2020-03-07 18:19:12.302 I: createFragment 1
2020-03-07 18:19:12.303 I: onBindViewHolder 1
2020-03-07 18:19:12.303 I: onCreate 1
2020-03-07 18:19:12.303 I: onStart 1 // 初始化第一頁和第二頁,??
//這個時間點開始滑動切換第2頁(注意,下標為1!)
2020-03-07 18:19:18.556 I: createFragment 2
2020-03-07 18:19:18.557 I: onBindViewHolder 2
2020-03-07 18:19:18.561 I: onCreate 2
2020-03-07 18:19:18.566 I: onStart 2
// 看這里,執行了第四頁的onBindViewHolder!
2020-03-07 18:19:18.590 I: createFragment 3
2020-03-07 18:19:18.591 I: onBindViewHolder 3
2020-03-07 18:19:18.942 I: onPause 0
2020-03-07 18:19:18.943 I: onResume 1
// 切換至第三頁,第四頁的生命周期才被加載!
2020-03-07 18:31:35.898 I: onCreate 3
2020-03-07 18:31:35.900 I: onStart 3

OFFSCREEN_PAGE_LIMIT_DEFAULT的情況也類似!也就是說,滑動會預先實例化Fragment的情況,若在onBindViewHolder進行綁定,Fragment的生命周期開始執行,那就出問題了嘛。

我原本以為這只是ViewPager2才會這樣(預加載),是不是自己遺漏了什么。然后用RecycleView直接加載一個一些普通View試試,同時也用了support 包下28.0.0版本RecycleView進行驗證。才發現其實這個預加載在RecycleView中本來就有的,以前居然沒注意過,太失敗了。

End

這下真的尾聲了。

請大佬不吝賜教。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370