Como mostrar uma caixa de diálogo para confirmar que o usuário deseja sair de uma atividade do Android?

274

Eu tenho tentado mostrar um "você quer sair?" tipo de diálogo quando o usuário tenta sair de uma atividade.

No entanto, não consigo encontrar os ganchos de API apropriados. Activity.onUserLeaveHint()inicialmente pareceu promissor, mas não consigo encontrar uma maneira de interromper a atividade.

Peter A
fonte
39
É absolutamente essencial que você tenha esse prompt? Quando um usuário deseja concluir uma atividade, deve poder fazê-lo imediatamente. Você pode repensar sua estratégia.
Tom R
4
A exibição de uma opção de confirmação de saída é obrigatória para listagem na App Store da Samsung. Um dos meus aplicativos foi rejeitado por não ter isso.
precisa
6
Isso é desagradável, @ Samsung.
Dokkaebi
3
Depende do que você deseja fazer ao sair. Se o estado for preservado e você puder simplesmente navegar de volta, talvez não seja necessário mostrar uma caixa de diálogo. No entanto, em um dos meus aplicativos, limpo os cookies, os dados e as conexões fechadas definitivamente, com uma confirmação final dos dados. O usuário deve estar ciente da finalidade de sua escolha, especialmente porque hoje é incomum ter esse senso de finalidade em um aplicativo móvel.
Eric
15
@ Tom R: Discordo totalmente. Existem aplicativos que você não deseja concluir por acidente. Eles trabalham no ambiente de negócios e assim por diante.
TomeeNS

Respostas:

390

No Android 2.0 ou superior, isso seria semelhante a:

@Override
public void onBackPressed() {
    new AlertDialog.Builder(this)
        .setIcon(android.R.drawable.ic_dialog_alert)
        .setTitle("Closing Activity")
        .setMessage("Are you sure you want to close this activity?")
        .setPositiveButton("Yes", new DialogInterface.OnClickListener()
    {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            finish();    
        }

    })
    .setNegativeButton("No", null)
    .show();
}

Nas versões anteriores, seria semelhante a:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    //Handle the back button
    if(keyCode == KeyEvent.KEYCODE_BACK) {
        //Ask the user if they want to quit
        new AlertDialog.Builder(this)
        .setIcon(android.R.drawable.ic_dialog_alert)
        .setTitle(R.string.quit)
        .setMessage(R.string.really_quit)
        .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {

                //Stop the activity
                YourClass.this.finish();    
            }

        })
        .setNegativeButton(R.string.no, null)
        .show();

        return true;
    }
    else {
        return super.onKeyDown(keyCode, event);
    }

}
jax
fonte
15
Também na 2.0 e acima, há um novo evento onBackPressed recomendado em onKeyDown developer.android.com/intl/zh-TW/reference/android/app/… Há uma seção aqui falando sobre as alterações e a nova abordagem recomendada. developer.android.com/intl/zh-TW/sdk/android-2.0.html
Patrick Kafka
3
Postagem no blog sobre como recuperar a chave de retorno aqui: android-developers.blogspot.com/2009/12/… Observe que isso não permite que você identifique outras maneiras pelas quais o usuário pode sair do aplicativo: pressionando home, selecionando uma notificação, recebendo um telefone chamada, etc.
hackbod
Isso funciona perfeitamente, mas como você força a execução do código a parar enquanto é mostrada ao usuário?
precisa saber é o seguinte
encontrei, esta é uma ótima solução aqui: stackoverflow.com/questions/4381296/…
anon58192932 5/13
1
Este é o método () o que estou procurando, mas não sabia onde chamar esse método ().
user2841300
186
@Override
public void onBackPressed() {
    new AlertDialog.Builder(this)
           .setMessage("Are you sure you want to exit?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   ExampleActivity.super.onBackPressed();
               }
           })
           .setNegativeButton("No", null)
           .show();
}
Chanakya Vadla
fonte
5
Um comentário foi feito por elBradford: Chamar o super onBackPressed é melhor do que assumir que o onBackPressed chamará apenas finish (). Mesmo se isso é verdade agora, pode não ser verdade em APIs futurosCustomTabActivity.super.onBackPressed
mplungjan
@mplungjan Você pode esclarecer seu comentário ... Você está dizendo que é melhor substituir o finish()código super.onBackPressed()?
ban-geoengineering
O comentário tem 3 anos. Eu não tenho idéia o que é uma boa prática em 2015
mplungjan
@mplungjan Seu comentário se refere a futuras APIs, mas seria útil se você pudesse esclarecer de qualquer maneira.
ban-geoengineering
Sem probs. Bem, eu apenas tentei MyActivity.super.onBackPressed();e funciona bem para mim. Parece a abordagem mais lógica também - chamar o método que você está substituindo, se desejar o comportamento padrão quando o usuário tocar em "Não".
ban-geoengineering
33

Modificaram o código @ user919216 .. e o tornaram compatível com o WebView

@Override
public void onBackPressed() {
    if (webview.canGoBack()) {
        webview.goBack();

    }
    else
    {
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to exit?")
       .setCancelable(false)
       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                finish();
           }
       })
       .setNegativeButton("No", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
           }
       });
AlertDialog alert = builder.create();
alert.show();
    }

}
suraj jain
fonte
22

Eu preferiria sair com um toque duplo no botão voltar do que com uma caixa de diálogo de saída.

Nesta solução, ele mostra um brinde ao voltar pela primeira vez, avisando que outra pressão traseira fechará o aplicativo. Neste exemplo, menos de 4 segundos.

private Toast toast;
private long lastBackPressTime = 0;

