Flutter Duration Picker包手动编辑后滚轮异常问题求助
问题根源
- 错误的时长叠加逻辑:从编辑弹窗返回后,
isAddedManually被设为true,此时滚轮的onChange回调会执行_duration = _duration + val,但val是滚轮当前选中的总时长,不是增量,这会导致时长数值错误,进而引发滚动异常。 - 状态未及时重置:手动编辑完成后,没有在滚轮开始交互时重置
isAddedManually,导致后续滚轮操作一直走错误的叠加逻辑。
修复方案
- 移除错误的叠加逻辑,统一在
onChange中直接用滚轮返回的val更新_duration。 - 给
DurationPicker添加onScrollStart回调,在滚轮开始滚动时重置isAddedManually为false,确保滚轮操作始终使用正确的更新逻辑。 - 优化编辑弹窗的时长管理,用局部变量保存编辑状态,避免修改外部原始值。
修改后的完整代码
import 'package:duration_picker/duration_picker.dart'; 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( title: 'Flutter App', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Flutter App'), ), body: Center( child: ElevatedButton( onPressed: () { activityDurationDialog(context); }, child: const Text('click'), )), ); } } Future<void> activityDurationDialog(BuildContext context, {double topBottomPadding = 10}) async { Duration _duration = const Duration(hours: 0, minutes: 0); final TextEditingController _hourController = TextEditingController(text: _duration.inHours.toString()); final TextEditingController _minuteController = TextEditingController(text: (_duration.inMinutes.remainder(60)).toString()); bool isAddedManually = false; void setIsAddedManually(bool newValue) { isAddedManually = newValue; } await showDialog<void>( context: context, barrierDismissible: true, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( title: const Text('duration'), content: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ const Divider(color: Colors.black), Padding( padding: const EdgeInsets.only(top: 25.0), child: DurationPicker( width: MediaQuery.of(context).size.width * 0.9, duration: _duration, onChange: (val) { setState(() { _duration = val; _hourController.text = _duration.inHours.toString(); _minuteController.text = (_duration.inMinutes.remainder(60)).toString(); }); }, onScrollStart: () { setState(() { isAddedManually = false; }); }, snapToMins: 5.0, ), ), ], ), ), ), actions: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(left: 8.0), child: IconButton( icon: const Icon( Icons.keyboard, size: 30, ), onPressed: () { activityDurationEditingDialog( context, _hourController, _minuteController, _duration, setIsAddedManually, (newDuration) { setState(() { _duration = newDuration; _hourController.text = _duration.inHours.toString(); _minuteController.text = (_duration.inMinutes.remainder(60)).toString(); }); }, ); }, ), ), ], ), ], ); }, ); }, ); } Future<void> activityDurationEditingDialog( BuildContext context, TextEditingController _hourController, TextEditingController _minuteController, Duration initialDuration, Function(bool) setIsAddedManually, Function(Duration) onUpdate, { double topBottomPadding = 10, }) async { Duration _editingDuration = initialDuration; return showDialog<void>( context: context, barrierDismissible: true, builder: (BuildContext context) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( title: const Text('edit'), content: SingleChildScrollView( child: Column( children: [ const Divider(color: Colors.black), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Column( children: [ const Text('hour'), const SizedBox(height: 10), SizedBox( width: 100, child: TextField( decoration: const InputDecoration( border: OutlineInputBorder(), ), maxLength: 3, controller: _hourController, keyboardType: TextInputType.number, textAlign: TextAlign.center, onChanged: (value) { int hours = int.tryParse(value) ?? 0; setState(() { _editingDuration = Duration( hours: hours, minutes: _editingDuration.inMinutes.remainder(60), ); }); }, ), ), ], ), Column( children: [ const Text('minute'), const SizedBox(height: 10), SizedBox( width: 100, child: TextField( decoration: const InputDecoration( border: OutlineInputBorder(), ), maxLength: 2, controller: _minuteController, keyboardType: TextInputType.number, textAlign: TextAlign.center, onChanged: (value) { int minutes = int.tryParse(value) ?? 0; minutes = minutes.clamp(0, 59); setState(() { _editingDuration = Duration( hours: _editingDuration.inHours, minutes: minutes, ); _minuteController.text = minutes.toString(); }); }, ), ), ], ), ], ), ], ), ), actions: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.only(left: 8.0), child: IconButton( icon: const Icon( Icons.access_time, size: 30, ), onPressed: () { setIsAddedManually(true); onUpdate(_editingDuration); Navigator.of(context).pop(); }, ), ), ], ), ], ); }, ); }, ); }
额外优化点
- 给分钟输入添加了
clamp(0,59)限制,避免输入超过60的非法值。 - 编辑弹窗使用局部变量
_editingDuration保存编辑状态,逻辑更清晰。 - 添加
const修饰符优化Widget性能。
内容的提问来源于stack exchange,提问作者behnam
相关产品推荐
相关产品推荐

