Flutter中跨类传递变量并返回修改后值的实现方案(含倒计时场景)
Hey there! Let's tackle your Flutter countdown timer step by step, and also clean up your code to make it more maintainable along the way.
1. 传递countdown到CupertinoTimePickerButton并回传修改后的值
Your original code has two key issues here: the countdown variable is marked final (so it can't be updated), and directly modifying widget.passedValue in the child widget is bad practice (widget properties are immutable in Flutter). We'll fix this using a callback function to send updated values back to the parent.
First, update the SetTimerPage to hold a mutable countdown state and pass a callback to the child:
class _SetTimerPageState extends State<SetTimerPage> { // Remove `final` so we can update this value Duration countdown = Duration(minutes: 0, seconds: 0); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('RoundONE')), body: Container( padding: const EdgeInsets.all(16), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CustomRowWidget( icon: Icons.alarm_sharp, text: 'COUNTDOWN', button: CupertinoTimePickerButton( initialValue: countdown, // Add callback to receive updated duration from child onChanged: (newDuration) { setState(() { countdown = newDuration; }); }, ), ), StartButton(countdown: countdown), ], ), ), ); } }
Then rewrite the CupertinoTimePickerButton to manage its own local state and use the callback:
class CupertinoTimePickerButton extends StatefulWidget { final Duration initialValue; final ValueChanged<Duration> onChanged; // Use super.key and required for null safety const CupertinoTimePickerButton({ super.key, required this.initialValue, required this.onChanged, }); @override _CupertinoTimePickerButtonState createState() => _CupertinoTimePickerButtonState(); } class _CupertinoTimePickerButtonState extends State<CupertinoTimePickerButton> { late Duration _currentDuration; @override void initState() { super.initState(); _currentDuration = widget.initialValue; } @override Widget build(BuildContext context) { return MaterialButton( child: Text( _formatDuration(_currentDuration), style: const TextStyle(color: Colors.white), ), color: Colors.redAccent, onPressed: () => _cupertinoTimeSetter(context), ); } // Explicit return type for clarity Future<void> _cupertinoTimeSetter(BuildContext context) async { await showModalBottomSheet( context: context, builder: (BuildContext builder) { return SizedBox( height: MediaQuery.of(context).size.height / 2, child: CupertinoTimerPicker( initialTimerDuration: _currentDuration, onTimerDurationChanged: (Duration newDuration) { setState(() { _currentDuration = newDuration; }); // Send updated value back to parent via callback widget.onChanged(newDuration); }, minuteInterval: 1, secondInterval: 5, mode: CupertinoTimerPickerMode.ms, ), ); }, ); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); final twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); final twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "$twoDigitMinutes:$twoDigitSeconds"; } }
2. Pass updated countdown to the next page for animation
Update your StartButton to navigate to the countdown page and pass the duration:
class StartButton extends StatelessWidget { final Duration countdown; const StartButton({super.key, required this.countdown}); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { // Only navigate if countdown is greater than 0 if (countdown.inSeconds > 0) { Navigator.push( context, MaterialPageRoute( builder: (context) => CountdownAnimationPage(countdown: countdown), ), ); } }, child: const Text('START COUNTDOWN'), ); } }
Create the countdown animation page to use the passed duration:
class CountdownAnimationPage extends StatefulWidget { final Duration countdown; const CountdownAnimationPage({super.key, required this.countdown}); @override _CountdownAnimationPageState createState() => _CountdownAnimationPageState(); } class _CountdownAnimationPageState extends State<CountdownAnimationPage> with TickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: widget.countdown, )..forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Countdown')), body: Center( child: AnimatedBuilder( animation: _controller, builder: (context, child) { final remainingTime = widget.countdown * (1 - _controller.value); return Text( _formatDuration(remainingTime), style: const TextStyle(fontSize: 48), ); }, ), ), ); } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); final twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); final twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "$twoDigitMinutes:$twoDigitSeconds"; } }
Let's make your code cleaner and more Flutter-idiomatic:
- Use null safety properly: Always mark required parameters with
required, and uselatefor variables initialized ininitState. - Avoid modifying widget properties: Never change
widget.xxxin a State class — use local state variables instead (like_currentDuration). - Extract repeated code: The
_formatDurationmethod is used in two places — turn it into a global utility function to avoid duplication:String formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); final twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); final twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); return "$twoDigitMinutes:$twoDigitSeconds"; } - Use
constwhere possible: Addconstto widgets that don't change (likeconst TextStyle,const EdgeInsets) to improve performance. - Explicit return types: Always specify return types for functions (e.g.,
Future<void>instead of justFuture) for better readability. - Simplify function calls: Replace
onPressed: () { someFunction(); }withonPressed: () => someFunction()for shorter, cleaner code.
内容的提问来源于stack exchange,提问作者matatoMatata94

