Flutter: SimpleDialog 中的 ListView

我想用这段代码在我的 Flutter 应用程序中显示一个带 ListView.build 的 SimpleDialog:

showDialog(
context: context,
builder: (BuildContext context) {
return new SimpleDialog(
children: <Widget>[
new FittedBox(
child: new ListView(
children: <Widget>[
new Text("one"),
new Text("two"),
],
),
)
],
);
},
);

它会产生这个错误(对不起,我不能将日志包装为代码,因为 Stackoverflow 抱怨代码太多) :

毕业典礼上被爆出的例外情况: 毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典礼上,毕业典 I/flutter (4481) : 在 PerformLayout ()期间抛出以下断言: I/flutter (4481) : RenderViewport 不支持返回内部维度。 I/flutter (4481) : 计算内部维度将需要实例化 viewport 的每个子级,其 I/flutter (4481) : 击败了视图懒惰的观点。 I/flutter (4481) : 如果您只是试图在主轴方向收缩视图,请考虑 I/flutter (4481) : RenderShrinkWrappingViewport 呈现对象(ShrinkWrappingViewport 小部件) ,它实现了 I/flutter (4481) : 没有实现本征维度 API 的效果。 I/flutter (4481) : ... I/flutter (4481) : 另一个异常被抛出: RenderBox 未被布局: RenderPhysicalShape # 83d92 relayoutBorder = up2 NEEDS-PAINT I/flutter (4481) : 另一个异常被抛出: ‘ package: flutter/src/rending/shift _ box. dart’: 失败断言: 第310行 pos 12: ‘ child. hasSize’: 不正确。 I/flutter (4481) : 另一个异常被抛出: RenderBox 未被布局: RenderPhysicalForm # 83d92 relayoutBorder = up2

我尝试使用具有特定高度和宽度的 Container,它工作正常,但是我希望 ListView 能够适应对话框。

如何在 SimpleDialog 中包含 ListView?

87970 次浏览

you can create a separate method method for SimpleDialogOptions code below:

final SimpleDialog dialog = new SimpleDialog(
title: const Text('Select assignment'),
children: <Widget>[
new SimpleDialogOption(
onPressed: () { Navigator.pop(context); },
child: const Text('Text one'),
),
new SimpleDialogOption(
onPressed: () {},
child: const Text('Text two'),
),
],
);
return dialog;

I found a way... Although it's a bit hacky, and so there may be a better option.

You'll need this package:

import 'package:flutter/services.dart';

Create the widget:

class MyDialog extends StatefulWidget {
MyDialog ({Key key}) : super(key: key);
MyDialogState createState() => new MyDialogState();
}
class MyDialogState extends State<MyDialog> {

If the screen rotates it screws things up because the dialog maintains it's original size. You can probably fix that with a bit of effort, but I just lock it to prevent rotating, like this:

  @override
initState() { super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}

Then I unlock it like this at the end:

  @override
dispose() { super.dispose();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}

So that stops the dialog from screwing up. Then I get the size and width of the screen in the build method:

@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;

Then followed with this layout:

 return ConstrainedBox(
constraints: BoxConstraints(maxHeight: height, maxWidth: width),
child: Column(
children: <Widget>[
Expanded(
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(20.0),
crossAxisSpacing: 10.0,
crossAxisCount: 3,
children: _images
)
),
]
),
);
}

..again, I don't think it's the best, but it's been working for me so far.

Tried it with itemExtent property first, but that doesn't work. Simply wrap the ListView in a Container with defined height and width, if your items are static.

showDialog(
context: context,
builder: (BuildContext context) {
return new SimpleDialog(
children: <Widget>[
new Container(
height: 100.0,
width: 100.0,
child: new ListView(
children: <Widget>[
new Text("one"),
new Text("two"),
],
),
)
],
);
},
);

Just wrap ListView.builder in a Container with a specific height and width.

Widget setupAlertDialoadContainer() {
return Container(
height: 300.0, // Change as per your requirement
width: 300.0, // Change as per your requirement
child: ListView.builder(
shrinkWrap: true,
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Gujarat, India'),
);
},
),
);
}

And call the above method in showDialog.

showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Country List'),
content: setupAlertDialoadContainer(),
);
});

EDITED:

You can go with @Rap's comment too.

This is a more general answer for future visitors.

How to create a dialog with a list

If you want a dialog with a ListView, you should consider a SimpleDialog. A SimpleDialog is designed to show options in a list (as opposed to an AlertDialog, which is meant to notify the user of something).

Here is a simple example:

enter image description here

The process of creating a SimpleDialog is basically the same as for an AlertDialog (they are both based on Dialog), except that you define list item widgets called SimpleDialogOptions instead of buttons. When a list option is pressed a callback is fired that you can respond to.

  // set up the list options
Widget optionOne = SimpleDialogOption(
child: const Text('horse'),
onPressed: () {},
);
Widget optionTwo = SimpleDialogOption(
child: const Text('cow'),
onPressed: () {},
);
Widget optionThree = SimpleDialogOption(
child: const Text('camel'),
onPressed: () {},
);
Widget optionFour = SimpleDialogOption(
child: const Text('sheep'),
onPressed: () {},
);
Widget optionFive = SimpleDialogOption(
child: const Text('goat'),
onPressed: () {},
);


// set up the SimpleDialog
SimpleDialog dialog = SimpleDialog(
title: const Text('Choose an animal'),
children: <Widget>[
optionOne,
optionTwo,
optionThree,
optionFour,
optionFive,
],
);


// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
},
);

Handling option presses

When a user clicks an item you can close the dialog an perform some action.

  Widget optionOne = SimpleDialogOption(
child: const Text('horse'),
onPressed: () {
Navigator.of(context).pop();
_doSomething();
},
);

Notes

