在耗时操作的时候,一般都要弹出一个加载框,然后在完成的时候再把加载框关掉,在Flutter中可以直接用showDialog()来弹出一个对话框。
showDialog(
context: context,
builder: (ctx) {
return SimpleDialog(
title: Text(""),
titlePadding: EdgeInsets.all(10),
backgroundColor: Colors.white,
elevation: 5,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6))),
children: <Widget>[
ListTile(
title: Center(
child: Text(
"请填写完整信息!",
style: TextStyle(color: Colors.black, fontSize: 18),
),
),
),
SizedBox(height: 15),
ListTile(
title: Center(
child: Container(
padding: EdgeInsets.only(left: 12, right: 12, top: 8, bottom: 8),
width: 100,
alignment: Alignment.center,
decoration: BoxDecoration(color: MyColors.primaryColor),
child: Text(
"知道了",
style: TextStyle(color: Colors.white, fontSize: 18),
),
)),
onTap: () {
Navigator.pop(context);
},
),
],
);
});
这是一个简单的提示对话框,包含了关闭按钮,点击就能关闭。但一般的耗时操作完成,就需要我们自己把dialog关闭掉。
首先,开启dialog的时机。由于我们需要获取到BuildContext,所以就得等build()方法走完,这里可以用Future.delayed()来等创建好BuildContext再进行创建,或者用Timer来延迟操作,我选择了前者。
Future.delayed(Duration.zero, () {
_showDialog();
});
void _showDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return LoadingDialog(true);
});
}
其中delayed()在initState()结尾来做就行,这里参考网友封装了一个LoadingDialog。
class LoadingDialog extends Dialog {
LoadingDialog(this.canceledOnTouchOutside) : super();
///点击背景是否能够退出
final bool canceledOnTouchOutside;
@override
Widget build(BuildContext context) {
return Center(
child: new Material(
///背景透明
color: Colors.transparent,
///保证控件居中效果
child: Stack(
children: <Widget>[
GestureDetector(
///点击事件
onTap: () {
if (canceledOnTouchOutside) {
Navigator.pop(context);
}
},
),
_dialog()
],
)),
);
}
Widget _dialog() {
return new Center(
///弹框大小
child: new SizedBox(
width: 120.0,
height: 120.0,
child: new Container(
///弹框背景和圆角
decoration: ShapeDecoration(
color: Color(0xffffffff),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
new Padding(
padding: const EdgeInsets.only(
top: 20.0,
),
child: new Text(
"加载中",
style: new TextStyle(fontSize: 16.0),
),
),
],
),
),
),
);
}
}
那么接下来要在什么时机关闭呢?
一开始,我理所当然的以为,是在异步方法结束后,去更新界面的时候关闭,也就是setState(() {})的时候,可是不管怎么尝试,用Navigator.pop()不行,用Navigator.of(context, rootNavigator: true).pop(result)也不行,用FlutterBoost.singleton.close(id)也不行,用FlutterBoost.singleton.closeCurrent()也不行,都会直接把非Dialog的页面也关闭掉,这让我百思不得其解,因为showDialog()的本质也是新建了一个Route出来,也就是最顶层的页面是弹出的Dialog,可是为什么关不掉呢。
一番思前想后,把showDialog的逻辑移到和异步逻辑同级,也就是setState(() {})外面,然后把showDialog()自身创建的BuildContext传进去就能正常关闭了。也就是,在setState(() {})的时候,其实用的context还是非Dialog页面的,所以关闭的当然就不是Dialog了。
BuildContext dialogContext;
void _showDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
dialogContext = context;
return LoadingDialog(true);
});
}
持有Dialog自己的BuildContext,然后在异步以后调用就行了。
Future _getMsg() async {
try {
var restHomeId = await DataManager.getInstance().getLocalRestHomeId();
CurrentStaffModel cs = await DataManager.getInstance().getCurrentStaffInfo();
Map<String, dynamic> params = Map();
params["staffId"] = cs.staffId;
params["restHomeId"] = restHomeId;
var result = await CourseManager.getInstance().getStudentCourseList(params);
setState(() {});
Navigator.pop(dialogContext); //直接传入context关闭就行
} catch (e) {
LogUtil.e(e.toString());
}
}