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

Flutter - Auto size AlertDialog to fit list content

提问人:user1209216 提问时间:2/13/2019 最后编辑:saeeduser1209216 更新时间:10/25/2022 访问量:106045

问:

我需要从 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 个城市可用,底部也会留下一个不必要的空间:

enter image description here

如何使对话框宽度/高度适合实际项目大小?如果我省略和参数,我会得到异常并且没有显示对话框。在原生 Android Java 中,我不需要指定任何尺寸,因为对话框会自动调整大小以适应。heightwidth

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

[编辑]

按照建议,我用列包装了内容:

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: () => {},
                       );
                     },
                   ),
                 )
               ]
           ),
         );
       }
   );
flutter listview flutter-layout 大小 android-alertdialog

评论

0赞 Tom Alabaster 2/13/2019
尝试从容器中删除宽度和高度值?
0赞 user1209216 2/13/2019
你是什么意思?如果缺少宽度、高度,我会收到异常“RenderViewport 不支持返回内部尺寸”。

答:

235赞 Fellipe Malta 2/13/2019 #1

将 括在 里面 ,在 content 参数中,在 Column 属性中设置ContainerColumnmainAxisSize.min

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

评论

3赞 Vitali 8/27/2019
工作!Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ //list ], );
1赞 Ethan K 1/23/2020
谢谢!像魅力一样工作。我不知道它默认为 ..意义。mainAxisSize: MainAxisSize.max
1赞 Kamlesh 6/13/2021
我想创建一个固定大小为 150 x 150 像素的 showDialog。我已将容器宽度和高度更新为返回容器(高度:150,宽度:150,);但仍然不起作用。我得到的是 3:2 比例的矩形框而不是正方形份额。任何建议。
0赞 alones 4/23/2023
容器不是必需的。只需 mainAxisSize 就足够了。
0赞 Daibaku 2/13/2019 #2

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

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

更新

您只需要在按下按钮或按下某些东西后显示此 AreaPicker。

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

评论

0赞 user1209216 2/13/2019
如何在我的示例中使用它?
0赞 user1209216 2/13/2019
我添加了未绑定到数据源的通用代码,以便快速测试。它引发异常。
0赞 user1209216 2/13/2019
我想显示alertDialog,里面有列表视图。看看我在问题中包含的图片。
0赞 user1209216 2/13/2019
正如我所看到的,您编写了自定义对话框,但我不确定如何显示它
0赞 user1209216 2/14/2019
我的结果:i.imgur.com/fCAJCJA.png 文本居中,并且还有额外的空间
70赞 bshears 8/27/2019 #3

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

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

评论

0赞 Michel Thomas 11/24/2019
这对我有用!每次我使用灵活小部件时,它都会开始不必要地扩展。固定大小的小部件似乎有效地利用了空间。
0赞 benChung 1/10/2020
对我来说,这就像调用小部件一样好:)wrap_content
2赞 AlexPad 3/11/2020
哇!这是一个非常简单的解决方案,只需添加 mainAxisSize: MainAxisSize.min 即可
0赞 bamossza 6/15/2020
谢谢。>> mainAxisSize: MainAxisSize.min
0赞 user1209216 9/9/2019 #4

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

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 到目前为止工作正常。我正在分享它,因为它可能有用,问题不是微不足道的。

5赞 Panda World 2/26/2020 #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),
            ),
          ),
      ],
    ),
  ),
);
16赞 Prashant Dwivedi 6/25/2020 #6

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

评论

0赞 giorgio79 6/18/2021
添加了它,但现在我得到了未处理的异常:无法命中测试从未布局过的渲染框。
0赞 fsulser 11/23/2021
在这里添加屁股为我解决了这个问题,谢谢scrollable
1赞 hicnar 12/8/2020 #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 的字段)

50赞 CopsOnRoad 12/31/2020 #8

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

1. 设置:scrollable: trueAlertDialog

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

2. 包裹:ColumnSingleChildScrollView

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

3. 设置:shrinkWrap: trueListView

AlertDialog(
  content: SizedBox(
    width: double.maxFinite,
    child: ListView(
      shrinkWrap: true, // <-- Set this to true
      children: [...],
    ),
  ),
)
3赞 Asquare17 1/15/2021 #9

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

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

所以你有这样的东西

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

评论

0赞 iDecode 10/10/2021
但是使用一个可以按预期工作。ListView
0赞 Asquare17 10/11/2021
只有 listview?你能分享一个代码片段吗?
0赞 iDecode 10/11/2021
ListView, , ...它适用于所有情况。示例代码如下GridView
1赞 Ved Prakash 7/20/2022 #10
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]);
          },
        );
      },
    ),
  ),
);

评论

0赞 Community 7/22/2022
正如目前所写的那样,你的答案尚不清楚。请编辑以添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。您可以在帮助中心找到有关如何写出好答案的更多信息。
0赞 Drogbut 10/25/2022 #11

除了已经说过的内容部分之外,将 AlertDialog 与 FractionallySizedBox 包装在一起,以便为艺术框提供动态高度和宽度。