web-dev-qa-db-fra.com

Masquer le tiroir de navigation lorsque l'utilisateur appuie sur le bouton Précédent

J'ai suivi les didacticiels officiels destinés aux développeurs de Google ici pour créer un tiroir de navigation. 

Pour le moment, tout fonctionne bien, sauf lorsque l'utilisateur utilise le bouton de retour natif qu'Android fournit en bas de l'écran (avec les boutons d'accueil et récents). Si l'utilisateur revient en arrière en utilisant ce bouton de retour natif, le tiroir de navigation sera toujours ouvert. Si, au lieu de cela, l'utilisateur revient dans ActionBar, le tiroir de navigation sera fermé comme je le souhaite. 

Mon code est presque identique à celui des didacticiels officiels, à l'exception de la façon dont je gère l'utilisateur qui sélectionne un élément dans le tiroir:

   mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
    {
        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id)
        {
            switch(position)
            {
                case 0:
                {
                    Intent intent = new Intent(MainActivity.this, NextActivity.class);
                    startActivity(intent);
                }
            }
        }
    });

Comment puis-je fermer le tiroir de navigation lorsque l'utilisateur revient en arrière à l'aide du bouton Retour natif? Tout conseil apprécié. Merci!

21
pez

Vous devez remplacer onBackPressed () . De la docs:

Appelé lorsque l'activité a détecté une pression de l'utilisateur sur le dos clé. L'implémentation par défaut termine simplement l'activité en cours, mais vous pouvez remplacer cela pour faire ce que vous voulez.

Donc vous pouvez avoir un code comme celui-ci:

@Override
public void onBackPressed() {
    if (this.drawerLayout.isDrawerOpen(GravityCompat.START)) {
        this.drawerLayout.closeDrawer(GravityCompat.START);
    } else {
        super.onBackPressed();
    }
}

Si est ouvert, cette méthode la ferme, sinon le comportement par défaut est rétabli.

54
mt0s

Vous devez remplacer onBackPressed() dans votre activité et vérifier la condition dans laquelle le tiroir de navigation est ouvert. S'il est ouvert, fermez-le, sinon utilisez une méthode normale de retour. Voici du code mélangé avec du pseudocode pour vous aider:

@Override
public void onBackPressed(){
  if(drawer.isDrawerOpen()){ //replace this with actual function which returns if the drawer is open
   drawer.close();     // replace this with actual function which closes drawer
  }
  else{
   super.onBackPressed();
  }
}

Pour remplacer le pseudocode, consultez la documentation du tiroir. Je sais que ces deux méthodes existent. 

10
qazimusab

METTRE À JOUR:

Depuis la bibliothèque de support 24.0.0, cela est possible sans solution de contournement. Deux nouvelles openDrawer et closeDrawer methods ont été ajoutées à DrawerLayout permettant au tiroir d'être ouvert ou fermé sans animation.

Vous pouvez maintenant utiliser openDrawer(drawerView, false) et closeDrawer(drawerView, false) pour ouvrir et fermer le tiroir sans délai.


Si vous appelez startActivity() sans appeler closeDrawer(), le tiroir restera ouvert dans cette instance de l'activité lorsque vous y retournerez à l'aide du bouton Précédent. L'appel de closeDrawer() lorsque vous appelez startActivity() pose plusieurs problèmes, qui vont de l'animation saccadée à un long délai de perception, en fonction de la solution de contournement utilisée. Je conviens donc que la meilleure approche consiste à appeler simplement startActivity() puis à fermer le tiroir lors du retour.

Pour que cela fonctionne correctement, vous avez besoin d’un moyen de fermer le tiroir sans une animation rapprochée lorsque vous revenez à l’activité avec le bouton Précédent. (Une solution de rechange relativement peu rentable consisterait simplement à forcer l'activité à recreate() lors de la navigation arrière, mais il est possible de résoudre ce problème sans le faire.)

Vous devez également vous assurer de ne fermer le tiroir que si vous revenez après la navigation, et non après un changement d'orientation, mais c'est facile.


Détails

(Vous pouvez ignorer cette explication si vous voulez seulement voir le code.)}

Bien que l'appel de closeDrawer() à partir de onCreate() fasse en sorte que le tiroir soit fermé sans aucune animation, il n'en va pas de même de onResume(). L'appel de closeDrawer() à partir de onResume() fermera le tiroir avec une animation momentanément visible par l'utilisateur. DrawerLayout ne fournit aucune méthode pour fermer le tiroir sans cette animation, mais il est possible de l'étendre pour en ajouter une.

