Flutter之Dialog使用和踩坑

簡單介紹

最近使用了Flutter的展示對(duì)話框的功能,踩了一點(diǎn)坑,順便做下總結(jié),方便各位以后少踩坑,如果有說錯(cuò)的地方,還請大家指出來。

image

下面將介紹對(duì)話框的幾種場景和踩坑。

  • 展示普通對(duì)話框
  • 展示包含列表項(xiàng)的對(duì)話框
  • 對(duì)話框界面需要?jiǎng)討B(tài)刷新
  • 自定義對(duì)話框。

先理解一些東西

  • 對(duì)話框本質(zhì)上是屬于一個(gè)路由的頁面route,由Navigator進(jìn)行管理,所以控制對(duì)話框的顯示和隱藏,也是調(diào)用Navigator.of(context)的push和pop方法。

  • 在Flutter中,對(duì)話框會(huì)有兩種風(fēng)格,調(diào)用showDialog()方法展示的是material風(fēng)格的對(duì)話框,調(diào)用showCupertinoDialog()方法展示的是ios風(fēng)格的對(duì)話框。 而這兩個(gè)方法其實(shí)都會(huì)去調(diào)用showGeneralDialog()方法,可以從源碼中看到最后是利用Navigator.of(context, rootNavigator: true).push()一個(gè)頁面。

基本要傳的參數(shù):context上下文,builder用于創(chuàng)建顯示的widget,barrierDismissible可以控制點(diǎn)擊對(duì)話框以外的區(qū)域是否隱藏對(duì)話框。

  • 你會(huì)注意到,showDialog()方法返回的是一個(gè)Future對(duì)象,可以通過這個(gè)future對(duì)象來獲取對(duì)話框所傳遞的數(shù)據(jù)。 比如我們想知道想知道用戶是點(diǎn)擊了對(duì)話框的確認(rèn)按鈕還是取消按鈕,那就在退出對(duì)話框的時(shí)候,利用Navigator.of(context).pop("一些數(shù)據(jù)");
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,
  ));
}

簡單的顯示對(duì)話框

Flutter中的Dialog主要是SimpleDialog和AlertDialog。

  • SimpleDialog,一般可以利用多個(gè)SimpleDialogOption為用戶提供了幾個(gè)選項(xiàng)。
  • AlertDialog,警告對(duì)話框。警告對(duì)話框有一個(gè)可選標(biāo)題title和一個(gè)可選列表的actions選項(xiàng)。

展示一個(gè)簡單的SimpleDialog,代碼如下:

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");
                },
              ),
            ],
          );
        });
  }

展示一個(gè)簡單的Material風(fēng)格的AlertDialog,代碼如下:

void showMyMaterialDialog(BuildContext context) {
    showDialog(
        context: context,
        builder: (context) {
          return new AlertDialog(
            title: new Text("title"),
            content: new Text("內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容"),
            actions: <Widget>[
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: new Text("確認(rèn)"),
              ),
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: new Text("取消"),
              ),
            ],
          );
        });
  }

展示一個(gè)簡單的IOS風(fēng)格的AlertDialog,代碼如下:

void showMyCupertinoDialog(BuildContext context) {
    showCupertinoDialog(
        context: context,
        builder: (context) {
          return new CupertinoAlertDialog(
            title: new Text("title"),
            content: new Text("內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容內(nèi)容"),
            actions: <Widget>[
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop("點(diǎn)擊了確定");
                },
                child: new Text("確認(rèn)"),
              ),
              new FlatButton(
                onPressed: () {
                  Navigator.of(context).pop("點(diǎn)擊了取消");
                },
                child: new Text("取消"),
              ),
            ],
          );
        });
  }

展示列表項(xiàng)對(duì)話框(踩坑了)

構(gòu)造對(duì)話框的時(shí)候,我們都需要傳一個(gè)content對(duì)象,來構(gòu)造對(duì)話框的主要內(nèi)容。一般情況下,如果content里面只是簡單的一些內(nèi)容,那問題不大,可以正常顯示。 但是有時(shí)候,我們需要展示一個(gè)列表對(duì)話框。這個(gè)時(shí)候,如果列表項(xiàng)比較多,就會(huì)出現(xiàn)一些問題。

  • 使用Column+SingleChildScrollView來顯示列表對(duì)話框。

當(dāng)Column的列表項(xiàng)數(shù)據(jù)比較多的時(shí)候,屏幕已經(jīng)放不了,就會(huì)出現(xiàn)overflow錯(cuò)誤了,所以這個(gè)時(shí)候需要在外部嵌套一個(gè)SingleChildScrollView控件,使內(nèi)部child控件可以滾動(dòng), 不會(huì)出現(xiàn)overflow錯(cuò)誤。(哈哈,可以使用下面的代碼跑一跑,然后去掉SingleChildScrollView,對(duì)比運(yùn)行結(jié)果)

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("確認(rèn)"),
              ),
              new FlatButton(
                onPressed: () {},
                child: new Text("取消"),
              ),
            ],
          );
        });
  }

  • 使用ListView+指定寬度和高度的Container來顯示對(duì)話框

