最近我在许多Android应用和游戏中注意到这种模式:当点击后退按钮“退出”应用时,Toast会出现类似于“请再次点击后退退出”的消息。

我在想,当我越来越频繁地看到它时,这是一个内置的功能,你可以在某个活动中访问它吗?我已经看了很多类的源代码,但我似乎找不到任何关于这一点。

当然,我可以想到一些很容易实现相同功能的方法(最简单的可能是在活动中保留一个布尔值,指示用户是否已经单击过一次…),但我想知道这里是否已经有一些东西。

编辑:正如@LAS_VEGAS所提到的,我并不是指传统意义上的“退出”。(即终止)我的意思是“回到应用程序启动活动启动之前打开的任何东西”,如果这有意义的话:)


当前回答

在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);
        }

其他回答

还有另一种方法……使用CountDownTimer方法

private boolean exit = false;
@Override
public void onBackPressed() {
        if (exit) {
            finish();
        } else {
            Toast.makeText(this, "Press back again to exit",
                    Toast.LENGTH_SHORT).show();
            exit = true;
            new CountDownTimer(3000,1000) {

                @Override
                public void onTick(long l) {

                }

                @Override
                public void onFinish() {
                    exit = false;
                }
            }.start();
        }

    }

这个答案很容易使用,但我们需要双击退出。我只是修改了答案,

    @Override
public void onBackPressed() {
    ++k;
    if(k==1){
        Toast.makeText(this, "Press back one more time to exit", Toast.LENGTH_SHORT).show();
        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
            @Override
            public void run() {
                --k;
            }
        },1000);
    }else{
        //do whatever you want to do on the click after the first for example:
        finishAffinity();
    }
}

你可以使用小吃店而不是吐司,所以你可以依靠它们的可见性来决定是否关闭应用程序。举个例子:

Snackbar mSnackbar;

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

    final LinearLayout layout = findViewById(R.id.layout_main);
    mSnackbar = Snackbar.make(layout, R.string.press_back_again, Snackbar.LENGTH_SHORT);
}

@Override
public void onBackPressed() {
    if (mSnackbar.isShown()) {
        super.onBackPressed();
    } else {
        mSnackbar.show();
    }
}

我已经尝试为此创建了一个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)
}