使用Fragments在Android中为每个选项卡单独备份堆栈

我试图在Android应用程序中实现导航的标签。 由于TabActivity和ActivityGroup已被弃用,我想用碎片来实现它。

我知道如何为每个选项卡设置一个片段,然后在单击选项卡时切换片段。 但是,我怎样才能有一个单独的背部堆栈为每个选项卡?

例如,片段A和B将位于Tab 1下,片段C和D位于Tab 2下面。当应用程序启动时,将显示片段A并selectTab 1。 然后片段A可能被replace为片段B.当select了片段2时,应该显示片段C. 如果select了Tab 1,则应再次显示片段B. 此时应该可以使用后退button来显示片段A.

此外,旋转设备时,维护每个选项卡的状态也很重要。

BR Martin

该框架目前不会自动为你做这个。 您将需要为每个选项卡构build和pipe理自己的背堆栈。

说实话,这似乎是一个真正值得怀疑的事情。 我无法想象它会产生一个像样的用户界面 – 如果后退键将根据我的选项卡做不同的事情,特别是如果后退键也具有closures整个活动时的正常行为堆栈…听起来讨厌。

如果你正在尝试构build一个类似于Web浏览器的用户界面,那么为了获得一个用户自然的用户体验,将会根据上下文对行为进行很多细微的调整,所以你一定要做自己的后退堆栈pipe理而不是依靠框架中的一些默认实现。 举个例子,请注意后退键与标准浏览器之间的交互方式。 (浏览器中的每个“窗口”本质上是一个选项卡。)

这个问题我迟到了。 但是由于这个线程对我来说非常有用,对我很有帮助,所以我觉得我最好在这里发表我的两个便士。

我需要一个像这样的屏幕stream(每个标签中有2个标签和2个视图的简约devise),

tabA -> ScreenA1, ScreenA2 tabB -> ScreenB1, ScreenB2 

过去我有相同的要求,而且我使用TabActivityGroup (当时也弃用)和活动。 这一次我想使用碎片。

所以我就是这么做的

1.创build一个基本的片段类

 public class BaseFragment extends Fragment { AppMainTabActivity mActivity; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActivity = (AppMainTabActivity) this.getActivity(); } public void onBackPressed(){ } public void onActivityResult(int requestCode, int resultCode, Intent data){ } } 

你应用中的所有片段都可以扩展这个基类。 如果你想使用像ListFragment这样的特殊片段,你也应该为它创build一个基类。 如果您完整阅读post,您将清楚onBackPressed()onActivityResult()的用法。

