Flutter入门:自定义dialog

2,309 阅读4分钟

「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战

自定义dialog

先来看看一个示例

class ExamResultDialog extends Dialog{
  ...
  @override
  Widget build(BuildContext context) {
    return new ExamResultDialogContent(entity, listener);
  }
}

class ExamResultDialogContent extends StatefulWidget{
  ...
  @override
  State<StatefulWidget> createState() {
    ...
    return _ExamResultDialogContent();
  }
  ...
}

class _ExamResultDialogContent extends State<ExamResultDialogContent>{
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      body: Center(
        child: Container(
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.all(Radius.circular(20)),
          ),
          width: 752,
          height: 426,
          child: ...,   //内容部分
        ),
      ),
    );
  }
}

从上面可以看到先继承dialog,在它的build函数然后widget,剩下的与正常的widget差不多。

但是注意几点:

  • 最外层需要用Scaffold包裹,否则所有默认样式都会失效。
  • 使用Scaffold后,背景是不透明的,需要再设置backgroundColor为透明的。
  • dialog默认是全屏的,所以需要用Container来限制内容的大小,并用Center包裹使内容居中。同时也可以对Container添加decoration实现弹窗背景

大小动态调整的Dialog

开发中经常遇到这样的dialog,内容变化很大,所以dialog的大小也要跟着变化,但是为了美观又不能太大,当内容过多的时候dialog大小就不再改变,而是内容可滚动查看。

下面我们一步步实现这个dialog

Text内容可滚动

这里同时也解决Text显示不全的问题。

我们用最简单的dialog,内容只有文字,那么第一步要解决的就是如果文字特别多的情况下,怎么让Text的内容可以滚动。

答案是用SingleChildScrollView,它只有一个child,作用就是如果child太大的情况下可以滚动查看。

代码:

SingleChildScrollView(
   child: Text(
      widget.msg,
      style:TextStyle(color: Color(0xff919294), fontSize: 18),
),

但是这里有一个问题,就是虽然能滚动了,但是Text显示不全,现象是当Text文字内容很多的时候,最后一行看不到,而倒数第二行只能显示一半。

这个不是SingleChildScrollView的问题,经测试,在空白页面上单独使用Text,高度不限,依然会出现这样的情况,甚至Text下半部分还空白着,后面的文字依然不显示。

注意:这个情况是发生在flutter web,在chrome上出现,在Android或ios未测试,可能是web特有的问题

经过反复尝试,最终发现为Text设置overflow: TextOverflow.ellipsis可以解决,文字可以完整显示出来了。

overflow的作用就是文字显示不下时采用什么样的处理,ellipsis就是省略,还有shape(渐变)、visible等等。这里不知道为什么设置ellipsis可以起作用,反而设置visible不行。

但是设置overflow: TextOverflow.ellipsis可以解决单独使用Text时的问题,如果使用SingleChildScrollView嵌套到达滚动效果的话,Text只显示一行。。。

这里我的解决方法时设置Text的maxLines: 100 这里的100只是一个较大的数,可以是1000,只有保证内容完全显示即可。

这个解决方案并不理想,但是暂时只想到这个解决方案。最终代码:

SingleChildScrollView(
   child: Text(
      widget.msg,
      overflow: TextOverflow.ellipsis,
      maxLines: 100,
      style:TextStyle(color: Color(0xff919294), fontSize: 18),
),

窗口大小动态调整并限制最大高度

这里使用LimitedBox来实现高度的限制,直接上代码:

SizedBox(
    width: 400,
    child: Container(
      child: Stack(
        children: [
          Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                child: Text(
                  "公告",
                  style: TextStyle(color: Color(0xff212223), fontSize: 24),
                ),
                width: double.infinity,
                alignment: Alignment.center,
                margin: EdgeInsets.only(top: 10, bottom: 20),
              ),
              LimitedBox(
                maxHeight: 400,
                child: SingleChildScrollView(
                  child: Text(
                    widget.msg,
                    overflow: TextOverflow.ellipsis,
                    maxLines: 100,
                    style:
                        TextStyle(color: Color(0xff919294), fontSize: 18),
                  ),
                  padding: EdgeInsets.only(left: 20, right: 20),
                ),
              ),
              Padding(padding: EdgeInsets.all(10))
            ],
          ),
          GestureDetector(
            child: Container(
              child: Image.asset(
                R.assetsAlertClose,
                width: 20,
                height: 20,
              ),
              padding: EdgeInsets.all(10),
              alignment: Alignment.topRight,
            ),
            onTap: () {
              Navigator.of(context).pop();
            },
          )
        ],
      ),
      decoration: ShapeDecoration(
        color: Colors.white,
        shape: RoundedRectangleBorder(
          side: BorderSide(color: Colors.transparent),
          borderRadius: BorderRadius.all(
            Radius.circular(13),
          ),
        ),
      ),
    ));

最外层是一个SizedBox,目的是设置窗口的宽度,这个是固定的,而窗口的高度是动态的。 然后就遇到了第一个问题,一开始我在第二层就使用LimitedBox,如下:

SizedBox(
    width: 400,
    child: LimitedBox(
       maxHeight: 400,
    child: Container(
     ...

但是这样无论Container内容有多大,Container高度(甚至里面没有任何内容)都一直是400,而我们期望的是Container高度可以随着内容变化,而最大高度限制在400

经过尝试,将LimitedBox放在Container里层即可,具体原因还不得而知,Flutter UI嵌套感觉问题一直很多。

最终结果就像上面一样,只在内容部分的外层套一层LimitedBox来限制最大高度,这样就实现了动态变化且有最大限制。

这里还要注意,在Column中我设置了mainAxisSize: MainAxisSize.min 因为默认Column是在垂直方向上是完全展开的,这样高度就不能动态变化了,所以这里的设置让Column垂直方向上根据内容展示即可。

在flutter中由于各种ui的嵌套和各种默认值,做一些大小动态调整的组件还是需要一些工作量,要注意一些坑。