You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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";
  }
}
Code Optimization Suggestions

Let's make your code cleaner and more Flutter-idiomatic:

  • Use null safety properly: Always mark required parameters with required, and use late for variables initialized in initState.
  • Avoid modifying widget properties: Never change widget.xxx in a State class — use local state variables instead (like _currentDuration).
  • Extract repeated code: The _formatDuration method 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 const where possible: Add const to widgets that don't change (like const TextStyle, const EdgeInsets) to improve performance.
  • Explicit return types: Always specify return types for functions (e.g., Future<void> instead of just Future) for better readability.
  • Simplify function calls: Replace onPressed: () { someFunction(); } with onPressed: () => someFunction() for shorter, cleaner code.

内容的提问来源于stack exchange,提问作者matatoMatata94

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 07:12:32