2.创build一些Tab标识符,可以在项目中随处访问

 public class AppConstants{ public static final String TAB_A = "tab_a_identifier"; public static final String TAB_B = "tab_b_identifier"; //Your other constants, if you have them.. } 

这里没有任何解释..

好的,主标签活动 – 请通过代码中的评论..

 public class AppMainFragmentActivity extends FragmentActivity{ /* Your Tab host */ private TabHost mTabHost; /* A HashMap of stacks, where we use tab identifier as keys..*/ private HashMap<String, Stack<Fragment>> mStacks; /*Save current tabs identifier in this..*/ private String mCurrentTab; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_main_tab_fragment_layout); /* * Navigation stacks for each tab gets created.. * tab identifier is used as key to get respective stack for each tab */ mStacks = new HashMap<String, Stack<Fragment>>(); mStacks.put(AppConstants.TAB_A, new Stack<Fragment>()); mStacks.put(AppConstants.TAB_B, new Stack<Fragment>()); mTabHost = (TabHost)findViewById(android.R.id.tabhost); mTabHost.setOnTabChangedListener(listener); mTabHost.setup(); initializeTabs(); } private View createTabView(final int id) { View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null); ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon); imageView.setImageDrawable(getResources().getDrawable(id)); return view; } public void initializeTabs(){ /* Setup your tab icons and content views.. Nothing special in this..*/ TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A); mTabHost.setCurrentTab(-3); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_home_state_btn)); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(AppConstants.TAB_B); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_status_state_btn)); mTabHost.addTab(spec); } /*Comes here when user switch tab, or we do programmatically*/ TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() { public void onTabChanged(String tabId) { /*Set current tab..*/ mCurrentTab = tabId; if(mStacks.get(tabId).size() == 0){ /* * First time this tab is selected. So add first fragment of that tab. * Dont need animation, so that argument is false. * We are adding a new fragment which is not present in stack. So add to stack is true. */ if(tabId.equals(AppConstants.TAB_A)){ pushFragments(tabId, new AppTabAFirstFragment(), false,true); }else if(tabId.equals(AppConstants.TAB_B)){ pushFragments(tabId, new AppTabBFirstFragment(), false,true); } }else { /* * We are switching tabs, and target tab is already has atleast one fragment. * No need of animation, no need of stack pushing. Just show the target fragment */ pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false); } } }; /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/ public void setCurrentTab(int val){ mTabHost.setCurrentTab(val); } /* * To add fragment to a tab. * tag -> Tab identifier * fragment -> Fragment to show, in tab identified by tag * shouldAnimate -> should animate transaction. false when we switch tabs, or adding first fragment to a tab * true when when we are pushing more fragment into navigation stack. * shouldAdd -> Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time) * true in all other cases. */ public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){ if(shouldAdd) mStacks.get(tag).push(fragment); FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); if(shouldAnimate) ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left); ft.replace(R.id.realtabcontent, fragment); ft.commit(); } public void popFragments(){ /* * Select the second last fragment in current tab's stack.. * which will be shown after the fragment transaction given below */ Fragment fragment = mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2); /*pop current fragment from stack.. */ mStacks.get(mCurrentTab).pop(); /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/ FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right); ft.replace(R.id.realtabcontent, fragment); ft.commit(); } @Override public void onBackPressed() { if(mStacks.get(mCurrentTab).size() == 1){ // We are already showing first fragment of current tab, so when back pressed, we will finish this activity.. finish(); return; } /* Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action * when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult() * kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself. */ ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed(); /* Goto previous fragment in navigation stack of this tab */ popFragments(); } /* * Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function * in that fragment, and called it from the activity. But couldn't resist myself. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(mStacks.get(mCurrentTab).size() == 0){ return; } /*Now current fragment on screen gets onActivityResult callback..*/ mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data); } } 

4. app_main_tab_fragment_layout.xml(如果有人感兴趣的话)

 <?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0"/> <FrameLayout android:id="@+android:id/realtabcontent" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1"/> <TabWidget android:id="@android:id/tabs" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="0"/> </LinearLayout> </TabHost> 

5. AppTabAFirstFragment.java(Tab A中的第一个片段,与所有Tabs相似)

 public class AppTabAFragment extends BaseFragment { private Button mGotoButton; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_one_layout, container, false); mGoToButton = (Button) view.findViewById(R.id.goto_button); mGoToButton.setOnClickListener(listener); return view; } private OnClickListener listener = new View.OnClickListener(){ @Override public void onClick(View v){ /* Go to next fragment in navigation stack*/ mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true); } } } 

这可能不是最完善和正确的方法。 但在我的情况下,它工作得很好。 我也只有在肖像模式下这个要求。 我从来不需要在支持这两个方向的项目中使用这个代码。 所以不能说我面对的是什么样的挑战..

编辑:

如果有人想要一个完整的项目,我已经推出了一个示例项目到github 。

我们不得不执行最近为应用描述的相同行为。 应用程序的屏幕和整体stream程已经定义,所以我们必须坚持下去(这是一个iOS应用程序的克隆…)。 幸运的是,我们设法摆脱了屏幕上的后退button:)

我们使用TabActivity,FragmentActivities(我们正在使用支持库的片段)和Fragments混合使用这个解决scheme。 回顾展,我很确定这不是最好的架构决定,但我们设法得到的东西工作。 如果我不得不这样做,我可能会尝试做一个更基于活动的解决scheme(没有片段),或尝试只有一个活动的标签,让所有其余的视图(我发现更多比活动总体可重用)。

