Flutter中Row嵌套Column内ListView.Builder布局报错求助
解决Flutter飞镖游戏计算器双列布局的RenderBox未布局问题
错误原因
把包含ListView的Column嵌套到Row中时,Row不会给子Column提供明确的垂直约束,而Column默认会自适应子组件高度。ListView在shrinkWrap: true下虽会根据内容收缩,但缺少外部高度限制时,布局系统无法计算其最终尺寸,从而抛出RenderBox was not laid out错误。
修复方案
通过两步调整布局约束即可解决:
- 给Row中的每个Column包裹
Expanded,让它们平分Row的水平空间,同时继承父容器的垂直高度约束 - 在Column内部,用
Expanded包裹ListView,让它占据Column内除标题、剩余分数外的所有垂直空间
修改后的完整代码
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const GamePage(), ); } } class GamePage extends StatefulWidget { const GamePage({super.key}); @override State<GamePage> createState() => _GamePageState(); } class _GamePageState extends State<GamePage> { List<String> thrownScoresOne = []; List<String> thrownScoresTwo = []; int playerOne501 = 501; int playerTwo501 = 501; final TextEditingController _playerOneScoreController = TextEditingController(); final TextEditingController _playerTwoScoreController = TextEditingController(); @override void dispose() { _playerOneScoreController.dispose(); _playerTwoScoreController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("飞镖游戏计算器"), ), body: SafeArea( child: Container( padding: const EdgeInsets.symmetric(horizontal: 32), width: double.infinity, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 玩家一列,用Expanded约束宽度和高度 Expanded( child: Column( children: [ const Text("玩家一"), Text(playerOne501.toString()), // 用Expanded让ListView占据剩余垂直空间 Expanded( child: ListView.builder( shrinkWrap: true, itemCount: thrownScoresOne.length, itemBuilder: (BuildContext context, int index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text(thrownScoresOne[index]), ); }), ), // TextField放在Column底部 Container( margin: const EdgeInsets.only(bottom: 16), child: TextField( controller: _playerOneScoreController, decoration: InputDecoration( hintText: "输入得分", border: OutlineInputBorder( borderSide: Divider.createBorderSide(context)), focusedBorder: OutlineInputBorder( borderSide: Divider.createBorderSide(context)), enabledBorder: OutlineInputBorder( borderSide: Divider.createBorderSide(context)), filled: true, contentPadding: const EdgeInsets.all(8), ), onSubmitted: (text) { thrownScoresOne.add(text); // 可选:更新剩余分数逻辑 if (int.tryParse(text) != null) { setState(() { playerOne501 -= int.parse(text); }); } _playerOneScoreController.clear(); setState(() {}); }, ), ), ], ), ), // 玩家二列,同玩家一的布局约束 Expanded( child: Column( children: [ const Text("玩家二"), Text(playerTwo501.toString()), Expanded( child: ListView.builder( shrinkWrap: true, itemCount: thrownScoresTwo.length, itemBuilder: (BuildContext context, int index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text(thrownScoresTwo[index]), ); }), ), Container( margin: const EdgeInsets.only(bottom: 16), child: TextField( controller: _playerTwoScoreController, decoration: InputDecoration( hintText: "输入得分", border: OutlineInputBorder( borderSide: Divider.createBorderSide(context)), focusedBorder: OutlineInputBorder( borderSide: Divider.createBorderSide(context)), enabledBorder: OutlineInputBorder( borderSide: Divider.createBorderSide(context)), filled: true, contentPadding: const EdgeInsets.all(8), ), onSubmitted: (text) { thrownScoresTwo.add(text); // 可选:更新剩余分数逻辑 if (int.tryParse(text) != null) { setState(() { playerTwo501 -= int.parse(text); }); } _playerTwoScoreController.clear(); setState(() {}); }, ), ), ], ), ), ], ), ), ), ); } }
额外优化点
- 给ListView的每个item添加
Padding,提升视觉体验 - 增加分数合法性判断,避免非数字输入导致崩溃
- 给TextField添加底部
margin,避免被屏幕底部遮挡
内容的提问来源于stack exchange,提问作者Michiel Janssen
相关产品推荐
相关产品推荐

