Basculer entre l'image du tiroir de navigation Android et le curseur vers le haut lors de l'utilisation de fragments

177

Lors de l'utilisation du tiroir de navigation, les développeurs Android recommandent que dans l'ActionBar "seuls les écrans qui sont représentés dans le tiroir de navigation doivent réellement avoir l'image du tiroir de navigation" et que "tous les autres écrans ont le carat traditionnel."

Voir ici pour plus de détails: http://youtu.be/F5COhlbpIbY

J'utilise une activité pour contrôler plusieurs niveaux de fragments et je peux faire en sorte que l'image du tiroir de navigation s'affiche et fonctionne à tous les niveaux.

Lors de la création de fragments de niveau inférieur, je peux appeler ActionBarDrawerToggle setDrawerIndicatorEnabled(false)pour masquer l'image du tiroir de navigation et afficher le curseur Haut

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

Le problème que j'ai, c'est lorsque je reviens aux fragments de niveau supérieur que Up carat affiche toujours au lieu de l'image originale du tiroir de navigation. Des suggestions sur la façon de "rafraîchir" l'ActionBar sur les fragments de niveau supérieur pour réafficher l'image du tiroir de navigation?


Solution

La suggestion de Tom a fonctionné pour moi. Voici ce que j'ai fait:

Activité principale

Cette activité contrôle tous les fragments de l'application.

Lors de la préparation de nouveaux fragments pour en remplacer d'autres, j'ai défini le DrawerToggle setDrawerIndicatorEnabled(false)comme ceci:

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

Ensuite, dans un remplacement de onBackPressed, j'ai inversé ce qui précède en définissant le DrawerToggle setDrawerIndicatorEnabled(true)comme ceci:

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

Dans le niveau inférieur

Dans les fragments que j'ai modifiés onCreateet onOptionsItemSelectedcomme ceci:

Dans onCreateajouté setHasOptionsMenu(true)pour permettre la configuration du menu d'options. Également défini setDisplayHomeAsUpEnabled(true)pour activer le < dans la barre d'actions:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

Ensuite, onOptionsItemSelectedchaque fois que le < est enfoncé, il appelle le onBackPressed()de l'activité pour remonter d'un niveau dans la hiérarchie et afficher l'image du tiroir de navigation:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
         
    }
EvilAsh
la source
2
Également dans votre méthode onBackPressed (), vous pouvez vérifier le nombre d'entrées dans la pile arrière avec la méthode getFragmentManager (). GetBackStackEntryCount () et activer l'indicateur de tiroir uniquement si le résultat est 0. Dans ce cas, il n'est pas nécessaire d'activer homeAsUpIndicator dans chaque LowerLevelFragments.
pvshnik
2
Ceci est très utile! Vous devez déplacer la partie «solution» de votre message et en faire une véritable «réponse». Vous obtiendrez plus de points pour upvotes et il est une réponse après tout
Oleksiy
Pourquoi êtes - vous ici remplacez le fragment: .replace(R.id.frag_layout. S'il s'agit d'un niveau de hiérarchie de plus, je m'attendrais à ce que vous .addle mettiez en réserve.
JJD
5
Frère, comment faites-vous référence à l' theDrawerToggle.setDrawerIndicatorEnabled(false);intérieur du fragment? Je pense qu'il est déclaré dans le fichier de classe d'activité principale. Je ne trouve pas un moyen de faire référence à cela. Des indices?
Skynet
1
Lors de l'utilisation d'une barre d'outils, j'ai dû changer les options d'affichage pour ne pas utiliser la maison comme en place entre-temps. Sinon, la setDisplayOptions()méthode dans ToolbarWidgetWrapper(du package interne android.support.v7.internal.widget) ne recréerait pas l'icône lors de la saisie du même fragment une deuxième fois. Il suffit de laisser cela ici pour que d'autres tombent également sur ce problème.
Wolfram Rittmeyer

Réponses:

29

Vous avez écrit que, pour implémenter des fragments de niveau inférieur, vous remplacez le fragment existant, par opposition à l'implémentation du fragment de niveau inférieur dans une nouvelle activité.

Je pense que vous devrez alors implémenter la fonctionnalité de retour manuellement: lorsque l'utilisateur appuie sur vous avez du code qui fait apparaître la pile (par exemple en Activity::onBackPressedremplacement). Ainsi, partout où vous faites cela, vous pouvez inverser le setDrawerIndicatorEnabled.

À M
la source
Merci Tom, cela a fonctionné! J'ai mis à jour le message d'origine avec la solution que j'ai utilisée.
EvilAsh
6
Comment référencer theDrawerToggle dans un fragment de niveau inférieur? Il a été défini dans l'activité principale, je ne peux pas comprendre cela!
Skynet
1
Vous pouvez obtenir l'activité à partir du fragment. Il suffit donc d'ajouter un getter dans votre activité principale pour accéder à la bascule du tiroir.
Raphael Royer-Rivard
83

C'est aussi simple que 1-2-3.

Si vous souhaitez atteindre:

1) Indicateur de tiroir - lorsqu'il n'y a pas de fragments dans la pile arrière ou que le tiroir est ouvert

