使用Fragments为Android中的每个选项卡分隔Back Stack

mar*_*dah 155 tabs android android-fragments

我正在尝试在Android应用中实现导航标签.由于不推荐使用TabActivity和ActivityGroup,我想使用Fragments来实现它.

我知道如何为每个选项卡设置一个片段,然后在单击选项卡时切换片段.但是,如何为每个选项卡分别设置一个后备堆栈?

例如,片段A和B将位于选项卡1下,片段C和D位于选项卡2下.启动应用程序时,将显示片段A并选择选项卡1.然后片段A可能被片段B替换.当选择标签2时,应显示片段C. 如果选择了选项卡1,则应再次显示片段B. 此时应该可以使用后退按钮显示片段A.

此外,重要的是在旋转设备时保持每个标签的状态.

BR马丁

Kri*_*dra 137

我对这个问题非常迟.但是因为这个帖子非常有用,对我有帮助,所以我觉得我最好在这里发两个便士.

我需要这样的屏幕流程(一个简约的设计,每个标签有2个标签和2个视图),

tabA
    ->  ScreenA1, ScreenA2
tabB
    ->  ScreenB1, ScreenB2
Run Code Online (Sandbox Code Playgroud)

我过去有同样的要求,我使用它TabActivityGroup(当时也被弃用)和活动.这次我想使用Fragments.

所以我就这样做了.

1.创建基础片段类

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){
    }
}
Run Code Online (Sandbox Code Playgroud)

应用中的所有片段都可以扩展此Base类.如果你想使用像ListFragment你这样的特殊片段,也应该为它创建一个基类.您将清楚地了解使用情况onBackPressed()以及onActivityResult()如果您完整阅读该帖子.

2.创建一些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..
}
Run Code Online (Sandbox Code Playgroud)

没什么可解释的..

3.好的,主要标签活动 - 请在代码中查看评论..

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

5. AppTabAFirstFragment.java(选项卡A中的第一个片段,适用于所有选项卡的simliar)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能不是最完美和正确的方式.但它在我的案例中运作得很好.此外,我只在纵向模式下有此要求.我从来不必在支持两种方向的项目中使用此代码.所以不能说我面临什么样的挑战..

编辑:

如果有人想要一个完整的项目,我已经将一个示例项目推送到github.

  • @omegatai完全同意你的问题..所有的问题都出现了,因为Android没有为我们管理堆栈(*iOS所做的和方向更改或带有多个片段的选项卡是轻而易举的*)并且它将我们带回到原来的讨论中Q/A线程.没有好回到现在.. (3认同)
  • 为每个片段存储数据,重新创建每个片段,重建堆栈......为简单的方向更改做了很多工作. (2认同)
  • @Krishnabhadra好的,听起来好多了.如果我错了,请纠正.根据您的示例,只有一个活动,因此只有一个捆绑.在BaseFragment中创建适配器实例(引用您的项目)并在其中保存数据.无论何时构建视图,都要使用它们. (2认同)

epi*_*ian 95

我们必须实现与您最近为应用描述的完全相同的行为.应用程序的屏幕和整体流程已经定义,因此我们必须坚持使用它(这是一个iOS应用程序克隆...).幸运的是,我们设法摆脱了屏幕上的后退按钮:)

我们使用TabActivity,FragmentActivities(我们使用片段支持库)和片段的混合物来破解解决方案.回顾过去,我很确定这不是最好的架构决策,但我们设法让事情发挥作用.如果我不得不再次这样做,我可能会尝试做一个更基于活动的解决方案(没有片段),或尝试只有一个活动的标签,让所有其余的视图(我发现更多可重复使用而不是整体活动).

因此要求是在每个选项卡中都有一些选项卡和可嵌套屏幕:

tab 1
  screen 1 -> screen 2 -> screen 3
tab 2
  screen 4
tab 3
  screen 5 -> 6
Run Code Online (Sandbox Code Playgroud)

等等...

所以说:用户在标签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
Run Code Online (Sandbox Code Playgroud)

每个ActivityInTab一次只保存一个片段,并且知道如何将一个片段替换为另一个片段(与ActvityGroup几乎相同).很酷的是,通过这种方式为每个标签保留单独的后备堆很容易.

每个ActivityInTab的功能都完全相同:知道如何从一个片段导航到另一个片段并维护一个后台,所以我们将它放在一个基类中.我们简单地称之为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();
    }

}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

如您所见,每个选项卡的视图布局都是相同的.那是因为它只是一个名为内容的FrameLayout,它将保存每个片段.片段是具有每个屏幕视图的片段.

只是为了奖励积分,我们还添加了一些小代码,以便在用户按下Back时显示确认对话框,并且没有更多片段可以返回:

// 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是设置.正如您所看到的,每个FragmentActivity(或者只是Android中的Activity> 3)都会使用它自己的FragmentManager来处理所有后向堆叠.

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

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

然后,如果一个片段需要导航到另一个片段,它必须做一些讨厌的演员...但它并没有那么糟糕:

// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());
Run Code Online (Sandbox Code Playgroud)

所以这就是它.我很确定这不是一个非常规范(并且肯定不是很好)的解决方案,所以我想问经验丰富的Android开发人员什么是更好的方法来实现这个功能,如果这不是"它是怎么回事做" Android中,我会很感激,如果你能指出我的一些链接或材料,说明这是解决这个(标签,嵌套屏幕中的标签等),Android的方式.请在评论中随意撕开这个答案:)

作为这个解决方案不是很好的标志,最近我不得不在应用程序中添加一些导航功能.一些奇怪的按钮,应该将用户从一个选项卡带到另一个选项卡并进入嵌套屏幕.以编程方式执行此操作是一个痛苦的屁股,因为谁知道谁的问题和处理何时实际实例化和初始化的片段和活动.我认为如果这些屏幕和标签都只是视图真的会更容易.


