Flutter之Dialog使用和踩坑

简单介绍

最近使用了Flutter的展现对话框的功能,踩了一点坑,顺便作下总结,方便各位之后少踩坑,若是有说错的地方,还请你们指出来。ios

下面将介绍对话框的几种场景和踩坑。git

  • 展现普通对话框
  • 展现包含列表项的对话框
  • 对话框界面须要动态刷新
  • 自定义对话框。

先理解一些东西

  • 对话框本质上是属于一个路由的页面route,由Navigator进行管理,因此控制对话框的显示和隐藏,也是调用Navigator.of(context)的push和pop方法。github

  • 在Flutter中,对话框会有两种风格,调用showDialog()方法展现的是material风格的对话框,调用showCupertinoDialog()方法展现的是ios风格的对话框。 而这两个方法其实都会去调用showGeneralDialog()方法,能够从源码中看到最后是利用Navigator.of(context, rootNavigator: true).push()一个页面。bash

基本要传的参数:context上下文,builder用于建立显示的widget,barrierDismissible能够控制点击对话框之外的区域是否隐藏对话框。app

  • 你会注意到,showDialog()方法返回的是一个Future对象,能够经过这个future对象来获取对话框所传递的数据。 好比咱们想知道想知道用户是点击了对话框的确认按钮仍是取消按钮,那就在退出对话框的时候,利用Navigator.of(context).pop("一些数据");
Future<T> showCupertinoDialog<T>({
  @required BuildContext context,
  @required WidgetBuilder builder,
});

Future<T> showDialog<T>({
  @required BuildContext context,
  bool barrierDismissible = true,
  WidgetBuilder builder,
})

Future<T> showGeneralDialog<T>({
  @required BuildContext context,
  @required RoutePageBuilder pageBuilder,
  bool barrierDismissible,
  String barrierLabel,
  Color barrierColor,
  Duration transitionDuration,
  RouteTransitionsBuilder transitionBuilder,
}) {
  assert(pageBuilder != null);
  assert(!barrierDismissible || barrierLabel != null);
  return Navigator.of(context, rootNavigator: true).push<T>(_DialogRoute<T>(
    pageBuilder: pageBuilder,
    barrierDismissible: barrierDismissible,
    barrierLabel: barrierLabel,
    barrierColor: barrierColor,
    transitionDuration: transitionDuration,
    transitionBuilder: transitionBuilder,
  ));
}
复制代码

简单的显示对话框

Flutter中的Dialog主要是SimpleDialog和AlertDialog。less

  • SimpleDialog,通常能够利用多个SimpleDialogOption为用户提供了几个选项。
  • AlertDialog,警告对话框。警告对话框有一个可选标题title和一个可选列表的actions选项。

展现一个简单的SimpleDialog,代码以下:ide

void showMySimpleDialog(BuildContext context) {
    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");
                },
              ),
            ],
          );
        });
  }
复制代码

展现一个简单的Material风格的AlertDialog,代码以下:学习

void showMyMaterialDialog(BuildContext context) {
    showDialog(
        context: context,
        builder: (context) {
          return new AlertDialog(
            title: new Text("title"),
            content: new Text("内容内容内容内容内容内容内容内容内容内容内容"),
            actions: <Widget>[
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: new Text("确认"),
              ),
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: new Text("取消"),
              ),
            ],
          );
        });
  }
复制代码

展现一个简单的IOS风格的AlertDialog,代码以下:动画

void showMyCupertinoDialog(BuildContext context) {
    showCupertinoDialog(
        context: context,
        builder: (context) {
          return new CupertinoAlertDialog(
            title: new Text("title"),
            content: new Text("内容内容内容内容内容内容内容内容内容内容内容"),
            actions: <Widget>[
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop("点击了肯定");
                },
                child: new Text("确认"),
              ),
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop("点击了取消");
                },
                child: new Text("取消"),
              ),
            ],
          );
        });
  }
复制代码

展现列表项对话框(踩坑了)

构造对话框的时候,咱们都须要传一个content对象,来构造对话框的主要内容。通常状况下,若是content里面只是简单的一些内容,那问题不大,能够正常显示。 可是有时候,咱们须要展现一个列表对话框。这个时候,若是列表项比较多,就会出现一些问题。ui

  • 使用Column+SingleChildScrollView来显示列表对话框。

当Column的列表项数据比较多的时候,屏幕已经放不了,就会出现overflow错误了,因此这个时候须要在外部嵌套一个SingleChildScrollView控件,使内部child控件能够滚动, 不会出现overflow错误。(哈哈,可使用下面的代码跑一跑,而后去掉SingleChildScrollView,对比运行结果)

