1. 秦子帅的博客首页
  2. Flutter

Flutter-UI 弹窗系 Widget

作者 |  前行的乌龟
地址 |  juejin.im/post/5dbff51bf265da4d4e3001b2

前言

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 是不会帮我们做的
图是随便找的:
Flutter UI - 弹窗系 Widget
主要属性如下:
  • 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,不过不能向往拖拽成一个页面
Flutter UI - 弹窗系 Widget
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 版的
样式图:
Flutter UI - 弹窗系 Widget
主要参数:
  • 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(‘设置’),
),
),
];
}

Flutter UI - 弹窗系 Widget
除了 actionBar 之外,其他地方也可以用啊, 置于显示位置,你要知道 item 的宽度,+- 代表左右方向,在下方按钮显示,Y 坐标写 100 就行

AlertDialog

1. 明确 Flutter 中 dialog 的基本特性

  • Flutterdialog 实际上是一个由 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 本页面层级的,看下图:

 

Flutter UI - 弹窗系 Widget

 

center 里 column 就是对话框,这里我是写的自定义的对话框,用的就是 column。可见 Flutter 的对话框相比 android 要成熟好用多了,这下我们轻松的实现全局对话框了…
但是有一点要知道,Flutter 中 dialog 和所在页面不在一个层级上,所以我们修改页面的数据,setState 方法就不能影响到 dialog 了,这样就实现不了数据变化了,所以在自定义 dialog 时,我们一般使用使用 StatefulWidget 来承载 dialog widget

4. AlertDialog

哈哈,看名字是不是很亲切啊,AlertDialog 作者非常 Nice,从使用习惯上就考虑照顾从 android 切过来的开发者,比其他一些 widget 的作者要强多了
张这个样子:

 

Flutter UI - 弹窗系 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 基本上就是这个套路,大家要熟悉啊
Flutter UI - 弹窗系 Widget
自定义 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 的样式
Flutter UI - 弹窗系 Widget
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

Flutter UI - 弹窗系 Widget
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,要不会报错,道理和上面的那条一样的
—END—
Flutter UI - 弹窗系 Widget 

发布者:秦子帅,转转请注明出处:http://qinzishuai.cn/7066/0e2dbdc8d8/