24
votes

Flutter - alerte de taille automatique

Je dois charger dynamiquement les villes de liste à partir du service Web de repos et laisser l'utilisateur choisir une ville dans la boîte de dialogue d'alerte. Mon code:

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: () => {},
                       );
                     },
                   ),
                 )
               ]
           ),
         );
       }
   );

Résultat - la boîte de dialogue est toujours de 200x400, même si seulement 2 villes sont disponibles, il reste de la place inutile en bas:

entrez la description de l'image ici

Comment adapter la largeur / hauteur du dialogue à la taille réelle des éléments? Si j'omets les paramètres de height et de width , je reçois une exception et aucune boîte de dialogue ne s'affiche. Dans Android Java natif, 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 que la boîte de dialogue soit correctement dimensionnée? Notez que je ne connais pas le nombre d'articles, c'est dynamique.

[Éditer]

Comme suggéré, j'ai enveloppé le contenu avec la 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 COMPTE PAR LA BIBLIOTHÈQUE DE RENDU Â • žâ • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • â • I / flutter (5917): l'assertion suivante a été lancée pendant performLayout ( ): I / flutter (5917): RenderViewport ne prend pas en charge le renvoi de dimensions intrinsèques. I / flutter (5917): Le calcul des dimensions intrinsèques nécessiterait d'instancier chaque enfant de la fenêtre, qui I / flutter (5917): annule le point de vue des fenêtres paresseux.

Code plus générique à tester:

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


2 commentaires

Essayez de retirer les valeurs de largeur et de hauteur de votre conteneur?


Que voulez-vous dire? Si la largeur, la hauteur sont manquantes, j'obtiens l'exception «RenderViewport ne prend pas en charge le retour des dimensions intrinsèques.


6 Réponses :


71
votes

Enveloppez votre Container dans une Column , dans le paramètre de contenu, à l'intérieur de celui-ci, définissez le mainAxisSize.min , dans la propriété Column


2 commentaires

Travaillé! Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ //list ], );


Merci! a fonctionné comme un charme. Je n'avais aucune idée qu'il mainAxisSize: MainAxisSize.max défaut mainAxisSize: MainAxisSize.max .. mainAxisSize: MainAxisSize.max sens.



0
votes

Pouvez-vous essayer ça?
Cela a fonctionné au moins pour moi. Si vous avez besoin d'un exemple, dites-moi.

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

MISE À JOUR

Il vous suffit de montrer ce AreaPicker après le bouton ou quelque chose qui a été pressé.

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


6 commentaires

Comment l'utiliser dans mon exemple?


J'ai ajouté du code générique non lié à ma source de données, pour des tests rapides. Il jette une exception.


Je veux afficher alertDialog, avec une vue de liste à l'intérieur. Regardez l'image que j'ai incluse dans ma question.


Comme je peux le voir, vous avez écrit une boîte de dialogue personnalisée, mais je ne sais pas comment l'afficher


Mon résultat: i.imgur.com/fCAJCJA.png le texte est centré et il y a aussi de la place supplémentaire


J'ai terminé avec ceci (mauvaise solution): supposez une certaine hauteur pour un élément de liste unique et calculez la hauteur finale de la boîte de dialogue en multipliant le nombre d'éléments



42
votes

Je sais qu'il est assez tard, mais avez-vous essayé cela?

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


4 commentaires

Cela a fonctionné pour moi! Il commence à se développer inutilement à chaque fois que j'utilise un widget flexible. Les widgets de tailles fixes semblent utiliser l'espace de manière efficace.


Cela pour moi est aussi bon que d'appeler wrap_content sur le widget :)


Hou la la! C'était une solution très simple, il suffisait d'ajouter mainAxisSize: MainAxisSize.min


Merci. >> mainAxisSize: MainAxisSize.min



0
votes

Voilà donc ma solution finale:

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 cela peut être utile, le problème n'est pas anodin.


0 commentaires

2
votes

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),
            ),
          ),
      ],
    ),
  ),
);


0 commentaires

7
votes

J'ai le même problème. Je l'ai corrigé en ajoutant: scrollable: true dans AlertDialog

Le code mis à jour sera:

 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 commentaires