void showMyDialogWithColumn(BuildContext context) {
    showDialog(
        context: context,
        builder: (context) {
          return new AlertDialog(
            title: new Text("title"),
            content: new SingleChildScrollView(
              child: new Column(
                children: <Widget>[
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                  new SizedBox(
                    height: 100,
                    child: new Text("1"),
                  ),
                ],
              ),
            ),
            actions: <Widget>[
              new FlatButton(
                onPressed: () {},
                child: new Text("确认"),
              ),
              new FlatButton(
                onPressed: () {},
                child: new Text("取消"),
              ),
            ],
          );
        });
  }
复制代码
  • 使用ListView+指定宽度和高度的Container来显示对话框

要将ListView包装在具备特定宽度和高度的Container中。 若是Container没有定义这两个属性的话,会报错,没法显示ListView。(目前我也是这样解决的,不知道有没有人有其余更好的方法哈。) 报错以下:

void showMyDialogWithListView(BuildContext context) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return new AlertDialog(
            content: new Container(
          /*
              暂时的解决方法:要将ListView包装在具备特定宽度和高度的Container中
              若是Container没有定义这两个属性的话,会报错,没法显示ListView
               */
          width: MediaQuery.of(context).size.width * 0.9,
          height: MediaQuery.of(context).size.height * 0.9,
          child: new ListView.builder(
            itemBuilder: (context, index) {
              return new SizedBox(
                height: 100,
                child: new Text("1"),
              );
            },
            itemCount: 10,
            shrinkWrap: true,
          ),
        ));
      },
    );

  //若是直接将ListView放在dialog中,会报错,好比
  //下面这种写法会报错:I/flutter (10721): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
  //    I/flutter (10721): The following assertion was thrown during performLayout():
  //    I/flutter (10721): RenderShrinkWrappingViewport does not support returning intrinsic dimensions.
  //    I/flutter (10721): Calculating the intrinsic dimensions would require instantiating every child of the viewport, which
  //    I/flutter (10721): defeats the point of viewports being lazy.
  //    I/flutter (10721): If you are merely trying to shrink-wrap the viewport in the main axis direction, you should be able
  //    I/flutter (10721): to achieve that effect by just giving the viewport loose constraints, without needing to measure its
  //    I/flutter (10721): intrinsic dimensions.
  //    I/flutter (10721):
  //    I/flutter (10721): When the exception was thrown, this was the stack:
  //    I/flutter (10721): #0 RenderShrinkWrappingViewport.debugThrowIfNotCheckingIntrinsics.<anonymous closure> (package:flutter/src/rendering/viewport.dart:1544:9)
  //    I/flutter (10721): #1 RenderShrinkWrappingViewport.debugThrowIfNotCheckingIntrinsics (package:flutter/src/rendering/viewport.dart:1554:6)
  //    I/flutter (10721): #2 RenderViewportBase.computeMaxIntrinsicWidth (package:flutter/src/rendering/viewport.dart:321:12)
  //    I/flutter (10721): #3 RenderBox._computeIntrinsicDimension.<anonymous closure> (package:flutter/src/rendering/box.dart:1109:23)
  //    I/flutter (10721): #4 __InternalLinkedHashMap&_HashVMBase&MapMixin&_LinkedHashMapMixin.putIfAbsent (dart:collection/runtime/libcompact_hash.dart:277:23)
  //    I/flutter (10721): #5 RenderBox._computeIntrinsicDimension (package:flutter/src/rendering/box.dart:1107:41)
  //    I/flutter (10721): #6 RenderBox.getMaxIntrinsicWidth (package:flutter/src/rendering/box.dart:1291:12)
  //    I/flutter (10721): #7 _RenderProxyBox&RenderBox&RenderObjectWithChildMixin&RenderProxyBoxMixin.computeMaxIntrinsicWidth (package:flutter/src/rendering/proxy_box.dart:81:20)

  //        showDialog(context: context, builder: (context) {
  //      return new AlertDialog(title: new Text("title"),
  //        content: new SingleChildScrollView(
  //          child: new Container(
  //            height: 200,
  //            child: new ListView.builder(
  //              itemBuilder: (context, index) {
  //                return new SizedBox(height: 100, child: new Text("1"),);
  //              }, itemCount: 10, shrinkWrap: true,),
  //          ),
  //        ),
  //        actions: <Widget>[
  //          new FlatButton(onPressed: () {}, child: new Text("确认"),),
  //          new FlatButton(onPressed: () {}, child: new Text("取消"),),
  //        ],);
  //    });


