Flutter 2.5.1适配LiteRollingSwitch偶现!_needsLayout断言错误
问题根因
你遇到的text_painter.dart: Failed assertion: line 900 pos 12: '!_needsLayout': is not true报错,本质是动画运行过程中频繁触发组件全量重建,导致Text组件的布局计算流程被打断,上一次布局还未完成就触发新的重绘,触发了Flutter的布局断言。
触发的具体诱因是点击开关图标时,若前一次开关动画还未执行完成,重复触发了状态更新,全量重绘所有的Text和Icon组件,导致布局冲突。
修复方案
按以下步骤修改代码即可彻底解决偶发报错:
- 第一步:拦截动画运行中的重复触发请求,避免重复执行动画逻辑
修改_action方法,增加动画状态判断:
_action() { // 动画运行中直接拦截,不处理新的触发请求 if (animationController.isAnimating) return; _determine(changeState: true); }
- 第二步:缩小状态更新范围,用AnimatedBuilder替代全量setState
删除initState中动画控制器的addListener逻辑,将build方法中需要随动画更新的部分用AnimatedBuilder包裹,避免不必要的组件重绘:
修改initState:
@override void initState() { super.initState(); animationController = AnimationController( vsync: this, lowerBound: 0.0, upperBound: 1.0, duration: widget.animationDuration, ); animation = CurvedAnimation( parent: animationController, curve: Curves.easeInOut, ); // 删除原来的addListener逻辑,不需要全量setState turnState = widget.value; _determine(); }
修改build方法:
@override Widget build(BuildContext context) { return GestureDetector( onDoubleTap: () { _action(); if (widget.onDoubleTap != null) widget.onDoubleTap!(); }, onTap: () { _action(); if (widget.onTap != null) widget.onTap!(); }, onPanEnd: (details) { _action(); if (widget.onSwipe != null) widget.onSwipe!(); }, // 用AnimatedBuilder仅更新需要随动画变化的部分 child: AnimatedBuilder( animation: animation, builder: (context, child) { final value = animation.value; Color transitionColor = Color.lerp(widget.colorOff, widget.colorOn, value)!; return Container( padding: const EdgeInsets.all(5), width: widget.width, height: 50, // 明确指定容器高度,避免布局计算异常 decoration: BoxDecoration( color: transitionColor, borderRadius: BorderRadius.circular(50), ), child: Stack( // 明确指定Stack适配父容器尺寸,避免布局计算异常 fit: StackFit.expand, children: <Widget>[ // the "off" text Transform.translate( offset: Offset(10 * value, 0), child: Opacity( opacity: (1 - value).clamp(0.0, 1.0), child: Container( padding: const EdgeInsets.only(right: 5), alignment: Alignment.centerRight, height: 40, child: Text( widget.textOff, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: widget.textSize, ), ), ), ), ), // the "on" text Transform.translate( offset: Offset(10 * (1 - value), 0), child: Opacity( opacity: value.clamp(0.0, 1.0), child: Container( padding: const EdgeInsets.only(left: 5), alignment: Alignment.centerLeft, height: 40, child: Text( widget.textOn, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: widget.textSize, ), ), ), ), ), // The icons Transform.translate( offset: Offset(widget.width / 2 * value, 0), child: Transform.rotate( angle: lerpDouble(0, 2 * pi, value)!, child: Container( height: 40, width: 40, alignment: Alignment.center, decoration: const BoxDecoration( shape: BoxShape.circle, color: Colors.white, ), child: Stack( children: <Widget>[ Center( child: Opacity( opacity: value.clamp(0.0, 1.0), child: Icon( widget.iconOn, size: 21, color: transitionColor, ), ), ), Center( child: Opacity( opacity: (1 - value).clamp(0.0, 1.0), child: Icon( widget.iconOff, size: 21, color: transitionColor, ), ), ), ], ), ), ), ) ], ), ); }, ), ); }
- 第三步:调整
_determine方法的执行顺序,将onChanged回调移到setState外部,避免业务逻辑触发的UI更新干扰当前布局流程:
_determine({bool changeState = false}) { bool newState = turnState; if (changeState) newState = !turnState; setState(() { turnState = newState; (turnState) ? animationController.forward() : animationController.reverse(); }); // 移到setState外部执行 widget.onChanged(newState); }
内容的提问来源于stack exchange,提问作者ThomasG2201
相关产品推荐
相关产品推荐

