该文已受权公众号 「码个蛋」,转载请指明出处android
前面的小节把经常使用的一些部件都介绍了,这节介绍下 Flutter
中的一些操做提示。Flutter
中的操做提示主要有这么几种 SnackBar
、BottomSheet
、Dialog
,由于 Dialog
样式比较多,放最后讲好了git
SnackBar
的源码相对简单github
const SnackBar({
Key key,
@required this.content, // 提示信息
this.backgroundColor, // 背景色
this.action, // SnackBar 尾部的按钮,用于一些回退操做等
this.duration = _kSnackBarDisplayDuration, // 停留的时长,默认 4000ms
this.animation, // 进出动画
})
复制代码
例如咱们须要实现一个功能,修改某个值,修改后给用户一个提示,同时给用户一个撤销该操做的按钮,那么就能够经过 SnackBar
来简单实现。还有就是 SnackBar
能够和 floatingActionButton
完美的配合,弹出的时候不会遮挡住 fab
markdown
class _PromptDemoPageState extends State<PromptDemoPage> {
var count = 0;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
// 自增操做
increase() {
setState(() => count++);
}
// 自减操做
decrease() {
setState(() => count--);
}
_changeValue(BuildContext context) {
increase();
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('当前值已修改'),
action: SnackBarAction(label: '撤销', onPressed: decrease),
duration: Duration(milliseconds: 2000)));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Prompt Demo'),
),
body: Column(children: <Widget>[
Text('当前值:$count', style: TextStyle(fontSize: 20.0)),
Expanded(
// 为了方便拓展,我这边提取了 `snackBar` 的方法,并把按钮放在列表
child: ListView(padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), children: <Widget>[
// SnackBar 须要提供一个包含 context,可是 context 不能是 Scaffold 节点下的 context,因此须要经过 Builder 包裹一层
Builder(builder: (context) => RaisedButton(onPressed: () => _changeValue(context), child: Text('修改当前值'))),
]))
]),
// 当 SnackBar 弹出时,fab 会上移一段距离
floatingActionButton: Builder(
builder: (context) => FloatingActionButton(onPressed: () => _changeValue(context), child: Icon(Icons.send))),
);
}
}
复制代码
能够看下最后的效果图,请注意看 fab
和值的变化:app
BottomSheet
看命名就知道是从底部弹出的菜单,展现 BottomSheet
有两种方式,分别是 showBottomSheet
和 showModalBottomSheet
,两种方式只有在展现类型上的差异,方法调用无差,并且 showBottomSheet
和 fab
有组合动画,showModalBottomSheet
则没有,看下实际的例子吧。在 ListView
中增长一个 BottomSheet
的按钮,由于 BottomSheet
须要的 context
也不能是 Scaffold
下的 context
,因此须要经过 Builder
进行包裹一层,而后增长 _showBottomSheet
的方法ide
_showBottomSheet(BuildContext context) {
showBottomSheet(
context: context,
builder: (context) => ListView(
// 生成一个列表选择器
children: List.generate(
20,
(index) => InkWell(
child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')),
onTap: () {
print('tapped item ${index + 1}');
Navigator.pop(context);
}),
)),
);
}
复制代码
把 showBottomSheet
替换成 showModalBottomSheet
就是另一种展现方式了,内部不须要作任何改变,咱们看下两种的运行效果:post
能够看到 showBottomSheet
会充满整个屏幕,而后 fab
会跟随一块儿到 AppBar
的底部位置,而 showModalBottomSheet
展现的高度不会超过半个屏幕的高度,可是 fab
被其遮挡了。假如咱们只须要展现 2-3 个 item
,可是按照刚才的方式 showModalBottomSheet
的高度过高了,那咱们能够在 ListView
外层包裹一层 Container
,而后指定 height
便可动画
_showModalBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
child: ListView(
children: List.generate(
2,
(index) => InkWell(
child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')),
onTap: () {
print('tapped item ${index + 1}');
Navigator.pop(context);
}),
)),
height: 120,
),
);
}
复制代码
修改高度后的效果:ui
相对于 SnackBar
和 BottomSheet
,Dialog
的使用场景相对会更多,在 MaterialDesign
下,Dialog
主要有 3 种:AlertDialog
,SimpleDialog
和 AboutDialog
,固然在 Cupertino
风格下也有相应的 Dialog
,由于这个系列以 MaterialDesign
风格为主,因此 Cupertiono
等下次有时间再写吧。this
在 ListView
中增长一个 AlertDialog
的按钮,用于点击显示 AlertDialog
用,而后加入显示 AlertDilaog
的方法,并将按钮的 onPressed
指向该方法,Dialog
的 context
能够是 Scaffold
下的 context
,因此不须要用 Builder
来包裹一层。
_showAlertDialog() {
showDialog(
// 设置点击 dialog 外部不取消 dialog,默认可以取消
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text('我是个标题...嗯,标题..'),
titleTextStyle: TextStyle(color: Colors.purple), // 标题文字样式
content: Text(r'我是内容\(^o^)/~, 我是内容\(^o^)/~, 我是内容\(^o^)/~'),
contentTextStyle: TextStyle(color: Colors.green), // 内容文字样式
backgroundColor: CupertinoColors.white,
elevation: 8.0, // 投影的阴影高度
semanticLabel: 'Label', // 这个用于无障碍下弹出 dialog 的提示
shape: Border.all(),
// dialog 的操做按钮,actions 的个数尽可能控制不要过多,不然会溢出 `Overflow`
actions: <Widget>[
// 点击增长显示的值
FlatButton(onPressed: increase, child: Text('点我增长')),
// 点击减小显示的值
FlatButton(onPressed: decrease, child: Text('点我减小')),
// 点击关闭 dialog,须要经过 Navigator 进行操做
FlatButton(onPressed: () => Navigator.pop(context),
child: Text('你点我试试.')),
],
));
}
复制代码
最后看下效果:
SimpleDialog
相比于 AlertDialog
少了 content
和 action
参数,多了 children
属性,须要传入 Widget
列表,那就能够自定义所有内容了。那咱们这里就实现一个性别选择的 Dialog
,选择后经过 Taost
提示选择的内容,Taost
就是以前导入的第三方插件,先看下效果图吧
只要实现 children
是个列表选择器就能够了,比较简单,直接上代码
_showSimpleDialog() {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => SimpleDialog(
title: Text('我是个比较正经的标题...\n选择你的性别'),
// 这里传入一个选择器列表便可
children: _genders
.map((gender) => InkWell(
child: Container(height: 40.0, child: Text(gender), alignment: Alignment.center),
onTap: () {
Navigator.pop(context);
Fluttertoast.showToast(msg: '你选择的性别是 $gender');
},
))
.toList(),
));
}
复制代码
AboutDialog
主要是用于展现你的 App
或者别的相关东西的内容信息的,平时用的比较少,显示 AboutDialog
有两种方式能够展现,一种是前面同样的 showDialog
方法,传入一个 AboutDialog
实例,还有中方法是直接调用 showAboutDialog
方法。咱们仍是同样在列表加个按钮,并指向显示 AboutDialog
的事件。
_showAboutDialog() {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AboutDialog(
// App 的名字
applicationName: 'Flutter 入门指北',
// App 的版本号
applicationVersion: '0.1.1',
// App 基本信息下面会显示一行小字,主要用来显示版权信息
applicationLegalese: 'Copyright: this is a copyright notice topically',
// App 的图标
applicationIcon: Icon(Icons.android, size: 28.0, color: CupertinoColors.activeBlue),
// 任何你想展现的
children: <Widget>[Text('我是个比较正经的对话框内容...你能够随便把我替换成任何部件,只要你喜欢(*^▽^*)')],
));
}
复制代码
也能够经过 showAboutDialog
实现一样的效果
_showAboutDialog() {
showAboutDialog(
context: context,
applicationName: 'Flutter 入门指北',
applicationVersion: '0.1.1',
applicationLegalese: 'Copyright: this is a copyright notice topically',
applicationIcon: Image.asset('images/app_icon.png', width: 40.0, height: 40.0),
children: <Widget>[Text('我是个比较正经的对话框内容...你能够随便把我替换成任何部件,只要你喜欢(*^▽^*)')],
);
}
复制代码
最后的效果:
AboutDialog
会自带两个按钮 VIEW LICENSES
和 CLOSE
,VIEW LICENSES
会跳转一个 Flutter Licenses
的网页,CLOSE
会关闭,至于为何是英文的,是由于咱们没有设置语言的缘由,这个涉及到多语言,这边推荐几篇以前看过的文章,若是下次有时间的话会单独拿出来说下
这边为了支持中文,咱们作下以下的修改,首先打开 pubspec.ymal
文件加入以下支持
get package
后给 MaterialApp
加入以下属性,这样就会支持中文了,这里须要导入包 package:flutter_localizations/flutter_localizations.dart
,再次运行,就会发现以前的英文变成中文了,固然你也能够设置成别的语言。
假若有个需求,须要在弹出的 Dialog
显示当前被改变的值,而后经过按钮能够修改这个值 ,该如何实现。相信不少小伙伴都会这么认为,经过 setState
来修改不就好了吗,没错,我一开始的确这么去实现的,咱们先看下代码好了,增长一个 DialogState
按钮,而后指向对应的点击事件
_showStateDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => SimpleDialog(
title: Text('我这边能实时修改状态值'),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
children: <Widget>[
Text('当前的值是: $_count', style: TextStyle(fontSize: 18.0)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
RaisedButton(
onPressed: increase,
child: Text('点我自增'),
),
RaisedButton(
onPressed: decrease,
child: Text('点我自减'),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('点我关闭'),
)
]),
)
],
));
}
复制代码
而后咱们运行看下
诶诶诶,怎么 Dialog
的值不改变呢,明明界面上的已经修改了啊。因此说图样图森破咯,看下官方对 showDialog
方法的解释吧
/// This function takes a `builder` which typically builds a [Dialog] widget.
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
/// returned by the `builder` does not share a context with the location that
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
/// custom [StatefulWidget] if the dialog needs to update dynamically.
复制代码
糟糕透的翻译又来了:该方法经过 builder
参数来传入一个 Dialog
部件,dialog
下的内容被一个「模态障碍」阻隔,builder
的 context
和调用 showDialog
时候的 context
不是共享的,若是须要动态修改 dialog
的状态值,须要经过 StatefulBuilder
或者自定义 dialog
继承于 StatefulWidget
来实现
因此解决的方法很明确,对上面的代码进行修改,在外层嵌套一个 StatefulBuilder
部件
_showStateDialog() {
showDialog(
context: context,
barrierDismissible: false,
// 经过 StatefulBuilder 来保存 dialog 状态
// builder 须要传入一个 BuildContext 和 StateSetter 类型参数
// StateSetter 有一个 VoidCallback,修改状态的方法在这写
builder: (context) => StatefulBuilder(
builder: (context, dialogStateState) => SimpleDialog(
title: Text('我这边能实时修改状态值'),
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
children: <Widget>[
Text('当前的值是: $_count', style: TextStyle(fontSize: 18.0)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
RaisedButton(
// 经过 StatefulBuilder 的 StateSetter 来修改值
onPressed: () => dialogStateState(() => increase()),
child: Text('点我自增'),
),
RaisedButton(
onPressed: () => dialogStateState(() => decrease()),
child: Text('点我自减'),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('点我关闭'),
)
]),
)
],
)));
}
复制代码
而后再运行下,能够看到 dialog
和界面的值保持一致了
以上部分代码查看 prompt_main.dart 文件
差很少经常使用弹窗和操做提示就在这了,好好消化吧~
最后代码的地址仍是要的:
文章中涉及的代码:demos
基于郭神 cool weather
接口的一个项目,实现 BLoC
模式,实现状态管理:flutter_weather
一个课程(当时买了想看下代码规范的,代码更新会比较慢,虽然是跟着课上的一些写代码,可是仍是作了本身的修改,不少地方看着不舒服,而后就改为本身的实现方式了):flutter_shop
若是对你有帮助的话,记得给个 Star,先谢过,你的承认就是支持我继续写下去的动力~