FragmentでViewPagerを使うときにFragmentPagerAdapterに渡すFragmentManagerの注意点
嵌ったのでメモ。
BottomNavigationBar
のタブの Fragment
内で ViewPager
を使っていて、そのタブから別タブに切り替えて再びそのタブに戻ってきたとき、FragmentPagerAdapter#getItem()
が呼ばれず、 ViewPager
内が再読込みされない現象が起きた。
調査の結果、 FragmentPagerAdapter
のインスタンスを作るときに渡す FragmentManager
が FragmentActivity#getSupportFragmentManager()
だと起きる。
そもそも FragmentPagerAdapter
はメモリ上に全てViewPager
の子Fragment
達を保持する。そのためタブを切り替えて戻ってきたときには以前の Fragment
が使い回される。
FragmentPagerAdapter | Android Developers
実際に FragmentPagerAdapter#getItem()
が呼ばれる付近のコードを見てみると確かに Fragment
が保存されている場合は FragmentPagerAdapter#getItem()
が呼ばれない。
public abstract class FragmentPagerAdapter extends PagerAdapter { ... @NonNull public Object instantiateItem(@NonNull ViewGroup container, int position) { if (this.mCurTransaction == null) { this.mCurTransaction = this.mFragmentManager.beginTransaction(); } long itemId = this.getItemId(position); String name = makeFragmentName(container.getId(), itemId); Fragment fragment = this.mFragmentManager.findFragmentByTag(name); if (fragment != null) { this.mCurTransaction.attach(fragment); } else { fragment = this.getItem(position); this.mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != this.mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } ... }
BottomNavigationView
のタブ切り替えでは Activity
は生きたままなのでその FragmentManager
もViewPager
内の子Fragment
達を保持したままになり、 FragmentPagerAdapter#getItem()
が呼ばれない。
↓の回答を見ると Fragment#getChildFragmentManager()
をFragmentPagerAdapter
に渡すといいとある。
stackoverflow.com
この場合、保持される ViewPager
内の子Fragment
達はその親Fragment
のFragmentManager
で管理される。タブを切り替えたときには通常、OnNavigationItemSelectedListener#onNavigationItemSelected()
で親Fragment
の新しいインスタンスを作るので、FragmentManager
もリセットされ、 FragmentPagerAdapter#getItem()
が呼ばれて再読込みが始まるようになる。
ViewPager
のスワイプが起きたときは 親 Fragment
が生きている限り、 FragmentManager
内で子 Fragment
達が管理されるので、通常通り効率的にスワイプできるようになる。
ちなみにこの問題は子Fragment
をメモリ上に保持しないFragmentStatePagerAdapter
では発生しない。これはページ数が多いなどメモリ保持するには重い場合に使う。
FragmentStatePagerAdapter | Android Developers
↑StackOverflowの回答の中で一番+が多い案は FragmentStatePagerAdapter
を使う案である。この場合はメモリ上に子Fragment
を保持しないのでスワイプ時の挙動が変わる。どちらが良いかはページ数などの状況で決めるといいと思う。