2) Flèche - lorsque certains fragments sont dans la pile arrière

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3) Les deux indicateurs agissent selon leur forme

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

PS Voir Création d'un tiroir de navigation sur les développeurs Android sur d'autres conseils sur le comportement de l'indicateur à 3 lignes.

riwnodennyk
la source
3
J'ai fini sur quelque chose de similaire au vôtre. Je pense que cette solution (en utilisant le BackStackChangedListener pour basculer entre ce qui est montré) est la plus élégante. Cependant, j'ai deux changements: 1) Je n'appelle / ne change pas l'indicateur de tiroir dans les appels onDrawerClosed / onDrawerOpened - ce n'est pas nécessaire, et 2) Je laisse les fragments eux-mêmes gérer la navigation vers le haut en créant un AbstractFragment dont ils héritent tous implémente onOptionsItemSelected (..) et qui appelle toujours setHasOptionsMenu (true);
Espen Riskedal
2
Vous devez également verrouiller le geste de balayage pour le tiroir de navigation en mode flèche: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); et
réactivez
5
J'ai fini par faire tout cela dans mon fragment NavigationDrawer. Fonctionne comme un charme btw, merci.
frostymarvelous
1
setActionBarArrowDependingOnFragmentsBackStack()... quel long nom: P
S.Thiongane
2
Cela ne montre pas le bouton haut pour moi lorsque je passe du fragment A à B et ajoute A à la backstack. :(
aandis le
14

J'ai utilisé la chose suivante:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });
Yuriy Sych
la source
1
MERCI BEAUCOUP, c'est le seul qui a réellement fonctionné. Une fois que j'ajoute cela, drawerToggle.setToolbarNavigationClickListener(cet auditeur est appelé en cliquant sur la flèche
EpicPandaForce
Merci @Yuriy, cela m'a aidé à résoudre mon problème
Muhammad Shoaib Murtaza
12

Si votre bouton de barre d'action haut ne fonctionne pas, n'oubliez pas d'ajouter l'auditeur:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

J'ai du mal à implémenter une navigation dans le tiroir avec un bouton d'accueil, tout a fonctionné sauf le bouton d'action.

Burrich
la source
10

Essayez de gérer la sélection de l'élément Accueil dans MainActivity en fonction de l'état du DrawerToggle. De cette façon, vous n'avez pas à ajouter le même code à chaque fragment.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}
Dzeikei
la source
+1 solution soignée. Pour que votre réponse soit pleinement utilisable, vous devez ajouter un chèque sur la backstack. Lorsqu'il est vide, réglez automatiquement l'indicateur de tiroir sur vrai.
HpTerm
@HpTerm Je gère la backstack onBackPressed()car je voulais le même comportement pour les deux.
dzeikei
6

SUIVRE

La solution donnée par @dzeikei est soignée, mais elle peut être étendue, lors de l'utilisation de fragments, pour gérer automatiquement la remise à zéro de l'indicateur de tiroir lorsque la backstack est vide.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

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

ÉDITER

Pour la question de @JJD.

Les fragments sont conservés / gérés dans une activité. Le code ci-dessus est écrit une fois dans cette activité, mais ne gère que le curseur vers le haut pour leonOptionsItemSelected .

Dans l'une de mes applications, je devais également gérer le comportement du curseur haut lorsque le bouton de retour était enfoncé. Cela peut être géré en remplaçant onBackPressed.

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

Notez la duplication de code entre onOptionsItemSelectedetonBackPressed qui peut être évitée en créant une méthode et en appelant cette méthode aux deux endroits.

Notez également que j'ajoute encore deux fois executePendingTransactionsce qui dans mon cas était obligatoire ou bien j'avais parfois des comportements étranges du curseur haut.

HpTerm
la source
Est-ce le code complet que je dois ajouter pour implémenter le comportement de curseur Up ou faites-vous référence à l'un des autres messages? Veuillez clarifier ceci.
JJD
Merci pour la modification. En fait, je maintiens mDrawerToggledans une NavigationDrawerFragmentclasse. Pour le faire fonctionner , je dois également basculer l'état du bouton d'accueil / indicateur - voir: NavigationDrawerFragment#toggleDrawerIndicator. De plus, je ne suis pas sûr que vous ayez besoin de l'enregistrement initial onOptionsItemSelected: je ne l'ai pas commenté. - Exemple simplifié
JJD
Mise en œuvre révisée : vous avez raison lors de l'enregistrement initial onOptionsItemSelected. Cela garantit que le tiroir de navigation s'ouvre toujours dans la hiérarchie de niveau supérieur. Cependant, j'ai déplacé le code dans le NavigationDrawerFragment#onOptionsItemSelected. Cela m'aide à ne pas exposer mDrawerToggleau MainActivity.
JJD
@JJD Je me souviens avoir eu un peu de mal à avoir tout fonctionnant pour le curseur supérieur pour chaque niveau de la hiérarchie. Tant que cela fonctionne pour vous aussi, c'est bien. Bien sûr, comme vous le dites, vous pouvez déplacer le code ailleurs pour ne pas l'exposer.
HpTerm
2

J'ai créé une interface pour l'activité d'hébergement pour mettre à jour l'état d'affichage du menu hamburger. Pour les fragments de niveau supérieur, j'ai défini la bascule sur trueet pour les fragments pour lesquels je veux afficher la flèche vers le haut <sur laquelle j'ai réglé la bascule false.

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mListener.showDrawerToggle(false);
    }
}

