【问题标题】:Flutter - Auto size AlertDialog to fit list contentFlutter - 自动调整大小的 AlertDialog 以适应列表内容
【发布时间】:2019-07-07 06:05:21
【问题描述】:

我需要从 REST Web 服务动态加载列表城市,并让用户从警报对话框中选择一个城市。我的代码:

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 个城市可用,底部仍有不必要的空间:

如何使对话框宽度/高度适合实际项目大小?如果我省略 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/颤振 ( 5917):在 performLayout() 期间引发了以下断言: 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: () => {},
                       );
                     },
                   ),
                 )
               ]
           ),
         );
       }
   );

【问题讨论】:

  • 尝试从容器中取出宽度和高度值?
  • 什么意思?如果缺少宽度、高度,我会收到异常“RenderViewport 不支持返回固有尺寸。”

标签: flutter listview flutter-layout size android-alertdialog


【解决方案1】:

Container 包装在Column 中,在内容参数中,在其中设置mainAxisSize.min,在Column 属性中

【讨论】:

  • 成功了! Column( mainAxisSize: MainAxisSize.min, children: &lt;Widget&gt;[ //list ], );
  • 谢谢!像魅力一样工作。我不知道它默认为mainAxisSize: MainAxisSize.max.. 有道理。
  • 我想创建一个固定大小为 150 x 150 像素的 showDialog。我已将容器宽度和高度更新为 return Container( height: 150, width: 150, );但仍然无法正常工作。我得到的是 3:2 比例的矩形框,而不是方形份额。任何建议。
  • 谢谢伙计。我一直在寻找这个答案几个小时。
【解决方案2】:

我知道已经很晚了,但你试过了吗?

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

【讨论】:

  • 这对我有用!每次我使用灵活的小部件时,它都会开始不必要地扩展。固定大小的小部件似乎有效地利用了空间。
  • 这对我来说就像在小部件上调用wrap_content 一样好:)
  • 哇!这是一个非常简单的解决方案,添加 mainAxisSize 就足够了: MainAxisSize.min
  • 谢谢。 >> mainAxisSize: MainAxisSize.min
【解决方案3】:

我也有类似的问题。 我通过在 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),
                    );
                  },
                ),
              ),
            );
          }
      );
    });
  }

【讨论】:

  • 添加了它,但现在我得到未处理的异常:无法测试从未布置过的渲染框。
  • 在这里添加scrollable ass 为我解决了这个问题,谢谢
【解决方案4】:

不要在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: [...],
    ),
  ),
)