复制代码

须要动态更新界面的对话框

利用StatefulBuilder来实现一些对话框场景,须要对话框动态更新界面的。

好比在对话框里面显示一个checkbox,而后点击会修改checkbox的显示状态。若是是跟以前同样的实现对话框方法, 是没法实现动态去刷新对话框的界面的。

StatefulBuilder能够包含一个child,具备状态,能够调用setState刷新界面。

builder参数,用于建立想要显示的widget,能够调用StateSetter类型的setState参数来进行刷新界面。

typedef StatefulWidgetBuilder = Widget Function(BuildContext context, StateSetter setState);

 const StatefulBuilder({
    Key key,
    @required this.builder
  }) : assert(builder != null),
       super(key: key);
复制代码

实例的代码以下:

void showMyDialogWithStateBuilder(BuildContext context) {
    showDialog(
        context: context,
        builder: (context) {
          bool selected = false;
          return new AlertDialog(
            title: new Text("StatefulBuilder"),
            content:
                new StatefulBuilder(builder: (context, StateSetter setState) {
              return Container(
                child: new CheckboxListTile(
                    title: new Text("选项"),
                    value: selected,
                    onChanged: (bool) {
                      setState(() {
                        selected = !selected;
                      });
                    }),
              );
            }),
          );
        });
  }
复制代码

自定义Dialog

好比我想显示一个菊花的loading加载框,那么用上面的方法都是行不通的。这个时候就须要咱们去自定义一个对话框。

首先咱们能够先去看一下Dialog的源码实现,而后只需再照着源码的实现,修改一下就好了。大部分代码是保持一致的,因此 对话框的显示效果好比动画,主题都是一致的。

下面是Dialog源码中的build方法实现。简单的修改下child属性所传的参数就好了。

@override
  Widget build(BuildContext context) {
    final DialogTheme dialogTheme = DialogTheme.of(context);
    return AnimatedPadding(
      padding: MediaQuery.of(context).viewInsets + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
      duration: insetAnimationDuration,
      curve: insetAnimationCurve,
      child: MediaQuery.removeViewInsets(
        removeLeft: true,
        removeTop: true,
        removeRight: true,
        removeBottom: true,
        context: context,
        //因此咱们其实只须要修改child这个属性了,改为咱们想要展现的widget就好了。
        child: Center(
          child: ConstrainedBox(
            constraints: const BoxConstraints(minWidth: 280.0),
            child: Material(
              elevation: 24.0,
              color: _getColor(context),
              type: MaterialType.card,
              child: child,
              shape: shape ?? dialogTheme.shape ?? _defaultDialogShape,
            ),
          ),
        ),
      ),
    );
  }
复制代码

下面是一个自定义加载框Dialog的例子,就是将AlertDialog的源码进行刚才所说的修改就好了。

void showMyCustomLoadingDialog(BuildContext context) {
    showDialog(
        context: context,
        barrierDismissible: false,
        builder: (context) {
          return new MyCustomLoadingDialog();
        });
  }

class MyCustomLoadingDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Duration insetAnimationDuration = const Duration(milliseconds: 100);
    Curve insetAnimationCurve = Curves.decelerate;

    RoundedRectangleBorder _defaultDialogShape = RoundedRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(2.0)));

    return AnimatedPadding(
      padding: MediaQuery.of(context).viewInsets +
          const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
      duration: insetAnimationDuration,
      curve: insetAnimationCurve,
      child: MediaQuery.removeViewInsets(
        removeLeft: true,
        removeTop: true,
        removeRight: true,
        removeBottom: true,
        context: context,
        child: Center(
          child: SizedBox(
            width: 120,
            height: 120,
            child: Material(
              elevation: 24.0,
              color: Theme.of(context).dialogBackgroundColor,
              type: MaterialType.card,
              //在这里修改为咱们想要显示的widget就好了,外部的属性跟其余Dialog保持一致
              child: new Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new CircularProgressIndicator(),
                  Padding(
                    padding: const EdgeInsets.only(top: 20),
                    child: new Text("加载中"),
                  ),
                ],
              ),
              shape: _defaultDialogShape,
            ),
          ),
        ),
      ),
    );
  }
}
复制代码

Demo的Github地址

github.com/LXD31256949…

公众号

最近弄了个学习Flutter的公众号(入魔的冬瓜),一部分是搬砖一些国外文章再加些本身的理解,一部分是本身平时的总结。

但愿与你们在2019一块儿学习,共同进步!

祝你们狗年快乐!

相关文章
相关标签/搜索