最后,如果您需要更改方向更改,则使用setArguments/getArguments创建片段非常重要.如果在片段的构造函数中设置实例变量,则会被搞砸.但幸运的是,这很容易解决:只需在构造函数中的setArguments中保存所有内容,然后在onCreate中使用getArguments检索这些内容以使用它们.

  • 很好的答案,但我想很少会看到这个.我选择了完全相同的路径(你可以从之前的答案中的对话中看到)并且我对你感到不满意.我认为谷歌真的搞砸了这个片段,因为这个API不包括主要的用例.您可能遇到的另一个问题是不可能将片段嵌入到另一个片段中. (13认同)

hac*_*bod 23

该框架目前不会自动为您执行此操作.您需要为每个选项卡构建和管理自己的后台堆栈.

说实话,这似乎是一个值得怀疑的事情.我无法想象它会产生一个像样的UI - 如果后面的键会根据我的选项卡做不同的事情,特别是如果后面的键也有正常的行为关闭整个活动的时候在顶部堆栈......听起来很讨厌.

如果您正在尝试构建类似Web浏览器UI的东西,那么获取对用户来说很自然的用户体验将会涉及很多细微的行为调整,这取决于上下文,所以你肯定需要做自己的后台栈管理而不是依赖于框架中的一些默认实现.举个例子,尝试注意后面的键如何以各种方式与标准浏览器进行交互.(浏览器中的每个"窗口"本质上都是一个选项卡.)

  • @hackbod我试图遵循你的观点,但在实现自定义后台堆栈行为时遇到了麻烦.我意识到参与了这个设计,你会对它的容易程度有一个深刻的了解.是否有可能为OP的用例提供演示应用程序,因为它确实是一种非常常见的情况,特别是对于那些必须为提出这些请求的客户编写和移植iOS应用程序的人来说....管理分离片段堆叠每个FragmentActivity中的后台堆栈. (22认同)
  • 疯了吧.iPhone甚至没有后退按钮.有一些API演示显示了在标签中实现片段的非常简单的代码.被问到的问题是每个选项卡都有不同的后台堆栈,我的回答是框架不会自动提供这个*,因为在后退按钮的语义上它很可能是一个糟糕的用户体验.如果你愿意,你可以很容易地自己实现反向语义. (13认同)
  • 这种类型的导航,然后您在每个选项卡上都有选项卡和页面层次结构,例如iPhone应用程序非常常见(您可以检查App Store和iPod应用程序).我发现他们的用户体验相当不错. (9认同)
  • 不要那样做.框架几乎没用.它没有为你提供这种东西的自动支持,正如我所说,我无法想象会产生一个体面的用户体验,除非在非常特殊的情况下,你需要仔细控制背部行为. (7认同)
  • 同样,iPhone没有后退按钮,因此它在语义上没有像Android这样的后端堆栈行为.另外"更好地坚持活动并节省自己很多时间"在这里没有任何意义,因为活动不允许你将维护选项卡放在具有自己不同后台的UI中; 事实上,活动的后台堆栈管理不如Fragment框架提供的灵活性. (4认同)
  • 它没有硬后退按钮,但默认情况下它会在页面的左上角添加软"后退"按钮.并管理此按钮的行为.就像Android片段的Backstack一样,但支持完整的标签.现在我有带有选项卡的ActivityGroup,每个选项卡有一个FragmentActivity.所以我每个标签都有单独的碎片背板,一切都工作得非常完美. (3认同)
  • 这个问题是关于在iOS应用程序中非常常见的UX模式.我们中的许多人都希望移植iOS应用程序,我们的客户喜欢或多或少类似的导航.坦率地说,我找不到与我的客户发生冲突并在Android上使用完全不同的导航行为的充分理由.我的可用性测试确认(如果做得好)Android用户完全得到它.所以,如果没有看到应用程序,你是谁声称这是糟糕的用户体验? (3认同)
  • 你认为类似iPhone的导航使用后退键是一个坏主意吗?没想过这个.我认为这是有争议的.无论如何,实现良好的backstack行为是一项相当重要的工作.我认为最好只坚持活动并节省很多时间. (2认同)
  • 没错。我笑@hackbod 试图假装问题不在于Android 的编写有多糟糕。Android 框架的每一部分都是一朵脆弱的花朵,只能按照 Android 创始人(和母亲)的意图使用。即便如此,交叉你的手指。 (2认同)

ser*_*1pt 6

存储对片段的强引用不是正确的方法.

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

任何一个都足以实现一个后端堆栈.


使用putFragment,而不是替换片段,您可以分离旧片段并添加新片段.这就是框架对替换添加到backstack的事务所做的工作.putFragment存储当前活动碎片列表的索引,并且这些碎片在方向更改期间由框架保存.

第二种方法,使用saveFragmentInstanceState,将整个片段状态保存到Bundle,允许您真正删除它,而不是分离.使用这种方法可以更容易地操作后堆栈,因为您可以随时弹出Fragment.


我在这个用例中使用了第二种方法:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
               \                          /
                \------------------------/
Run Code Online (Sandbox Code Playgroud)

我不希望用户通过按后退按钮从第三个屏幕返回到"注册"屏幕.我也在他们之间翻转动画(使用onCreateAnimation),所以hacky解决方案将无法工作,至少没有用户明确注意到某些事情是不对的.

这是自定义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();
    }
}
Run Code Online (Sandbox Code Playgroud)
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];
            }
    };
}
Run Code Online (Sandbox Code Playgroud)
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];
            }
    };
}
Run Code Online (Sandbox Code Playgroud)


tau*_*siq 6

使用ChildFragmentManager可以轻松实现这一点

这是与相关项目有关的帖子.看一看,

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