J'ai besoin de charger la liste des villes de manière dynamique à partir du service Web de repos et de laisser l'utilisateur choisir une ville dans la boîte de dialogue d'alerte. Mon code:
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),
);
},
),
),
);
}
);
});
}
Résultat - la boîte de dialogue est toujours 200x400, même si seulement 2 villes sont disponibles, il reste une pièce inutile en bas:
Comment faire la largeur/hauteur du dialogue pour s'adapter à la taille réelle des éléments? Si j'oublie les paramètres height
et width
, je reçois une exception et aucune boîte de dialogue ne s'affiche. En natif Android Java je n'ai jamais besoin de spécifier de dimensions, car la boîte de dialogue se taille automatiquement pour s'adapter).
Comment réparer mon code pour obtenir une taille de dialogue correcte? Notez que je ne connais pas le nombre d'articles, c'est dynamique.
[Éditer]
Comme suggéré, j'ai encapsulé le contenu avec une colonne:
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),
);
},
),
)
]
),
);
}
);
});
}
Résultat - exception:
I/flutter (5917): ══╡ EXCEPTION PRISE EN RENDANT LA BIBLIOTHÈQUE ╞══════════════════════════════════ ═══════════════════════ I/flutter (5917): l'assertion suivante a été émise lors de performLayout (): I/flutter (5917): RenderViewport ne soutenir le retour des dimensions intrinsèques. I/flutter (5917): Le calcul des dimensions intrinsèques nécessiterait l'instanciation de chaque enfant de la fenêtre, ce que I/flutter (5917): vainc le point de vue des fenêtres paresseuses.
Code plus générique à tester:
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: () => {},
);
},
),
)
]
),
);
}
);
Enveloppez votre Container
dans un Column
, dans le paramètre de contenu, à l'intérieur de celui-ci, définissez le mainAxisSize.min
, dans la propriété Column
Je sais qu'il est assez tard, mais avez-vous essayé cela?
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
child: ListView.builder(
shrinkWrap: true,
...
),
);
],
);
Pourriez-vous essayer cela?
Cela a fonctionné au moins pour moi. Si vous avez besoin d'un exemple, dites-moi.
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);
}
}
MISE À JOUR
Vous avez juste besoin d'afficher ce AreaPicker après le bouton ou quelque chose de pressé.
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.
}
);
},
),
)
);
}
}
Vous pouvez voir comment SimpleDialog le fait.
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),
),
),
],
),
),
);
Voilà ma dernière solution:
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);
}
}
Il est basé sur https://pub.dev/packages/easy_dialogs et jusqu'à présent, cela fonctionne bien. Je le partage, car il peut être utile, le problème n'est pas anodin.