375 votes

Cliquer deux fois sur le bouton retour pour quitter une activité

J'ai remarqué ce modèle dans beaucoup d'applications et de jeux Android récemment : lorsque l'on clique sur le bouton retour pour "quitter" l'application, un message d'erreur s'affiche. Toast s'affiche avec un message similaire à "Please click BACK again to exit".

Je me demandais, car je le vois de plus en plus souvent, s'il s'agit d'une fonctionnalité intégrée à laquelle on peut accéder d'une manière ou d'une autre dans une activité ? J'ai consulté le code source de nombreuses classes, mais je n'ai rien trouvé à ce sujet.

Bien sûr, je peux penser à plusieurs façons de réaliser la même fonctionnalité assez facilement (le plus simple est probablement de garder un booléen dans l'activité qui indique si l'utilisateur a déjà cliqué une fois...) mais je me demandais s'il y avait déjà quelque chose ici.

EDIT : Comme l'a mentionné @LAS_VEGAS, je ne voulais pas vraiment dire "sortie" dans le sens traditionnel. (c'est-à-dire terminé) Je voulais dire "revenir à ce qui était ouvert avant le lancement de l'activité de démarrage de l'application", si cela a un sens :)

0 votes

[Android - Confirmez la sortie de l'application avec un toast] [1] : stackoverflow.com/questions/14006461/

1 votes

J'ai eu le même problème en utilisant la bibliothèque HoloEverywhere. Il suffit d'ajouter Android:launchMode="singleTask" à la définition de votre activité dans le fichier manifeste.

0 votes

1039voto

Sudheesh B Nair Points 1844

Dans Java Activity :

boolean doubleBackToExitPressedOnce = false;

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

    new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 

Dans Kotlin Activity :

private var doubleBackToExitPressedOnce = false
override fun onBackPressed() {
        if (doubleBackToExitPressedOnce) {
            super.onBackPressed()
            return
        }

        this.doubleBackToExitPressedOnce = true
        Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show()

        Handler(Looper.getMainLooper()).postDelayed(Runnable { doubleBackToExitPressedOnce = false }, 2000)
    }

Je pense que ce gestionnaire permet de réinitialiser la variable après 2 secondes.

48 votes

Meilleure réponse ! Vous pouvez également ajouter une condition if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0) {en cas d'utilisation de Fragment, ajoutez

2 votes

Je suis d'accord, c'est définitivement la meilleure réponse et devrait être la réponse acceptée.

3 votes

Vous devez supprimer le Runnable lorsque vous quittez l'application.

250voto

Zefnus Points 2079

Sudheesh B Nair Le site de l'association a une bonne réponse (et acceptée) à la question, mais je pense qu'il devrait y avoir une meilleure alternative, par exemple ;

Qu'y a-t-il de mal à mesurer le temps passé et à vérifier si TIME_INTERVAL milisecondes (disons 2000) écoulées depuis la dernière pression arrière. L'exemple de code suivant utilise System.currentTimeMillis(); pour enregistrer l'heure onBackPressed() s'appelle ;

private static final int TIME_INTERVAL = 2000; // # milliseconds, desired time passed between two back presses.
private long mBackPressed;

@Override
public void onBackPressed()
{
    if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) 
    { 
        super.onBackPressed(); 
        return;
    }
    else { Toast.makeText(getBaseContext(), "Tap back button in order to exit", Toast.LENGTH_SHORT).show(); }

    mBackPressed = System.currentTimeMillis();
}

Retour sur la réponse acceptée critique ; Utilisation d'un flag pour indiquer si elle a été pressée en dernier TIME_INTERVAL (disons 2000) millisecondes et set - reset est via Handler 's postDelayed() la méthode a été la première chose qui m'est venue à l'esprit. Mais la postDelayed() doit être annulée lors de la clôture de l'activité, en supprimant l'action Runnable .

Afin d'enlever le Runnable il ne doit pas être déclaré anonyme et être déclarée comme membre avec le Handler également. Puis removeCallbacks() méthode de Handler peut être appelé de manière appropriée.

L'exemple suivant en est la démonstration ;

private boolean doubleBackToExitPressedOnce;
private Handler mHandler = new Handler();

private final Runnable mRunnable = new Runnable() {
    @Override
    public void run() {
        doubleBackToExitPressedOnce = false;                       
    }
};

@Override 
protected void onDestroy() 
{ 
    super.onDestroy();

    if (mHandler != null) { mHandler.removeCallbacks(mRunnable); }
}

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

    mHandler.postDelayed(mRunnable, 2000);
}

Merci à @NSouth pour sa contribution ; Afin de prévenir message de toast apparaissant même après la fermeture de l'application, Toast peut être déclaré comme un membre - disons mExitToast - et peut être annulé via mExitToast.cancel(); juste avant super.onBackPressed(); appeler.

12 votes

Pour ceux qui pensent que c'est la même chose que ce que Sudheesh B Nair a dit : Même fonctionnalité, meilleures performances. Donc +1.

4 votes

J'aime cette réponse et je pense que c'est la meilleure. Je veux dire que je ne pense pas qu'elle l'est, c'est la meilleure réponse, pour les raisons mentionnées ci-dessus. J'espère que vous obtiendrez plus de votes positifs pour celle-ci. Une remarque cependant : personne ne trouve bizarre que le toast persiste quelques secondes après la fermeture de l'application ? Personne ne se soucie d'annuler le toast ? Je sais que c'est un petit détail mais je pense que cela devrait être possible. Qu'en pensez-vous, les gars ?

