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

Flutter中使用AnimationController触发生命周期状态崩溃错误求助

Flutter AnimationController 生命周期崩溃问题修复

错误现象

触发以下断言错误,导致应用直接崩溃,需重新加载:

'package:flutter/src/widgets/framework.dart': Failed assertion: line 4519 pos 12: '_lifecycleState != _ElementLifecycle.defunct': is not true

问题根源

  1. 重复调用super.initState():initState中连续执行两次super.initState(),违反Flutter生命周期规范
  2. 未安全处理控制器实例:多处直接用animationController!强制解包,未判断控制器是否初始化或已销毁
  3. GetX控制器重复注入:类成员已初始化TimerTabController,initState中再次调用Get.put()造成重复实例
  4. 动画监听未清理:动画控制器的监听函数未在组件销毁时移除,组件进入defunct状态后仍可能触发setState

修复后的完整代码

// ignore_for_file: depend_on_referenced_packages, sized_box_for_whitespace, avoid_print

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:habbit_app/controllers/timer_tab_controller.dart';
import 'package:habbit_app/screens/timer/set_timer_component.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:habbit_app/widgets/text_widget/label_text.dart';

class TimerTab extends StatefulWidget {
  const TimerTab({super.key});

  @override
  State<TimerTab> createState() => _TimerTabState();
}

class _TimerTabState extends State<TimerTab> with TickerProviderStateMixin {
  AnimationController? animationController;
  final TimerTabController tabController =
      Get.put(TimerTabController(), permanent: false);

  late TabController _controller;
  bool isPlaying = false;
  double progress = 1.0;

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

  void notifiy() {
    if (countText == "0:00:00") {
      FlutterRingtonePlayer.playNotification();
    }
  }

  @override
  void initState() {
    super.initState();
    _controller = TabController(length: 3, vsync: this);
    _controller.addListener(() {
      tabController.tabIndex.value = _controller.index;
    });
    
    animationController = AnimationController(
        vsync: this, duration: Duration(seconds: tabController.totalSeconds));
    animationController!.addListener(_animationListener);
  }

  void _animationListener() {
    if (!mounted) return;
    
    notifiy();
    if (animationController!.isAnimating) {
      setState(() {
        progress = animationController!.value;
      });
    } else {
      setState(() {
        progress = 1.0;
        isPlaying = false;
      });
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    if (animationController != null) {
      animationController!.removeListener(_animationListener);
      animationController!.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var color = Theme.of(context);
    return Obx(
      () => tabController.isFirst.value
          ? SetTimerComponent(
              changeAnimation: () {
                if (animationController != null) {
                  animationController!.duration =
                      Duration(seconds: tabController.totalSeconds);
                }
                tabController.changeFirst();
              },
            )
          : Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  height: 350,
                  width: MediaQuery.of(context).size.width,
                  child: Column(children: [
                    Stack(
                      alignment: Alignment.center,
                      children: [
                        SizedBox(
                            width: 300,
                            height: 300,
                            child: CircularProgressIndicator(
                              color: color.primaryColor,
                              backgroundColor:
                                  color.disabledColor.withOpacity(0.6),
                              value: progress,
                              strokeWidth: 10,
                            )),
                        GestureDetector(
                          onTap: () {
                            if (animationController?.isDismissed ?? false) {
                              showModalBottomSheet(
                                  context: context,
                                  builder: (context) => Container(
                                        height: 300,
                                        child: CupertinoTimerPicker(
                                          initialTimerDuration:
                                              animationController?.duration ?? const Duration(seconds: 0),
                                          backgroundColor:
                                              color.backgroundColor,
                                          onTimerDurationChanged: (time) {
                                            if (mounted && animationController != null) {
                                              setState(() {
                                                animationController!.duration = time;
                                              });
                                            }
                                          },
                                        ),
                                      ));
                            }
                          },
                          child: AnimatedBuilder(
                            animation: animationController!,
                            builder: (context, child) => Text(
                              countText,
                              style: TextStyle(
                                  fontSize: 60,
                                  fontWeight: FontWeight.bold,
                                  color: color.primaryColor),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ]),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    GestureDetector(
                      onTap: () {
                        print(tabController.totalSeconds);
                        if (animationController == null) return;
                        
                        if (animationController!.isAnimating) {
                          animationController!.stop();
                          if (mounted) {
                            setState(() {
                              isPlaying = false;
                            });
                          }
                        } else {
                          animationController!.reverse(
                              from: animationController!.value == 0
                                  ? 1.0
                                  : animationController!.value);
                          if (mounted) {
                            setState(() {
                              isPlaying = true;
                            });
                          }
                        }
                      },
                      child: Container(
                        height: 35,
                        width: 90,
                        decoration: BoxDecoration(
                            borderRadius:
                                const BorderRadius.all(Radius.circular(30)),
                            color: color.primaryColor),
                        child: Center(
                            child: LabelText(
                          text: isPlaying ? "PAUSE" : "RESUME",
                          isBold: true,
                          isColor: true,
                          color: Colors.white,
                        )),
                      ),
                    ),
                    GestureDetector(
                      onTap: () {
                        if (animationController != null) {
                          animationController!.reset();
                        }
                        tabController.currentvalueHour.value = 0;
                        tabController.currentvalueMin.value = 0;
                        tabController.currentvalueSec.value = 0;
                        if (mounted) {
                          setState(() {
                            isPlaying = false;
                          });
                        }
                        tabController.changeFirst2();
                      },
                      child: Container(
                        height: 35,
                        width: 90,
                        decoration: BoxDecoration(
                            borderRadius:
                                const BorderRadius.all(Radius.circular(30)),
                            color: color.disabledColor.withOpacity(0.5)),
                        child: const Center(
                            child: LabelText(
                          text: "DELETE",
                          isColor: true,
                          color: Colors.white,
                          isBold: true,
                        )),
                      ),
                    )
                  ],
                )
              ],
            ),
    );
  }
}

修复要点说明

  • 移除重复的super.initState()调用,确保生命周期流程正确
  • 将动画监听提取为独立方法,在dispose时调用removeListener清理
  • 所有使用animationController的位置增加判空检查,配合mounted状态避免组件销毁后调用setState
  • 移除initState中重复的GetX控制器注入
  • 在dispose中优先清理TabController和动画监听,再执行控制器销毁操作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:15:35