@Override
public void onBackPressed() {
  if (this.lastBackPressTime < System.currentTimeMillis() - 4000) {
    toast = Toast.makeText(this, "Press back again to close this app", 4000);
    toast.show();
    this.lastBackPressTime = System.currentTimeMillis();
  } else {
    if (toast != null) {
    toast.cancel();
  }
  super.onBackPressed();
 }
}

Token de: http://www.androiduipatterns.com/2011/03/back-button-behavior.html

Toni Gamez
fonte
Este é definitivamente o meu favorito, pois a caixa de diálogo pode ser complicada. Eu mudei um pouco para fazer 3 segundos e parece um pouco mais natural com esse marcador. Obrigado pelo post.
PGMacDesign
na imprensa double back idealmente aplicativo me deve sair, mas no meu código é aberto a atividade de login (meios backup atividade com girador habilitados)
techDigi
21

Se você não tiver certeza se a chamada para "voltar" sairá do aplicativo ou levará o usuário a outra atividade, você pode agrupar as respostas acima em uma seleção, isTaskRoot (). Isso pode acontecer se sua atividade principal puder ser adicionada à pilha traseira várias vezes ou se você estiver manipulando seu histórico.

if(isTaskRoot()) {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Are you sure you want to exit?")
       .setCancelable(false)
       .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                YourActivity.super.onBackPressed;
           }
       })
       .setNegativeButton("No", new DialogInterface.OnClickListener() {
           public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();
           }
       });
    AlertDialog alert = builder.create();
    alert.show();

} else {
    super.onBackPressed();
}
Alegria
fonte
5

Usando o Lambda:

    new AlertDialog.Builder(this).setMessage(getString(R.string.exit_msg))
        .setTitle(getString(R.string.info))
        .setPositiveButton(getString(R.string.yes), (arg0, arg1) -> {
            moveTaskToBack(true);
            finish();
        })
        .setNegativeButton(getString(R.string.no), (arg0, arg1) -> {
        })
        .show();

Você também precisa definir um idioma de nível para suportar o java 8 em seu gradle.build:

compileOptions {
       targetCompatibility 1.8
       sourceCompatibility 1.8
}
vasiljevski
fonte
5

na China, a maioria dos aplicativos confirmará a saída "clique duas vezes":

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().postDelayed(new Runnable() {

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 
Siwei Shen 申思维
fonte
5

Primeiro remova super.onBackPressed();do onbackPressed()método que e abaixo do código:

@Override
public void onBackPressed() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("Are you sure you want to exit?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    MyActivity.this.finish();
               }
           })
           .setNegativeButton("No", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    dialog.cancel();
               }
           });
    AlertDialog alert = builder.create();
    alert.show();

}
Anjali-Systematix
fonte
2

Eu gosto de uma abordagem @GLee e usá-lo com fragmento como abaixo.

@Override
public void onBackPressed() {
    if(isTaskRoot()) {
        new ExitDialogFragment().show(getSupportFragmentManager(), null);
    } else {
        super.onBackPressed();
    }
}

Diálogo usando Fragmento:

public class ExitDialogFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
            .setTitle(R.string.exit_question)
            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    getActivity().finish();
                }
            })
            .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    getDialog().cancel();
                }
            })
            .create();
    }
}
marioosh
fonte
2
Just put this code in your first activity 

@Override
    public void onBackPressed() {
        if (drawerLayout.isDrawerOpen(GravityCompat.END)) {
            drawerLayout.closeDrawer(GravityCompat.END);
        }
        else {
// if your using fragment then you can do this way
            int fragments = getSupportFragmentManager().getBackStackEntryCount();
            if (fragments == 1) {
new AlertDialog.Builder(this)
           .setMessage("Are you sure you want to exit?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                    finish();
               }
           })
           .setNegativeButton("No", null)
           .show();


            } else {
                if (getFragmentManager().getBackStackEntryCount() > 1) {
                    getFragmentManager().popBackStack();
                } else {

           super.onBackPressed();
                }
            }
        }
    }
Mohit Hooda
fonte
y primeira atividade, não posso colocá-lo em atividade no meio. porque agora apenas fecha a atividade atual e mostra a anterior
shareef 11/02/19
1

Outra alternativa seria mostrar um Toast/ Snackbarna primeira imprensa pressionandoAlertDialog a tecla Sair , o que é muito menos intrusivo do que mostrar uma para confirmar se o usuário deseja sair do aplicativo.

Você pode usar o DoubleBackPress Android Librarypara conseguir isso com algumas linhas de código. Exemplo de GIF mostrando comportamento semelhante.

Para começar, adicione a dependência ao seu aplicativo:

dependencies {
    implementation 'com.github.kaushikthedeveloper:double-back-press:0.0.1'
}

Em seguida, em sua atividade, implemente o comportamento necessário.

// set the Toast to be shown on FirstBackPress (ToastDisplay - builtin template)
// can be replaced by custom action (new FirstBackPressAction{...})
FirstBackPressAction firstBackPressAction = new ToastDisplay().standard(this);

// set the Action on DoubleBackPress
DoubleBackPressAction doubleBackPressAction = new DoubleBackPressAction() {
    @Override
    public void actionCall() {
        // TODO : Exit the application
        finish();
        System.exit(0);
    }
};

// setup DoubleBackPress behaviour : close the current Activity
DoubleBackPress doubleBackPress = new DoubleBackPress()
        .withDoublePressDuration(3000)     // msec - wait for second back press
        .withFirstBackPressAction(firstBackPressAction)
        .withDoubleBackPressAction(doubleBackPressAction);

Por fim, defina isso como o comportamento na contrapressão.

@Override
public void onBackPressed() {
    doubleBackPress.onBackPressed();
}
Kaushik NP
fonte