ViewPager片段在FragmentTransaction.replace()后跟随返回按钮未重新创建。

29
我正在尝试实现保存和恢复状态,但是当我用PreferenceFragment替换主要的Fragment,然后点击返回按钮时遇到了问题。我的主要Fragment由一个ViewPager组成,其中包含一个FragmentPagerAdapter和3个Fragments以进行滑动。在点击返回按钮后,我的3个Fragment的任何Fragment.onCreateView()回调函数都没有被调用。我尝试了所有我在SO上找到的解决方案,但是我无法解决这个问题。
另一个可能重要的事情是,我的3个ViewPagerFragment的数据存储在单独的类中,可以通过Activity实例化和访问。这3个Fragment都包含RecyclerView,用于列出相当数量的数据。这样做是为了清洁,也为了使这些数据在Activity中持久存在。这可能不是一个问题,因为当启动应用程序时它可以正常工作,但是主要问题是我的Fragments没有被重新创建。
意外的行为:
在创建应用程序时,一切都正常,但当我将包含ViewPagerFragmentPagerAdapter的主要Fragment替换为另一个Fragment并按下返回按钮时,ViewPager中的Fragment不会被重新创建。我的主要FragmentonCreateView()确实被调用了。
问题:
我错过了什么?还有其他回调函数需要创建吗?在哪里以及如何重新创建Fragments
我尝试过的:
1.更改正在使用的FragmentAdapter,但我真的应该使用getSupportFragmentManager(),就像这里的解决方案一样。
编辑:错误使用FragmentManager实际上是我的麻烦的根源。请参见下面的答案。
  1. 按照这里的建议,将@Override public int getItemPosition(Object object) {return POSITION_NONE;}添加到FragmentPagerAdapter中。
  2. 更改FragmentPagerAdapterFragmentStatePagerAdapter,在我看来应该不会有影响。实际上,FragmentStatePagerAdapter内部保留了Fragments,但它们不会重新渲染。
  3. 当主要的FragmentonCreateView()被调用以及该Fragment的其他回调中,使用FragmentTransaction将其添加回来,参见下面的MMMainFragment
  4. 其他各种事情。

我的MainFragment类和对应的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类,目前是一个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>
2个回答

28

在苦苦挣扎几天后,我意识到问题实际上是我将错误的FragmentManager传递给了ViewPager。确实应该使用由Fragment.getChildFragmentManager()返回的FragmentManager。这是有道理的,因为它是Fragment本身将子Fragment的状态存储在ViewPager中。我不确定为什么之前我无法解决这个问题,但现在通过在我的主要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;
}

这也是另一个问题的解决方案。顺便一提,我使用这个解决方案来获取我的ViewPager中的Fragment,并且它在生命周期方面运行良好。

测试应用程序:

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


getChildFragmentManager仅适用于API 17及以上版本。如果我还要支持API 16,是否有其他解决方案? - Elye
1
你是否从 android.support.v4.app 导入 Fragment 和 ViewPager? - Krøllebølle
View Pager 来自 android.support.v4.app,但 Fragment 是普通的 Fragment。 - Elye
1
将其更改为android.support.v4.app.Fragment,然后您就可以继续了。如果要支持旧版本的Android,则必须使用支持库。 - Krøllebølle
是的,那就是方法。不幸的是,我们拥有的片段是从一个被其他人使用的基础片段继承而来的。要进行更改,需要大量的代码更改 :( - Elye
显示剩余2条评论

0

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

帮助我保留片段状态的方法是替换

@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。

这是通过实现getItemPosition(Object object)方法并返回POSITION_NONE;或正确的项目位置来有效且“智能”地进行的。

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

如果您需要进一步的信息,我想我可以挖掘我的文件系统 :)


是的,这是另一个问题;如何获取当前存储在ViewPager中的Fragment。据我所知,您提供的解决方案是可行的。我自己使用了这个解决方案来解决这个问题。 - Krøllebølle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接