天天看點

FragmentPagerAdapter、FragmentStatePagerAdapter和FragmentStateAdapter的差別你知道嘛

前段時間在給公司項目做優化,換用了ViewPager,可以左右切換頁面,互動更順滑,改完之後發現Fragment總是不複用,每次切換回來會重複走

onCreateView()

onDestroyView()

生命周期???這咋能行呢,于是就在

onCreateView()

做了簡單的判斷,因為項目用的是ViewBinding,是以就ViewBinding不為空就直接傳回

ViewBinding.getRoot()

了。這幾天在看Android Jetpack的ViewPager2發現多了個

FragmentStateAdapter

用法比之前簡單不少:

class CollectionDemoFragment : Fragment() {
    // When requested, this adapter returns a DemoObjectFragment,
    // representing an object in the collection.
    private lateinit var demoCollectionAdapter: DemoCollectionAdapter
    private lateinit var viewPager: ViewPager2

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.collection_demo, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        demoCollectionAdapter = DemoCollectionAdapter(this)
        viewPager = view.findViewById(R.id.pager)
        viewPager.adapter = demoCollectionAdapter
    }
}
//重點在這裡 就重寫了兩個方法
class DemoCollectionAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount(): Int = 100

    override fun createFragment(position: Int): Fragment {
        // Return a NEW fragment instance in createFragment(int)
        val fragment = DemoObjectFragment()
        fragment.arguments = Bundle().apply {
            // Our object is just an integer :-P
            putInt(ARG_OBJECT, position + 1)
        }
        return fragment
    }
}

           

然後興沖沖的給項目安排上,感覺自己又跟上大佬的腳步了,可是無意間看到居然沒有重寫走

onCreateView()

onDestroyView()

生命周期(順便提一下

FragmentManager.FragmentLifecycleCallbacks

可以監控Fragment生命周期類似Activity生命周期監聽

Application.ActivityLifecycleCallbacks

),本着打破砂鍋問到底的原則詳細了解了下FragmentPagerAdapter、FragmentStatePagerAdapter和FragmentStateAdapter三者的差別。

FragmentPagerAdapter

源于androidx.fragment,繼承于

PagerAdapter

,已于androidx.fragment 1.3.0版本廢棄,重點在

instantiateItem()

@NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        final long itemId = getItemId(position);

        // Do we already have this fragment?
        //這個fragment已經存在過?
        String name = makeFragmentName(container.getId(), itemId);
        Fragment fragment = mFragmentManager.findFragmentByTag(name);
        //已經存在執行attach(fragment);
        if (fragment != null) {
            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
            mCurTransaction.attach(fragment);
        } else {//沒有存在通過getItem()擷取
            fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
            mCurTransaction.add(container.getId(), fragment,
                    makeFragmentName(container.getId(), itemId));
        }
        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;
    }
           

可以看到

FragmentPagerAdapter

的處理邏輯是存在這個Fragment就會執行

attach()

必然會重走一遍Fragment的生命周期,并不會複用之前建立的Fragment,顯然跟我們要的不符,這個時候我們就用到了

FragmentStatePagerAdapter

FragmentStatePagerAdapter

同樣源于androidx.fragment,繼承于

PagerAdapter

,已于androidx.fragment 1.3.0版本廢棄,同樣我們也看一下

instantiateItem()

裡的實作

@NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
        if (mSavedState.size() > position) {
            Fragment.SavedState fss = mSavedState.get(position);
            if (fss != null) {
                fragment.setInitialSavedState(fss);
            }
        }
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
            fragment.setUserVisibleHint(false);
        }

        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment);

        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
        }

        return fragment;
    }
           

可以看到

FragmentStatePagerAdapter

有緩存Fragment隊列,當Fragment執行個體化之後會取已經執行個體化的Fragment,并且多了個SavedState的邏輯,用于儲存Fragment的狀态(滑過後會儲存目前界面,以及下一個界面和上一個界面(如果有),最多儲存3個,其他會被銷毀掉,可以通過ViewPager的

setOffscreenPageLimit(int limit)

設定)

但在回調

onDestroy()

方法之前會回調

onSaveInstanceState(Bundle outState)

方法來儲存Fragment的狀态,下次Fragment顯示時通過

onCreate(Bundle savedInstanceState)

把存儲的狀态值取出來,

FragmentStatePagerAdapter

比較适合頁面比較多的情況。

FragmentStateAdapter

源于ViewPager2是

FragmentPagerAdapter

FragmentStatePagerAdapter

的替換類,繼承于

RecyclerView.Adapter

FragmentStatePagerAdapter

相似,該擴充卡實作了

StatefulAdapter

用于儲存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);
        }
    }
           

可以看到

FragmentStateAdapter

同樣有緩存Fragment隊列