J'essaie d'implémenter des onglets pour la navigation dans une application Android. Puisque TabActivity et ActivityGroup sont obsolètes, je voudrais plutôt l'implémenter en utilisant des fragments.
Je sais comment configurer un fragment pour chaque onglet, puis changer de fragment lorsqu'un onglet est cliqué. Mais comment puis-je avoir une pile arrière distincte pour chaque onglet?
Pour un 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 démarré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 alors sélectionné, le fragment B doit à nouveau être affiché. À ce stade, il devrait être possible d'utiliser le bouton de retour pour afficher le fragment A.
En outre, il est important que l'état de chaque onglet soit conservé lorsque le périphérique est tourné.
BR Martin
la source
Je suis terriblement en retard pour cette question. Mais comme ce fil a été très instructif et utile pour moi, j'ai pensé que je ferais mieux de poster mes deux pence ici.
J'avais besoin d'un flux d'écran comme celui-ci (un design minimaliste avec 2 onglets et 2 vues dans chaque onglet),
J'avais les mêmes exigences dans le passé, et je l'ai fait en utilisant
TabActivityGroup
(qui était également obsolète à l'époque) et Activities. Cette fois, je voulais utiliser des fragments.Voilà donc comment je l'ai fait.
1. Créez une classe de fragments de base
Tous les fragments de votre application peuvent étendre cette classe de base. Si vous souhaitez utiliser des fragments spéciaux comme
ListFragment
vous devez créer une classe de base pour cela également. Vous serez clair sur l'utilisation deonBackPressed()
etonActivityResult()
si vous lisez le message dans son intégralité.2. Créez des identifiants d'onglets, accessibles partout dans le projet
rien à expliquer ici ..
3. Ok, Activité de l'onglet principal - Veuillez lire les commentaires dans le code.
4. app_main_tab_fragment_layout.xml (au cas où quelqu'un serait intéressé.)
5. AppTabAFirstFragment.java (premier fragment de l'onglet A, similaire pour tous les onglets)
Ce n'est peut-être pas la manière la plus raffinée et la plus correcte. Mais cela a très bien fonctionné dans mon cas. De plus, 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. Je ne peux donc pas dire à quel genre de défis je suis confronté là-bas
ÉDITER :
Si quelqu'un veut un projet complet, j'ai poussé un exemple de projet sur github .
la source
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:
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:
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:
Le fichier activity_in_tab.xml est juste ceci:
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:
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):
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:
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.
la source
Ceci peut être facilement réalisé avec ChildFragmentManager
Voici un article à ce sujet avec le projet associé. regarde,
http://tausiq.wordpress.com/2014/06/06/android-multiple-fragments-stack-in-each-viewpager-tab/
la source
Le stockage de références fortes à des fragments n'est pas la bonne manière.
FragmentManager fournit
putFragment(Bundle, String, Fragment)
etsaveFragmentInstanceState(Fragment)
.L'un ou l'autre suffit pour implémenter une backstack.
En utilisant
putFragment
, au lieu de remplacer un fragment, vous détachez l'ancien et ajoutez le nouveau. C'est ce que fait le framework à une transaction de remplacement qui est ajoutée à la backstack.putFragment
stocke un index de la liste actuelle des fragments actifs et ces fragments sont enregistrés par l'infrastructure lors des changements d'orientation.La deuxième façon, en utilisant
saveFragmentInstanceState
, enregistre l'état du fragment entier dans un Bundle, ce qui vous permet de vraiment le supprimer, plutôt que de le détacher. L'utilisation de cette approche facilite la manipulation de la pile arrière, car vous pouvez afficher un fragment quand vous le souhaitez.J'ai utilisé la deuxième méthode pour ce cas d'utilisation:
Je ne veux pas que l'utilisateur revienne à l'écran d'inscription, à partir du troisième, en appuyant sur le bouton retour. Je fais aussi des animations inversées entre eux (en utilisant
onCreateAnimation
), donc les solutions hacky ne fonctionneront pas, du moins sans que l'utilisateur ne remarque clairement que quelque chose ne va pas.Il s'agit d'un cas d'utilisation valide pour une backstack personnalisée, faisant ce que l'utilisateur attend ...
la source
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 un problème Android assez standard. Cela ne résoudra pas le problème pour tout le monde, mais cela peut en aider certains.
Si la principale différence entre vos fragments réside uniquement dans les données qui les sauvegardent (c'est-à-dire qu'il n'y a pas beaucoup de grandes différences de mise en page), vous n'aurez peut-être pas besoin de remplacer réellement le fragment, mais simplement d'échanger 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 s'ouvrir avec ces enfants, dans le même onglet ActionBar que la liste d'origine. Ces listes imbriquées ont une présentation très similaire (quelques ajustements conditionnels ici et là peut-être), mais les données sont différentes.
Cette application a 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 remplir la vue de liste avec des éléments de liste, tout ce qui doit se produire lorsqu'un clic est enregistré est:
1) Créez un nouvel adaptateur avec les champs «à» et «de» appropriés qui correspondent 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) Créez un nouvel URI basé sur l'élément sur lequel vous avez cliqué et redémarrez le chargeur de curseur avec le nouvel URI (et la nouvelle projection). Dans cet exemple, l'URI est mappé à des requêtes spécifiques avec les arguments de sélection transmis depuis l'interface utilisateur.
4) Lorsque les nouvelles données ont été chargées à partir de l'URI, permutez le curseur associé à l'adaptateur sur le nouveau curseur, et la liste sera alors actualisée.
Il n'y a pas de backstack associé à cela car nous n'utilisons pas de transactions, vous devrez donc soit créer les vôtres, soit lire les requêtes à l'envers lors du retrait de la hiérarchie. Lorsque j'ai essayé cela, les requêtes étaient suffisamment rapides pour que je les exécute à nouveau dans oNBackPressed () jusqu'à ce que je sois en haut de la hiérarchie, auquel cas le framework reprend le bouton Précédent.
Si vous vous trouvez dans une situation similaire, assurez-vous de lire la documentation: 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!
la source
J'ai eu exactement le même problème et j'ai implémenté un projet github open source qui couvre les onglets empilés, la navigation arrière et supérieure et qui est bien testé et documenté:
https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs
la source
C'est un problème complexe car Android ne gère qu'une seule pile arrière, 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 des 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 voir que le comportement correspond à vos besoins:
https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp
Si vous avez des questions, n'hésitez pas à nous envoyer un mail.
la source
Je voudrais suggérer ma propre solution au cas où quelqu'un chercherait et voudrait essayer de choisir la meilleure pour ses besoins.
https://github.com/drusak/tabactivity
Le but de la création de la bibliothèque est assez banal - implémentez-la comme un iPhone.
Les principaux avantages:
la source
ListFragment
s en plus deFragment
s donc j'ai dupliqué BaseTabFragment.java en BaseTabListFragment.java et l'ai fait étendre ListFragment. Ensuite, j'ai dû changer différentes parties du code où il supposait toujours s'attendre à un BaseTabFragment. Y a-t-il un meilleur moyen?Une solution simple:
Chaque fois que vous changez d'appel d'onglet / vue racine:
Cela effacera le BackStack. N'oubliez pas d'appeler cela avant de modifier le fragment racine.
Et ajoutez des fragments avec ceci:
Notez que le
.addToBackStack(null)
et letransaction.add
peuvent être modifiés par exemple avectransaction.replace
.la source
Ce fil était très très intéressant et utile.
Merci Krishnabhadra pour votre explication et votre code, j'utilise votre code et je l'ai amélioré un peu, permettant de persister les stacks, currentTab, etc ... à partir du changement de configuration (rotation principalement).
Testé sur de vrais appareils 4.0.4 et 2.3.6, non testé sur émulateur
Je change cette partie de code sur "AppMainTabActivity.java", le reste reste le même. Peut-être que Krishnabhadra ajoutera ceci sur son code.
Récupérer des données surCréer:
Enregistrez les variables et mettez-les dans Bundle:
S'il existe un CurrentTab précédent, définissez ceci, sinon créez un nouveau Tab_A:
J'espère que cela aide d'autres personnes.
la source
Je recommanderais de ne pas utiliser de backstack basé sur HashMap> il y a beaucoup de bogues en mode "ne pas garder les activités". Il ne restaurera pas correctement l'état au cas où vous seriez profondément dans la pile du fragment. Et sera également créé dans un fragment de carte imbriqué (à l'exception: Fragment aucune vue trouvée pour l'ID). Coz HashMap> après l'arrière-plan \ l'application de premier plan sera nulle
J'optimise le code ci-dessus pour travailler avec la backstack de fragment
C'est en bas TabView
Classe d'activité principale
tags_activity.xml
<
tags_icon.xml
la source