在屏幕旋转时在 ViewPager 中显示带有数据的片段

Displaying Fragments with data in a ViewPager on screen rotation

因此,在我的 MainActivity OnCreate 中,我创建了 3 个片段,每个片段都填充了不同的数据。然后我将这些片段添加到我的 ViewPagerAdapter。

    ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
    MealsFragment exploreFragment = null;
    MealsFragment favoriteFragment = null;
    MealsFragment localFragment = null;

    //if there is no saved instance, create fragments with passed data as args and add them to the viewpageradapter
    if(savedInstanceState == null){
        exploreFragment = fetchExploreMealsDataAndCreateFragment();
        favoriteFragment = fetchFavoriteMealsDataAndCreateFragment();
        localFragment = fetchLocalMealsDataAndCreateFragment();
    }

    //add fragments to the adapter
    viewPagerAdapter.addFragment(exploreFragment, "EXPLORE");
    viewPagerAdapter.addFragment(favoriteFragment, "FAVORITES");
    viewPagerAdapter.addFragment(localFragment, "LOCAL");

    //set adapter to the viewpager and link it with the different tabs
    mviewPager.setAdapter(viewPagerAdapter);
    mtabLayout.setupWithViewPager(mviewPager);

3 种不同的获取方法从 API 或数据库中获取数据,因此我不想在每次旋转屏幕和经历我的生命周期时调用这些方法。这就是为什么我首先检查 savedInstanceState 是否为空。但是现在发生的情况是,如果我的 savedInstanceState 不为 null,则 Fragments 将为 null,因为我以这种方式初始化了它们。

显然这不是问题,因为当我旋转屏幕时,片段保持不变。我想知道幕后发生了什么,因为我认为这不是处理我的情况的正确方法。也欢迎任何改进建议。

提前致谢!

编辑:

忘了说 ViewPagerAdapter 是我自己实现的 FragmentPageAdapter

public class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> fragmentList = new ArrayList<>();
private final List<String> fragmentTitleList = new ArrayList<>();

public ViewPagerAdapter(FragmentManager fm) {
    super(fm);
}

@Override
public Fragment getItem(int i) {
    return fragmentList.get(i);
}

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

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

public void addFragment(Fragment fragment, String title){
    fragmentList.add(fragment);
    fragmentTitleList.add(title);
}

}

I was wondering what is going on here behind the scenes

好的,所以,这将涉及查看 FragmentPagerAdapter 的来源。相关的是 instantiateItem() 方法的实现……尽管实际上只是其中的一部分。

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    [...]

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }

    [...]
}

本质上,ViewPagerAdapter 中的 getItem() 方法在 Activity 的生命周期内(即使在配置更改时)只为每个位置调用一次。除了第一次之外,每次 Fragment 对象都直接从 FragmentManager 中检索,而不是从适配器中检索。

所以是的,对于您的 Activity 的所有重新创建,您的适配器持有一个 List<Fragment>,它只是 nullnullnull...不过没关系,因为这个列表不会再访问了。

然而

以上陈述假设您的适配器中的每个片段都已构建并添加到 FragmentManager 在您的配置更改之前,而这不是必然保证。

默认情况下,ViewPager 的离屏页面限制为 1。这意味着您的第三页(您的 localFragment)不一定会在首次启动时添加到 ViewPager,因此也不一定会添加到 FragmentManager。如果你滚动到下一页,即使是一次,它也会,但这不一定是真的。

或者您可能手动将离屏页面限制设置为 2,在这种情况下所有 pages/Fragments 将被立即添加。


也许最好的办法就是完全改变您使用 FragmentPagerAdapter 的方式。我会把它放在你的 Activity 里面作为内部 class:

private class ExampleAdapter extends FragmentPagerAdapter {

    public ExampleAdapter() {
        super(getSupportFragmentManager());
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0: return fetchExploreMealsDataAndCreateFragment();
            case 1: return fetchFavoriteMealsDataAndCreateFragment();
            case 2: return fetchLocalMealsDataAndCreateFragment();

            default: throw new IllegalArgumentException("unexpected position: " + position);
        }
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
            case 0: return "EXPLORE";
            case 1: return "FAVORITES";
            case 2: return "LOCAL";

            default: throw new IllegalArgumentException("unexpected position: " + position);
        }
    }

    @Override
    public int getCount() {
        return 3;
    }
}

然后你的onCreate()可以改成这样:

FragmentPagerAdapter viewPagerAdapter = new ExampleAdapter();
mviewPager.setAdapter(viewPagerAdapter);
mtabLayout.setupWithViewPager(mviewPager);

这样做的好处是您只需按需调用 "fetch and create" 方法,但在配置更改后未在配置之前加载它们的情况下仍然可以调用它们改变。