所以要求在每个标签中都有一些标签和可嵌套的屏幕:

 tab 1 screen 1 -> screen 2 -> screen 3 tab 2 screen 4 tab 3 screen 5 -> 6 

等等…

所以说:用户从标签1开始,从屏幕1导航到屏幕2,然后到屏幕3,然后他切换到标签3并从屏幕4导航到6; 如果切换回标签1,他应该再次看到屏幕3,如果他按下了,他应该返回到屏幕2; 又回来了,他在屏幕1上; 切换到标签3,他再次在屏幕6。

应用程序中的主要Activity是MainTabActivity,它扩展了TabActivity。 每个选项卡都与一个活动相关联,可以说ActivityInTab1,2和3.然后每个屏幕将是一个片段:

 MainTabActivity ActivityInTab1 Fragment1 -> Fragment2 -> Fragment3 ActivityInTab2 Fragment4 ActivityInTab3 Fragment5 -> Fragment6 

每个ActivityInTab一次仅保存一个片段,并知道如何replace另一个片段(几乎与ActvityGroup相同)。 很酷的事情是,这样很容易为每个选项卡单独备份堆栈。

每个ActivityInTab的function是完全相同的:知道如何从一个片段导航到另一个片段,并维护一个后端堆栈,所以我们把它放在一个基类中。 我们简单地称之为ActivityInTab:

 abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_in_tab); } /** * Navigates to a new fragment, which is added in the fragment container * view. * * @param newFragment */ protected void navigateTo(Fragment newFragment) { FragmentManager manager = getSupportFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); ft.replace(R.id.content, newFragment); // Add this transaction to the back stack, so when the user presses back, // it rollbacks. ft.addToBackStack(null); ft.commit(); } } 

activity_in_tab.xml就是这样的:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:layout_width="fill_parent" android:layout_height="fill_parent" android:isScrollContainer="true"> </RelativeLayout> 

正如你所看到的,每个选项卡的视图布局是相同的。 这是因为它只是一个叫做内容的FrameLayout,它将容纳每个片段。 这些碎片是每个屏幕的视图。

只是为了奖励积分,我们还添加了一些小代码来显示一个确认对话框,当用户按下返回,没有更多的片段回到:

 // In ActivityInTab.java... @Override public void onBackPressed() { FragmentManager manager = getSupportFragmentManager(); if (manager.getBackStackEntryCount() > 0) { // If there are back-stack entries, leave the FragmentActivity // implementation take care of them. super.onBackPressed(); } else { // Otherwise, ask user if he wants to leave :) showExitDialog(); } } 

这几乎是设置。 正如你所看到的,每个FragmentActivity(或者简单地说就是Android> 3中的Activity)正在利用它自己的FragmentManager来照顾所有的后端堆栈。

像ActivityInTab1这样的活动将非常简单,它只会显示它的第一个片段(即屏幕):

 public class ActivityInTab1 extends ActivityInTab { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); navigateTo(new Fragment1()); } } 

然后,如果一个片段需要导航到另一个片段,它必须做一个令人讨厌的铸造…但它并没有那么糟糕:

 // In Fragment1.java for example... // Need to navigate to Fragment2. ((ActivityIntab) getActivity()).navigateTo(new Fragment2()); 

所以这是非常多的。 我敢肯定,这不是一个非常规范的(大多数肯定不是很好)的解决scheme,所以我想问一些经验丰富的Android开发人员,如果能够实现这个function,更好的方法是什么,如果这不是“怎么样完成“,我会很感激,如果你可以指向我的一些链接或材料,解释这是Android的方式来接近这个(标签,嵌套屏幕在标签等)。 随意撕开评论中的这个答案:)

作为这个解决scheme不太好的一个信号,最近我不得不向应用程序添加一些导航function。 一些奇怪的button,应该把用户从一个标签到另一个到嵌套的屏幕。 以编程的方式做这件事是一个痛苦的事情,因为谁知道谁是谁的问题,并处理什么时候碎片和活动被实例化和初始化。 如果这些屏幕和标签都是真正的视图,我认为会更容易。


