【发布时间】:2019-04-28 23:40:29
【问题描述】:
我是 Flutter 的新手,我看到很多 Android 应用在双击返回按钮时会退出。
第一次按下后退按钮,应用程序显示吐司“再次按下退出应用程序”。 第二次按下,应用程序退出。 当然,两次按下之间的时间一定不长。
flutter 中怎么做?
【问题讨论】:
我是 Flutter 的新手,我看到很多 Android 应用在双击返回按钮时会退出。
第一次按下后退按钮,应用程序显示吐司“再次按下退出应用程序”。 第二次按下,应用程序退出。 当然,两次按下之间的时间一定不长。
flutter 中怎么做?
【问题讨论】:
这是我的代码示例(我使用“fluttertoast”来显示 toast 消息,您可以使用 snapbar 或 alert 或其他任何东西)
DateTime currentBackPressTime;
@override
Widget build(BuildContext context) {
return Scaffold(
...
body: WillPopScope(child: getBody(), onWillPop: onWillPop),
);
}
Future<bool> onWillPop() {
DateTime now = DateTime.now();
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
Fluttertoast.showToast(msg: exit_warning);
return Future.value(false);
}
return Future.value(true);
}
【讨论】:
WillPopScope 只管理背压。因此,可以在第一页上实现此代码,以防止意外关闭应用程序。如果要关闭应用,可以拨打SystemChannels.platform.invokeMethod('SystemNavigator.pop');或SystemNavigator.pop()
Scaffold 包含一个Drawer。抽屉打开时,通常可以用返回按钮将其关闭。但现在抽屉在第一次点击时打开,我有一个带有 exit_warning 的吐司,第二次点击时抽屉关闭。我的意思是,“再次按下退出”不应该影响抽屉。但我无法弄清楚我该如何解决它。我使用WillPopScope 作为Scaffold 的主体,与答案完全相同。谢谢。
ScaffoldState 有 isDrawerOpen 属性,所以我应该使用它。
你可以试试this包。
在包装所有小部件的Scaffold 中,将DoubleBackToCloseApp 放置在传递SnackBar 的位置:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoubleBackToCloseApp(
child: Home(),
snackBar: const SnackBar(
content: Text('Tap back again to leave'),
),
),
),
);
}
}
下面的解决方案必须被视为已弃用,因为它会导致提到的包中已解决的一些问题。例如,如果快餐店被用户关闭,应用程序将关闭(请参阅hcbpassos/double_back_to_close_app#2)。
您还可以选择涉及SnackBar 的解决方案。它不像Andrey Turkovsky 的回答那么简单,但它更加优雅,您不会依赖库。
class _FooState extends State<Foo> {
static const snackBarDuration = Duration(seconds: 3);
final snackBar = SnackBar(
content: Text('Press back again to leave'),
duration: snackBarDuration,
);
DateTime backButtonPressTime;
@override
Widget build(BuildContext context) {
return Scaffold(
// The BuildContext must be from one of the Scaffold's children.
body: Builder(
builder: (context) {
return WillPopScope(
onWillPop: () => handleWillPop(context),
child: Text('Place your child here'),
);
},
),
);
}
Future<bool> handleWillPop(BuildContext context) async {
final now = DateTime.now();
final backButtonHasNotBeenPressedOrSnackBarHasBeenClosed =
backButtonPressTime == null ||
now.difference(backButtonPressTime) > snackBarDuration;
if (backButtonHasNotBeenPressedOrSnackBarHasBeenClosed) {
backButtonPressTime = now;
Scaffold.of(context).showSnackBar(snackBar);
return false;
}
return true;
}
}
【讨论】:
第一次按下返回按钮,应用程序显示一个AlertDialog“按是退出应用程序,按否无法退出应用程序”。 这是我的代码示例(我使用了“AlertDialog”)
@override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onBackPressed,
child: DefaultTabController(
initialIndex: _selectedIndex,
length: choices.length,
child: Scaffold(
appBar: AppBar(
),
),
),
);
}
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Are you sure?'),
content: Text('Do you want to exit an App'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true);
},
)
],
);
},
) ?? false;
}
【讨论】:
这是我的回答。我使用 AlertDialog() 来实现这一点
@override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: _onBackPressed,
child: Scaffold(
appBar: AppBar(),
body: Container(),
),
);
}
Future<bool> _onBackPressed() {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Confirm'),
content: Text('Do you want to exit the App'),
actions: <Widget>[
FlatButton(
child: Text('No'),
onPressed: () {
Navigator.of(context).pop(false); //Will not exit the App
},
),
FlatButton(
child: Text('Yes'),
onPressed: () {
Navigator.of(context).pop(true); //Will exit the App
},
)
],
);
},
) ?? false;
}
【讨论】:
只需使用 double_back_to_close_app 库
https://pub.dev/packages/double_back_to_close_app
在pubspec.yaml文件的依赖项下添加double_back_to_close_app
dependencies:
double_back_to_close_app: ^1.2.0
这里是示例代码
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'package:flutter/material.dart';
void main() => runApp(Example());
class Example extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoubleBackToCloseApp(
snackBar: const SnackBar(
content: Text('Tap back again to leave'),
),
child: Center(
child: OutlineButton(
child: const Text('Tap to simulate back'),
// ignore: invalid_use_of_protected_member
onPressed: WidgetsBinding.instance.handlePopRoute,
),
),
),
),
);
}
}
只需将你的正文内容移动到“DoubleBackToCloseApp's”的孩子
【讨论】:
如果你想要一个snackbar,你应该提供一个scaffold key,因为它与scaffold相关,所以这个key应该可以在它的scaffold parent之外调用snackbar。
这是一个解决方案:
class Home extends StatelessWidget {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async{
DateTime initTime = DateTime.now();
popped +=1;
if(popped>=2) return true;
await _scaffoldKey.currentState.showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: Text('Tap one more time to exit.',textAlign: TextAlign.center,),
duration: Duration(seconds: 2),
)).closed;
// if timer is > 2 seconds reset popped counter
if(DateTime.now().difference(initTime)>=Duration(seconds: 2)) {
popped = 0;
}
return false;
},
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(title : Text("Demo")),
body: Text("body")
);
)
}
【讨论】:
这是我的解决方案,你可以将 backPressTotal 值更改为你想要的按下次数!
int backPressCounter = 0;
int backPressTotal = 2;
@override
Widget build(BuildContext context) {
return Scaffold(
...
body: WillPopScope(child: getBody(), onWillPop: onWillPop),
);
}
Future<bool> onWillPop() {
if (backPressCounter < 2) {
Fluttertoast.showToast(msg: "Press ${backPressTotal - backPressCounter} time to exit app");
backPressCounter++;
Future.delayed(Duration(seconds: 1, milliseconds: 500), () {
backPressCounter--;
});
return Future.value(false);
} else {
return Future.value(true);
}
}
【讨论】:
如果条件是用户只按了两次,当然可以使用第一种方案。 如果你想增加点击次数,可以使用这个方案。用户必须在两秒内按 3 次才能退出
DateTime currentBackPressTime;
/// init counter of clicks
int pressCount=1;
然后:
Future<bool> onWillPop() async {
DateTime now = DateTime.now();
/// here I check if number of clicks equal 3
if(pressCount!=3){
///should be assigned at the first click.
if(pressCount ==1 )
currentBackPressTime = now;
pressCount+=1;
return Future.value(false);
}else{
if (currentBackPressTime == null ||
now.difference(currentBackPressTime) > Duration(seconds: 2)) {
currentBackPressTime = now;
pressCount=0;
return Future.value(false);
}
}
return Future.value(true);
}
【讨论】:
不幸的是,它们都不适合我,我编写了一个通用类(小部件)来处理双击退出。如果有人感兴趣
class DoubleBackToCloseWidget extends StatefulWidget {
final Widget child; // Make Sure this child has a Scaffold widget as parent.
const DoubleBackToCloseWidget({
@required this.child,
});
@override
_DoubleBackToCloseWidgetState createState() =>
_DoubleBackToCloseWidgetState();
}
class _DoubleBackToCloseWidgetState extends State<DoubleBackToCloseWidget> {
int _lastTimeBackButtonWasTapped;
static const exitTimeInMillis = 2000;
bool get _isAndroid => Theme.of(context).platform == TargetPlatform.android;
@override
Widget build(BuildContext context) {
if (_isAndroid) {
return WillPopScope(
onWillPop: _handleWillPop,
child: widget.child,
);
} else {
return widget.child;
}
}
Future<bool> _handleWillPop() async {
final _currentTime = DateTime.now().millisecondsSinceEpoch;
if (_lastTimeBackButtonWasTapped != null &&
(_currentTime - _lastTimeBackButtonWasTapped) < exitTimeInMillis) {
Scaffold.of(context).removeCurrentSnackBar();
return true;
} else {
_lastTimeBackButtonWasTapped = DateTime.now().millisecondsSinceEpoch;
Scaffold.of(context).removeCurrentSnackBar();
Scaffold.of(context).showSnackBar(
_getExitSnackBar(context),
);
return false;
}
}
SnackBar _getExitSnackBar(
BuildContext context,
) {
return SnackBar(
content: Text(
'Press BACK again to exit!',
color: Colors.white,
),
backgroundColor: Colors.red,
duration: const Duration(
seconds: 2,
),
behavior: SnackBarBehavior.floating,
);
}
}
使用这个类的方式如下:
class Dashboard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: DoubleBackToCloseWidget(
child: Container(
child: Column(
children: [
const Text('Hello there'),
const Text('Hello there again'),
],
),
),
),
),
);
}
}
【讨论】:
不使用包使用系统的最佳解决方案
SystemChannels.platform.invokeMethod<void>('SystemNavigator.pop');
或
SystemNavigator.pop();
完整代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ExitApp extends StatefulWidget {
final Widget child;
const ExitApp({
Key? key,
required this.child,
}) : super(key: key);
@override
_ExitAppState createState() => _ExitAppState();
}
class _ExitAppState extends State<ExitApp> {
@override
build(BuildContext context) {
DateTime timeBackPressed = DateTime.now();
return WillPopScope(
child: widget.child,
onWillPop: () async {
final differeance = DateTime.now().difference(timeBackPressed);
timeBackPressed = DateTime.now();
if (differeance >= Duration(seconds: 2)) {
final String msg = 'Press the back button to exit';
Fluttertoast.showToast(
msg: msg,
);
return false;
} else {
Fluttertoast.cancel();
SystemNavigator.pop();
return true;
}
},
);
}
}
【讨论】: