Flutter - 自动调整AlertDialog大小以适应列表内容

90

我需要从rest webservice动态加载城市列表,并允许用户从警告对话框中选择一个城市。

我的代码:

createDialog() {

    fetchCities().then((response) {

      showDialog(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              title: Text('Wybierz miasto'),
              content: Container(
                height: 200.0,
                width: 400.0,
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: response.length,
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(
                      title: Text(response[index].name),
                      onTap: () => citySelected(response[index].id),
                    );
                  },
                ),
              ),
            );
          }
      );
    });
  }

结果-对话框始终为200x400,即使只有2个城市可用,底部仍留有不必要的空间:

enter image description here

如何使对话框的宽度/高度适合实际项目大小?如果省略heightwidth参数,将会收到异常并且不显示对话框。 在本机Android Java中,我从不需要指定任何维度,因为对话框会自动调整大小以适应。

如何修改我的代码以正确调整对话框大小?注意:我不知道项目计数,它是动态的。

[编辑]

如建议的那样,我用列包装内容:

createDialog() {
    fetchCities().then((response) {
      showDialog(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              title: Text('Wybierz miasto'),
              content: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    Container(
                      child: ListView.builder(
                        shrinkWrap: true,
                        itemCount: response.length,
                        itemBuilder: (BuildContext context, int index) {
                          return ListTile(
                            title: Text(response[index].name),
                            onTap: () => citySelected(response[index].id),
                          );
                        },
                      ),
                    )
                  ]
              ),
            );
          }
      );
    });
  }

结果 - 异常:

I/flutter ( 5917): ══╡ 渲染库捕获的异常 ╞═════════════════════════════════════════════════════════ I/flutter ( 5917): 在执行布局过程中抛出了以下断言: I/flutter ( 5917): RenderViewport不支持返回固有尺寸。 I/flutter ( 5917): 计算固有尺寸需要实例化视口的每个子项, I/flutter ( 5917): 这违反了视口惰性加载的目标。

更通用的测试代码:

showDialog(
       context: context,
       builder: (BuildContext context) {
         return AlertDialog(
           title: Text('Select city'),
           content: Column(
               mainAxisSize: MainAxisSize.min,
               children: <Widget>[
                 Container(
                   child: ListView.builder(
                     shrinkWrap: true,
                     itemCount: 2,
                     itemBuilder: (BuildContext context, int index) {
                       return ListTile(
                         title: Text("City"),
                         onTap: () => {},
                       );
                     },
                   ),
                 )
               ]
           ),
         );
       }
   );

尝试将容器的宽度和高度值去掉? - Tom Alabaster
什么意思?如果缺少宽度、高度,我会收到异常信息“RenderViewport不支持返回内在尺寸。” - user1209216
11个回答

236

将您的 Container 包装在一个 Column 中,在其 content 参数内,设置 mainAxisSize.min,在 Column 的属性中

Container(
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      ...
    ],
  )
)

3
完成了!Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ //列表 ], ); - Vitali
1
谢谢!运行得很好。 我不知道它默认为 mainAxisSize: MainAxisSize.max.. 很合理。 - Ethan K
1
我想创建一个固定大小为150 x 150像素的showDialog。我已经将容器的宽度和高度更新为return Container( height: 150, width: 150, );但仍然无法正常工作。我得到了一个3:2比例的矩形框,而不是正方形。有什么建议吗? - Kamlesh
不需要容器,只需要mainAxisSize即可。 - alones

70

我知道现在已经很晚了,但是你试过这个吗?

Column(
    mainAxisSize: MainAxisSize.min,
    children: <Widget>[
       Container(
         child: ListView.builder(
           shrinkWrap: true,
           ...
         ),
       )
    ],
);

这对我有用!每次使用Flexible小部件时,它都开始不必要地扩展。固定大小的小部件似乎更有效地利用了空间。 - Michel Thomas
这对我来说就像在小部件上调用 wrap_content 一样好 :) - benChung
2
哇!这是非常简单的解决方案,只需要添加mainAxisSize:MainAxisSize.min就足够了。 - AlexPad
mainAxisSize: MainAxisSize.min - bamossza

50

不要在 Column 中设置 mainAxisSize.min,否则如果内容超出视口大小,则可能会遇到溢出错误。要解决此问题,请使用以下方法之一。

1. 在 AlertDialog 中设置 scrollable: true

AlertDialog(
  scrollable: true, // <-- Set it to true
  content: Column(
    children: [...],
  ),
)

2. 将 Column 包装在 SingleChildScrollView 中:

AlertDialog(
  content: SingleChildScrollView( 
    child: Column(
      children: [...],
    ),
  ),
)

3. 在ListView中设置shrinkWrap: true

AlertDialog(
  content: SizedBox(
    width: double.maxFinite,
    child: ListView(
      shrinkWrap: true, // <-- Set this to true
      children: [...],
    ),
  ),
)

16

我有一个类似的问题。 我通过在AlertDialog中添加scrollable: true来解决它。

更新后的代码将是:

 createDialog() {
    fetchCities().then((response) {
      showDialog(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              scrollable: true,
              title: Text('Wybierz miasto'),
              content: Container(
                height: 200.0,
                width: 400.0,
                child: ListView.builder(
                  shrinkWrap: true,
                  itemCount: response.length,
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(
                      title: Text(response[index].name),
                      onTap: () => citySelected(response[index].id),
                    );
                  },
                ),
              ),
            );
          }
      );
    });
  }

已添加,但现在出现了未处理的异常:无法测试从未布局过的渲染框。 - giorgio79
在这里添加 scrollable 属性解决了我的问题,谢谢。 - fsulser

5
你可以看一下SimpleDialog是如何实现的。
Widget dialogChild = IntrinsicWidth(
  stepWidth: 56.0,
  child: ConstrainedBox(
    constraints: const BoxConstraints(minWidth: 280.0),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        if (title != null)
          Padding(
            padding: titlePadding,
            child: DefaultTextStyle(
              style: theme.textTheme.title,
              child: Semantics(namesRoute: true, child: title),
            ),
          ),
        if (children != null)
          Flexible(
            child: SingleChildScrollView(
              padding: contentPadding,
              child: ListBody(children: children),
            ),
          ),
      ],
    ),
  ),
);

3
不要使用像listView这样的惰性视口,而是用SingleChildScrollView包装列。

AlertDialog尝试使用其子项的内在尺寸来调整自身大小,诸如ListView、GridView和CustomScrollView之类使用惰性视口的小部件将无法工作。考虑使用滚动小部件处理大型内容,例如SingleChildScrollView,以避免溢出。在这里阅读更多信息!

所以你有这样的东西
SingleChildScrollView(          
   child: Column(
      mainAxisSize: MainAxisSize.min,
        children: <Widget>[
           Container(
             child: ListView.builder(
               shrinkWrap: true,
           ...
         ),
       )
    ],
);

但是使用 ListView 可以正常工作。 - iDecode
仅限 ListView?你能分享一段代码片段吗? - Asquare17
ListView、GridView...在所有情况下都能正常工作。这里是示例代码 - iDecode

1
return AlertDialog(
  shape: ShapeConstant.shapeBorder(radius: 18, borderSide: false),
  actionsPadding: PaddingConstant.defaultPadding16,
  insetPadding: const EdgeInsets.all(64.0),
  contentPadding: PaddingConstant.defaultPadding,
  title: BaseDialogTopBar.baseDialogTopBar(
      isVisible: false,
      text: StringConst.settingText9,
      onSubmit: () {
        Navigator.pop(context);
      }),
  actions: [
    Align(
      alignment: Alignment.center,
      child: Buttons.basePositiveBtn(
          width: 200,
          widgetKey: "widgetKey",
          text: "add",
          onSubmit: () {
            debugPrint("callClick");
          }),
    )
  ],
  content: SizedBox(
    width: 500,
    child: ListView.builder(
      itemCount: colorList.length,
      shrinkWrap: true,
      itemBuilder: (context, int index) {
        return ListTile(
          title: Text(colorList[index]),
          onTap: () {
            Navigator.pop(context, colorList[index]);
          },
        );
      },
    ),
  ),
);

目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

1
我曾经遇到过非常类似的问题,并想出了一个既适用于Material又适用于Cupertino的解决方案。与具有scrollable标志=true和mainAxisSize: MainAxisSize.min的Column的警报对话框相比,尤其是当元素列表变得很长时,性能(特别是内容的加载和滚动)要好得多-只需在此处查看视频:https://www.youtube.com/watch?v=2nKTGFZosr0
此外,对话框的标题不会随着其他元素一起“向上滚动”(与您的解决方案类似),因此您可以在顶部添加筛选工具,并仅显示与搜索短语匹配的元素。
源代码在此处可用https://github.com/hicnar/fluttery_stuff。只需检出整个文件并运行位于lib/dialogs/main.dart中的main()即可。显然,您可以复制,粘贴,修改并以任何方式使用它。没有版权问题。
最后,在这个例子中,我限制了ListView对话框内容的高度,使其最大为屏幕高度的45%。您会很容易地找到它,如果您将因子更改为1.0,则会获得与基于Column方法相同的大小行为(搜索名为screenHeightFactor的字段)。

0
除了已经提到的内容部分,使用FractionallySizedBox包装AlertDialog可以为艺术区域提供动态高度和宽度。

0

你能试一下吗?
至少对我来说是有效的。 如果你需要一个例子,告诉我。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class SmartDialog extends StatelessWidget {
  const SmartDialog({
    Key key,
    this.title,
    this.titlePadding,
    this.content,
    this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0),
    this.actions,
    this.semanticLabel,
  }) : assert(contentPadding != null),
       super(key: key);

  final Widget title;
  final EdgeInsetsGeometry titlePadding;
  final Widget content;
  final EdgeInsetsGeometry contentPadding;
  final List<Widget> actions;
  final String semanticLabel;

  @override
  Widget build(BuildContext context) {
    final List<Widget> children = <Widget>[];
    String label = semanticLabel;

    if (title != null) {
      children.add(new Padding(
        padding: titlePadding ?? new EdgeInsets.fromLTRB(24.0, 24.0, 24.0, content == null ? 20.0 : 0.0),
        child: new DefaultTextStyle(
          style: Theme.of(context).textTheme.title,
          child: new Semantics(child: title, namesRoute: true),
        ),
      ));
    } else {
      switch (defaultTargetPlatform) {
        case TargetPlatform.iOS:
          label = semanticLabel;
          break;
        case TargetPlatform.android:
        case TargetPlatform.fuchsia:
          label = semanticLabel ?? MaterialLocalizations.of(context)?.alertDialogLabel;
      }
    }

    if (content != null) {
      children.add(new Flexible(
        child: new Padding(
          padding: contentPadding,
          child: new DefaultTextStyle(
            style: Theme.of(context).textTheme.subhead,
            child: content,
          ),
        ),
      ));
    }

    if (actions != null) {
      children.add(new ButtonTheme.bar(
        child: new ButtonBar(
          children: actions,
        ),
      ));
    }

    Widget dialogChild = new Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: children,
    );

    if (label != null)
      dialogChild = new Semantics(
        namesRoute: true,
        label: label,
        child: dialogChild
      );

    return new Dialog(child: dialogChild);
  }
}

更新

只需在按下按钮或其他触发后显示此区域选择器。

class AreaPicker extends StatelessWidget {
  final List<Area> items;
  AreaPicker(this.items);
  @override
  Widget build(BuildContext context) {
    return SmartDialog(
      title: Text('Select Area'),
      actions: <Widget>[
        FlatButton(
          textColor: Colors.black,
          child: Text('Rather not say'),
          onPressed: () {
            Navigator.of(context, rootNavigator: true).pop();
          },
        )
      ],
      content: Container(
        height: MediaQuery.of(context).size.height / 4,
        child: ListView.builder(
          shrinkWrap: true,
          itemExtent: 70.0,
          itemCount: areas.length,
          itemBuilder: (BuildContext context, int index) {
            final Area area = areas[index];
            return GestureDetector(
              child: Center(
                child: Text(area.name),
              ),
              onTap: () { 
                Navigator.of(context, rootNavigator: true).pop();
                // some callback here.
              }
            );
          },
        ),
      )
    );
  }
}

如何在我的示例中使用它? - user1209216
我添加了通用代码,不绑定我的数据源,以进行快速测试。它会抛出异常。 - user1209216
我想展示一个带有列表视图的AlertDialog。请看我在问题中附加的图片。 - user1209216
据我所见,您编写了自定义对话框,但我不确定如何显示它。 - user1209216
我的结果:https://i.imgur.com/fCAJCJA.png 文本居中,还有额外的空间。 - user1209216
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接