Code

Here is the full code for the example above.

main.dart

import 'package:flutter/material.dart';


void main() => runApp(MyApp());


class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SimpleDialog',
home: Scaffold(
appBar: AppBar(
title: Text('SimpleDialog'),
),
body: MyLayout()),
);
}
}


class MyLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
child: Text('Show alert'),
onPressed: () {
showAlertDialog(context);
},
),
);
}
}


// replace this function with the examples above
showAlertDialog(BuildContext context) {


// set up the list options
Widget optionOne = SimpleDialogOption(
child: const Text('horse'),
onPressed: () {
print('horse');
Navigator.of(context).pop();
},
);
Widget optionTwo = SimpleDialogOption(
child: const Text('cow'),
onPressed: () {
print('cow');
Navigator.of(context).pop();
},
);
Widget optionThree = SimpleDialogOption(
child: const Text('camel'),
onPressed: () {
print('camel');
Navigator.of(context).pop();
},
);
Widget optionFour = SimpleDialogOption(
child: const Text('sheep'),
onPressed: () {
print('sheep');
Navigator.of(context).pop();
},
);
Widget optionFive = SimpleDialogOption(
child: const Text('goat'),
onPressed: () {
print('goat');
Navigator.of(context).pop();
},
);


// set up the SimpleDialog
SimpleDialog dialog = SimpleDialog(
title: const Text('Choose an animal'),
children: <Widget>[
optionOne,
optionTwo,
optionThree,
optionFour,
optionFive,
],
);


// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
},
);
}

Wraping the ListView in a Container and giving it a width: double.maxFinite, solves the problem with iOS/Android having trouble with ListView inside a dialog:

showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
width: double.maxFinite,
child: ListView(
children: <Widget>[]
),
),
);
}
);

In the case of a ListView inside a Column that is inside an AlertDialog:

showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
width: double.maxFinite,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[]
)
)
]
),
),
);
}
);
showDialog(context: parentcontext,
builder: (context){
return SimpleDialog(
title: Text('create post'),
children: <Widget>[
SimpleDialogOption(
onPressed: handleImageTaking,
child: Text('take a pic'),
),
SimpleDialogOption(
onPressed: handleImageSelecting,
child: Text('select a pic'),
),
SimpleDialogOption(
child: Text('cancel'),
onPressed: (){
Navigator.pop(context);
},
)
],
);
});

Used SingleChildScrollView with physics: NeverScrollableScrollPhysics() and shrinkWrap: true in your ListView

  showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: widget,
content: SingleChildScrollView( //MUST TO ADDED
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...//any widgets,
ListView.builder(
shrinkWrap: true, //MUST TO ADDED
physics: NeverScrollableScrollPhysics(), //MUST TO ADDED
itemCount: model.length,
itemBuilder: (BuildContext c, int index) {
return ListTile();
})
],
),
),
);
},
);

SImply in the content we need to use a Container with fixed height and width. and the use ListView as a child of Container.

 scrollDialogFunction(){
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('List of Index'),
content: Container(
width: 350.0,
height: 250,// Change as per your requirement
child: ListView.builder(
itemCount: 150,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(index.toString()),
);
},
),
),
actions: [Padding(
padding: const EdgeInsets.all(8.0),
child: Text("ok",style: TextStyle(fontSize: 18),),
)],
);
});
}

adding width: double.maxFinite to the container solved my problem.

 @override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Sample Dialog'),
content: Container(
width: double.maxFinite,
child: ListView(
children: <Widget>[
Text('Item 1'),
Text('Item 2'),
],
),
),
);
}

Iterate List with spread operator (...) or if List can be null then use null-aware spread operator (...?)

List<String> values = ['one', 'two', 'three'];
await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text("Select Value"),
children: [
...values.map((value) {
return SimpleDialogOption(child: Text(value));
}),
],
);
},
);

Using Listview:

List<String> values = ['one', 'two', 'three'];
await showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text("Select Value"),
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (ctx, index) {
return SimpleDialogOption(
onPressed: () => Navigator.pop(context),
child: Center(
child: Text(values[index]),
),
);
},
itemCount: values.length,
),
)
],
);
},
);

Output:

enter image description here

Use predefined width for the container which wraps the ListView.builder(). This code will help you

  • Shrink wrap to control the height from going infinity.

  • Using Limited Width for the Listview

           showDialog(
    context: context,
    builder: (context) => AlertDialog(
    title: Text('Orders'),
    content: SizedBox(
    width: double.maxFinite,  //  <------- Use SizedBox to limit width
    child: ListView.builder(
    shrinkWrap: true,  //            <------  USE SHRINK WRAP
    itemCount: 5,
    itemBuilder: (context, index) =>
    Text('Order'),
    ),
    ),
    ),
    );
    

A good alernative is setting scrollAble to true in AlertDialog and using a Column as content. This will scale to fit the screen

AlertDialog(
scrollable: true,
title: Text("Gjenta sjekkliste"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: choices
.map((e) => ListTile(
title: Text(e ?? "Ingen gjentakelse"),
onTap: () {
Navigator.pop(context, e);
},
))
.toList(),
),
);

I also had the same problem and it was solved by below:

  @override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Sample Dialog'),
content: Container(
width: double.maxFinite, //<- this line is important
child: ListView(
shrinkWrap: true, //<- this line is important
children: <Widget>[
Text('Item 1'),
Text('Item 2'),
Text('Item 3'),
],
),
),
);
}

See details here.

A bit late but I have found a way to solve this problem without creating a container so you will have a dynamic sized list. Instead of creating a listview, map the list to a widget then put the resulting widgets inside a column children, also don't forget to set the column size to min, that way it will only expand as much as it needs to.

    showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: list.map((element) =>
Text(element.text)).toList(),
),
);
},
);