Nous avons dû mettre en œuvre exactement le même comportement que vous décrivez récemment pour une application. Les écrans et le flux global de l'application étaient déjà définis, il fallait donc s'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. Rétrospectivement, je suis à peu près sûr que ce n'était pas la meilleure décision d'architecture, mais nous avons réussi à faire fonctionner la chose. 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 tout le reste être des vues (ce que je trouve est beaucoup plus réutilisable que les activités en général).
Les exigences étaient donc d'avoir des onglets et des écrans imbriqués dans chaque onglet:
tab 1
screen 1 -> screen 2 -> screen 3
tab 2
screen 4
tab 3
screen 5 -> 6
etc...
Disons donc: l'utilisateur démarre dans 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 revient à l'onglet 1, il doit revoir l'écran 3 et s'il appuie sur Retour, il doit revenir à l'écran 2; De retour et il est à l'écran 1; passez à l'onglet 3 et il est à nouveau à l'écran 6.
L'activité principale de l'application est MainTabActivity, qui étend TabActivity. Chaque onglet est associé à une activité, disons ActivityInTab1, 2 et 3. Et puis chaque écran sera un fragment:
MainTabActivity
ActivityInTab1
Fragment1 -> Fragment2 -> Fragment3
ActivityInTab2
Fragment4
ActivityInTab3
Fragment5 -> Fragment6
Chaque ActivityInTab ne contient qu'un seul fragment à la fois et sait comment remplacer un fragment par un autre (à peu près la même chose qu'un ActvityGroup). Ce qui est cool, c'est qu'il est assez facile de conserver des piles arrière séparées pour chaque onglet de cette façon.
La fonctionnalité de chaque ActivityInTab était tout à fait la même: savoir naviguer d'un fragment à un autre et maintenir une pile arrière, nous avons donc mis cela dans une classe de base. Appelons cela 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();
}
}
Le fichier activity_in_tab.xml est juste 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 voir, la disposition de la vue pour chaque onglet était la même. C'est parce que c'est juste un FrameLayout appelé contenu 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 petit 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 vers lesquels revenir:
// 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 voir, chaque FragmentActivity (ou tout simplement Activity dans Android> 3) s'occupe de tout le back-stack avec son propre FragmentManager.
Une activité comme ActivityInTab1 sera vraiment simple, elle montrera juste 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 a besoin de naviguer vers un autre fragment, il doit faire un petit casting méchant ... mais ce n'est pas si mal:
// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());
C'est donc à 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 Android chevronnés quelle serait la meilleure approche pour obtenir cette fonctionnalité, et si ce n'est pas "comment c'est done "sous Android, j'apprécierais si vous pouviez m'indiquer un lien ou du matériel qui explique quelle est la manière Android d'aborder cela (onglets, écrans imbriqués dans les 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 emmener l'utilisateur d'un onglet à un autre et dans un écran imbriqué. Faire cela par programme était une douleur dans le cul, à cause des problèmes qui-sait-qui et de gérer quand les fragments et les activités sont réellement instanciés et initialisés. Je pense que cela aurait été beaucoup plus facile si ces écrans et ces onglets n'étaient vraiment 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 foutu. Mais heureusement, c'est vraiment facile à corriger: il suffit de sauvegarder tout dans setArguments dans le constructeur, puis de récupérer ces choses avec getArguments dans onCreate pour les utiliser.