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

Flutter实现按钮触发不同书本翻页动画的技术问题

解决方案

核心问题在于你用Stack堆叠了所有动画,导致上层动画始终遮挡下层。正确的做法是只渲染当前激活的动画,而非同时显示所有动画。以下是优化后的实现:

修订代码

class OpeningBook extends StatefulWidget {
  const OpeningBook({super.key});

  @override
  State<OpeningBook> createState() => _OpeningBookState();
}

class _OpeningBookState extends State<OpeningBook>
    with TickerProviderStateMixin {
  late final AnimationController _animationController;
  String? _currentAnimationAsset; // 记录当前要播放的动画资源
  bool _isNavigating = false; // 防止重复触发导航

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );

    // 监听动画状态,完成后跳转页面
    _animationController.addStatusListener((status) {
      if (status == AnimationStatus.completed && !_isNavigating) {
        _isNavigating = true;
        _navigateToSubpage();
      } else if (status == AnimationStatus.dismissed) {
        // 动画反向完成后,重置当前动画资源
        setState(() => _currentAnimationAsset = null);
      }
    });

    // 监听路由变化,返回首页时触发反向动画
    GoRouter.of(context).addListener(_handleRouteChange);
  }

  // 处理路由切换,返回首页时播放反向动画
  void _handleRouteChange() {
    final currentRoute = GoRouter.of(context).location;
    if (currentRoute == '/' && 
        _currentAnimationAsset != null && 
        _animationController.status != AnimationStatus.reverse && 
        _animationController.status != AnimationStatus.forward) {
      _animationController.reverse();
    }
  }

  // 根据当前动画资源跳转对应子页面
  void _navigateToSubpage() {
    switch (_currentAnimationAsset) {
      case 'assets/animations/Calendar_Ani.json':
        context.goNamed(AppRoute.calendar.name);
        break;
      case 'assets/animations/LessonPrep_Ani.json':
        context.goNamed(AppRoute.weeks.name);
        break;
      // 添加另外3个动画对应的路由跳转逻辑
    }
    // 延迟重置导航标记,避免重复触发
    Future.delayed(const Duration(milliseconds: 500), () => _isNavigating = false);
  }

  @override
  void dispose() {
    _animationController.dispose();
    GoRouter.of(context).removeListener(_handleRouteChange);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          // 动画容器:只渲染当前激活的动画
          Container(
            height: 600,
            width: 600,
            alignment: Alignment.center,
            child: _currentAnimationAsset != null
                ? Lottie.asset(
                    _currentAnimationAsset!,
                    controller: _animationController,
                  )
                : // 默认显示书本闭合状态(可替换为静态图或闭合动画帧)
                Lottie.asset(
                    'assets/animations/ClosedBook_Ani.json',
                    repeat: false,
                  ),
          ),
          // 导航按钮组
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              IconButton(
                onPressed: () {
                  if (_animationController.status != AnimationStatus.forward && !_isNavigating) {
                    setState(() {
                      _currentAnimationAsset = 'assets/animations/Calendar_Ani.json';
                    });
                    _animationController.reset();
                    _animationController.forward();
                  }
                },
                iconSize: 50,
                icon: const Icon(Icons.calendar_month),
                tooltip: context.loc.calendar,
              ),
              const SizedBox(height: 35),
              IconButton(
                onPressed: () {
                  if (_animationController.status != AnimationStatus.forward && !_isNavigating) {
                    setState(() {
                      _currentAnimationAsset = 'assets/animations/LessonPrep_Ani.json';
                    });
                    _animationController.reset();
                    _animationController.forward();
                  }
                },
                iconSize: 50,
                icon: const Icon(Icons.auto_stories),
                tooltip: context.loc.lessonPlanner,
              ),
              // 添加剩余3个按钮
            ],
          ),
        ],
      ),
    );
  }
}

关键优化点

  1. 单控制器+动态切换资源:用一个AnimationController控制所有动画,通过_currentAnimationAsset动态切换要播放的Lottie文件,避免堆叠遮挡。
  2. 动画状态监听:通过addStatusListener处理动画完成后的跳转,替代原代码中不可靠的Future.delayed。
  3. 路由监听:监听GoRouter的路由变化,返回首页时自动触发反向动画,实现书本闭合效果。
  4. 防重复触发:通过_isNavigating和控制器状态检查,避免用户重复点击按钮导致的动画混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:24:54