Flutter/Dart中如何实现弹窗仅两个按钮激活、禁用其余交互?
Flutter 强制交互弹窗实现问题
在我的代码中,满足特定条件时会弹出如下对话框:
对应初始实现代码如下:
//Alert Dialog about questions and answers void _showAlertDialog() { // set up the buttons Widget Answer1Button = TextButton( child: Text(_listData[_listCount][3]), onPressed: () {}, ); Widget Answer2Button = TextButton( child: Text(_listData[_listCount][4]), onPressed: () {}, ); // set up the AlertDialog AlertDialog alert = AlertDialog( // title: Text(), content: Text(_listData[_listCount][2]), actions: [ Answer1Button, Answer2Button, ], ); // show the dialog showDialog( context: context, builder: (BuildContext context) { return alert; }, ); }
需求为:禁止用户点击屏幕空白区域关闭弹窗、禁止按压系统返回键关闭弹窗,仅弹窗内的两个按钮(本示例中为Answer 1.1和Answer 1.2)处于可交互激活状态。
未生效的尝试
参考相关方案在页面根节点添加WillPopScope拦截返回后功能未生效,修改后的页面代码如下:
@override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { await showDialog; // await showDialog or Show add banners or whatever // return true if the route to be popped return false; // return false if you want to disable device back button click }, child: Scaffold( appBar: AppBar( title: const Text('New Game'), ), body: Container( height: MediaQuery.of(context).size.height, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(assetPath), fit: BoxFit.cover)), child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ Visibility( child: Column( children: [ ClipRRect( borderRadius: BorderRadius.circular(4), child: Stack( children: <Widget>[ Positioned.fill( child: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('files/sheet.jpg'), fit: BoxFit.cover)), ), ), Text(_listData[_listCount][0]), ], ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ImageButton(label: 'OK', onButtonTap: _nextCSV), ImageButton(label: 'Hide', onButtonTap: _isVisible), ImageButton(label: 'Test1', onButtonTap: _showAlertDialog), ], ), ], ), visible: isVisible, ), // your other widgets Visibility( child: ImageButton(label: 'Show', onButtonTap: _isVisible), visible: !isVisible, ) ], ), ), ), ); }
最初猜测问题出在await showDialog;这段代码,但将onWillPop改为直接返回false后问题依然存在,返回按钮仍可关闭弹窗:
@override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { return false; // return false if you want to disable device back button click }, child: Scaffold( ...
弹窗弹出时点击系统返回键依然可以关闭弹窗,不符合预期。
解决方案
页面根节点的WillPopScope不生效的核心原因:showDialog弹出的对话框是压在当前页面路由之上的独立路由,系统返回键事件会被最上层的对话框路由优先消费,根本不会传递到下层页面的WillPopScope,所以修改页面层的返回拦截完全无效。
只需要调整两处配置即可实现需求:
- 禁止点击空白区域关闭弹窗:给
showDialog添加参数barrierDismissible: false,该参数默认值为true,设为false后点击弹窗外部遮罩区域不会触发关闭 - 禁止系统返回键关闭弹窗:将
AlertDialog用WillPopScope包裹,在对话框路由层直接拦截返回事件,不需要在下层页面做拦截
修改后的_showAlertDialog代码如下:
//Alert Dialog about questions and answers void _showAlertDialog() { // set up the buttons Widget Answer1Button = TextButton( child: Text(_listData[_listCount][3]), onPressed: () { // 在此处写按钮点击逻辑,需要关闭弹窗时主动调用Navigator.pop(context) }, ); Widget Answer2Button = TextButton( child: Text(_listData[_listCount][4]), onPressed: () { // 在此处写按钮点击逻辑,需要关闭弹窗时主动调用Navigator.pop(context) }, ); // set up the AlertDialog AlertDialog alert = AlertDialog( // title: Text(), content: Text(_listData[_listCount][2]), actions: [ Answer1Button, Answer2Button, ], ); // show the dialog showDialog( context: context, barrierDismissible: false, // 禁止点击空白区域关闭弹窗 builder: (BuildContext context) { // 在弹窗层包裹WillPopScope拦截返回事件 return WillPopScope( onWillPop: () async => false, // 返回false表示拦截返回事件,不关闭弹窗 child: alert, ); }, ); }
调整后效果:
- 点击弹窗外部空白区域不会关闭弹窗
- 按压系统返回键不会关闭弹窗
- 只有弹窗内的两个按钮可响应交互,需要关闭弹窗时在按钮的
onPressed回调中主动调用Navigator.pop(context)即可 - 页面根节点添加的
WillPopScope如果没有其他页面返回拦截需求,可以直接删除,避免弹窗未弹出时按返回键无法退出页面。
内容的提问来源于stack exchange,提问作者Texter
相关产品推荐
相关产品推荐

