提问人:user1209216 提问时间:2/13/2019 最后编辑:saeeduser1209216 更新时间:10/25/2022 访问量:106045
Flutter - 自动调整 AlertDialog 的大小以适应列表内容
Flutter - Auto size AlertDialog to fit list content
问:
我需要从 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 个城市可用,底部也会留下一个不必要的空间:
如何使对话框宽度/高度适合实际项目大小?如果我省略和参数,我会得到异常并且没有显示对话框。在原生 Android Java 中,我不需要指定任何尺寸,因为对话框会自动调整大小以适应。height
width
如何修复我的代码以正确调整对话框大小?注意:我不知道项目计数,它是动态的。
[编辑]
按照建议,我用列包装了内容:
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: () => {},
);
},
),
)
]
),
);
}
);
答:
将 括在 里面 ,在 content 参数中,在 Column 属性中设置Container
Column
mainAxisSize.min
Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...
],
)
)
评论
Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ //list ], );
mainAxisSize: MainAxisSize.max
你能试试这个吗?
它至少对我有用。
如果你需要一个例子,请告诉我。
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.
}
);
},
),
)
);
}
}
评论
我知道已经很晚了,但你试过这个吗?
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: ListView.builder(
shrinkWrap: true,
...
),
)
],
);
评论
wrap_content
这就是我的最终解决方案:
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 到目前为止工作正常。我正在分享它,因为它可能有用,问题不是微不足道的。
您可以看看 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),
),
),
],
),
),
);
我也有类似的问题。
我通过添加:在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
我遇到了一个非常类似的问题,我想出了一个适用于 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 的字段)
不要在列
中设置 mainAxisSize.min
,否则如果内容长于视口,可能会遇到溢出错误。若要解决此问题,请使用以下任一方法。
1. 设置:scrollable: true
AlertDialog
AlertDialog(
scrollable: true, // <-- Set it to true
content: Column(
children: [...],
),
)
2. 包裹:Column
SingleChildScrollView
AlertDialog(
content: SingleChildScrollView(
child: Column(
children: [...],
),
),
)
3. 设置:shrinkWrap: true
ListView
AlertDialog(
content: SizedBox(
width: double.maxFinite,
child: ListView(
shrinkWrap: true, // <-- Set this to true
children: [...],
),
),
)
不要使用像 listView 这样的惰性视区,而是使用 SingleChildScrollView 包装列
AlertDialog 尝试使用 它的子项、小部件(如 ListView、GridView 和 使用惰性视口的 CustomScrollView 将不起作用。考虑 将滚动小组件用于大型内容,例如 SingleChildScrollView,以避免溢出。在这里阅读更多内容!
所以你有这样的东西
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: ListView.builder(
shrinkWrap: true,
...
),
)
],
);
评论
ListView
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]);
},
);
},
),
),
);
评论
除了已经说过的内容部分之外,将 AlertDialog 与 FractionallySizedBox 包装在一起,以便为艺术框提供动态高度和宽度。
评论