web-dev-qa-db-fra.com

Séparer pile arrière pour chaque onglet dans Android en utilisant des fragments

J'essaie d'implémenter des onglets pour la navigation dans une Android app. Depuis que TabActivity et ActivityGroup sont obsolètes, j'aimerais les implémenter à l'aide de Fragments.

Je sais comment configurer un fragment pour chaque onglet, puis en changer lorsque vous cliquez sur un onglet. Mais comment puis-je avoir une pile de retour distincte pour chaque onglet?

Par exemple, les fragments A et B seraient sous l'onglet 1 et les fragments C et D sous l'onglet 2. Lorsque l'application est lancée, le fragment A est affiché et l'onglet 1 est sélectionné. Ensuite, le fragment A peut être remplacé par le fragment B. Lorsque l'onglet 2 est sélectionné, le fragment C doit être affiché. Si l'onglet 1 est ensuite sélectionné, le fragment B devrait à nouveau être affiché. À ce stade, il devrait être possible d’utiliser le bouton Précédent pour afficher le fragment A.

De plus, il est important que l'état de chaque onglet soit conservé lors de la rotation du périphérique.

BR Martin

155
mardah

Actuellement, le framework ne le fait pas automatiquement pour vous. Vous devrez créer et gérer vos propres piles d'arrière-plan pour chaque onglet.

Pour être honnête, cela semble être une chose vraiment discutable à faire. Je ne peux pas imaginer que cela se traduise par une interface utilisateur décente - si la touche Précédent fait différentes choses en fonction de l'onglet que je suis, en particulier si la touche Retour a également pour comportement normal de fermer toute l'activité lorsque vous êtes au sommet de l'écran. la pile ... semble méchante.

Si vous essayez de construire quelque chose comme une interface utilisateur de navigateur Web, obtenir un UX naturel pour l'utilisateur impliquera de nombreuses modifications subtiles de comportement en fonction du contexte. Vous devrez donc vous débrouiller seul. gestion plutôt que de compter sur une implémentation par défaut dans le cadre. Pour un exemple, essayez de faire attention à la manière dont la touche Retour interagit avec le navigateur standard de différentes manières. (Chaque "fenêtre" du navigateur est essentiellement un onglet.)

23
hackbod

Je suis terriblement en retard à cette question. Mais depuis que ce fil a été très informatif et utile pour moi, j'ai pensé que je ferais mieux de poster mes deux sous ici.

J'avais besoin d'un flux d'écran comme celui-ci (design minimaliste avec 2 onglets et 2 vues dans chaque onglet),

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

J'avais les mêmes exigences dans le passé et je l'ai fait avec TabActivityGroup (qui était également déconseillé à cette époque) et Activités. Cette fois, j'ai voulu utiliser des fragments.

Alors c'est comme ça que je l'ai fait.

1. Créer une classe de fragments de base

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){
    }
}

Tous les fragments de votre application peuvent étendre cette classe de base. Si vous voulez utiliser des fragments spéciaux comme ListFragment, vous devez également créer une classe de base pour cela. Vous serez clair sur l'utilisation de onBackPressed() et onActivityResult() si vous lisez l'article complet.

2. Créer des identifiants d’onglet, accessibles partout dans le projet

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..
}

rien à expliquer ici ..

3. OK, Activité de l'onglet principal - Veuillez passer en revue les commentaires dans le code.

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 (au cas où quelqu'un serait intéressé.)

<?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 (Premier fragment de l'onglet A, identique pour tous les onglets)

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);
        }
    }
}

Cela pourrait ne pas être la manière la plus polie et correcte. Mais cela a fonctionné à merveille dans mon cas. Aussi, je n'avais cette exigence qu'en mode portrait. Je n'ai jamais eu à utiliser ce code dans un projet prenant en charge les deux orientations. Donc, je ne peux pas dire quel genre de défis je suis confronté là-bas ..

MODIFIER :

