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

Flutter中如何将StartPomodoro的启停按钮集成到GNav组件

Flutter计时器UI重构:将开始/暂停按钮迁移至底部GNav并保持功能

问题描述

我正在开发一款Flutter计时器应用,UI重构时需要把StartPomodoro组件里的开始/暂停按钮迁移到HomePageTimerUI的GNav组件中,同时保留计时器的功能和动画控制。

当前布局:
当前布局截图

目标布局:
目标布局截图

难点

计时器的控制逻辑和动画代码都在StartPomodoro组件里,直接剪切按钮到HomePageTimerUI后无法正常集成。

解决方案

核心思路

将计时器的核心状态与控制逻辑上移至HomePageTimerUI(父组件),通过参数传递给StartPomodoro(子组件)负责UI渲染,同时在GNav中绑定控制方法实现按钮功能。

修改后的代码

HomePageTimerUI.dart

class HomePageTimerUI extends StatefulWidget {
  const HomePageTimerUI({Key? key}) : super(key: key);

  @override
  State<HomePageTimerUI> createState() => _HomePageTimerUIState();
}

class _HomePageTimerUIState extends State<HomePageTimerUI>
    with TickerProviderStateMixin {
  late TabController _tabController;
  late AnimationController controller;
  bool isTimerAnimating = false;
  bool isLongBreak = true;

  String get countText {
    Duration count = controller.duration! * controller.value;
    return controller.isDismissed
        ? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
        : '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
  }

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    // 初始化计时器控制器,默认25分钟
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1500),
    );
    controller.addListener(() {
      notify();
      setState(() {
        isTimerAnimating = controller.isAnimating;
        if (!controller.isAnimating) {
          isLongBreak = true;
        }
      });
    });
  }

  @override
  void dispose() {
    _tabController.dispose();
    controller.dispose();
    super.dispose();
  }

  void notify() {
    if (countText == '00:00:00') {
      _tabController.animateTo(1, duration: const Duration(milliseconds: 300));
    }
  }

  // 切换开始/暂停状态
  void toggleTimer() {
    if (controller.isAnimating) {
      controller.stop();
      setState(() => isLongBreak = false);
    } else {
      controller.reverse(
          from: controller.value == 0 ? 1.0 : controller.value);
      setState(() => isLongBreak = false);
    }
  }

  // 打开时长选择弹窗
  void showDurationPicker() {
    if (controller.isDismissed) {
      showModalBottomSheet(
        context: context,
        builder: (context) => Container(
          height: 300,
          child: CupertinoTimerPicker(
            initialTimerDuration: controller.duration!,
            onTimerDurationChanged: (time) {
              setState(() {
                controller.duration = time;
              });
            },
          ),
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        height: 600,
        width: double.infinity,
        child: DefaultTabController(
            length: 3,
            child: Scaffold(
              appBar: AppBar(
                elevation: 0,
                backgroundColor: Colors.transparent,
                bottom: PreferredSize(
                  preferredSize: const Size.fromHeight(55),
                  child: Container(
                    color: Colors.transparent,
                    child: SafeArea(
                      child: Column(
                        children: <Widget>[
                          TabBar(
                              controller: _tabController,
                              indicator: const UnderlineTabIndicator(
                                  borderSide: BorderSide(
                                      color: Color(0xff3B3B3B), width: 4.0),
                                  insets: EdgeInsets.fromLTRB(
                                      12.0, 12.0, 12.0, 11.0)),
                              indicatorWeight: 15,
                              indicatorSize: TabBarIndicatorSize.label,
                              labelColor: const Color(0xff3B3B3B),
                              labelStyle: const TextStyle(
                                  fontSize: 12,
                                  letterSpacing: 1.3,
                                  fontWeight: FontWeight.w500),
                              unselectedLabelColor: const Color(0xffD7D7D7),
                              tabs: const [
                                Tab(
                                  text: "POMODORO",
                                  icon: Icon(Icons.work_history, size: 40),
                                ),
                                Tab(
                                  text: "SHORT BREAK",
                                  icon: Icon(Icons.ramen_dining, size: 40),
                                ),
                                Tab(
                                  text: "LONG BREAK",
                                  icon: Icon(
                                      Icons.battery_charging_full_rounded,
                                      size: 40),
                                ),
                              ])
                        ],
                      ),
                    ),
                  ),
                ),
              ),
              body: TabBarView(
                controller: _tabController,
                children: <Widget>[
                  StartPomodoro(
                    controller: controller,
                    countText: countText,
                    isLongBreak: isLongBreak,
                    onTap: showDurationPicker,
                  ),
                  // Center(child: ShortBreak()),
                  // Center(child: LongBreak()),
                ],
              ),
              bottomNavigationBar: Container(
                height: 110,
                color: const Color(0xffFAFAFA),
                child: Padding(
                  padding: const EdgeInsets.symmetric(
                      horizontal: 15.0, vertical: 20),
                  child: GNav(
                    iconSize: 40,
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    backgroundColor: const Color(0xffFAFAFA),
                    color: const Color(0xffD7D7D7),
                    activeColor: const Color(0xff3B3B3B),
                    tabBackgroundColor: const Color(0xffF0F0F0),
                    gap: 8,
                    padding: const EdgeInsets.all(15),
                    tabs: [
                      const GButton(
                        icon: Icons.settings,
                        text: 'Settings',
                      ),
                      GButton(
                        icon: isTimerAnimating ? Icons.pause : Icons.play_arrow,
                        text: isTimerAnimating ? "Pause" : "Start",
                        onPressed: toggleTimer,
                        iconColor: const Color(0xff3B3B3B),
                      ),
                      const GButton(
                        icon: Icons.task,
                        text: "Tasks",
                      ),
                      const GButton(
                        icon: Icons.show_chart,
                        text: "Performance",
                      ),
                    ],
                  ),
                ),
              ),
            )));
  }
}

startpomodoro.dart

class StartPomodoro extends StatelessWidget {
  final AnimationController controller;
  final String countText;
  final bool isLongBreak;
  final VoidCallback onTap;

  const StartPomodoro({
    Key? key,
    required this.controller,
    required this.countText,
    required this.isLongBreak,
    required this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: isLongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
      body: GestureDetector(
        onTap: onTap,
        child: AnimatedBuilder(
          animation: controller,
          builder: (context, child) {
            return Stack(
              children: <Widget>[
                Align(
                  alignment: Alignment.bottomCenter,
                  child: Container(
                    color: const Color(0xffD94530),
                    height: controller.value *
                        MediaQuery.of(context).size.height *
                        0.640,
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Expanded(
                        child: Align(
                          alignment: Alignment.bottomCenter,
                          child: SingleChildScrollView(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.end,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: <Widget>[
                                Text(
                                  countText,
                                  style: const TextStyle(
                                    fontSize: 90.0,
                                    color: Color(0xffFAFAFA),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ),
                      const SizedBox(height: 100),
                    ],
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

关键修改说明

  • 状态上移:将AnimationController、计时器状态(isTimerAnimating、isLongBreak)和控制方法(toggleTimer、showDurationPicker)迁移到HomePageTimerUI,让父组件持有核心逻辑,便于底部GNav调用。
  • 组件解耦:StartPomodoro改为无状态组件,通过构造函数接收所需参数,仅负责UI渲染,不再处理业务逻辑。
  • GNav集成:替换GNav的第二个tab为开始/暂停按钮,根据isTimerAnimating动态切换图标和文字,点击时调用toggleTimer方法。
  • 初始化优化:给AnimationController设置默认时长(25分钟),避免初始值为0导致的异常。

内容的提问来源于stack exchange,提问作者pomoworko.com

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 19:30:34