Flutter中showModalBottomSheet内TimePicker setState不更新问题
问题分析
你遇到的核心问题有三点:
openAt/closeAt是test()函数的局部变量,不属于任何组件的状态,修改它们不会触发UI重建。- 你调用的
setState()是外层组件的状态更新方法,但模态框的UI是独立渲染的,外层setState不会刷新模态框内部内容。 - 你在
setState里只写了openAt;,这行代码没有实际作用,无法触发状态更新。
两种解决方案
方案一:使用StatefulBuilder快速实现
不需要拆分组件,直接在模态框内部通过StatefulBuilder获取能刷新模态框UI的状态更新方法:
test() { showModalBottomSheet<void>( isScrollControlled: true, isDismissible: false, enableDrag: false, barrierColor: Colors.black.withOpacity(0.1), backgroundColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15), ), ), context: context, builder: (BuildContext context) { String openAt = "00:00"; String closeAt = "00:00"; Size size = MediaQuery.of(context).size; // 用StatefulBuilder包裹内容,获取模态框内部的setState方法 return StatefulBuilder( builder: (context, setStateModal) { return Padding( padding: MediaQuery.of(context).viewInsets, child: Padding( padding: const EdgeInsets.all(8.0), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( onPressed: () => context.pop(), icon: Icon(Icons.close, color: Styles.blackColor), ) ], ), s_height, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Open at", style: TextStyle(fontWeight: FontWeight.w600)), GestureDetector( onTap: () { showTimePicker( context: context, initialTime: TimeOfDay.now(), ).then((value) { if (value != null) { // 格式化时间为hh:mm格式 openAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}"; // 触发模态框内部UI更新 setStateModal(() {}); } }); }, child: Container( decoration: BoxDecoration( color: Styles.greyLightColor, borderRadius: BorderRadius.all(Radius.circular(10)), ), padding: EdgeInsets.all(7.0), child: Text(openAt), ), ) ], ), Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Close at", style: TextStyle(fontWeight: FontWeight.w600)), GestureDetector( onTap: () { showTimePicker( context: context, initialTime: TimeOfDay.now(), ).then((value) { if (value != null) { closeAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}"; setStateModal(() {}); } }); }, child: Container( decoration: BoxDecoration( color: Styles.greyLightColor, borderRadius: BorderRadius.all(Radius.circular(10)), ), padding: EdgeInsets.all(7.0), child: Text(closeAt), ), ) ], ), s_height, Container( width: size.width * 0.8, child: ElevatedButton( style: ButtonStyle( foregroundColor: MaterialStateProperty.all<Color>(Styles.whiteColor), backgroundColor: MaterialStateProperty.all<Color>(Styles.blackColor), ), onPressed: () => context.pop({"openAt": openAt, "closeAt": closeAt}), child: Text("Save"), ), ) ], ), ), ), ); }, ); }, ); }
方案二:拆分为独立的StatefulWidget(推荐复杂场景)
把模态框内容抽成单独的组件,代码结构更清晰,便于维护:
1. 定义时间选择模态框组件
class TimePickerBottomSheet extends StatefulWidget { const TimePickerBottomSheet({super.key}); @override State<TimePickerBottomSheet> createState() => _TimePickerBottomSheetState(); } class _TimePickerBottomSheetState extends State<TimePickerBottomSheet> { String openAt = "00:00"; String closeAt = "00:00"; @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return Padding( padding: MediaQuery.of(context).viewInsets, child: Padding( padding: const EdgeInsets.all(8.0), child: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( onPressed: () => context.pop(), icon: Icon(Icons.close, color: Styles.blackColor), ) ], ), s_height, Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Open at", style: TextStyle(fontWeight: FontWeight.w600)), GestureDetector( onTap: () { showTimePicker( context: context, initialTime: TimeOfDay.now(), ).then((value) { if (value != null) { setState(() { openAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}"; }); } }); }, child: Container( decoration: BoxDecoration( color: Styles.greyLightColor, borderRadius: BorderRadius.all(Radius.circular(10)), ), padding: EdgeInsets.all(7.0), child: Text(openAt), ), ) ], ), Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Close at", style: TextStyle(fontWeight: FontWeight.w600)), GestureDetector( onTap: () { showTimePicker( context: context, initialTime: TimeOfDay.now(), ).then((value) { if (value != null) { setState(() { closeAt = "${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}"; }); } }); }, child: Container( decoration: BoxDecoration( color: Styles.greyLightColor, borderRadius: BorderRadius.all(Radius.circular(10)), ), padding: EdgeInsets.all(7.0), child: Text(closeAt), ), ) ], ), s_height, Container( width: size.width * 0.8, child: ElevatedButton( style: ButtonStyle( foregroundColor: MaterialStateProperty.all<Color>(Styles.whiteColor), backgroundColor: MaterialStateProperty.all<Color>(Styles.blackColor), ), onPressed: () => context.pop({"openAt": openAt, "closeAt": closeAt}), child: Text("Save"), ), ) ], ), ), ), ); } }
2. 调用模态框
test() { showModalBottomSheet<void>( isScrollControlled: true, isDismissible: false, enableDrag: false, barrierColor: Colors.black.withOpacity(0.1), backgroundColor: Colors.white, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15), ), ), context: context, builder: (BuildContext context) => const TimePickerBottomSheet(), ).then((result) { // 接收保存的时间,更新上层组件状态 if (result != null) { setState(() { // _openAt = result["openAt"]; // _closeAt = result["closeAt"]; }); } }); }
补充细节
- 处理
showTimePicker返回null的情况(用户取消选择),避免空指针。 - 不要直接使用
value.toString(),默认格式为TimeOfDay(hh:mm),需手动格式化为hh:mm字符串。 - 补充了Close at区域的时间选择逻辑(原代码未实现)。
内容的提问来源于stack exchange,提问作者Alexander Rogsberg
相关产品推荐
相关产品推荐