Si quelqu'un veut un projet complet, j'ai poussé un exemple de projet vers github .

136
Krishnabhadra

Nous avons dû implémenter exactement le même comportement que vous avez décrit récemment pour une application. Les écrans et le flux global de l'application étant déjà définis, nous avons dû nous y tenir (c'est un clone d'application iOS ...). Heureusement, nous avons réussi à nous débarrasser des boutons de retour à l'écran :)

Nous avons piraté la solution en utilisant un mélange de TabActivity, FragmentActivities (nous utilisions la bibliothèque de support pour les fragments) et Fragments. En rétrospective, je suis à peu près sûr que ce n’était pas la meilleure décision en matière d’architecture, mais nous avons réussi à faire fonctionner le logiciel. Si je devais le refaire, j'essaierais probablement de faire une solution plus basée sur l'activité (pas de fragments), ou d'essayer de n'avoir qu'une seule activité pour les onglets et de laisser le reste sous forme de vues (que je trouve beaucoup plus réutilisable que l’ensemble des activités).

Les exigences devaient donc comporter des onglets et des écrans emboîtables dans chaque onglet:

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

etc...

Donc, disons: l'utilisateur commence à l'onglet 1, navigue de l'écran 1 à l'écran 2 puis à l'écran 3, il passe ensuite à l'onglet 3 et navigue de l'écran 4 à 6; s'il revenait à l'onglet 1, il devrait voir l'écran 3 à nouveau et s'il appuyait sur Retour, il devait revenir à l'écran 2; De retour et il est dans l'écran 1; passez à l'onglet 3 et il est à l'écran 6 à nouveau.

L'activité principale de l'application est MainTabActivity, qui étend TabActivity. Chaque onglet est associé à une activité, disons ActivityInTab1, 2 et 3. Ensuite, chaque écran sera un fragment:

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

Chaque ActivityInTab ne contient qu'un fragment à la fois et sait comment remplacer un fragment par un autre (à peu près la même chose qu'un groupe ActvityGroup). Ce qui est bien, c’est qu’il est assez facile de conserver des piles de dos séparées pour chaque onglet de cette façon.

La fonctionnalité de chaque ActivityInTab était quasiment identique: savoir naviguer d'un fragment à un autre et conserver une pile d'arrière-plan, nous l'avons donc placée dans une classe de base. Appelons-le simplement 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 est simplement ceci:

<?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>

Comme vous pouvez le constater, la présentation de chaque onglet était la même. C'est parce que c'est juste un contenu appelé FrameLayout qui contiendra chaque fragment. Les fragments sont ceux qui ont la vue de chaque écran.

Juste pour les points bonus, nous avons également ajouté un peu de code pour afficher une boîte de dialogue de confirmation lorsque l'utilisateur appuie sur Retour et qu'il n'y a plus de fragments sur lesquels retourner:

// 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();
    }
}

C'est à peu près la configuration. Comme vous pouvez le constater, chaque FragmentActivity (ou tout simplement Activité dans Android> 3) prend en charge tout le back-stacking avec son propre FragmentManager.

Une activité comme ActivityInTab1 sera très simple, elle montrera simplement son premier fragment (c'est-à-dire l'écran):

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

Ensuite, si un fragment doit naviguer vers un autre fragment, il doit faire un casting un peu méchant ... mais ce n'est pas si grave:

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

Donc c'est à peu près tout. Je suis à peu près sûr que ce n'est pas une solution très canonique (et surtout pas très bonne), alors j'aimerais demander aux développeurs chevronnés Android développeurs) quelle serait la meilleure approche pour obtenir cette fonctionnalité , et si cela n’est pas "comment on le fait" sous Android, je vous serais reconnaissant de me signaler un lien ou un élément expliquant ce qui est the Android façon d'aborder cette question (onglets, écrans imbriqués dans des onglets, etc.). N'hésitez pas à déchirer cette réponse dans les commentaires :)