最后,如果您需要生存方向更改,则使用setArguments / getArguments创build片段是很重要的。 如果你在你的fragment的构造函数中设置了实例variables,那么你将会被拧紧。 但幸运的是,这很容易解决:只需在构造函数中保存setArguments中的所有内容,然后使用onCreate中的getArguments检索这些内容即可使用它们。

存储强大的片段引用不是正确的方法。

FragmentManager提供了putFragment(Bundle, String, Fragment)saveFragmentInstanceState(Fragment)

任何一个都足以实现一个后台。


使用putFragment ,而不是取代一个片段,你分离旧的,并添加新的。 这是框架对replace添加到后台堆栈的事务所做的事情。 putFragment将索引存储到活动碎片的当前列表中,这些碎片在方向更改期间由框架保存。

第二种方法,使用saveFragmentInstanceState ,将整个片段状态保存到一个Bundle中,以便真正删除它,而不是分离。 使用这种方法可以使背堆栈更容易操作,因为您可以随时popup碎片。


我用这个用例的第二个方法:

 SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment \ / \------------------------/ 

我不希望用户通过按下后退button从第三个返回到注册屏幕。 我也做animation之间的animation(使用onCreateAnimation ),所以hacky的解决scheme将无法正常工作,至less没有用户清楚地注意到事情是不正确的。