En fait, il suffit de fermer le tiroir pour le faire glisser de l'écran. Vous pouvez donc ignorer l'animation en déplaçant le tiroir directement vers sa position "fermé". Le sens de la translation varie en fonction de la gravité (tiroir gauche ou droit) et la position exacte dépend de la taille du tiroir une fois celui-ci posé.

Cependant, le déplacer ne suffit pas, car DrawerLayout conserve un état interne dans la variable étendue LayoutParams qu'il utilise pour savoir si le tiroir est ouvert. Si vous déplacez simplement le tiroir hors de l'écran, il ne saura pas qu'il est fermé et cela causera d'autres problèmes. (Par exemple, le tiroir réapparaîtra lors du prochain changement d'orientation.)

Puisque vous compilez la bibliothèque de support dans votre application, vous pouvez créer une classe dans le package Android.support.v4.widget pour accéder à ses composants par défaut (package-private), ou étendre DrawerLayout sans copier aucune des autres classes dont elle a besoin. Cela réduira également la charge de mise à jour de votre code avec les modifications futures de la bibliothèque de support. (Il est toujours préférable d'isoler autant que possible votre code des détails d'implémentation.) Vous pouvez utiliser moveDrawerToOffset() pour déplacer le tiroir et définir la LayoutParams pour qu'il sache que le tiroir est fermé.


Code

C'est le code qui va ignorer l'animation:

        // move drawer directly to the closed position
        moveDrawerToOffset(drawerView, 0.f); 

        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        invalidate();

Remarque: si vous appelez simplement moveDrawerToOffset() sans changer la LayoutParams, le tiroir reviendra à sa position ouverte lors du prochain changement d'orientation.


Option 1 (utiliser DrawerLayout existant)

Cette approche ajoute une classe d’utilitaire au package support.v4 pour accéder aux parties privées du package dont nous avons besoin dans DrawerLayout.

Placez cette classe dans/src/Android/support/v4/widget /:

package Android.support.v4.widget;

import Android.support.annotation.IntDef;
import Android.support.v4.view.GravityCompat;
import Android.view.Gravity;
import Android.view.View;

import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;

public class Support4Widget {

    /** @hide */
    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
    @Retention(RetentionPolicy.SOURCE)
    private @interface EdgeGravity {}

    public static void setDrawerClosed(DrawerLayout drawerLayout, @EdgeGravity int gravity) {
        final View drawerView = drawerLayout.findDrawerWithGravity(gravity);
        if (drawerView == null) {
            throw new IllegalArgumentException("No drawer view found with gravity " +
                    DrawerLayout.gravityToString(gravity));
        }

        // move drawer directly to the closed position
        drawerLayout.moveDrawerToOffset(drawerView, 0.f); 

        // set internal state so DrawerLayout knows it's closed
        final DrawerLayout.LayoutParams lp = (DrawerLayout.LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        drawerLayout.invalidate();
    }
}

Définissez un booléen dans votre activité lorsque vous vous éloignez, indiquant que le tiroir doit être fermé:

public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    if (savedInstanceState != null) {
        mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
    }
}

@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {

    // ...

    startActivity(intent);
    mCloseNavDrawer = true;
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
    super.onSaveInstanceState(savedInstanceState);
}   

... et utilisez la méthode setDrawerClosed() pour fermer le tiroir dans onResume() sans animation:

@Overrid6e
protected void onResume() {
    super.onResume();

    if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        Support4Widget.setDrawerClosed(mDrawerLayout, GravityCompat.START);
        mCloseNavDrawer = false;
    }
}

