Flutter底部弹窗计时器不实时递减及验证功能异常问题排查
解决Flutter底部弹窗计时器不实时递减的问题
问题描述
Flutter开发中,底部弹窗(bottom sheet)的计时器无法实现每秒实时递减,同时表单验证逻辑也存在错误,尝试过StreamBuilder和StatefulBuilder方案均未解决。
现有代码的核心问题
- 计时器逻辑空实现:
startTimer函数里的Timer.periodic没有编写任何倒计时逻辑,既没减少duration,也没触发状态更新 - 状态更新未触发:即使修改了
duration,也没有调用StatefulBuilder的bottomState方法通知UI重建 - 表单验证key不匹配:弹窗内的Form用的是
_validatorKey,但按钮点击时却调用_formKey的validate,导致验证无效 - StreamBuilder无状态关联:当前StreamBuilder只是每秒触发,但没有关联
duration的变化,所以UI始终显示初始值
修复步骤
1. 完善计时器核心逻辑
修改startTimer函数,让它接收StatefulBuilder的状态更新回调,每次倒计时都更新状态:
void startTimer(StateSetter bottomState) { timer?.cancel(); // 重置倒计时为3分钟 duration = Duration(minutes: 3); timer = Timer.periodic(Duration(seconds: 1), (timer) { bottomState(() { if (duration.inSeconds <= 0) { timer.cancel(); // 倒计时结束后停止计时器 } else { duration -= Duration(seconds: 1); // 每秒减1秒 } }); }); }
2. 替换无用的StreamBuilder
既然用StatefulBuilder管理状态,直接渲染当前的duration即可,去掉冗余的StreamBuilder:
// 替换原来的StreamBuilder代码 Text( '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}', style: TextStyle( fontFamily: 'Pretendard', fontSize: 14, fontWeight: FontWeight.w300, ), ),
3. 修正表单验证逻辑
统一Form的key和按钮验证时使用的key:
// 按钮onPressed代码修改为 onPressed: () { final form = _validatorKey.currentState!; if (form.validate()) { timer?.cancel(); // 关闭弹窗时记得取消计时器 Navigator.of(context).pop(); _contractSheet(context); } else { print('Validation failed.'); } },
4. 优化计时器启动时机
避免StatefulBuilder每次重建都重复启动计时器,用addPostFrameCallback确保只在第一次构建时启动:
return StatefulBuilder(builder: (context, StateSetter bottomState) { // 延迟到首次布局完成后启动计时器 WidgetsBinding.instance.addPostFrameCallback((_) { startTimer(bottomState); }); // ... 后续的Container布局代码不变 });
完整修改后的弹窗函数示例
Future<dynamic> _validate_modal(BuildContext context) { return showModalBottomSheet( context: context, isScrollControlled: true, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(30.0), topRight: Radius.circular(30.0)), ), builder: (context) { return StatefulBuilder(builder: (context, StateSetter bottomState) { WidgetsBinding.instance.addPostFrameCallback((_) { startTimer(bottomState); }); return Container( padding: MediaQuery.of(context).viewInsets, child: Padding( padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Center( child: Text( '输入验证码', style: TextStyle( fontFamily: 'Pretendard', fontWeight: FontWeight.w500, fontSize: 16), ), ), Padding( padding: const EdgeInsets.only(top: 20), child: Form( key: _validatorKey, child: TextFormField( controller: _validatorController, validator: (value) { if (value == null || value.isEmpty) { return '请输入验证码'; } else if (value.length != 6) { return '请输入6位验证码'; } return null; }, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6), ], style: TextStyle( fontFamily: 'Pretendard', fontSize: 30, fontWeight: FontWeight.w300), cursorColor: Colors.black, textAlign: TextAlign.center, decoration: InputDecoration( border: InputBorder.none, ), ), ), ), // 修正后的计时器文本 Text( '${duration.inMinutes}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}', style: TextStyle( fontFamily: 'Pretendard', fontSize: 14, fontWeight: FontWeight.w300, ), ), Padding( padding: const EdgeInsets.only(top: 35, bottom: 10), child: Align( alignment: Alignment.bottomCenter, child: Container( width: MediaQuery.of(context).size.width * 1, height: 56, child: FilledButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.white), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(0), side: BorderSide( color: Colors.black, width: 1.0)))), onPressed: () { final form = _validatorKey.currentState!; if (form.validate()) { timer?.cancel(); Navigator.of(context).pop(); _contractSheet(context); } else { print('Validation failed.'); } }, child: Center( child: Text( '다음', style: TextStyle( fontFamily: 'Pretendard', fontSize: 16, fontWeight: FontWeight.w700, color: Colors.black), ), )), ), ), ), ], ), ), ); }); }, ); }
内容的提问来源于stack exchange,提问作者Loolii
相关产品推荐
相关产品推荐