【讨论】:

    【解决方案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),
                ),
              ),
          ],
        ),
      ),
    );
    

    【讨论】:

      【解决方案6】:

      不要使用像 listView 这样的惰性视口,并用 SingleChildScrollView 包装列

      AlertDialog 尝试使用 它的子组件,例如 ListView、GridView 和 使用惰性视口的 CustomScrollView 将不起作用。考虑 对大型内容使用滚动小部件,例如 SingleChildScrollView,避免溢出。阅读更多here

      所以你有这样的东西

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

      【讨论】:

      • 但是使用 ListView 可以正常工作。
      • 只有列表视图?能分享一下sn-p的代码吗?
      • ListView, GridView, ... 它适用于所有情况。这是sample code
      【解决方案7】:

      我遇到了一个非常相似的问题,我想出了一个适用于 Material 和 Cupertino 的解决方案。

      与带有可滚动标志 = true 的警报对话框和带有 mainAxisSize: MainAxisSize.min 的列所提供的性能相比,性能(特别是如果元素列表变长)在加载和滚动内容方面要好得多- 看看这里的视频:https://www.youtube.com/watch?v=2nKTGFZosr0

      此外,对话框的标题不会与其余元素一起“向上滚动”(类似于您的解决方案),因此您可以在顶部添加一个过滤工具并仅显示与搜索短语匹配的元素.

      源代码在这里https://github.com/hicnar/fluttery_stuff 只需检查整个内容并运行位于 lib/dialogs/main.dart 中的 main() 显然您可以以任何您喜欢的方式复制、粘贴、修改和使用它。这里没有版权。

      最后,在示例中,我将基于 ListView 的对话框内容的高度限制为屏幕的最大 45% 高度,您会轻松找到它,如果将因子更改为 1.0,您将获得与来自基于列的方法(搜索名为 screenHeightFactor 的字段)

      【讨论】:

        【解决方案8】:

        你能试试这个吗?
        它至少对我有用。 如果你需要一个例子告诉我。

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

        【讨论】:

        • 在我的例子中如何使用它?
        • 我添加了未绑定到我的数据源的通用代码,用于快速测试。它抛出异常。
        • 我想显示alertDialog,里面有列表视图。看看我的问题中包含的图片。
        • 如我所见,您编写了自定义对话框,但我不知道如何显示它
        • 我的结果:i.imgur.com/fCAJCJA.png 文字居中,还有额外的空间
        【解决方案9】:

        这就是我的最终解决方案:

        import 'package:flutter/material.dart';
        import 'package:flutter/foundation.dart';
        
        typedef Widget ItemBuilder<T>(T item);
        
        class CityChoiceDialog<T> extends StatefulWidget {
          final T initialValue;
          final List<T> items;
          final ValueChanged<T> onSelected;
          final ValueChanged<T> onSubmitted;
          final ValueChanged<T> onCancelled;
          final Widget title;
          final EdgeInsetsGeometry titlePadding;
          final EdgeInsetsGeometry contentPadding;
          final String semanticLabel;
          final ItemBuilder<T> itemBuilder;
          final List<Widget> actions;
          final Color activeColor;
          final String cancelActionButtonLabel;
          final String submitActionButtonLabel;
          final Color actionButtonLabelColor;
        
          final Widget divider;
        
          CityChoiceDialog({
            Key key,
            this.initialValue,
            @required this.items,
            this.onSelected,
            this.onSubmitted,
            this.onCancelled,
            this.title,
            this.titlePadding,
            this.contentPadding = const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
            this.semanticLabel,
            this.actions,
            this.itemBuilder,
            this.activeColor,
            this.cancelActionButtonLabel,
            this.submitActionButtonLabel,
            this.actionButtonLabelColor,
            this.divider = const Divider(height: 0.0),
          })  : assert(items != null),
                super(key: key);
        
          @override
          _CityChoiceDialogState<T> createState() =>
              _CityChoiceDialogState<T>();
        }
        
        class _CityChoiceDialogState<T>
            extends State<CityChoiceDialog<T>> {
          T _chosenItem;
        
          @override
          void initState() {
            _chosenItem = widget.initialValue;
            super.initState();
          }
        
          @override
          Widget build(BuildContext context) {
            return MyAlertDialog(
              title: widget.title,
              titlePadding: widget.titlePadding,
              contentPadding: widget.contentPadding,
              semanticLabel: widget.semanticLabel,
              content: _buildContent(),
              actions: _buildActions(),
              divider: widget.divider,
            );
          }
        
          _buildContent() {
            return ListView(
              shrinkWrap: true,
              children: widget.items
                  .map(
                    (item) => RadioListTile(
                  title: widget.itemBuilder != null
                      ? widget.itemBuilder(item)
                      : Text(item.toString()),
                  activeColor:
                  widget.activeColor ?? Theme.of(context).accentColor,
                  value: item,
                  groupValue: _chosenItem,
                  onChanged: (value) {
                    if (widget.onSelected != null) widget.onSelected(value);
                    setState(() {
                      _chosenItem = value;
                    });
                  },
                ),
              )
                  .toList(),
            );
          }
        
          _buildActions() {
            return widget.actions ??
                <Widget>[
                  FlatButton(
                    textColor:
                    widget.actionButtonLabelColor ?? Theme.of(context).accentColor,
                    child: Text(widget.cancelActionButtonLabel ?? 'ANULUJ'),
                    onPressed: () {
                      Navigator.pop(context);
                      if (widget.onCancelled!= null) widget.onCancelled(_chosenItem);
                    },
                  ),
                  FlatButton(
                    textColor:
                    widget.actionButtonLabelColor ?? Theme.of(context).accentColor,
                    child: Text(widget.submitActionButtonLabel ?? 'WYBIERZ'),
                    onPressed: () {
                      Navigator.pop(context);
                      if (widget.onSubmitted != null) widget.onSubmitted(_chosenItem);
                    },
                  )
                ];
          }
        }
        
        class MyAlertDialog<T> extends StatelessWidget {
          const MyAlertDialog({
            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,
            this.divider = const Divider(
              height: 0.0,
            ),
            this.isDividerEnabled = true,
          })  : 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;
          final Widget divider;
        
          final bool isDividerEnabled;
        
          @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, isDividerEnabled ? 20.0 : 0.0),
                child: new DefaultTextStyle(
                  style: Theme.of(context).textTheme.title,
                  child: new Semantics(child: title, namesRoute: true),
                ),
              ));
              if (isDividerEnabled) children.add(divider);
            } 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) {
              if (isDividerEnabled) children.add(divider);
              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);
          }
        }
        

        它基于https://pub.dev/packages/easy_dialogs,到目前为止它运行良好。我分享一下,可能有用,问题不小。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-07-02
          • 2019-01-20
          • 2011-11-03
          • 2013-06-05
          • 2021-09-16
          • 2011-08-20
          • 1970-01-01
          相关资源
          最近更新 更多