Comme signe que cette solution n’est pas très bonne, c’est que récemment, j’ai dû ajouter des fonctionnalités de navigation à l’application. Un bouton bizarre qui devrait amener l'utilisateur d'un onglet à un autre et à un écran imbriqué. Faire cela par programmation était une tâche ardue, à cause des problèmes de qui-sait-qui-à-qui-est-il, et de savoir quand sont des fragments et des activités réellement instanciés et initialisés. Je pense que cela aurait été beaucoup plus facile si ces écrans et ces onglets n'étaient en réalité que des vues.


Enfin, si vous devez survivre aux changements d'orientation, il est important que vos fragments soient créés à l'aide de setArguments/getArguments. Si vous définissez des variables d'instance dans les constructeurs de vos fragments, vous serez foutus. Heureusement, c’est très facile à corriger: il suffit de tout sauvegarder dans setArguments dans le constructeur, puis de récupérer ces éléments avec getArguments dans onCreate pour pouvoir les utiliser.

94
epidemian

Stocker des références fortes à des fragments n'est pas la bonne façon.

FragmentManager fournit putFragment(Bundle, String, Fragment) et saveFragmentInstanceState(Fragment) .

L'un ou l'autre suffit pour implémenter un backstack.


En utilisant putFragment, au lieu de remplacer un fragment, vous détachez l’ancien et ajoutez le nouveau. C'est ce que fait la structure pour une transaction de remplacement ajoutée au backstack. putFragment stocke un index dans la liste actuelle des fragments actifs et ceux-ci sont enregistrés par la structure lors des changements d'orientation.

La seconde façon, en utilisant saveFragmentInstanceState, enregistre l’ensemble de l’état du fragment dans un Bundle, ce qui vous permet de le supprimer réellement plutôt que de le détacher. Cette approche facilite la manipulation de la pile arrière, car vous pouvez faire apparaître un fragment à tout moment.


J'ai utilisé la deuxième méthode pour ce cas d'utilisation:

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

Je ne souhaite pas que l'utilisateur revienne à l'écran d'inscription, à partir du troisième, en appuyant sur le bouton Précédent. Je fais aussi pivoter les animations entre eux (en utilisant onCreateAnimation), donc les solutions de hacky ne fonctionneront pas, du moins sans que l'utilisateur remarque clairement que quelque chose ne va pas.

Ceci est un cas d'utilisation valide pour un backstack personnalisé, faisant ce que l'utilisateur attend ...

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];
            }
    };
}
6
sergio91pt

Ceci peut être facilement réalisé avec ChildFragmentManager

Voici le post à ce sujet avec le projet associé. regarde,

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

5
tausiq

C’est un problème complexe car Android ne gère qu’une seule pile, mais c’est faisable. Il m’a fallu des jours pour créer une bibliothèque appelée Tab Stacker qui fait exactement ce que vous recherchez: un historique de fragments Pour chaque onglet. Il est open source et entièrement documenté, et peut être facilement inclus avec gradle. Vous pouvez trouver la bibliothèque sur github: https://github.com/smart-fun/TabStacker

Vous pouvez également télécharger l'exemple d'application pour vérifier que le comportement correspond à vos besoins:

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

Si vous avez des questions, n'hésitez pas à envoyer un mail.

2
Arnaud SmartFun

Avertissement:


Je pense que c'est le meilleur endroit pour publier une solution connexe sur laquelle j'ai travaillé pour un type de problème similaire qui semble être plutôt standard Android). Cela ne résoudra pas le problème pour tout le monde, mais cela peut aider certains.


Si la différence principale entre vos fragments réside uniquement dans les données les sauvegardant (c.-à-d. Pas beaucoup de différences de mise en page importantes), vous n'aurez peut-être pas besoin de remplacer le fragment, mais simplement de permuter les données sous-jacentes et d'actualiser la vue.

Voici une description d'un exemple possible pour cette approche:

