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

时间:2019-02-13 11:55:44

标签: listview flutter size alertdialog

我需要从其余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

如何使对话框的宽度/高度适合实际项目的大小?如果我省略了heightwidth参数,则表示异常,并且未显示任何对话框。在本机Android Java中,我不需要指定任何尺寸,因为对话框本身会自动调整大小。

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

[编辑]

根据建议,我将内容包裹在列中:

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):渲染库引起的异常   ╞═════════════════════════════════════════════════ ════════我/颤振(   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: () => {},
                       );
                     },
                   ),
                 )
               ]
           ),
         );
       }
   );

8 个答案:

答案 0 :(得分:7)

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

答案 1 :(得分:6)

不要在您的 useEffect(() => { const results = data?.drinks?.filter(({ strDrink }) => data.strDrink.toLowerCase().includes(searchTerm) ) ?? []; setSearchResults(results); }, [searchTerm, data]); 中设置 mainAxisSize.min,否则如果内容长于视口,您可能会遇到溢出错误。要解决此问题,请使用其中一种方法。

1.将 Column 包裹在 Column 中:

SingleChildScrollView

2.在 AlertDialog( content: SingleChildScrollView( child: Column( children: [...], ), ), ) 中使用 shrinkWrap: true

ListView

答案 2 :(得分:5)

我知道已经很晚了,但是您尝试过吗?

svg-sprite-loader

答案 3 :(得分:3)

您可以看看SimpleDialog是如何做到的。

<script>
function format ( d ) {
        // `d` is the original data object for the row
    return '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">'+
        '<tr>'+
            '<td>Full name:</td>'+
            '<td>'+d.owner+'</td>'+
        '</tr>'+
        '<tr>'+
            '<td>Extension number:</td>'+
            '<td>'+d.extn+'</td>'+
        '</tr>'+
        '<tr>'+
            '<td>Extra info:</td>'+
            '<td>And any further details here (images etc)...</td>'+
        '</tr>'+
    '</table>';

}


$(document).ready(function() {
    var dt = $('#tblVendor').DataTable( {
        "responsive": true,
        "dom": 'Bfrtip',

        "buttons":[
         { extend: 'copy', text: 'Copy to Clipboard', className: 'btn btn-info btn-fill' },
         { extend: 'pdf', text: 'Export PDF', className: 'btn btn-info btn-fill' },
         { extend: 'excel', text: 'Export Excel', className: 'btn btn-info btn-fill' },
         { extend: 'csv', text: 'Export CSV', className: 'btn btn-info btn-fill' },

        {"className": "btn btn-info btn-fill move", "text":"New Vendor", "action":function(e, dt, node, config){window.location.replace("new.php"); }

        }],

        "processing": true,
        "serverSide": true,
        "ajax": "ajax.php",
        'serverMethod': 'post',
        "columns": [
            {
                "width": "5%",
                "class":          "details-control",
                "orderable":      false,
                "data":           null,
                "defaultContent": ""

            },
            { "data": "name" },
            { "data": "company" },
            { "data": "type" },
            { "data": "status" },
            { "data": "owner", "visible": false},
            { "data": "dept" },


        ],  "order": [[1, 'asc']],


    } );

        new $.fn.dataTable.Buttons( table, {
        buttons: [
            {
                text: 'Button 2',
                action: function ( e, dt, node, conf ) {
                    alert( 'Button 2 clicked on' );
                }
            },
            {
                text: 'Button 3',
                action: function ( e, dt, node, conf ) {
                    alert( 'Button 3 clicked on' );
                }
            }
        ]
    } );

    // Array to track the ids of the details displayed rows
    var detailRows = [];

    $('#tblVendor tbody').on( 'click', 'tr td.details-control', function () {
        var tr = $(this).closest('tr');
        var row = dt.row( tr );
        var idx = $.inArray( tr.attr('id'), detailRows );

        if ( row.child.isShown() ) {
            tr.removeClass( 'details' );
            row.child.hide();

            // Remove from the 'open' array
            detailRows.splice( idx, 1 );
        }
        else {
            tr.addClass( 'details' );
            row.child( format( row.data() ) ).show();

            // Add to the 'open' array
            if ( idx === -1 ) {
                detailRows.push( tr.attr('id') );
            }
        }
    } );

    // On each draw, loop over the `detailRows` array and show any child rows
    dt.on( 'draw', function () {
        $.each( detailRows, function ( i, id ) {
            $('#tblVendor'+id+' td.details-control').trigger( 'click' );
        } );
    } );

    table.buttons( 1, null ).container().appendTo(
        table.table().container()
    );
} );
</script>

答案 4 :(得分:1)

Container包裹在Column内的内容参数器中,在其内部的Column属性中设置mainAxisSize.min

答案 5 :(得分:1)

我遇到了一个非常相似的问题,并且想出了一个适用于Material和Cupertino的解决方案。

与带有可滚动标志= true和带有mainAxisSize:MainAxisSize.min的Column的警报对话框相比,性能(尤其是元素列表变长)的方式是更好地加载和滚动内容的方式-只需在此处观看视频:https://www.youtube.com/watch?v=2nKTGFZosr0

此外,对话框的标题不会与其他元素一起“向上滚动”(类似于您的解决方案),因此您可以在顶部添加“说”过滤工具,仅显示与搜索短语匹配的元素

可在此处找到源代码https://github.com/hicnar/fluttery_stuff只需检查整个内容并运行lib / dialogs / main.dart中的main()显然,您可以按照自己喜欢的任何方式复制,粘贴,修改和使用它。这里没有版权。

最后,在示例中,我将基于ListView的对话框内容的高度限制为最大屏幕高度的45%,您会发现它很容易,如果将系数更改为1.0,您将获得与通过基于列的方法(搜索名为screenHeightFactor的字段)

答案 6 :(得分:0)

您可以尝试一下吗?
至少对我有用。 如果您需要一个例子,请告诉我。

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

答案 7 :(得分:0)

这是我的最终解决方案:

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