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

Flutter Web如何正确实现侧边栏双状态平滑过渡动画?

问题原因
  • AnimatedPositioned 生效的前置要求是作为Stack组件的直接子元素,原代码未将其放置在Stack中,组件无法识别定位变化的补间需求,直接触发布局重绘
  • 原代码同时配置left、right两个方向的对立定位值,状态切换时两个参数同时跳变,Flutter无法计算连续的中间布局状态,直接渲染最终位置
  • 侧边栏主体采用两个独立Container做展开/收起的分支渲染,组件宽度没有过渡过程,即使位置动画生效,内容区也会直接跳变
  • 已初始化的AnimationController仅被用来判断动画完成状态,没有参与动画值的计算,没有发挥补间动画的控制能力
关键修改说明
  • 强制要求AnimatedPositioned作为Stack的直接子组件,这是定位类动画组件生效的必要前提
  • 移除同时设置left、right跳变值的逻辑,固定垂直方向top、bottom为0撑满高度,仅通过left值的连续变化实现侧边栏滑入滑出
  • 用AnimatedContainer替代两个条件分支渲染的Container,实现侧边栏宽度从收起值到展开值的平滑过渡,避免内容跳变
  • 抽离固定尺寸常量,减少魔法值,避免布局计算冲突
修复后完整代码
class _SideNavigationBarState extends State<SideNavigationBar> with SingleTickerProviderStateMixin<SideNavigationBar> {
  late AnimationController _animationController;
  late StreamController<bool> isSideNavigationBarOpenedStreamController;
  late Stream<bool> isSideNavigationBarOpenedStream;
  late StreamSink<bool> isSideNavigationBarOpenedSink;
  final _animationDuration = const Duration(milliseconds: 500);
  // 侧边栏固定尺寸常量
  final double _collapseWidth = 45;
  final double _expandWidth = 300;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(vsync: this, duration: _animationDuration);
    isSideNavigationBarOpenedStreamController = PublishSubject<bool>();
    isSideNavigationBarOpenedStream = isSideNavigationBarOpenedStreamController.stream;
    isSideNavigationBarOpenedSink = isSideNavigationBarOpenedStreamController.sink;
  }

  @override
  void dispose() {
    _animationController.dispose();
    isSideNavigationBarOpenedStreamController.close();
    isSideNavigationBarOpenedSink.close();
    super.dispose();
  }

  void onIconPressed() {
    final animationStatus = _animationController.status;
    final isAnimationCompleted = animationStatus == AnimationStatus.completed;

    if (isAnimationCompleted) {
      isSideNavigationBarOpenedSink.add(false);
      _animationController.reverse();
    } else {
      isSideNavigationBarOpenedSink.add(true);
      _animationController.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    double triggerBtnSize = 52;
    final screenWidth = MediaQuery.of(context).size.width;

    return StreamBuilder<bool>(
      initialData: false,
      stream: isSideNavigationBarOpenedStream,
      builder: (context, isSideNavigationBarOpenedAsync) {
        final isOpened = isSideNavigationBarOpenedAsync.data!;
        final triggerIcon = isOpened ? Icons.arrow_back_ios : Icons.arrow_forward_ios;
        return AnimatedPositioned(
          duration: _animationDuration,
          top: 0,
          bottom: 0,
          left: isOpened ? 0 : -(_expandWidth - _collapseWidth),
          child: Row(
            children: <Widget>[
              AnimatedContainer(
                duration: _animationDuration,
                width: isOpened ? _expandWidth : _collapseWidth,
                color: Colors.grey,
              ),
              Align(
                alignment: const Alignment(0, -0.9),
                child: GestureDetector(
                  onTap: onIconPressed,
                  child: SizedBox(
                    width: triggerBtnSize,
                    height: triggerBtnSize,
                    child: Material(
                      color: Colors.transparent,
                      child: InkWell(
                        child: Icon(
                          triggerIcon,
                          color: Colors.grey.withOpacity(0.5),
                        ),
                      ),
                    ),
                  ),
                ),
              )
            ],
          ),
        );
      },
    );
  }
}
组件使用注意

侧边栏必须放在Stack组件内才能正常工作,使用示例:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        // 页面主内容区
        const Positioned.fill(
          child: MainPageContent(),
        ),
        // 侧边栏作为Stack的直接子组件
        const SideNavigationBar(),
      ],
    ),
  );
}

内容的提问来源于stack exchange,提问作者Eyal D

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 12:27:13