Flutter报错:Null check operator被用于null值(AnimationController相关)
报错详情
══╡ 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