Puis dans mon activité ...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}
Bill Mote
la source
2

Cette réponse fonctionnait mais il y avait un petit problème avec elle. Le getSupportActionBar().setDisplayHomeAsUpEnabled(false)n'était pas appelé explicitement et cela provoquait le masquage de l'icône du tiroir même s'il n'y avait aucun élément dans la backstack, donc changer la setActionBarArrowDependingOnFragmentsBackStack()méthode fonctionnait pour moi.

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }
Sheraz Ahmad Khilji
la source
dans mon cas, la solution rigth utilisait uniquement actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFragmentManager (). getBackStackEntryCount () <0);
Gorets
1

La logique est claire. Afficher le bouton de retour si la pile de retour de fragment est vide. Afficher l'animation de retour de hamburger du matériau si la pile de fragments n'est pas claire.

getSupportFragmentManager().addOnBackStackChangedListener(
    new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            syncActionBarArrowState();
        }
    }
);


private void syncActionBarArrowState() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    mNavigationDrawerFragment.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

//add these in Your NavigationDrawer fragment class

public void setDrawerIndicatorEnabled(boolean flag){
    ActionBar actionBar = getActionBar();
    if (!flag) {
        mDrawerToggle.setDrawerIndicatorEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        mDrawerToggle.setHomeAsUpIndicator(getColoredArrow());
    } else {
        mDrawerToggle.setDrawerIndicatorEnabled(true);
    }
    mDrawerToggle.syncState();
    getActivity().supportInvalidateOptionsMenu();
}

//download back button from this(https://www.google.com/design/icons/) website and add to your project

private Drawable getColoredArrow() {
    Drawable arrowDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_arrow_back_black_24dp);
    Drawable wrapped = DrawableCompat.wrap(arrowDrawable);

    if (arrowDrawable != null && wrapped != null) {
        // This should avoid tinting all the arrows
        arrowDrawable.mutate();
        DrawableCompat.setTint(wrapped, Color.GRAY);
    }
    return wrapped;
}
kml_ckr
la source
1

Si vous jetez un œil à l'application GMAIL et venez ici pour rechercher l'icône carret / d'affordance.

Je vous demanderais de le faire, aucune des réponses ci-dessus n'était claire. j'ai pu modifier la réponse acceptée.

  • NavigationDrawer -> Listview contient des sous-fragments.


  • les sous-fragments seront répertoriés comme ceci

  • firstFragment == position 0 ---> cela aura des sous-fragments -> fragment

  • secondFragment
  • troisième Fragment et ainsi de suite ...

Dans firstFragment, vous avez un autre fragment.

Appelez ça sur DrawerActivity

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

et en fragment

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

Dans la méthode d'activité OnBackPressed Drawer, définissez le bouton bascule du tiroir sur true pour réactiver l'icône de la liste de navigation.

Merci, Pusp

EngineSense
la source
0

IMO, utiliser onNavigateUp () (comme indiqué ici ) dans la solution de riwnodennyk ou de Tom est plus propre et semble mieux fonctionner. Remplacez simplement le code onOptionsItemSelected par ceci:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
0101100101
la source