
Flutter 里面的谭庄不管是重量级的还是轻量级的都在这里了:
DropdownButton
- 下拉菜单按钮BottomSheet
- 底部弹出弹窗PopupMenuButton
- pop 按钮Dialog
- 对话框Toast
- Flutter 没有内置 Toast,所有的 Toast 方案都是以外置插件的方式导进来使用的,这里多介绍几个OKToast
- 最好的 Toast 插件,功能非常完善,推荐使用SnackBar
- 喜闻乐见了啊,OKToast 其实已经可以取代 SnackBar 了,官方原生的 SnackBar 很死板
DropdownButton
DropdownButton
是一个简单的下拉选择框,构建特点:
- 不管是
hint
的样式还是可选择
的样式都是 widget - 每一项都是特定的 widget 类型:
DropdownMenuItem
,大家在其内部再写具体的 widget 样式,不过样式是自己写,但是DropdownMenuItem
内部有个value
对应该选项的值,这个必须要写的 - 当前选项的变化需要我们自己维护,
DropdownButton
是不会帮我们做的
图是随便找的:

主要属性如下:
hint
- value == null 时显示的文字disabledHint
- 禁用的提示items
- 选项 item,注意是值是列表类型style
- 字体样式iconSize
- 右侧三角大小isDense
- true: item 高度是下拉框高度的一半,false: 下拉框高度和 item 一样isExpanded
- false:下拉框最小宽度 true: 充满父容器value
- 当前选中的那项onChanged
- 选中事件
class TestWidgetState extends State<TestWidget> {
String selectValue;
@override
Widget build(BuildContext context) {
return DropdownButton(
hint: Text("请选择您要的号码:"),
items: getItems(),
value: selectValue,
onChanged: (value) {
print(value);
setState(() {
selectValue = value;
});
},
);
}
getItems() {
var items = List<DropdownMenuItem<String>>();
items.add(DropdownMenuItem(child: Text("AA"), value: "11"));
items.add(DropdownMenuItem(child: Text("BB"), value: "22",));
items.add(DropdownMenuItem(child: Text("CC"), value: "33",));
items.add(DropdownMenuItem(child: Text("DD"), value: "44",));
items.add(DropdownMenuItem(child: Text("EE"), value: "55",));
return items;
}
}
这里有个泛型的点要注意,我们声明 DropdownMenuItem
选项列表时要指定数值类型 List<DropdownMenuItem<String>>()
,要不 selectValue
那里就不能写具体的数值类型,只能写 var
了
BottomSheet
BottomSheet
很像 android 的 BottomSheetBehavior,不过不能向往拖拽成一个页面

RaisedButton(
child: Text('点击'),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min, // 设置最小的弹出
children: <Widget>[
new ListTile(
leading: new Icon(Icons.photo_camera),
title: new Text("Camera"),
onTap: () async {
},
),
new ListTile(
leading: new Icon(Icons.photo_library),
title: new Text("Gallery"),
onTap: () async {
},
),
],
);
}
);
},
)
PopupMenuButton
PopupMenuButton
android 里的 PopupWindow
大家很熟悉吧,这个就是 Flutter 版的
样式图:

