You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter在DropdownButton内添加ExpansionTile报错问题求助

解决DropdownButton内嵌ExpansionTile的布局报错问题

错误根源

DropdownButton的下拉菜单容器(DropdownMenu)对子组件的尺寸有严格的预计算约束,而ExpansionTile是动态高度组件(展开/折叠时高度会变化),Dropdown的渲染逻辑无法处理这种动态尺寸变化,导致布局阶段无法正确获取RenderBox的尺寸,触发RenderBox was not laid out的断言错误。

解决方案

方案1:改用PopupMenuButton替代DropdownButton

PopupMenuButton的布局逻辑更灵活,支持动态高度的子组件,直接替换即可实现需求:

String dropdownValue = 'Option 1';

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: PopupMenuButton<String>(
        // 自定义触发按钮样式,模拟DropdownButton外观
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              dropdownValue,
              style: TextStyle(color: Colors.deepPurple, fontSize: 16),
            ),
            SizedBox(width: 8),
            Icon(Icons.arrow_downward, color: Colors.deepPurple),
          ],
        ),
        elevation: 16,
        onSelected: (String newValue) {
          setState(() {
            dropdownValue = newValue;
          });
        },
        itemBuilder: (BuildContext context) {
          return <String>['Option 1', 'Option 2', 'Option 3']
              .map((String value) {
            return PopupMenuItem<String>(
              value: value,
              child: ExpansionTile(
                title: Text(value),
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    child: Text('Expansion tile details for $value'),
                  ),
                ],
              ),
            );
          }).toList();
        },
      ),
    ),
  );
}

方案2:自定义下拉菜单(OverlayEntry实现)

如果需要完全自定义下拉菜单的行为和样式,可以用OverlayEntry手动实现,完全控制布局逻辑:

bool isDropdownOpen = false;
String dropdownValue = 'Option 1';

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: GestureDetector(
        onTap: () {
          setState(() {
            isDropdownOpen = !isDropdownOpen;
          });
          if (isDropdownOpen) {
            Overlay.of(context)?.insert(_createOverlayEntry());
          } else {
            Overlay.of(context)?.entries.last.remove();
          }
        },
        // 模拟DropdownButton的触发按钮
        child: Container(
          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          decoration: BoxDecoration(
            border: Border(bottom: BorderSide(color: Colors.deepPurpleAccent, width: 2)),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                dropdownValue,
                style: TextStyle(color: Colors.deepPurple, fontSize: 16),
              ),
              SizedBox(width: 8),
              Icon(Icons.arrow_downward, color: Colors.deepPurple),
            ],
          ),
        ),
      ),
    ),
  );
}

OverlayEntry _createOverlayEntry() {
  RenderBox renderBox = context.findRenderObject() as RenderBox;
  final size = renderBox.size;
  final offset = renderBox.localToGlobal(Offset.zero);

  return OverlayEntry(
    builder: (context) => Positioned(
      left: offset.dx,
      top: offset.dy + size.height + 5,
      width: size.width,
      child: Material(
        elevation: 16,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <String>['Option 1', 'Option 2', 'Option 3']
              .map((String value) {
            return ExpansionTile(
              title: Text(value),
              onExpansionChanged: (_) {
                // 展开/折叠时刷新Overlay,更新布局
                setState(() {});
              },
              children: <Widget>[
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                  child: Text('Expansion tile details for $value'),
                ),
              ],
              onTap: () {
                setState(() {
                  dropdownValue = value;
                  isDropdownOpen = false;
                });
                Overlay.of(context)?.entries.last.remove();
              },
            );
          }).toList(),
        ),
      ),
    ),
  );
}

不推荐的方案:固定ExpansionTile尺寸

如果硬要使用DropdownButton,可以给ExpansionTile套一个固定高度的Container,但这样会导致展开后的内容被截断,完全失去ExpansionTile的意义,仅作参考:

// 不推荐,展开内容会被截断
DropdownMenuItem<String>(
  value: value,
  child: Container(
    height: 80, // 固定高度
    child: ExpansionTile(
      title: Text(value),
      children: <Widget>[
        Text('Expansion tile details'),
      ],
    ),
  ),
)

内容的提问来源于stack exchange,提问作者Muhammed Ajmal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 14:32:21