这是一个自定义的backstack的有效用例,做用户期望的…

 private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK"; private MyBackStack mBackStack; @Override protected void onCreate(Bundle state) { super.onCreate(state); if (state == null) { mBackStack = new MyBackStack(); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction tr = fm.beginTransaction(); tr.add(R.id.act_base_frg_container, new SignInFragment()); tr.commit(); } else { mBackStack = state.getParcelable(STATE_BACKSTACK); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(STATE_BACKSTACK, mBackStack); } private void showFragment(Fragment frg, boolean addOldToBackStack) { final FragmentManager fm = getSupportFragmentManager(); final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container); FragmentTransaction tr = fm.beginTransaction(); tr.replace(R.id.act_base_frg_container, frg); // This is async, the fragment will only be removed after this returns tr.commit(); if (addOldToBackStack) { mBackStack.push(fm, oldFrg); } } @Override public void onBackPressed() { MyBackStackEntry entry; if ((entry = mBackStack.pop()) != null) { Fragment frg = entry.recreate(this); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction tr = fm.beginTransaction(); tr.replace(R.id.act_base_frg_container, frg); tr.commit(); // Pop it now, like the framework implementation. fm.executePendingTransactions(); } else { super.onBackPressed(); } } 

 public class MyBackStack implements Parcelable { private final List<MyBackStackEntry> mList; public MyBackStack() { mList = new ArrayList<MyBackStackEntry>(4); } public void push(FragmentManager fm, Fragment frg) { push(MyBackStackEntry.newEntry(fm, frg); } public void push(MyBackStackEntry entry) { if (entry == null) { throw new NullPointerException(); } mList.add(entry); } public MyBackStackEntry pop() { int idx = mList.size() - 1; return (idx != -1) ? mList.remove(idx) : null; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { final int len = mList.size(); dest.writeInt(len); for (int i = 0; i < len; i++) { // MyBackStackEntry's class is final, theres no // need to use writeParcelable mList.get(i).writeToParcel(dest, flags); } } protected MyBackStack(Parcel in) { int len = in.readInt(); List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len); for (int i = 0; i < len; i++) { list.add(MyBackStackEntry.CREATOR.createFromParcel(in)); } mList = list; } public static final Parcelable.Creator<MyBackStack> CREATOR = new Parcelable.Creator<MyBackStack>() { @Override public MyBackStack createFromParcel(Parcel in) { return new MyBackStack(in); } @Override public MyBackStack[] newArray(int size) { return new MyBackStack[size]; } }; } 

 public final class MyBackStackEntry implements Parcelable { public final String fname; public final Fragment.SavedState state; public final Bundle arguments; public MyBackStackEntry(String clazz, Fragment.SavedState state, Bundle args) { this.fname = clazz; this.state = state; this.arguments = args; } public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) { final Fragment.SavedState state = fm.saveFragmentInstanceState(frg); final String name = frg.getClass().getName(); final Bundle args = frg.getArguments(); return new MyBackStackEntry(name, state, args); } public Fragment recreate(Context ctx) { Fragment frg = Fragment.instantiate(ctx, fname); frg.setInitialSavedState(state); frg.setArguments(arguments); return frg; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(fname); dest.writeBundle(arguments); if (state == null) { dest.writeInt(-1); } else if (state.getClass() == Fragment.SavedState.class) { dest.writeInt(0); state.writeToParcel(dest, flags); } else { dest.writeInt(1); dest.writeParcelable(state, flags); } } protected MyBackStackEntry(Parcel in) { final ClassLoader loader = getClass().getClassLoader(); fname = in.readString(); arguments = in.readBundle(loader); switch (in.readInt()) { case -1: state = null; break; case 0: state = Fragment.SavedState.CREATOR.createFromParcel(in); break; case 1: state = in.readParcelable(loader); break; default: throw new IllegalStateException(); } } public static final Parcelable.Creator<MyBackStackEntry> CREATOR = new Parcelable.Creator<MyBackStackEntry>() { @Override public MyBackStackEntry createFromParcel(Parcel in) { return new MyBackStackEntry(in); } @Override public MyBackStackEntry[] newArray(int size) { return new MyBackStackEntry[size]; } }; } 

这可以通过ChildFragmentManager轻松实现

这里是关于这个与相关项目。 看一看,

http://tausiq.wordpress.com/2014/06/06/android-multiple-fragments-stack-in-each-viewpager-tab/

免责声明:


我觉得这是发布相关解决scheme的最佳地点,我曾经为类似类似的问题进行过研究,这些问题似乎是非常标准的Android内容。 这不是为了解决所有人的问题,但它可能会有所帮助。


如果你的片段之间的主要区别只是支持它们的数据(即没有很多大的布局差异),那么你可能不需要实际replace片段,而只是换出底层数据并刷新视图。

下面是对这种方法的一个可能的例子的描述:

我有一个使用ListViews的应用程序。 列表中的每个项目都是具有一定数量子项的父项目。 当您点击该项目时,需要在与原始列表相同的ActionBar选项卡中打开一个新列表。 这些嵌套列表有一个非常相似的布局(一些有条件的调整可能),但数据是不同的。

这个应用程序在初始父列表下面有几层后代,当用户尝试访问超出第一层的特定深度时,我们可能会或可能不会从服务器获取数据。 由于列表是从数据库游标构造而来的,并且这些片段使用游标加载器和游标适配器来使用列表项目来填充列表视图,所以当注册点击时需要发生的事情是:

1)使用适当的“to”和“from”字段创build一个新的适配器,该字段将匹配添加到列表的新项目视图以及新光标返回的列。

2)将此适配器设置为ListView的新适配器。

3)根据被点击的项目build立一个新的URI,并用新的URI(和投影)重新启动游标加载器。 在这个例子中,URI被映射到具有从UI传递下来的select参数的特定查询。

4)当从URI加载新数据时,将与适配器关联的光标交换到新的光标,然后刷新列表。

由于我们没有使用事务,因此没有与此相关的backstack,所以您将不得不build立自己的事务,或者在退出层次结构时反向查询。 当我尝试这个时,查询速度足够快,我只是在oNBackPressed()中再次执行它们,直到我处于层次结构顶部,此时框架再次接pipe后退button。

如果您发现自己处于类似情况,请务必阅读文档: http : //developer.android.com/guide/topics/ui/layout/listview.html

http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html

我希望这可以帮助别人!

我有完全相同的问题,并实施了一个开放源代码github项目,涵盖了堆叠标签,向上和向上的导航,并经过充分testing和logging:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