主要参数:
-
itemBuilder
构建弹出菜单样式,是 list 结构的数据,使用PopupMenuItem
承载每个 item ,但是 list 外层的泛型要用List<PopupMenuEntry<String>>
,非常蛋疼,说实话 itemBuilder这里的 API 我是真不喜欢,太难用了,和
DropdownButton的设计都不统一,对于
itemBuilder内部的实现类谁关心呢,根本就不应该把
PopupMenuEntry` 暴露出来。写法固定的,大家记住吧 -
elevation
阴影高度 -
padding
边距 -
child
按钮样式和icon
互斥,只能用一个 -
icon
按钮样式和child
互斥,只能用一个 -
offset
偏移量,offset 的值>100 比如:offset(100,100)
就是在 actionBar 的正下面 -
onSelected
用户选中的回调 -
onCanceled
用户取消的回调 -
配合 actionBar 的写法:
class _MyHomePageState extends State<MyHomePage> {
var items = <String>["AA", "BB", "CC", "DD", "FF"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
PopupMenuButton<String>(
itemBuilder: (BuildContext context) {
return _getItemBuilder2();
},
icon: Icon(Icons.access_alarm),
onSelected: (value) {
print(value);
},
onCanceled: () {},
offset: Offset(200, 100),
)
],
),
body: Center(
child: TestWidget(),
),
);
}
}
常规生成 item 的写法:
List<PopupMenuEntry<String>> _getItemBuilder() {
return items
.map((item) => PopupMenuItem<String>(
child: Text(item),
value: item,
))
.toList();
}
加上分割线,带 icon 的 item
List<PopupMenuEntry<String>> _getItemBuilder2() {
return <PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: "1",
child: ListTile(
leading: Icon(Icons.share),
title: Text('分享'),
),
),
PopupMenuDivider(), //分割线
PopupMenuItem<String>(
value: "2",
child: ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
),
),
];
}

除了 actionBar 之外,其他地方也可以用啊, 置于显示位置,你要知道 item 的宽度,+-
代表左右方向,在下方按钮显示,Y 坐标写 100 就行
AlertDialog
1. 明确 Flutter 中 dialog 的基本特性
Flutter
中dialog
实际上是一个由route
直接切换显示的页面,所以使用Navigator.of(context) 的 push、pop(xx)
方法进行显示、关闭、返回数据Flutter
中有两种风格的dialog
showDialog()
启动的是material
风格的对话框showCupertinoDialog()
启动的是ios
风格的对话框
Flutter
中有两种样式的dialog
SimpleDialog
使用多个SimpleDialogOption
为用户提供了几个选项AlertDialog
一个可选标题 title 和一个可选列表的 actions 选项
2. showDialog
方法讲解
其方法定义如下:
Future<T> showDialog<T>({
@required BuildContext context,
bool barrierDismissible = true,
@Deprecated(
'Instead of using the "child" argument, return the child from a closure '
'provided to the "builder" argument. This will ensure that the BuildContext '
'is appropriate for widgets built in the dialog.'
) Widget child,
WidgetBuilder builder,
}) {
.......
}
context
上下文对象barrierDismissible
点外面是不是可以关闭,默认是 true 可以关闭的builder
是 widget 构造器FlatButton
标准 AlertDialog 中的按钮必须使用这个类型Navigator.of(context).pop();
对话框内关闭对话框
3. 对话框显示层级
和 android 不同,Flutter 里对话框是属于 root 本页面层级的,看下图:

center 里 column 就是对话框,这里我是写的自定义的对话框,用的就是 column。可见 Flutter 的对话框相比 android 要成熟好用多了,这下我们轻松的实现全局对话框了...
但是有一点要知道,Flutter 中 dialog 和所在页面不在一个层级上,所以我们修改页面的数据,setState 方法就不能影响到 dialog 了,这样就实现不了数据变化了,所以在自定义 dialog 时,我们一般使用使用 StatefulWidget 来承载 dialog widget
4. AlertDialog
哈哈,看名字是不是很亲切啊,AlertDialog
作者非常 Nice,从使用习惯上就考虑照顾从 android 切过来的开发者,比其他一些 widget 的作者要强多了
张这个样子:

示例:
// 定义对话框
show(BuildContext context,) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text("这里是测试标题"),
actions: <Widget>[
FlatButton(
child: Text("删除"),
onPressed: () {
print("删除");
Navigator.of(context).pop();
},
),
FlatButton(
child: Text("取消"),
onPressed: () {
print("取消");
Navigator.of(context).pop();
},
),
],
);
},
);
}
// 使用对话框
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.adb),
tooltip: "tips",
backgroundColor: Colors.blueAccent,
foregroundColor: Colors.amberAccent,
onPressed: () {
show(context);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.startTop,
body: Center(
child: TestWidget(),
),
);
}
5. 自定义对话框
Flutter 中自定义听容易的,widget 中 child 传不一样的 widget 就是不同的样式了,showDialog
方法中的 builder
我们传自己的样式就是自定义 dialog 了,很简单不是,Flutter 基本上就是这个套路,大家要熟悉啊

自定义 dialog
class TestDialog extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return TestDialogState();
}
}
class TestDialogState extends State<TestDialog> {
var num = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(num.toString(),style: TextStyle(decoration: TextDecoration.none),),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text("+"),
onPressed: () {
setState(() {
num++;
});
},
),
RaisedButton(
child: Text("-"),
onPressed: () {
setState(() {
num--;
});
},
),
],
),
],
);
}
}
启动 dialog
onPressed: () {
showDialog(
context: context,
builder: (context) {
return TestDialog();
});
},
6. SimpleDialog
SimpleDialog
的样式

showDialog(
context: context,
builder: (context) {
return new SimpleDialog(
title: new Text("SimpleDialog"),
children: <Widget>[
new SimpleDialogOption(
child: new Text("SimpleDialogOption One"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption One");
},
),
new SimpleDialogOption(
child: new Text("SimpleDialogOption Two"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption Two");
},
),
new SimpleDialogOption(
child: new Text("SimpleDialogOption Three"),
onPressed: () {
Navigator.of(context).pop("SimpleDialogOption Three");
},
),
],
);
});
7. iOS 风格的 dialog

void showCupertinoDialog() {
var dialog = CupertinoAlertDialog(
content: Text(
"你好,我是你苹果爸爸的界面",
style: TextStyle(fontSize: 20),
),
actions: <Widget>[
CupertinoButton(
child: Text("取消"),
onPressed: () {
Navigator.pop(context);
},
),
CupertinoButton(
child: Text("确定"),
onPressed: () {
Navigator.pop(context);
},
),
],
);
showDialog(context: context, builder: (_) => dialog);
}
8. 一些坑
- 自定义的 dialog 要是太长了超过屏幕长度了,请在外面加一个可以滚动的 SingleChildScrollView
- 自定义的 dialog 要是有 ListView 的话,必须在最外面加上一个确定宽度和高度的 Container,要不会报错,道理和上面的那条一样的
Toast
上面说了 Flutter 内置没有 Toast
, 这次介绍的插件就叫:Toast
导入:
dependencies:
toast: ^0.1.3
使用:
class TestWidgetState extends State<TestWidget> {
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("殿下您好"),
onPressed: () {
Toast.show('这是一个 toast', context);
},
);
}
}
这应该是目前最简单的 Toast 了吧,但是还有比他更 NB 的
OKToast
OKToast
基本上大家都是使用这个插件,OKToast
插件功能飞铲发完善,支持关闭已显示的 toast,支持自定义 view toast,和输入法的联动也非常好。另外 OKToast
插件自己会缓存全局 context,不用我们每次再传 context 了,使用上更灵活了,一般我们在 android 里也是要自己做全局 toast 工具的,flutter 中 OKToast
就是我们最好的选择
1. 导包:
dependencies:
oktoast: ^2.2.0
2. 使用 oktoast 包裹 MaterialApp widget
MaterialApp 一般都是看做 application 使的,所以 OKToast 包裹 MaterialApp 为的就是全局 context,另一个也是为了显示,OKToast 能包裹 MaterialApp 那么很明显这个 OKToast 一定是个 widget 了
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return OKToast(
// 有一些通用设置,大家自己看看
textStyle: TextStyle(
fontSize: 16,
color: Colors.white,
),
backgroundColor: Colors.grey.withAlpha(200),
child: MaterialApp(
title: 'Frist Blood!',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
3. 简单使用
这是最简单的用法,的确是非常简洁啊,我们自己写功能组件一定要以好用、清洗
这个目标位准啊
showToast("hello world")
4. 关闭 toast
oktoast
这里提供了挺多的选择,可见作者非常用心了
关闭所有 toast
dismissAllToast();
显示 toast 时关闭之前显示的 toast
showToas的t("msg", dismissOtherToast: true);
初始化时设置显示新的 toast 时自动关闭之前的 toast
OKToast(
dismissOtherOnShow: true,
...
)
关闭指定 toast
var future = showToast("msg");
future.dismiss();
5. oktoast 可以设置的参数
下面的参数在包裹 MaterialApp 那里或是具体显示时都可以使用,这个作者我真实太喜欢了,okToast 这个组件写的真是太好了
backgroundColor
- 背景颜色duration
- 延迟隐藏时间onDismiss
- 隐藏时的回调position
- toast 的位置radius
- 圆角的尺寸textAlign
- 文字在内部的对齐方式textDirection
- ltr 或 rtltextPadding
- 文本距离边框的 paddingtextStyle
- 文本的样式
比如我们在顶部显示 toast
showToast("AA",position:ToastPosition.top );
6. 自定义 toast
oktoast
提供了一个函数:showToastWidget
,往期中传自己的 widget 就行了,oktoast
本质上就是一个在 root 跟页面的页面

使用自定义 toast:
showToastWidget(getToastWidget());
自定义 widget:
getToastWidget() {
return Center(
child: Container(
padding: const EdgeInsets.all(5),
color: Colors.black.withOpacity(0.7),
child: Row(
children: <Widget>[
Icon(
Icons.add,
color: Colors.white,
),
Text(
'添加成功',
style: TextStyle(color: Colors.white),
),
],
mainAxisSize: MainAxisSize.min,
),
),
);
}
SnackBar
SnackBar
这里有出现变化了啊,虽然 SnackBar 还不是一个 widget,还是用方法启动,但是变化的是 SnackBar 需要 Scaffold 来启动,这就蛋疼了啊,之前的 dialog 自己专门有个 widget 层级的,使用 futrue 来启动不是挺好的嘛,你这里为啥飞得用 Scaffold 呢。我个人总之是不喜欢的,可见 Google 内部团队成员之间思路也是不统一的,团队建设有待加强啊。另外这样纷乱的 API 真是给 Google 掉份啊
1.
先创建 SnackBar 对象
var snackBar = SnackBar(
content: Text("测试版..."),
action: SnackBarAction(
label: "取消",
onPressed: () {
print("已取消!");
},
),
);
可选的参数:
content
- 内容backgroundColor
- 背景颜色elevation
- 阴影高度shape
behavior
- 位置SnackBarBehavior.fixed
- 底部behavior:SnackBarBehavior.floating
- 顶部,不过顶部真的是非常难看,会和状态栏重合,下面有图大家看一下
action
- 右边按钮duration
- 时间,默认是 4秒animation
- 显示/隐藏动画
2.
启动
Scaffold.of(context).showSnackBar(snackBar);
SnackBar 在顶部显示时可难看了
