• 【Flutter学习】基本组件之弹窗和提示(SnackBar、BottomSheet、Dialog)


    一,概述  

      Flutter中的操作提示主要有这么几种 SnackBarBottomSheetDialog,因为 Dialog样式比较多,放最后讲好了

    二,介绍

    • SnackBar

        SnackBar的源码相对简单

      • 构造函数
        const SnackBar({
            Key key,
            @required this.content, // 提示信息
            this.backgroundColor, // 背景色
            this.action, // SnackBar 尾部的按钮,用于一些回退操作等
            this.duration = _kSnackBarDisplayDuration, // 停留的时长,默认 4000ms
            this.animation, // 进出动画
          })
      • 示例demo
          假如我们需要实现一个功能,修改某个值,修改后给用户一个提示,同时给用户一个撤销该操作的按钮,那么就可以通过 SnackBar来简单实现。
        还有就是 SnackBar可以和 floatingActionButton完美的配合,弹出的时候不会遮挡住 fab
        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和值的变化:
    • BottomSheet

        BottomSheet看命名就知道是从底部弹出的菜单,展示 BottomSheet有两种方式,分别是 showBottomSheetshowModalBottomSheet,两种方式只有在展示类型上的差别,方法调用无差,而且 showBottomSheetfab有组合动画,showModalBottomSheet则没有,看下实际的例子吧。在 ListView中增加一个 BottomSheet的按钮,因为 BottomSheet需要的 context也不能是 Scaffold下的 context,所以需要通过 Builder进行包裹一层,然后增加 _showBottomSheet的方法
      • 示例方法  
        _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); }//onTap
        ),//container ) //InkWell
        ),//generate );//ListView }

        showBottomSheet替换成 showModalBottomSheet就是另外一种展示方式了,内部不需要做任何改变。

      • 运行效果
        我们看下两种的运行效果:
      • 拓展
        可以看到 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,
                  ),
            );
          }
        • 修改高度后的效果
    • Dialog

        相对于 SnackBarBottomSheetDialog的使用场景相对会更多,在 MaterialDesign下,
            Dialog主要有 3 种:AlertDialogSimpleDialogAboutDialog,当然在 Cupertino风格下也有相应的 Dialog,因为这个系列以 MaterialDesign风格为主,所以
            Cupertiono等下次有时间再写吧。
      • AlertDialog

          在 ListView中增加一个 AlertDialog的按钮,用于点击显示 AlertDialog用,然后加入显示 AlertDilaog的方法,并将按钮的 onPressed指向该方法,Dialogcontext可以是 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

        SimpleDialog相比于 AlertDialog少了 contentaction参数,多了 children属性,需要传入 Widget列表,那就可以自定义全部内容了。那我们这里就实现一个性别选择的 Dialog,选择后通过 Taost提示选择的内容,Taost就是之前导入的第三方插件,只要实现 children 是个列表选择器就可以了。

        • 示例代码
          _showSimpleDialog() {
              showDialog(
                  barrierDismissible: false,
                  context: context,
                  builder: (context) => SimpleDialog(
                        title: Text('我是个比较正经的标题...
          选择你的性别'),
                        // 这里传入一个选择器列表即可
                        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

        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',  40.0, height: 40.0),
                children: <Widget>[Text('我是个比较正经的对话框内容...你可以随便把我替换成任何部件,只要你喜欢(*^▽^*)')],
              );
            }
        • 最后的效果:

        • 拓展
          AboutDialog会自带两个按钮 VIEW LICENSESCLOSEVIEW LICENSES会跳转一个 Flutter Licenses的网页,CLOSE会关闭,至于为什么是英文的,是因为我们没有设置语言的原因,这个涉及到多语言。 

    三,Dialog 状态保持

      假如有个需求,需要在弹出的 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和界面的值保持一致了


  • 相关阅读:
    洛谷 U140360 购物清单
    洛谷 U140359 批量处理
    洛谷 U140358 操作系统
    洛谷U140357 Seaway连续
    洛谷 U141394 智
    洛谷 U141387 金
    CF1327F AND Segments
    刷题心得—连续位运算题目的小技巧
    CF743C Vladik and fractions
    洛谷 P6327 区间加区间sin和
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/11371271.html
Copyright © 2020-2023  润新知