0 votes

@acrespo Merci. Vous avez raison, cela semble étrange lorsque le toast continue d'apparaître après la fin de l'application. Peut-être que la plupart des utilisateurs s'en moquent parce que cela arrive. Dans Android, les toasts apparaissent dans le lanceur et c'est habituel.

30voto

Guillaume Points 10121

J'ai pensé que je pourrais partager la façon dont j'ai procédé, j'ai simplement ajouté mon activité :

private boolean doubleBackToExitPressedOnce = false;

@Override
protected void onResume() {
    super.onResume();
    // .... other stuff in my onResume ....
    this.doubleBackToExitPressedOnce = false;
}

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }
    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, R.string.exit_press_back_twice_message, Toast.LENGTH_SHORT).show();
}

Et ça fonctionne exactement comme je le veux. Y compris la réinitialisation de l'état à chaque fois que l'activité est reprise.

19 votes

Avec cette solution, les deux presses dorsales peuvent être séparées par un laps de temps arbitraire. Ainsi, vous pouvez appuyer sur Back une fois, puis appuyez sur Back à nouveau une minute plus tard et l'application se fermera. Ce n'est pas le comportement que l'utilisateur attend.

1 votes

Je pense que c'est une question de goût. Dans ce cas, vous n'informez l'utilisateur qu'une seule fois, pour qu'il sache qu'il est dans l'activité principale et qu'une autre pression sur le bouton "retour" quittera l'application (probablement après que l'utilisateur ait appuyé plusieurs fois sur le bouton "retour" pour revenir à l'écran principal). Si, plus tard, l'utilisateur appuie à nouveau sur la touche retour, nous pouvons supposer qu'il veut quitter l'application (à moins qu'il n'ait navigué dans une autre activité et qu'il ait probablement perdu la trace de la profondeur à laquelle il s'est rendu). Dans la réponse acceptée ci-dessus, vous considérez que l'utilisateur peut oublier qu'il est déjà dans l'écran principal. Les deux solutions sont acceptables, en fonction de ce que vous souhaitez ou envisagez.

4 votes

@FerranMaylinch - Je ne suis pas d'accord. Il ne s'agit pas d'une simple question de goût. Si un temps significatif s'est écoulé, nous devrait supposer que l'utilisateur a effectué d'autres actions entre-temps, et ne considère plus ce qu'il a fait précédemment et a choisi de ne pas poursuivre avec à appliquer. En effet, tous les utilisateurs, sauf les plus exceptionnels, n'auront même pas encore en tête qu'ils l'ont fait auparavant. Sans limite de temps, vous avez quitté l'application dans un mode invisible que l'utilisateur n'a aucun moyen de connaître. Je considère absolument que c'est une mauvaise conception de l'interface utilisateur. Les utilisateurs seront surpris.

26voto

DailyEfforts Points 101

Diagramme de flux de processus : Press again to exit.

Code Java :

private long lastPressedTime;
private static final int PERIOD = 2000;

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        switch (event.getAction()) {
        case KeyEvent.ACTION_DOWN:
            if (event.getDownTime() - lastPressedTime < PERIOD) {
                finish();
            } else {
                Toast.makeText(getApplicationContext(), "Press again to exit.",
                        Toast.LENGTH_SHORT).show();
                lastPressedTime = event.getEventTime();
            }
            return true;
        }
    }
    return false;
}

1 votes

J'ai d'abord voté pour que cela soit utile. Le problème est que, pour une raison quelconque, cela ne fonctionne pas sur certains téléphones. La méthode onBackPressed fonctionne mieux, mais vous n'avez pas les horodatages, vous avez donc besoin des gestionnaires comme le stipule la réponse acceptée.

13voto

Mehul Joisar Points 3900

Sur la base de la bonne réponse et des suggestions dans les commentaires, j'ai créé une démo qui fonctionne parfaitement et supprime les callbacks du gestionnaire après avoir été utilisés.

MainActivity.java

package com.mehuljoisar.d_pressbacktwicetoexit;

import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.widget.Toast;

public class MainActivity extends Activity {

    private static final long delay = 2000L;
    private boolean mRecentlyBackPressed = false;
    private Handler mExitHandler = new Handler();
    private Runnable mExitRunnable = new Runnable() {

        @Override
        public void run() {
            mRecentlyBackPressed=false;   
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onBackPressed() {

        //You may also add condition if (doubleBackToExitPressedOnce || fragmentManager.getBackStackEntryCount() != 0) // in case of Fragment-based add
        if (mRecentlyBackPressed) {
            mExitHandler.removeCallbacks(mExitRunnable);
            mExitHandler = null;
            super.onBackPressed();
        }
        else
        {
            mRecentlyBackPressed = true;
            Toast.makeText(this, "press again to exit", Toast.LENGTH_SHORT).show();
            mExitHandler.postDelayed(mExitRunnable, delay);
        }
    }

}

J'espère que cela vous sera utile !

0 votes

Etes-vous certain de supprimer le gestionnaire dans onBackPressed() fixer le fuite de mémoire question ?

0 votes

@Zefnus : Pour autant que je sache, cela va réparer. Corrigez-moi si je me trompe. Comment avez-vous repéré le problème de mémoire avec le code ci-dessus ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X