web-dev-qa-db-fra.com

Utilisation de la barre de navigation ActionBar Up dans les fragments maîtres/détails

J'ai une application avec une disposition maître/détail (1 activité, 1 fragment ListView et 1 fragment détail). Lorsque l'utilisateur clique sur un élément dans ListView, une transaction de fragment instancie un fragment de détail dans le volet de droite qui contient les informations correspondant à cet élément. Lorsque le fragment de détail est affiché, je masque les boutons/éléments de la barre d’action initiale et affiche 3 nouveaux éléments AB (terminé/supprimer/annuler). L'utilisateur peut nettoyer le volet droit et revenir à l'état initial de l'interface utilisateur en appuyant sur le bouton Précédent ou en appuyant sur l'un des 3 éléments AB.

Le problème que je rencontre est que lorsque l'utilisateur sélectionne l'icône de la page d'accueil de l'application (c'est-à-dire "la navigation vers le haut"), l'activité est rechargée (c'est-à-dire que l'animation indiquant que l'activité commence est visible à la fois dans la barre d'action et L’interface utilisateur a été redessinée). Le problème ne se produit que lorsque vous appuyez sur l'icône Accueil de l'application. Si l'utilisateur appuie sur le bouton Précédent ou sur un bouton de la barre d'action d'annulation/terminé/supprimer, le fragment est simplement supprimé du volet de droite et l'interface utilisateur revient à l'état initial sans aucun "rechargement".

La disposition XML de l'activité est la suivante (dans LinearLayout; prettify cache cette ligne):

<fragment class="*.*.*.ListFragment"
        Android:id="@+id/titles" Android:layout_weight="1"
        Android:layout_width="0px"
        Android:layout_height="match_parent" />

<FrameLayout Android:id="@+id/details" Android:layout_weight="2"
        Android:layout_width="0px"
        Android:layout_height="match_parent" />

DetailsFragement contient l'instruction actionBar.setDisplayHomeAsUpEnabled dans sa méthode onCreate:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);


    ActionBar actionBar = getSherlockActivity().getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);

}

Pour les fragments ListView et Detail, les méthodes onCreateOptionsMenu () et onOptionsItemSelected () sont implémentées dans les fragments. Ci-dessous le code du fragment Details:

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.edit_menu, menu);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    // some variable statements...

    switch (item.getItemId()) {
        case Android.R.id.home:
            //Toast.makeText(getSherlockActivity(), "Tapped home", Toast.LENGTH_SHORT).show();
            onHomeSelectedListener.onHomeSelected();
            return true;

        case R.id.menu_edit_item_done:
            editedTask.setTitle(editedTaskTitle);
            onTaskEditedListener.onTaskEdited(editedTask, UPDATE_TASK, true);
            return true;

        default:
            return super.onOptionsItemSelected(item);

    }
}

Dans l'activité Hôte, j'implémente onHomeSelectedListner pour gérer l'icône de la page d'accueil de l'application, puis appuyez sur (par exemple, "navigation vers le haut":

public void onHomeSelected(){

    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction ft = manager.beginTransaction();
    TaskFragment taskFragment = (TaskFragment)getSupportFragmentManager().findFragmentById(R.id.details);
    ft.remove(taskFragment);
    ft.commit();
    manager.popBackStack();

}

L’écouteur de l’activité chargé de traiter tous les autres boutons de la barre d’action (c’est-à-dire done/delete/cancel) est onTaskEditedListener et, mis à part le code qui traite certaines données, il contient les mêmes transactions de fragment que celles présentées ci-dessus. 

Update (1/24) Sur la base des commentaires tyczj et straya, j'ai placé des instructions de journal dans onCreate (), onResume (), onPause () de l'activité pour déterminer les différences entre les écouteurs onHomeSelected et onTaskEdited. Je suis en mesure de confirmer que lors de l'événement de "navigation ascendante" (c'est-à-dire onHomeSelected), onPause (), onCreate () et onResume () sont appelées. Considérant que lors de l’appel onTaskEdited (c’est-à-dire le bouton Précédent ou l’appui effectué/supprimé/annulé), aucun de ces événements n’est appelé.