这是一个简单而小巧的导航标签和片段切换以及上下导航处理的框架。 每个选项卡都有自己的碎片堆栈。 它使用ActionBarSherlock并兼容API级别8。

我想build议我自己的解决scheme,以防有人在寻找,想要尝试为他/她的需求select最好的解决scheme。

https://github.com/drusak/tabactivity

创build库的目的是相当平庸的 – 像iPhone一样实施。

主要优点:

  • 与TabLayout使用android.support.design库;
  • 每个选项卡都有自己的堆栈使用FragmentManager(不保存片段的引用);
  • 支持深层链接(当你需要打开特定标签和特定片段的级别时);
  • 保存/恢复标签的状态;
  • 选项卡中片段的自适应生命周期方法;
  • 很容易实现您的需求。

希望它可以帮助别人。

谢谢!

简单的解决scheme:

每当你改变标签/根视图调用:

 fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); 

它将清除BackStack。 请记住在更改根片段之前调用它。

并添加片段:

 FragmentTransaction transaction = getFragmentManager().beginTransaction(); NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId); transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit(); 

请注意.addToBackStack(null)transaction.add可以例如用transaction.replace进行更改。

这是一个复杂的问题,因为Android只处理1个堆栈,但这是可行的。 我花了好几天的时间创build一个名为Tab Stacker的库,它完全符合您的要求:每个选项卡的片段历史logging。 它是开源的,完整的文档,可以很容易地包含在Gradle中。 你可以在github上find这个库: https : //github.com/smart-fun/TabStacker

您也可以下载示例应用程序以查看行为是否符合您的需求:

https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp

如果您有任何问题,请不要犹豫,放下邮件。

这个线程是非常非常有趣和有用的。
感谢Krishnabhadra为你的解释和代码,我使用你的代码,并改善了一点,允许从更改configuration(主要是旋转)持久堆栈,currentTab,等等。
在真实的4.0.4和2.3.6设备上进行testing,未在仿真器上进行testing

我在“AppMainTabActivity.java”上更改这部分代码,其余部分保持不变。 也许Krishnabhadra会在他的代码中加上这个。

恢复数据创build:

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_main_tab_fragment_layout); /* * Navigation stacks for each tab gets created.. * tab identifier is used as key to get respective stack for each tab */ //if we are recreating this activity... if (savedInstanceState!=null) { mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack"); mCurrentTab = savedInstanceState.getString("currentTab"); } else { mStacks = new HashMap<String, Stack<Fragment>>(); mStacks.put(AppConstants.TAB_A, new Stack<Fragment>()); mStacks.put(AppConstants.TAB_B, new Stack<Fragment>()); } mTabHost = (TabHost)findViewById(android.R.id.tabhost); mTabHost.setup(); initializeTabs(); //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab mTabHost.setOnTabChangedListener(listener); } 

保存这些variables并放到Bundle中:

  //Save variables while recreating @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("stack", mStacks); outState.putString("currentTab", mCurrentTab); //outState.putInt("tabHost",mTabHost); } 

如果存在之前的CurrentTab,则设置此项,否则创build一个新的Tab_A:

 public void initializeTabs(){ /* Setup your tab icons and content views.. Nothing special in this..*/ TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_a_state_btn)); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(AppConstants.TAB_B); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_b_state_btn)); mTabHost.addTab(spec); //if we have non default Tab as current, change it if (mCurrentTab!=null) { mTabHost.setCurrentTabByTag(mCurrentTab); } else { mCurrentTab=AppConstants.TAB_A; pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true); } } 

我希望这有助于其他人。

我build议不要使用基于HashMap的backstack>在“不要活动”模式下有很多的错误。 如果你深入到片段堆栈中,它不会正确地恢复状态。 并且也将在嵌套的地图片段中进行分析(使用exeption:Fragment没有findID的视图)。 Coz HashMap>后台\前台应用程序将为空

我优化了上面代码片段的背后工作的代码

这是底部的TabView

