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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 17:36:03