要將ListView包裝在具有特定寬度和高度的Container中。 如果Container沒有定義這兩個(gè)屬性的話,會(huì)報(bào)錯(cuò),無法顯示ListView。(目前我也是這樣解決的,不知道有沒有人有其他更好的方法哈。) 報(bào)錯(cuò)如下:

void showMyDialogWithListView(BuildContext context) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return new AlertDialog(
            content: new Container(
          /*
              暫時(shí)的解決方法:要將ListView包裝在具有特定寬度和高度的Container中
              如果Container沒有定義這兩個(gè)屬性的話,會(huì)報(bào)錯(cuò),無法顯示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中,會(huì)報(bào)錯(cuò),比如
  //下面這種寫法會(huì)報(bào)錯(cuò):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("確認(rèn)"),),
  //          new FlatButton(onPressed: () {}, child: new Text("取消"),),
  //        ],);
  //    });

需要?jiǎng)討B(tài)更新界面的對(duì)話框

利用StatefulBuilder來實(shí)現(xiàn)一些對(duì)話框場景,需要對(duì)話框動(dòng)態(tài)更新界面的。

比如在對(duì)話框里面顯示一個(gè)checkbox,然后點(diǎn)擊會(huì)修改checkbox的顯示狀態(tài)。如果是跟之前一樣的實(shí)現(xiàn)對(duì)話框方法, 是無法實(shí)現(xiàn)動(dòng)態(tài)去刷新對(duì)話框的界面的。

StatefulBuilder可以包含一個(gè)child,具有狀態(tài),可以調(diào)用setState刷新界面。

builder參數(shù),用于創(chuàng)建想要顯示的widget,可以調(diào)用StateSetter類型的setState參數(shù)來進(jìn)行刷新界面。

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

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

實(shí)例的代碼如下:

 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("選項(xiàng)"),
                    value: selected,
                    onChanged: (bool) {
                      setState(() {
                        selected = !selected;
                      });
                    }),
              );
            }),
          );
        });
  }

自定義Dialog

比如我想顯示一個(gè)菊花的loading加載框,那么用上面的方法都是行不通的。這個(gè)時(shí)候就需要我們?nèi)プ远x一個(gè)對(duì)話框。

首先我們可以先去看一下Dialog的源碼實(shí)現(xiàn),然后只需再照著源碼的實(shí)現(xiàn),修改一下就行了。大部分代碼是保持一致的,所以 對(duì)話框的顯示效果比如動(dòng)畫,主題都是一致的。

下面是Dialog源碼中的build方法實(shí)現(xiàn)。簡單的修改下child屬性所傳的參數(shù)就行了。

 @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,
        //所以我們其實(shí)只需要修改child這個(gè)屬性了,改成我們想要展示的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,
            ),
          ),
        ),
      ),
    );
  }

下面是一個(gè)自定義加載框Dialog的例子,就是將AlertDialog的源碼進(jìn)行剛才所說的修改就行了。

image
  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地址

https://github.com/LXD312569496/flutter-learing/tree/master/dialog_learning

公眾號(hào)

最近弄了個(gè)學(xué)習(xí)Flutter的公眾號(hào)(入魔的冬瓜),一部分是搬磚一些國外文章再加些自己的理解,一部分是自己平時(shí)的總結(jié)。

希望與大家在2019一起學(xué)習(xí),共同進(jìn)步!

祝大家狗年快樂!

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 目錄介紹 1.簡單用法 2.AlertDialog源碼分析2.1 AlertDialog.Builder的構(gòu)造方法...
    楊充211閱讀 1,158評(píng)論 1 1
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,550評(píng)論 0 17
  • Google 出品,Dart語言,F(xiàn)lutter Engine引擎,響應(yīng)式設(shè)計(jì)模式,原生渲染。 目錄 1. 什么是...
    蘭朋友_閱讀 4,763評(píng)論 0 3
  • 每個(gè)人的心中都有一座安和橋 ,不管走多遠(yuǎn),總記得出發(fā)的地方。 那個(gè)夏天,父親騎著自行車,帶著我上學(xué)。 這是我第一次...
    戴香香閱讀 1,144評(píng)論 10 17
  • 很多加過的好友,其實(shí)你們之間不怎么聯(lián)系,但你還是把他們保存在通訊里,你不期待他們主動(dòng)聯(lián)系你,你也從沒想過去找他們?nèi)?..
    愛在櫻花下閱讀 185評(píng)論 0 0