Update (1/25) .__ Sur la base d'une suggestion de Mark Murphy, j'ai commenté l'appel de méthode onHomeSelected dans l'instruction "case Android.R.id.home" juste pour voir ce que l'activité ferait. L'idée était que l'application ne ferait rien, car il n'y a pas de déclarations. Il s'avère que ce n'est pas le cas. Même sans appel à la méthode d’écoute (c’est-à-dire qui supprime le fragment), l’activité est redémarrée et le fragment de détail est supprimé du conteneur de fragments.

Mise à jour (2/28) J'ai temporairement contourné le fait que mon activité principale était redémarrée en désactivant les animations de fenêtre (comme indiqué dans ma propre réponse). Cependant, grâce à des tests supplémentaires, j'ai découvert un bogue. Grâce à l'exemple de code de Wolfram Rittmeyer, j'ai pu comprendre la ou les vraies raisons du redémarrage de mon activité (dans la présentation simple/principale) lors de la navigation supérieure: 1) Bien que j'utilisais ce "onHomeSelectedListener" correctement supprimer le fragment du backstack, il me restait du code restant dans le onOptionsItemSelected du fragment ListView qui créait une nouvelle intention de démarrer l'activité d'hébergement. C’est pourquoi il était nécessaire d'appuyer sur l'icône d'accueil de l'application pour relancer l'activité . 2) Lors de ma dernière implémentation (indiquée dans ma propre réponse), je me suis débarrassé de onHomeSelectedListener dans l'activité et j'ai remplacé l'objectif de startActivity (code incriminé). onOptionsItemSelected du ListView pour utiliser le code de suppression de fragment + popBackStack à l'origine dans onHomeSelectedListener.

17
cavega

Après de nombreuses recherches et fouilles, il s'avère que la seule raison pour laquelle mon activité a été redémarrée lors de la "navigation avancée" pour la configuration maître/détail est que j'ai laissé du code dans onOptionsItemSelected du fragment ListView qui créait l'intention de démarrer l'activité principale à mon code de transaction de fragment complet ailleurs. Vous trouverez ci-dessous la dernière implémentation avec laquelle la "navigation avancée" a fonctionné correctement sur les configurations de téléphone (activités multiples) et de tablette (activité unique/à volets multiples). Merci à Wolfram Rittmeyer pour quelques astuces dans son code (lien dans la section commentaire) qui m'aident à cerner mon problème!

Activité principale: héberge les fragments et effectue d'autres opérations spécifiques à l'application

ListView Fragment: Gère "la navigation vers le haut dans la configuration de la table

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case Android.R.id.home:
            if(mDualPane){
                FragmentManager manager = getSherlockActivity().getSupportFragmentManager();
                FragmentTransaction ft = manager.beginTransaction();
                DetailFragment detailFragment = (DetailFragment)manager.findFragmentById(R.id.details);
                ft.remove(detailFragment);
                ft.commit();
                manager.popBackStack();
                getSherlockActivity().getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                getSherlockActivity().getSupportActionBar().setHomeButtonEnabled(false);
            }
            return true;

        // Other case statements...

        default:
            return super.onOptionsItemSelected(item);
    }
}

Détails Fragment: Gère la navigation dans la configuration du téléphone

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);

    // Sets "up navigation" for both phone/tablet configurations
    ActionBar actionBar = getSherlockActivity().getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    switch (item.getItemId()) {
        case Android.R.id.home:
            if(!mDualPane){
                Intent parentActivityIntent = new Intent(getSherlockActivity(), MainActivity.class);
                parentActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(parentActivityIntent);
                getSherlockActivity().finish();
            }
            return true;

        // Other case statements...

        default:
            return super.onOptionsItemSelected(item);
    }

}
8
cavega