Option 2 (s'étend de DrawerLayout)

Cette approche étend DrawerLayout pour ajouter une méthode setDrawerClosed ().

Placez cette classe dans/src/Android/support/v4/widget /:

package Android.support.v4.widget;

import Android.content.Context;
import Android.support.annotation.IntDef;
import Android.support.v4.view.GravityCompat;
import Android.util.AttributeSet;
import Android.view.Gravity;
import Android.view.View;

import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;

public class CustomDrawerLayout extends DrawerLayout {

    /** @hide */
    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
    @Retention(RetentionPolicy.SOURCE)
    private @interface EdgeGravity {}

    public CustomDrawerLayout(Context context) {
        super(context);
    }

    public CustomDrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setDrawerClosed(View drawerView) {
        if (!isDrawerView(drawerView)) {
            throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
        }

        // move drawer directly to the closed position
        moveDrawerToOffset(drawerView, 0.f); 

        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        invalidate();
    }

    public void setDrawerClosed(@EdgeGravity int gravity) {
        final View drawerView = findDrawerWithGravity(gravity);
        if (drawerView == null) {
            throw new IllegalArgumentException("No drawer view found with gravity " +
                    gravityToString(gravity));
        }

        // move drawer directly to the closed position
        moveDrawerToOffset(drawerView, 0.f); 

        // set internal state so DrawerLayout knows it's closed
        final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
        lp.onScreen = 0.f;
        lp.knownOpen = false;

        invalidate();
    }
}

Utilisez CustomDrawerLayout au lieu de DrawerLayout dans vos présentations d'activité:

<Android.support.v4.widget.CustomDrawerLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:id="@+id/drawer_layout"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true"
    >

... et définissez un booléen dans votre activité lorsque vous vous éloignez, indiquant que le tiroir doit être fermé:

public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER";
private boolean mCloseNavDrawer;

@Override
public void onCreate(Bundle savedInstanceState) {
    // ...
    if (savedInstanceState != null) {
        mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER);
    }
}

@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {

    // ...

    startActivity(intent);
    mCloseNavDrawer = true;
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer);
    super.onSaveInstanceState(savedInstanceState);
}   

... et utilisez la méthode setDrawerClosed() pour fermer le tiroir dans onResume() sans animation:

@Overrid6e
protected void onResume() {
    super.onResume();

    if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
        mDrawerLayout.setDrawerClosed(GravityCompat.START);
        mCloseNavDrawer = false;
    }
}
4
Lorne Laliberte

Voici une solution alternative à votre problème.

@Override    
public void onBackPressed(){    
    if(drawerLayout.isDrawerOpen(navigationView)){    
        drawerLayout.closeDrawer(navigationView);    
    }else {    
        finish();    
    }    
}    
3

Utiliser une implémentation de la réponse fournie par @James Cross a fonctionné, mais l'animation pour fermer le tiroir était indésirable et impossible à corriger, sans trop de soucis, exemple .

@Override
public void onResume()
{
    super.onResume();
    mDrawerLayout.closeDrawers();
}

Une solution consiste à redémarrer l'activité lorsque vous appuyez sur le bouton Retour de l'appareil. Cela ne me semble pas idéal, mais ça marche. Remplacement de onBackPressed(), comme suggéré par @ mt0s et @Qazi Ahmed, et passage d'un extra pour déterminer l'activité d'appel:

    mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener()
    {
        @Override
        public void onItemClick(AdapterView parent, View view, int position, long id)
        {
            switch(position)
            {
                case 0:
                {
                    Intent intent = new Intent(MainActivity.this, NextActivity.class);
                    //pass int extra to determine calling activity
                    intent.putExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
                    startActivity(intent);
                }
            }
        }
    });

Dans NextActivity.class, recherchez l'activité appelante:

@Override
public void onBackPressed()
{
    int callingActivity = getIntent().getIntExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY);
    switch(callingActivity)
    {
        case CallingActivityInterface.MAIN_ACTIVITY:
        {
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent);
            finish();
        }
        ...
    }
}

De cette façon, le tiroir est fermé sans animation lorsque je retourne dans MainActivity, que j'utilise le bouton haut ou le bouton retour. Il y a probablement de meilleures façons de le faire. Mon application est relativement simple pour le moment et cela fonctionne, mais j'attends une méthode plus efficace si quelqu'un en a une.

3
pez

Vous voudrez probablement vous assurer que le tirage de navigation est toujours fermé lorsque l'activité est ouverte. Utilisez ceci pour faire ça:

@Override
public void onResume(){
    mDrawerList.closeDrawer(Gravity.LEFT);
}
1
James Cross

Pourquoi ces tracas? Fermez simplement le tiroir lorsque vous cliquez sur un article du tiroir. C'est comme ça que ça se passe dans l'application officielle Google Play.

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         drawerLayout.closeDrawer(GravityCompat.START, false);
         selectItem(position); 
    }
}
1
Damnum

échantillon simple:

Classeur resultDrawer;

public void onBackPressed () {

if (this.resultDrawer.isDrawerOpen()) {    
    this.resultDrawer.closeDrawer();    
} else {    
    super.onBackPressed();    
}

}

0
Chirag Patel