最近我在许多Android应用和游戏中注意到这种模式:当点击后退按钮“退出”应用时,Toast会出现类似于“请再次点击后退退出”的消息。
我在想,当我越来越频繁地看到它时,这是一个内置的功能,你可以在某个活动中访问它吗?我已经看了很多类的源代码,但我似乎找不到任何关于这一点。
当然,我可以想到一些很容易实现相同功能的方法(最简单的可能是在活动中保留一个布尔值,指示用户是否已经单击过一次…),但我想知道这里是否已经有一些东西。
编辑:正如@LAS_VEGAS所提到的,我并不是指传统意义上的“退出”。(即终止)我的意思是“回到应用程序启动活动启动之前打开的任何东西”,如果这有意义的话:)
我只是想分享一下我是如何做到的,我只是在我的活动中添加了:
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();
}
它就像我想要的那样工作。包括每当活动恢复时对状态的重置。
在Java活动中:
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);
}
在Kotlin活动:
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)
}
我认为这个处理程序有助于在2秒后重置变量。
根据正确的答案和评论中的建议,我创建了一个演示,工作绝对很好,并在使用后删除处理程序回调。
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);
}
}
}
希望对大家有所帮助!!
Sudheesh B Nair对这个问题有一个很好的(被接受的)答案,我认为应该有一个更好的选择,比如;
测量所经过的时间并检查自上次回按以来TIME_INTERVAL毫秒(例如2000)是否经过了什么错误?下面的示例代码使用System.currentTimeMillis();onBackPressed()被调用来存储时间;
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();
}
回到公认的答案批评;使用一个标志来指示它是否在最后的TIME_INTERVAL(比如2000)毫秒内被按下,而set - reset是通过Handler的postDelayed()方法来执行的,这是我想到的第一件事。但是postDelayed()操作应该在活动关闭时取消,删除Runnable。
为了移除Runnable,它不能被声明为匿名,并且必须与Handler一起声明为成员。然后可以适当地调用Handler的removeCallbacks()方法。
下面的示例是演示;
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);
}
感谢@NSouth的贡献;为了防止在应用程序关闭后出现toast消息,toast可以被声明为成员,比如mExitToast,并且可以通过mExitToast.cancel()取消;就在super.onBackPressed()之前;调用。
Zefnus使用System.currentTimeMillis()的答案是最好的(+1)。我的方法并没有比这更好,但仍然发布它来补充上面的想法。
如果后退按钮按下时吐司不可见,则显示吐司,反之,如果它可见(后退已经在最后一个吐司中按了一次。LENGTH_SHORT time),然后退出。
exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT);
.
.
@Override
public void onBackPressed() {
if (exitToast.getView().getWindowToken() == null) //if toast is currently not visible
exitToast.show(); //then show toast saying 'press againt to exit'
else { //if toast is visible then
finish(); //or super.onBackPressed();
exitToast.cancel();
}
}
在所有这些答案中,有一个非常简单的方法。
只需在onBackPressed()方法中编写以下代码。
long back_pressed;
@Override
public void onBackPressed() {
if (back_pressed + 1000 > System.currentTimeMillis()){
super.onBackPressed();
}
else{
Toast.makeText(getBaseContext(),
"Press once again to exit!", Toast.LENGTH_SHORT)
.show();
}
back_pressed = System.currentTimeMillis();
}
您需要将back_pressed对象定义为活动中的long。
最近,我需要在我的一个应用程序中实现这个后退按钮功能。对最初问题的回答是有用的,但我必须考虑到另外两点:
在某些时间点,返回按钮被禁用
主要的活动是将片段与反向堆栈结合使用
根据回答和评论,我创建了以下代码:
private static final long BACK_PRESS_DELAY = 1000;
private boolean mBackPressCancelled = false;
private long mBackPressTimestamp;
private Toast mBackPressToast;
@Override
public void onBackPressed() {
// Do nothing if the back button is disabled.
if (!mBackPressCancelled) {
// Pop fragment if the back stack is not empty.
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
super.onBackPressed();
} else {
if (mBackPressToast != null) {
mBackPressToast.cancel();
}
long currentTimestamp = System.currentTimeMillis();
if (currentTimestamp < mBackPressTimestamp + BACK_PRESS_DELAY) {
super.onBackPressed();
} else {
mBackPressTimestamp = currentTimestamp;
mBackPressToast = Toast.makeText(this, getString(R.string.warning_exit), Toast.LENGTH_SHORT);
mBackPressToast.show();
}
}
}
}
上面的代码假设使用了支持库。如果您使用片段而不是支持库,则需要用getFragmentManager()替换getSupportFragmentManager()。
如果后退按钮从未取消,则删除第一个if。删除第二个if,如果你不使用片段或片段返回堆栈
另外,重要的是要知道onBackPressed方法从Android 2.0开始就被支持了。查看本页详细描述。为了使背按功能也适用于旧版本,将以下方法添加到您的活动中:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ECLAIR
&& keyCode == KeyEvent.KEYCODE_BACK
&& event.getRepeatCount() == 0) {
// Take care of calling this method on earlier versions of
// the platform where it doesn't exist.
onBackPressed();
}
return super.onKeyDown(keyCode, event);
}
退出应用程序时使用Runnable并不是一个好主意,我最近想出了一个更简单的方法来记录和比较两次后退按钮点击之间的时间。示例代码如下:
private static long back_pressed_time;
private static long PERIOD = 2000;
@Override
public void onBackPressed()
{
if (back_pressed_time + PERIOD > System.currentTimeMillis()) super.onBackPressed();
else Toast.makeText(getBaseContext(), "Press once again to exit!", Toast.LENGTH_SHORT).show();
back_pressed_time = System.currentTimeMillis();
}
这将在特定的延迟时间内(样本为2000毫秒)单击两次BACK按钮来退出应用程序。
在Sudheesh B Nair的回答中有一些改进,我注意到它会等待处理程序,即使在立即按回两次,所以取消处理程序如下所示。我已经取消吐司也防止它显示后应用程序退出。
boolean doubleBackToExitPressedOnce = false;
Handler myHandler;
Runnable myRunnable;
Toast myToast;
@Override
public void onBackPressed() {
if (doubleBackToExitPressedOnce) {
myHandler.removeCallbacks(myRunnable);
myToast.cancel();
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
myToast = Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT);
myToast.show();
myHandler = new Handler();
myRunnable = new Runnable() {
@Override
public void run() {
doubleBackToExitPressedOnce = false;
}
};
myHandler.postDelayed(myRunnable, 2000);
}
这是被接受和投票最多的回应,但这个片段用了Snackbar而不是Toast。
boolean doubleBackToExitPressedOnce = false;
@Override
public void onBackPressed() {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
return;
}
this.doubleBackToExitPressedOnce = true;
Snackbar.make(content, "Please click BACK again to exit", Snackbar.LENGTH_SHORT)
.setAction("Action", null).show();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
doubleBackToExitPressedOnce=false;
}
}, 2000);
}
在这里,我已经概括编写了N个点击计数的代码。代码类似地为android设备中的启用开发人员选项编写。甚至你可以在开发人员测试应用程序时使用它来启用功能。
private Handler tapHandler;
private Runnable tapRunnable;
private int mTapCount = 0;
private int milSecDealy = 2000;
onCreate(){
...
tapHandler = new Handler(Looper.getMainLooper());
}
在退按或注销选项时调用askToExit()。
private void askToExit() {
if (mTapCount >= 2) {
releaseTapValues();
/* ========= Exit = TRUE ========= */
}
mTapCount++;
validateTapCount();
}
/* Check with null to avoid create multiple instances of the runnable */
private void validateTapCount() {
if (tapRunnable == null) {
tapRunnable = new Runnable() {
@Override
public void run() {
releaseTapValues();
/* ========= Exit = FALSE ========= */
}
};
tapHandler.postDelayed(tapRunnable, milSecDealy);
}
}
private void releaseTapValues() {
/* Relase the value */
if (tapHandler != null) {
tapHandler.removeCallbacks(tapRunnable);
tapRunnable = null; /* release the object */
mTapCount = 0; /* release the value */
}
}
@Override
protected void onDestroy() {
super.onDestroy();
releaseTapValues();
}
我用这个
import android.app.Activity;
import android.support.annotation.StringRes;
import android.widget.Toast;
public class ExitApp {
private static long lastClickTime;
public static void now(Activity ctx, @StringRes int message) {
now(ctx, ctx.getString(message), 2500);
}
public static void now(Activity ctx, @StringRes int message, long time) {
now(ctx, ctx.getString(message), time);
}
public static void now(Activity ctx, String message, long time) {
if (ctx != null && !message.isEmpty() && time != 0) {
if (lastClickTime + time > System.currentTimeMillis()) {
ctx.finish();
} else {
Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show();
lastClickTime = System.currentTimeMillis();
}
}
}
}
使用到事件onBackPressed
@Override
public void onBackPressed() {
ExitApp.now(this,"Press again for close");
}
或ExitApp.now(这个,R.string.double_back_pressed)
对于需要关闭的更改秒,指定毫秒
ExitApp.now (R.string.double_back_pressed, 5000)
当HomeActivity包含导航抽屉和双backPressed()函数退出应用程序。
(不要忘记初始化全局变量布尔doubleBackToExitPressedOnce = false;)
将doubleBackPressedOnce变量设置为false
@Override
public void onBackPressed() {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.END)) {
drawer.closeDrawer(GravityCompat.END);
} else {
if (doubleBackToExitPressedOnce) {
super.onBackPressed();
moveTaskToBack(true);
return;
} else {
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);
}
}
}
在java中
private Boolean exit = false;
if (exit) {
onBackPressed();
}
@Override
public void onBackPressed() {
if (exit) {
finish(); // finish activity
} else {
Toast.makeText(this, "Press Back again to Exit.",
Toast.LENGTH_SHORT).show();
exit = true;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
exit = false;
}
}, 3 * 1000);
}
}
在kotlin
private var exit = false
if (exit) {
onBackPressed()
}
override fun onBackPressed(){
if (exit){
finish() // finish activity
}else{
Toast.makeText(this, "Press Back again to Exit.",
Toast.LENGTH_SHORT).show()
exit = true
Handler().postDelayed({ exit = false }, 3 * 1000)
}
}
在不得不多次实现相同的东西之后,决定是时候建立一个简单易用的库了。那是DoubleBackPress Android库。README解释了与示例一起提供的所有api(如ToastDisplay + Exit Activity),但这里有一个简短的步骤概述。
首先,在应用程序中添加依赖项:
dependencies {
implementation 'com.github.kaushikthedeveloper:double-back-press:0.0.1'
}
接下来,在您的活动中创建一个DoubleBackPress对象,它提供所需的行为。
DoubleBackPress doubleBackPress = new DoubleBackPress();
doubleBackPress.setDoublePressDuration(3000); // msec
然后创建一个Toast,需要在第一次背压时显示。在这里,您可以创建自己的Toast,或者使用库中提供的标准吐司。通过后面的选项来实现。
FirstBackPressAction firstBackPressAction = new ToastDisplay().standard(this);
doubleBackPress.setFirstBackPressAction(firstBackPressAction); // set the action
现在,定义当你第二次背推发生时应该发生什么。在这里,我们正在关闭Activity。
DoubleBackPressAction doubleBackPressAction = new DoubleBackPressAction() {
@Override
public void actionCall() {
finish();
System.exit(0);
}
};
最后,用DoubleBackPress行为覆盖你的背压行为。
@Override
public void onBackPressed() {
doubleBackPress.onBackPressed();
}
类似行为要求的GIF例子
在Kotlin的背面按下退出应用程序,你可以使用:
定义一个全局变量:
private var doubleBackToExitPressedOnce = false
覆盖onBackPressed:
override fun onBackPressed() {
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
doubleBackToExitPressedOnce = true
Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_LONG).show()
Handler().postDelayed({
doubleBackToExitPressedOnce = false;
}, 2000)
}
我通常会加一条评论,但我的声誉不允许这样做。
下面是我的观点:
在Kotlin中,你可以使用协程来延迟设置为false:
private var doubleBackPressed = false
private var toast : Toast ?= null
override fun onCreate(savedInstanceState: Bundle?) {
toast = Toast.maketext(this, "Press back again to exit", Toast.LENGTH_SHORT)
}
override fun onBackPressed() {
if (doubleBackPressed) {
toast?.cancel()
super.onBackPressed()
return
}
this.doubleBackPressed = true
toast?.show()
GlobalScope.launch {
delay(2000)
doubleBackPressed = false
}
}
你必须输入:
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import kotlinx.coroutines.GlobalScope
吐司的最佳解决方案
在Java中
private Toast exitToast;
@Override
public void onBackPressed() {
if (exitToast == null || exitToast.getView() == null || exitToast.getView().getWindowToken() == null) {
exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_LONG);
exitToast.show();
} else {
exitToast.cancel();
super.onBackPressed();
}
}
在Kotlin
private var exitToast: Toast? = null
override fun onBackPressed() {
if (exitToast == null || exitToast!!.view == null || exitToast!!.view.windowToken == null) {
exitToast = Toast.makeText(this, "Press again to exit", Toast.LENGTH_LONG)
exitToast!!.show()
} else {
exitToast!!.cancel()
super.onBackPressed()
}
}
我已经尝试为此创建了一个utils类,因此任何活动或片段都可以实现这一点,从而变得更简单。
代码是用Kotlin编写的,并且具有java互操作。
我使用协程来延迟和重置标志变量。但是您可以根据自己的需要进行修改。
其他文件:SafeToast.kt
lateinit var toast: Toast
fun Context.safeToast(msg: String, length: Int = Toast.LENGTH_LONG, action: (Context) -> Toast = default) {
toast = SafeToast.makeText(this@safeToast, msg, length).apply {
// do anything new here
action(this@safeToast)
show()
}
}
fun Context.toastSpammable(msg: String) {
cancel()
safeToast(msg, Toast.LENGTH_SHORT)
}
fun Fragment.toastSpammable(msg: String) {
cancel()
requireContext().safeToast(msg, Toast.LENGTH_SHORT)
}
private val default: (Context) -> Toast = { it -> SafeToast.makeText(it, "", Toast.LENGTH_LONG) }
private fun cancel() {
if (::toast.isInitialized) toast.cancel()
}
ActivityUtils.kt
@file:JvmMultifileClass
@file:JvmName("ActivityUtils")
package your.company.com
import android.app.Activity
import your.company.com.R
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private var backButtonPressedTwice = false
fun Activity.onBackPressedTwiceFinish() {
onBackPressedTwiceFinish(getString(R.string.msg_back_pressed_to_exit), 2000)
}
fun Activity.onBackPressedTwiceFinish(@StringRes message: Int, time: Long) {
onBackPressedTwiceFinish(getString(message), time)
}
fun Activity.onBackPressedTwiceFinish(message: String, time: Long) {
if (backButtonPressedTwice) {
onBackPressed()
} else {
backButtonPressedTwice = true
toastSpammable(message)
GlobalScope.launch {
delay(time)
backButtonPressedTwice = false
}
}
}
Kotlin中的用法
// ActivityA.kt
override fun onBackPressed() {
onBackPressedTwiceFinish()
}
在Java中使用
@Override
public void onBackPressed() {
ActivityUtils.onBackPressedTwiceFinish()
}
这段代码的灵感来自这里的@webserveis
这个解的独特之处在于它的行为;其中,非双击将显示吐司和成功双击将显示没有吐司,同时关闭应用程序。
唯一的缺点是吐司的显示将有650毫秒的延迟。我相信这是最佳行为的最佳解决方案,因为逻辑表明,如果没有这样的延迟,就不可能有这种行为
//App Closing Vars
private var doubleBackPressedInterval: Long = 650
private var doubleTap = false
private var pressCount = 0
private var timeLimit: Long = 0
override fun onBackPressed() {
pressCount++
if(pressCount == 1) {
timeLimit = System.currentTimeMillis() + doubleBackPressedInterval
if(!doubleTap) {
showExitInstructions()
}
}
if(pressCount == 2) {
if(timeLimit > System.currentTimeMillis()) {
doubleTap = true
super.onBackPressed()
}
else {
showExitInstructions()
}
pressCount = 1
timeLimit = System.currentTimeMillis() + doubleBackPressedInterval
}
}
private fun showExitInstructions() {
Handler().postDelayed({
if(!doubleTap) {
Toast.makeText(this, "Try Agian", Toast.LENGTH_SHORT).show()
}
}, doubleBackPressedInterval)
}
大多数现代应用程序只使用一个活动和多个片段。所以如果你在使用导航组件并且需要从home片段调用实现,这是解决方案。
override fun onAttach(context: Context) {
super.onAttach(context)
val callback: OnBackPressedCallback = object :
OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (doubleBackPressed) {
activity.finishAffinity()
}
doubleBackPressed = true
Toast.makeText(requireActivity(), "Press BACK again to exit", Toast.LENGTH_LONG).show()
Handler(Looper.myLooper()!!).postDelayed(Runnable {doubleBackPressed = false},
2000)
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}