Si vous regardez Modèle de conception de navigation , vous verrez que vous souhaitez revenir à l’activité de départ lorsque vous appuyez sur le bouton principal.

Donc, disons que vous avez 2 activités appelez-les A1 et A2. En cliquant sur quelque chose dans A1 vous amène à A2. Si l'utilisateur appuie sur le bouton d'accueil, vous devez le renvoyer à A1 en nettoyant la pile de tous les objets jusqu'à cette activité de ce type.

Intent intent = new Intent(this, A1.class);  
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

c'est ce que fait le drapeau Intent.FLAG_ACTIVITY_CLEAR_TOP

Si cette option est définie et que l'activité en cours est déjà en cours d'exécution dans la tâche en cours, au lieu de lancer une nouvelle instance de cette activité, toutes les autres activités s'ajoutant seront closes et cette intention sera transmise au (maintenant haut) ancienne activité comme nouvelle intention.

Par exemple, considérons une tâche composée des activités suivantes: A, B, C, D. Si D appelle startActivity () avec une intention qui se résume à la composante de l’activité B, C et D seront terminés et B recevra l’intention donnée. , la pile étant maintenant: A, B. » 

L'instance actuellement en cours d'activité B dans l'exemple ci-dessus recevra la nouvelle intention que vous commencez ici dans sa méthode onNewIntent () ou sera elle-même terminée et redémarrée avec la nouvelle intention. S'il a déclaré que son mode de lancement était "multiple" (valeur par défaut) et que vous n'avez pas défini FLAG_ACTIVITY_SINGLE_TOP dans la même intention, le processus sera terminé et recréé. pour tous les autres modes de lancement ou si FLAG_ACTIVITY_SINGLE_TOP est défini, cette intention sera transmise à l'instance actuelle onNewIntent ().

Ce mode de lancement peut également être utilisé à bon escient avec FLAG_ACTIVITY_NEW_TASK: s'il est utilisé pour démarrer l'activité racine d'une tâche, il affichera au premier plan toute instance en cours de cette tâche, puis l'effacera à son état racine. Ceci est particulièrement utile, par exemple, lors du lancement d'une activité à partir du gestionnaire de notifications.

3
tyczj

Je pense que vous devriez manipuler le bouton Haut uniquement à l'intérieur de l'activité . Si vous êtes dans un téléphone, le bouton Haut sera manipulé par une activité qui agit comme un wrapper de ce fragment, dans la tablette (motif maître/détail) que vous ne voulez pas. de toute façon

0
urSus

ne pas: casser puis renvoyer super.onOptionsItemSelected (item), plutôt juste: return true;

METTRE À JOUR:

Donc, vous dites que l'activité est "redémarrée" en fonction de ce que vous voyez se produire avec les vues, mais pouvez-vous confirmer ce qui peut ou non arriver à l'activité (et aux fragments d'ailleurs) en utilisant la journalisation dans les différentes méthodes de cycle de vie? De cette façon, vous pouvez être sûr du comportement actuel (erroné) avant de poursuivre le diagnostic.

METTRE À JOUR:

OK, bon pour être sûr du comportement:) Maintenant, en ce qui concerne votre question "Quelle est la bonne façon de mettre en œuvre la" navigation avancée "pour une présentation maître/détail (1 activité/2fragments)?": est que les 2 fragments ont été ajoutés dans une seule transaction FragmentTransaction et que vous n'avez qu'à popBackStack pour les supprimer et revenir à l'état précédent. Je pense que vous doublez en supprimant manuellement un fragment dans une FragmentTransaction, puis en faisant sauter le paquet. Essayez juste popBackStack. Oh, et juste pour être sûr et cohérent, puisque vous utilisez ActionBarSherlock et support.v4, utilisez-vous FragmentActivity (plutôt qu'une activité) et SherlockFragment?

0
straya