在 FragmentTransaction.replace() 后跟后退按钮后,不会重新创建 ViewPager 片段

ViewPager Fragments are not recreated after FragmentTransaction.replace() followed by back button

我正在尝试实现保存和恢复状态,但是在用 PreferenceFragment 替换主 Fragment 然后点击后退按钮时,我 运行 遇到了问题。我的主要 Fragment 由一个 ViewPager 和一个 FragmentPagerAdapter 以及 3 个 Fragments 组成。我的 3 FragmentFragment.onCreateView() 回调中的 None 在点击后退按钮后被调用。我已经尝试了我在 SO 上找到的所有解决方案,但我无法解决问题。

另一个可能需要注意的重要事项是我的 3 ViewPager Fragment 的数据存储在单独的 classes 中,这些 classes 可以通过 Activity. 3 个 Fragment 都包含 RecyclerView,用于列出大量数据。这样做是为了清洁,也是为了让这些数据持久存在 Activity。这 可能 不是问题,因为它在启动应用程序时工作正常,而且主要问题是我的 Fragments 没有重新创建。

意外行为:

在创建应用程序时,一切正常,但是当我用另一个替换我的主 Fragment(包含一个 ViewPager 和一个 FragmentPagerAdapter)然后按下后退按钮时, ViewPager 中的 Fragment 不会重新创建。我的主FragmentonCreateView()

问题:

我错过了什么?是否应该创建其他一些回调?我应该在哪里以及如何重新创建 Fragments?

我尝试了什么:

  1. 更改了正在使用的 FragmentAdapter,但我真的应该使用 getSupportFragmentManager() 作为解决方案 here

编辑:使用错误的 FragmentManager 实际上 我麻烦的根源。请参阅下面我的回答。

  1. 添加 @Override public int getItemPosition(Object object) {return POSITION_NONE;}FragmentPagerAdapter,建议here.
  2. FragmentPagerAdapter 更改为 FragmentStatePagerAdapter,据我所知,这在这里无关紧要。 FragmentStatePagerAdapter 的内部实际上保留了 Fragments,但它们没有重新呈现。
  3. onCreateView() 被调用时使用 FragmentTransaction 将主 Fragment 添加回来,并在 Fragment 的其他几个回调中,参见 MMMainFragment 下面。
  4. 其他各种东西。

我的 MainFragment class 具有相应的 XML 布局

public class MMMainFragment extends Fragment
{
    private MMViewPager mMMViewPager = null;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        // Note that this method IS called when the back button is pressed.
        // I have tried setting the content back to this instance in several places.

        View view = inflater.inflate(R.layout.mm_main_fragment, container, false);

        // Setup ViewPager.
        mMMViewPager = (MMViewPager) view.findViewById(R.id.mm_pager);
        TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
        tabLayout.setupWithViewPager(mMMViewPager);

        return view;
    }

    public MMViewPager getMMViewPager()
    {
        return mMMViewPager;
    }
}

mm_main_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.design.widget.TabLayout
        android:id="@+id/mm_tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="scrollable" />

    <com.mycompany.myapp.gui.mmpager.MMViewPager
        android:id="@+id/mm_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />


</LinearLayout>

然后,在 Activity.onCreate() 我只是做:

// Insert Main Fragment.
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
        .replace(R.id.mm_content_frame, new MMMainFragment())
        .commit();

其中 mm_content_frame 是一个 FrameLayout,我在其中替换并删除了 Fragment。然后,当按下查看首选项的按钮时,我 运行 下面的代码片段位于 Activity 中。我将此 Fragment 添加到后退堆栈以便能够使用后退按钮。

public void showSettingsFragment()
{
    getSupportActionBar().setTitle(getString(R.string.mm_drawer_settings));
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.mm_content_frame, new MMPreferencesFragment(), FragmentConstants.SETTINGS_FRAGMENT_TAG)
            .addToBackStack(null)
            .commit();
}

MMViewPager 类:

public class MMViewPager extends ViewPager
{
    private MMActivity mMMActivity;
    private MMPagerAdapter mMMPagerAdapter;

    public MMViewPager(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mMMActivity = (MMActivity) context;
        mMMPagerAdapter = new MMPagerAdapter(mMMActivity.getSupportFragmentManager(), mMMActivity);

        this.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
        this.setAdapter(mMMMPagerAdapter);
        this.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);
    }
}

MMPagerAdapter class,当前 FragmentStatePagerAdapter:

public class MMPagerAdapter extends FragmentStatePagerAdapter
{
    private MMActivity mMMActivity;