Main activity Class

 import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.widget.ImageView; import android.widget.TabHost; import android.widget.TextView; import com.strikersoft.nida.R; import com.strikersoft.nida.abstractActivity.BaseActivity; import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment; import com.strikersoft.nida.screens.tags.searchTab.SearchFragment; import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment; public class TagsActivity extends BaseActivity { public static final String M_CURRENT_TAB = "M_CURRENT_TAB"; private TabHost mTabHost; private String mCurrentTab; public static final String TAB_TAGS = "TAB_TAGS"; public static final String TAB_MAP = "TAB_MAP"; public static final String TAB_SETTINGS = "TAB_SETTINGS"; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_ACTION_BAR); getActionBar().hide(); setContentView(R.layout.tags_activity); mTabHost = (TabHost) findViewById(android.R.id.tabhost); mTabHost.setup(); if (savedInstanceState != null) { mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB); initializeTabs(); mTabHost.setCurrentTabByTag(mCurrentTab); /* when resume state it's important to set listener after initializeTabs */ mTabHost.setOnTabChangedListener(listener); } else { mTabHost.setOnTabChangedListener(listener); initializeTabs(); } } private View createTabView(final int id, final String text) { View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null); ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon); imageView.setImageDrawable(getResources().getDrawable(id)); TextView textView = (TextView) view.findViewById(R.id.tab_text); textView.setText(text); return view; } /* create 3 tabs with name and image and add it to TabHost */ public void initializeTabs() { TabHost.TabSpec spec; spec = mTabHost.newTabSpec(TAB_TAGS); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags))); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(TAB_MAP); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map))); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(TAB_SETTINGS); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings))); mTabHost.addTab(spec); } /* first time listener will be trigered immediatelly after first: mTabHost.addTab(spec); for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener */ TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() { public void onTabChanged(String tabId) { mCurrentTab = tabId; if (tabId.equals(TAB_TAGS)) { pushFragments(SearchFragment.getInstance(), false, false, null); } else if (tabId.equals(TAB_MAP)) { pushFragments(MapContainerFragment.getInstance(), false, false, null); } else if (tabId.equals(TAB_SETTINGS)) { pushFragments(SettingsFragment.getInstance(), false, false, null); } } }; /* Example of starting nested fragment from another fragment: Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac()); TagsActivity tAct = (TagsActivity)getActivity(); tAct.pushFragments(newFragment, true, true, null); */ public void pushFragments(Fragment fragment, boolean shouldAnimate, boolean shouldAdd, String tag) { FragmentManager manager = getFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); if (shouldAnimate) { ft.setCustomAnimations(R.animator.fragment_slide_left_enter, R.animator.fragment_slide_left_exit, R.animator.fragment_slide_right_enter, R.animator.fragment_slide_right_exit); } ft.replace(R.id.realtabcontent, fragment, tag); if (shouldAdd) { /* here you can create named backstack for realize another logic. ft.addToBackStack("name of your backstack"); */ ft.addToBackStack(null); } else { /* and remove named backstack: manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE); or remove whole: manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); */ manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } ft.commit(); } /* If you want to start this activity from another */ public static void startUrself(Activity context) { Intent newActivity = new Intent(context, TagsActivity.class); newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(newActivity); context.finish(); } @Override public void onSaveInstanceState(Bundle outState) { outState.putString(M_CURRENT_TAB, mCurrentTab); super.onSaveInstanceState(outState); } @Override public void onBackPressed(){ super.onBackPressed(); } } 

tags_activity.xml

<

 ?xml version="1.0" encoding="utf-8"?> <TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="0"/> <FrameLayout android:id="@+android:id/realtabcontent" android:background="@drawable/bg_main_app_gradient" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <TabWidget android:id="@android:id/tabs" android:background="#EAE7E1" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0"/> </LinearLayout> </TabHost> 

tags_icon.xml

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/tabsLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/bg_tab_gradient" android:gravity="center" android:orientation="vertical" tools:ignore="contentDescription" > <ImageView android:id="@+id/tab_icon" android:layout_marginTop="4dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tab_text" android:layout_marginBottom="3dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/tab_text_color"/> </LinearLayout> 

在这里输入图像描述