J'ai une application qui utilise ListViews. Chaque élément de la liste est un parent avec un certain nombre d’enfants. Lorsque vous appuyez sur l'élément, une nouvelle liste doit être ouverte avec ces enfants, dans le même onglet ActionBar que la liste d'origine. Ces listes imbriquées ont une disposition très similaire (quelques ajustements conditionnels ici et là peut-être), mais les données sont différentes.

Cette application comporte plusieurs couches de progéniture sous la liste parente initiale et nous pouvons ou non avoir des données du serveur au moment où un utilisateur tente d'accéder à une certaine profondeur au-delà de la première. Étant donné que la liste est construite à partir d'un curseur de base de données et que les fragments utilisent un chargeur de curseur et un adaptateur de curseur pour renseigner la vue liste avec des éléments de liste, il suffit qu'un clic soit enregistré pour:

1) Créez un nouvel adaptateur avec les champs "à" et "de" appropriés qui correspondront aux nouvelles vues d'élément ajoutées à la liste et aux colonnes renvoyées par le nouveau curseur.

2) Définissez cet adaptateur comme nouvel adaptateur pour ListView.

3) Construisez un nouvel URI en fonction de l'élément sur lequel vous avez cliqué et redémarrez le chargeur de curseur avec le nouvel URI (et la projection). Dans cet exemple, l'URI est mappé à des requêtes spécifiques avec les arguments de sélection transmis de l'interface utilisateur.

4) Lorsque les nouvelles données ont été chargées à partir de l'URI, remplacez le curseur associé à l'adaptateur par le nouveau curseur. La liste sera alors actualisée.

Étant donné que nous n'utilisons pas de transactions, il n'y a pas de backstack associé. Vous devrez donc créer la vôtre, ou lire les requêtes en sens inverse lors du retrait de la hiérarchie. Lorsque j'ai essayé cela, les requêtes ont été suffisamment rapides pour que je les répète dans la commande oNBackPressed () jusqu'à ce que je sois au sommet de la hiérarchie, moment auquel la structure reprend le bouton Précédent.

Si vous vous trouvez dans une situation similaire, assurez-vous de lire la documentation suivante: http://developer.Android.com/guide/topics/ui/layout/listview.html

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

J'espère que ça aidera quelqu'un!

2
courtf

J’ai eu exactement le même problème et j’ai mis en place un projet open source github qui couvre la navigation par onglets superposés, la navigation dans les deux sens, et qui est bien testé et documenté:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

Il s’agit d’un cadre simple et compact pour les onglets de navigation, la commutation de fragments et la gestion de la navigation en amont et en aval. Chaque onglet a sa propre pile de fragments. Il utilise ActionBarSherlock et est compatible avec l’API de niveau 8.

2
Sebastian Baltes

Je voudrais suggérer ma propre solution au cas où quelqu'un chercherait et voudrait essayer et choisir la meilleure pour ses besoins.

https://github.com/drusak/tabactivity

Le but de créer la bibliothèque est assez banal - l'implémenter comme un iPhone.

Les principaux avantages:

  • utilisez la bibliothèque Android.support.design avec TabLayout;
  • chaque onglet a sa propre pile en utilisant FragmentManager (sans enregistrer les références des fragments);
  • prise en charge des liens profonds (lorsque vous devez ouvrir un onglet spécifique et le niveau de fragment spécifique dans celui-ci);
  • sauvegarde/restauration des états des onglets;
  • méthodes de cycle de vie adaptatif de fragments dans des onglets;
  • assez facile à mettre en œuvre pour vos besoins.
2
kasurd

Une solution simple:

Chaque fois que vous modifiez un appel de vue onglet/racine:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Il va effacer le BackStack. Rappelez-vous d'appeler cela avant de changer le fragment racine.

Et ajoutez des fragments avec ceci:

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

Notez que la .addToBackStack(null) et le transaction.add Pourraient, par exemple, être changé avec transaction.replace.

1
Morten Holmgaard