    public MMPagerAdapter(FragmentManager fragmentManager, MMActivity mmActivity)
    {
        super(fragmentManager);
        mMMActivity = mmActivity;
    }

    @Override
    public Fragment getItem(int position)
    {
        switch (position)
        {
            case PagerConstants.PAGE_FILTER_RECIPES:
                return new FilteredRecipesFragment();

            case PagerConstants.PAGE_SELECTED_RECIPES:
                return new SelectedRecipesFragment();

            case PagerConstants.PAGE_SHOPPING_LIST:
                return new ShoppingListFragment();

            default:
                return null;
        }
    }

    @Override
    public int getCount()
    {
        return PagerConstants.NUMBER_OF_PAGES; // 3
    }

    @Override
    public CharSequence getPageTitle(int position)
    {
        switch (position)
        {
            case PagerConstants.PAGE_FILTER_RECIPES:
                return mMMActivity.getResources().getString(R.string.mm_title_recipe_filter_fragment);

            case PagerConstants.PAGE_SELECTED_RECIPES:
                return mMMActivity.getResources().getString(R.string.mm_title_selected_recipes_fragment);

            case PagerConstants.PAGE_SHOPPING_LIST:
                return mMMActivity.getResources().getString(R.string.mm_title_shopping_list_fragment);

            default:
                return "Tab";
        }
    }
}

供参考,这里也是Activity的主要布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mm_drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Source: http://developer.android.com/training/implementing-navigation/nav-drawer.html -->
    <!-- The main content view -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Source: http://android-developers.blogspot.in/2014/10/appcompat-v21-material-design-for-pre.html -->
        <android.support.v7.widget.Toolbar
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/mm_toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            android:layout_alignParentTop="true"
            style="@style/MMActionBar"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        <!-- The FrameLayout where I replace Fragments -->
        <FrameLayout
            android:id="@+id/mm_content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/mm_toolbar"/>

    </RelativeLayout>


    <ListView
        android:id="@+id/mm_drawer_listview"
        android:layout_width="260dp"
        android:layout_height="match_parent"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:paddingStart="16dp"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="@color/mm_white" />

</android.support.v4.widget.DrawerLayout>

纠结了几天后,我意识到问题实际上,我把错误的FragmentManager传给了ViewPager。确实应该使用 Fragment.getChildFragmentManager() 返回的 FragmentManager 。这是有道理的,因为 Fragment 本身将子 Fragment 的状态存储在 ViewPager 中。我不确定为什么我之前无法完成这项工作,但现在应用程序的 LifeCycle 在我的主要 FragmentonCreate() 方法中使用以下设置可以正常工作:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    View view = inflater.inflate(R.layout.mm_main_fragment, container, false);

    mMMActivity = (MMActivity) container.getContext();
    mMMActivity.getSupportActionBar().setTitle(getString(R.string.mm_toolbar_title_main_fragment));

    // Setup ViewPager.
    mViewPager = (ViewPager) view.findViewById(R.id.mm_pager);
    mAdapter = new MMPagerAdapter(getChildFragmentManager(), mMMActivity); // <-- This is the key
    mViewPager.setAdapter(mAdapter);
    mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
    mViewPager.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);

    // Setup TabLayout.
    TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
    tabLayout.setupWithViewPager(mViewPager);

    return view;
}

这也是 another question. As a side note, I use this solution 在我的 ViewPager 中获取 Fragment 的解决方案,它与 LifeCycle 一起工作得很好。

测试应用程序:

在研究这个问题的过程中,我为此创建了一个测试应用程序。有兴趣的可以从here克隆它。它有难看的颜色。

据我所知,你已经解决了你的问题。我过去遇到过一个非常相似的问题。也许这可以帮助其他人尝试类似的事情。

帮助我保持片段状态的是替换

@Override
public Fragment getItem(int position)
{
    switch (position)
    {
        case PagerConstants.PAGE_FILTER_RECIPES:
            return new FilteredRecipesFragment();

        case PagerConstants.PAGE_SELECTED_RECIPES:
            return new SelectedRecipesFragment();

        case PagerConstants.PAGE_SHOPPING_LIST:
            return new ShoppingListFragment();

        default:
            return null;
    }
}

我的适配器方法。

我更改了 Adapter 以保持 FragmentHashMap。这样,片段被创建一次,然后从 HashMap 中检索。根据您的结构,您可以使用 ArrayList 而不是 HashMap。

这是以一种高效且 "smart" 的方式实现的 getItemPosition(Object object)return POSITION_NONE; 或项目的正确位置。

我不太记得适配器中是否有 getChildFragmentManager

如果您需要更多信息,我想我可以深入了解我的文件系统:)