在 FragmentTransaction.replace() 后跟后退按钮后,不会重新创建 ViewPager 片段
ViewPager Fragments are not recreated after FragmentTransaction.replace() followed by back button
我正在尝试实现保存和恢复状态,但是在用 PreferenceFragment
替换主 Fragment
然后点击后退按钮时,我 运行 遇到了问题。我的主要 Fragment
由一个 ViewPager
和一个 FragmentPagerAdapter
以及 3 个 Fragments
组成。我的 3 Fragment
的 Fragment.onCreateView()
回调中的 None 在点击后退按钮后被调用。我已经尝试了我在 SO 上找到的所有解决方案,但我无法解决问题。
另一个可能需要注意的重要事项是我的 3 ViewPager
Fragment
的数据存储在单独的 classes 中,这些 classes 可以通过 Activity
. 3 个 Fragment
都包含 RecyclerView
,用于列出大量数据。这样做是为了清洁,也是为了让这些数据持久存在 Activity
。这 可能 不是问题,因为它在启动应用程序时工作正常,而且主要问题是我的 Fragments
没有重新创建。
意外行为:
在创建应用程序时,一切正常,但是当我用另一个替换我的主 Fragment
(包含一个 ViewPager
和一个 FragmentPagerAdapter
)然后按下后退按钮时, ViewPager
中的 Fragment
不会重新创建。我的主Fragment
的onCreateView()
叫
问题:
我错过了什么?是否应该创建其他一些回调?我应该在哪里以及如何重新创建 Fragments
?
我尝试了什么:
- 更改了正在使用的
FragmentAdapter
,但我真的应该使用 getSupportFragmentManager()
作为解决方案 here。
编辑:使用错误的 FragmentManager
实际上 是 我麻烦的根源。请参阅下面我的回答。
- 添加
@Override public int getItemPosition(Object object) {return POSITION_NONE;}
到FragmentPagerAdapter
,建议here.
- 从
FragmentPagerAdapter
更改为 FragmentStatePagerAdapter
,据我所知,这在这里无关紧要。 FragmentStatePagerAdapter
的内部实际上保留了 Fragments
,但它们没有重新呈现。
- 在
onCreateView()
被调用时使用 FragmentTransaction
将主 Fragment
添加回来,并在 Fragment
的其他几个回调中,参见 MMMainFragment
下面。
- 其他各种东西。
我的 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 在我的主要 Fragment
的 onCreate()
方法中使用以下设置可以正常工作:
@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
以保持 Fragment
的 HashMap
。这样,片段被创建一次,然后从 HashMap 中检索。根据您的结构,您可以使用 ArrayList
而不是 HashMap。
这是以一种高效且 "smart" 的方式实现的 getItemPosition(Object object)
到 return POSITION_NONE;
或项目的正确位置。
我不太记得适配器中是否有 getChildFragmentManager
。
如果您需要更多信息,我想我可以深入了解我的文件系统:)
我正在尝试实现保存和恢复状态,但是在用 PreferenceFragment
替换主 Fragment
然后点击后退按钮时,我 运行 遇到了问题。我的主要 Fragment
由一个 ViewPager
和一个 FragmentPagerAdapter
以及 3 个 Fragments
组成。我的 3 Fragment
的 Fragment.onCreateView()
回调中的 None 在点击后退按钮后被调用。我已经尝试了我在 SO 上找到的所有解决方案,但我无法解决问题。
另一个可能需要注意的重要事项是我的 3 ViewPager
Fragment
的数据存储在单独的 classes 中,这些 classes 可以通过 Activity
. 3 个 Fragment
都包含 RecyclerView
,用于列出大量数据。这样做是为了清洁,也是为了让这些数据持久存在 Activity
。这 可能 不是问题,因为它在启动应用程序时工作正常,而且主要问题是我的 Fragments
没有重新创建。
意外行为:
在创建应用程序时,一切正常,但是当我用另一个替换我的主 Fragment
(包含一个 ViewPager
和一个 FragmentPagerAdapter
)然后按下后退按钮时, ViewPager
中的 Fragment
不会重新创建。我的主Fragment
的onCreateView()
叫
问题:
我错过了什么?是否应该创建其他一些回调?我应该在哪里以及如何重新创建 Fragments
?
我尝试了什么:
- 更改了正在使用的
FragmentAdapter
,但我真的应该使用getSupportFragmentManager()
作为解决方案 here。
编辑:使用错误的 FragmentManager
实际上 是 我麻烦的根源。请参阅下面我的回答。
- 添加
@Override public int getItemPosition(Object object) {return POSITION_NONE;}
到FragmentPagerAdapter
,建议here. - 从
FragmentPagerAdapter
更改为FragmentStatePagerAdapter
,据我所知,这在这里无关紧要。FragmentStatePagerAdapter
的内部实际上保留了Fragments
,但它们没有重新呈现。 - 在
onCreateView()
被调用时使用FragmentTransaction
将主Fragment
添加回来,并在Fragment
的其他几个回调中,参见MMMainFragment
下面。 - 其他各种东西。
我的 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 在我的主要 Fragment
的 onCreate()
方法中使用以下设置可以正常工作:
@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
以保持 Fragment
的 HashMap
。这样,片段被创建一次,然后从 HashMap 中检索。根据您的结构,您可以使用 ArrayList
而不是 HashMap。
这是以一种高效且 "smart" 的方式实现的 getItemPosition(Object object)
到 return POSITION_NONE;
或项目的正确位置。
我不太记得适配器中是否有 getChildFragmentManager
。
如果您需要更多信息,我想我可以深入了解我的文件系统:)