Flutter中Column内SingleChildScrollView滚动失效及溢出问题
问题解决:Flutter中SingleChildScrollView无法滚动及类型转换错误
问题描述
- flex值为1的Expanded组件内的SingleChildScrollView可正常滚动,但flex值为2的Expanded组件中,由
createButtons(answerList)生成按钮列表的SingleChildScrollView无法滚动,当答案条目过多时页面底部溢出203像素。 - 同时出现类型转换错误:
type 'Null' is not a subtype of type 'FutureOr<int>'
代码示例
import 'package:flutter/material.dart'; import '../../backend/QuestionaireConstants/question.dart'; import '../../backend/utils/constants.dart'; import '../../backend/utils/setupcomingRoute.dart'; import '../../backend/widgets/button_content.dart'; import '../../backend/widgets/reusable_cart.dart'; int selectedAnswer = -1; int counter = 0; Color determineColor(int selection) { return selectedAnswer == selection ? Colors.white : Colors.black; } class SelectionWindow extends StatefulWidget { const SelectionWindow({Key? key}) : super(key: key); @override _SelectionWindowState createState() => _SelectionWindowState(); } class _SelectionWindowState extends State<SelectionWindow> { @override Widget build(BuildContext context) { final args = ModalRoute.of(context)!.settings.arguments as List<Question>; int count = args[0].counter; int arraylen = args.length; List answerList = args[count].possibleAnswers; String upcomingRoute = setUpcomingRoute(count, arraylen, args); List<Widget> createButtons(list) { List<Widget> buttons = []; // 创建单选按钮 for (var i = 0; i < list.length; i++) { buttons.add( Row( children: [ Expanded( child: GestureDetector( onTap: () { setState(() { selectedAnswer = i; }); }, child: ReusableCard( colour: selectedAnswer == i ? Colors.blue.shade900 : Colors.blue.shade200, cardChild: ButtonContent( label: args[count].possibleAnswers[i], textColour: determineColor(i), ), ), ), ), ], ), ); } return buttons; } // 显示错误弹窗 Future<void> _showMyDialog() async { return showDialog<void>( context: context, barrierDismissible: false, // 用户必须点击按钮关闭 builder: (BuildContext context) { return AlertDialog( title: const Text('提示'), content: SingleChildScrollView( child: ListBody( children: const <Widget>[ Text('请先选择一个答案再继续。'), ], ), ), actions: <Widget>[ TextButton( child: const Text( '确认', style: TextStyle(fontSize: 22), ), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } return Scaffold( appBar: AppBar( iconTheme: backwardsArrowBlack, automaticallyImplyLeading: false, title: appBarText, backgroundColor: Colors.blue.shade100, ), body: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( flex: 1, child: Container( alignment: Alignment.center, child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Text(args[count].questionText, style: multipleChoiceTextStyle), ), ), ), Expanded( flex: 2, child: Column( children: [ SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( children: createButtons(answerList), ), ), Expanded( child: Align( alignment: FractionalOffset.bottomCenter, child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: GestureDetector( onTap: () { if (args[0].counter > 0) { args[0].counter -= 1; args[count - 1].givenAnswers.clear(); } Navigator.pop(context); }, child: backwardsButton, ), ), Expanded( child: GestureDetector( onTap: () { if (selectedAnswer == -1) { _showMyDialog(); } else { args[count].givenAnswers.add(args[count] .possibleAnswers[selectedAnswer]); selectedAnswer = -1; if (args[0].counter == arraylen - 1) { args[0].counter += 1; Navigator.pushNamed(context, '/endScreen', arguments: args) .then((_) => setState(() {})); } else { args[0].counter += 1; Navigator.pushNamed( context, upcomingRoute, arguments: args, ).then((_) => setState(() {})); } } }, child: forwardButton, ), ), ], ), ), ), ], ), ), ], ), ); } }
错误日志
D/EGL_emulation(15191): app_time_stats: avg=10.35ms min=0.79ms max=89.84ms count=52 D/EGL_emulation(15191): app_time_stats: avg=104.68ms min=0.77ms max=1043.27ms count=11 D/EGL_emulation(15191): app_time_stats: avg=69.44ms min=11.30ms max=1017.83ms count=19 I/flutter (15191): type 'Null' is not a subtype of type 'FutureOr<int>' D/EGL_emulation(15191): app_time_stats: avg=151.10ms min=0.54ms max=925.70ms count=12 ======== Exception caught by rendering library ===================================================== The following assertion was thrown during layout: A RenderFlex overflowed by 203 pixels on the bottom.
解决方案
1. 修复SingleChildScrollView无法滚动及溢出问题
问题根源在于flex=2的Expanded内部的Column中,SingleChildScrollView没有被分配固定的可用空间,导致它会试图占据内容的全部高度,而非在剩余空间内滚动。
修改方式:给SingleChildScrollView外层包裹Expanded组件,让它占据Column中除底部按钮栏外的所有剩余空间:
Expanded( flex: 2, child: Column( children: [ // 新增Expanded包裹SingleChildScrollView Expanded( child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( children: createButtons(answerList), ), ), ), Expanded( child: Align( alignment: FractionalOffset.bottomCenter, child: Row( // 按钮代码保持不变 ), ), ), ], ), ),
2. 修复类型转换错误
错误type 'Null' is not a subtype of type 'FutureOr<int>'是因为Navigator.pushNamed的返回值被隐式期望为int类型,但实际返回了null。
修改方式:明确指定返回值类型为dynamic,或者确保目标路由返回正确的int值。例如修改then回调的参数类型:
Navigator.pushNamed(context, '/endScreen', arguments: args) .then((dynamic _) => setState(() {}));
以及:
Navigator.pushNamed( context, upcomingRoute, arguments: args, ).then((dynamic _) => setState(() {}));
另外,检查_showMyDialog中的Navigator.of(context).pop();,如果该对话框被期望返回int值,需要改为Navigator.of(context).pop(0);(或其他合法int值),否则保持当前写法即可。
内容的提问来源于stack exchange,提问作者Iman
相关产品推荐
相关产品推荐

