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

Flutter报错:Null check operator被用于null值(AnimationController相关)

Flutter空值检查运算符错误(Null check operator used on a null value)

报错详情

══╡ WIDGETS库捕获异常 ╞═══════════════════════════════════════════════════════════
构建Builder时抛出以下_TypeError:
Null check operator used on a null value

相关的错误触发组件是:
MaterialApp
MaterialApp:file:///C:/Users/Owner/Desktop/Dzidzo/Mobile%20application/dzidzo/lib/main.dart:21:18

异常抛出时的调用栈:
#0 _MyDiaryScreenState.initState (package:dzidzo/fitness_app/my_diary/my_diary_screen.dart:30:47)
#1 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5219:55)
#2 ComponentElement.mount (package:flutter/src/widgets/framework.dart:5062:5)

问题根源

MyDiaryScreen的animationController是可空参数,但代码里在initState、addAllListData、getAppBarUI等方法中大量使用!强制解包空值。当创建MyDiaryScreen时未传入有效的animationController,就会触发空值错误。单纯替换!为??无效,因为CurvedAnimation的parent必须是非null的动画控制器,缺省值无法满足依赖要求。

解决方案

以下两种方式任选其一即可解决问题:

方式1:创建MyDiaryScreen时传入有效AnimationController

在跳转或初始化MyDiaryScreen的位置,传入已初始化的动画控制器:

// 示例导航代码
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => MyDiaryScreen(
      animationController: AnimationController(
        duration: const Duration(milliseconds: 1000),
        vsync: this, // 需当前组件混入TickerProviderStateMixin
      ),
    ),
  ),
);

方式2:在MyDiaryScreen内部创建默认AnimationController

无需依赖外部传入,组件内部自行创建控制器,修改代码如下:

class _MyDiaryScreenState extends State<MyDiaryScreen>
    with TickerProviderStateMixin {
  Animation<double>? topBarAnimation;
  List<Widget> listViews = <Widget>[];
  final ScrollController scrollController = ScrollController();
  double topBarOpacity = 0.0;
  late AnimationController _internalController;

  @override
  void initState() {
    // 优先使用外部传入的控制器,无传入则创建内部默认控制器
    _internalController = widget.animationController ?? AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );
    // 替换所有widget.animationController!为_internalController
    topBarAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(
            parent: _internalController,
            curve: Interval(0, 0.5, curve: Curves.fastOutSlowIn)));
    addAllListData();

    scrollController.addListener(() {
      if (scrollController.offset >= 24) {
        if (topBarOpacity != 1.0) {
          setState(() => topBarOpacity = 1.0);
        }
      } else if (scrollController.offset <= 24 && scrollController.offset >= 0) {
        if (topBarOpacity != scrollController.offset / 24) {
          setState(() => topBarOpacity = scrollController.offset / 24);
        }
      } else if (scrollController.offset <= 0) {
        if (topBarOpacity != 0.0) {
          setState(() => topBarOpacity = 0.0);
        }
      }
    });
    super.initState();
  }

  void addAllListData() {
    const int count = 9;

    listViews.add(
      TitleView(
        titleTxt: 'Mediterranean diet',
        subTxt: 'Details',
        animation: Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(
            parent: _internalController,
            curve: Interval((1 / count) * 0, 1.0, curve: Curves.fastOutSlowIn))),
        animationController: _internalController,
      ),
    );
    // 剩余addAllListData代码中,所有widget.animationController!都替换为_internalController
    // ...(省略重复代码)
  }

  Widget getAppBarUI() {
    return Column(
      children: <Widget>[
        AnimatedBuilder(
          animation: _internalController,
          builder: (BuildContext context, Widget? child) {
            return FadeTransition(
              opacity: topBarAnimation!,
              child: Transform(
                transform: Matrix4.translationValues(
                    0.0, 30 * (1.0 - topBarAnimation!.value), 0.0),
                child: Container(
                  decoration: BoxDecoration(
                    color: FitnessAppTheme.white.withOpacity(topBarOpacity),
                    borderRadius: const BorderRadius.only(
                      bottomLeft: Radius.circular(32.0),
                    ),
                    boxShadow: <BoxShadow>[
                      BoxShadow(
                          color: FitnessAppTheme.grey
                              .withOpacity(0.4 * topBarOpacity),
                          offset: const Offset(1.1, 1.1),
                          blurRadius: 10.0),
                    ],
                  ),
                  // ...(剩余原代码不变)
                ),
              ),
            );
          },
        )
      ],
    );
  }

  @override
  void dispose() {
    // 仅销毁内部创建的控制器,避免重复销毁外部传入的控制器
    if (widget.animationController == null) {
      _internalController.dispose();
    }
    super.dispose();
  }
}

关键修改点

  • 所有widget.animationController!替换为非null的控制器实例(方式1的传入值或方式2的_internalController)
  • 避免滥用!强制解包,确保变量非null或提供合理的默认初始化逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:07:03