使用 FragmentStateAdapter 实现 ViewPager2 的问题

Issue on implementing ViewPager2 with FragmentStateAdapter

我来自 ViewPager 和 FragmentPagerAdapter,它有一个非常直接的实现,就像这样。

public class FragmentAdapters extends FragmentPagerAdapter {

    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public FragmentAdapters(@NonNull FragmentManager fm, int behavior) {
        super(fm, behavior);
    }

    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position);
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

}

父片段

adapter = new FragmentAdapters(getChildFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);

        CoinsFragment coinsFragment = new AssetFragment();
        NewsFragment newsFragment = new NewsFragment();
        VideoFragment videoFragment = new VideoFragment();

        adapter.addFragment(coinsFragment, getString(R.string.asset));
        adapter.addFragment(newsFragment, getString(R.string.news));
        adapter.addFragment(videoFragment, getString(R.string.videos));

        mViewPager.setAdapter(adapter);
        mViewPager.setOffscreenPageLimit(2);

Android 建议远离 ViewPager 并弃用心爱的 FragmentPagerAdapter,现在我正在尝试将 ViewPager2 与 FragmentStateAdapter 一起使用,但在其工作方式方面存在很多困惑和困难。

这些是我的实现

class AppFragmentAdapter(private val fragmentList: MutableList<Pair<String, Fragment>>, fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

private val pageIds = fragmentList.map { fragmentList.hashCode().toLong() }

override fun getItemCount(): Int = fragmentList.size

override fun createFragment(position: Int): Fragment = when (position) {
    0 -> {
        Log.wtf("WTF", fragmentList[position].first + " " + position)
        fragmentList[position].second
    }
    1 -> {
        Log.wtf("WTF", fragmentList[position].first + " " + position)
        fragmentList[position].second
    }
    2 -> {
        Log.wtf("WTF", fragmentList[position].first + " " + position)
        fragmentList[position].second
    }
    else -> throw IllegalStateException("Invalid adapter position")
}

fun getFragmentName(position: Int) = fragmentList[position].first

//    fun addFragment(fragment: Pair<String, Fragment>) {
//        fragmentList.add(fragment)
//        notifyItemInserted(fragmentList.size)
//        notifyItemRangeChanged(fragmentList.size, fragmentList.size)
//        notifyDataSetChanged()
//    }
//
//    fun removeFragment(position: Int) {
//        fragmentList.removeAt(position)
//        notifyItemRemoved(position)
//        notifyItemRangeChanged(fragmentList.size, fragmentList.size)
//        notifyDataSetChanged()
//    }

    override fun getItemId(position: Int): Long {
        return pageIds[position] // Make sure notifyDataSetChanged() works
    }

    override fun containsItem(itemId: Long): Boolean {
        return pageIds.contains(itemId)
    }

}

通常我们可以 return 立即从 createFragment 方法中的列表中提取片段,但存在巨大的混乱,因此我尝试扩展它以查看发生了什么。令我惊讶的是,当 createFragment 被调用一 (1) 次而不是有多少片段可用时 return 被 getItemCount 因此只显示一个片段并使其更多令人困惑 我应该是第一个片段 (AssetFragment) 被放在最后一个位置,前两个片段是空屏幕,我什至不知道那两个片段来自哪里。

父片段

val fragmentList : MutableList<Pair<String,Fragment>> = ArrayList()

        fragmentList.add(Pair(getString(R.string.assets), AssetFragment.newInstance()))
        fragmentList.add(Pair(getString(R.string.news), NewsFragment.newInstance()))
        fragmentList.add(Pair(getString(R.string.videos), VideosFragment.newInstance()))

        val adapter = AppFragmentAdapter(fragmentList, requireActivity())
        viewPager.adapter = adapter
        viewPager.offscreenPageLimit = 2

这就是我的 Fragment 的样子,基本上没什么特别的

class AssetFragment : BaseFragment() {

    companion object {
        fun newInstance() = AssetFragment()
    }

    private lateinit var viewModel: AssetViewModel

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

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(this).get(AssetViewModel::class.java)
        // TODO: Use the ViewModel
    }

}

谁能告诉我这是什么荒谬的行为? P.S。我试着用 getItemCount 做一些日志,它被调用了 23 次只是为了一个 3 片段?

这很奇怪,因为覆盖

getItemId()
containsItem()

在使用不同种类的片段时只会出现这种不良行为class 我有。

最后我只需要一个简单的FragmentStateAdapterclass像这样

class AppFragmentAdapter(private val fragmentList: MutableList<Pair<String, Fragment>>, fragment: Fragment) : FragmentStateAdapter(fragment) {

//    private var pageIds = fragmentList.map { fragmentList.hashCode().toLong() }

    override fun getItemCount(): Int = fragmentList.size

    override fun createFragment(position: Int): Fragment {
        return fragmentList[position].second
    }

//    override fun getItemId(position: Int): Long = pageIds[position] // Make sure notifyDataSetChanged() works

//    override fun containsItem(itemId: Long): Boolean = pageIds.contains(itemId)

    fun getFragmentName(position: Int) = fragmentList[position].first

    fun addFragment(fragment: Pair<String, Fragment>) {
        fragmentList.add(fragment)
        notifyDataSetChanged()
    }

    fun removeFragment(position: Int) {
        fragmentList.removeAt(position)
        notifyDataSetChanged()
    }

}

太糟糕了,我搜索了一个关于如何使 ViewPager2 动态化的直接答案,而没有先尝试我能想到的最简单的方法。许多关于 SO 的回答指出,在 ViewPager2 上添加或删除 Fragment 时需要覆盖 getItemId()containsItem(),这让我们头疼了将近 2 天。感觉被背叛了。