欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。

动画是 Flutter 应用 “活起来” 的灵魂 —— 一个恰到好处的动效能让界面交互更自然,用户体验提升一个档次。但很多开发者对 Flutter 动画的认知还停留在AnimatedContainer这类 “傻瓜式” 组件,遇到复杂动效就束手无策。本文将从基础的显隐动画入手,逐步拆解动画核心原理,最终实现一个可复用的自定义交错动画组件,既有严谨的代码逻辑,又有生动的效果拆解,让你真正理解 Flutter 动画的底层逻辑。

一、Flutter 动画的核心认知:为什么动效会 “丝滑”?

在写代码前,先搞懂 Flutter 动画的核心逻辑:

  • Flutter 动画本质是 “数值的连续变化”:比如从 0 到 1 的渐变数值,驱动组件的尺寸、透明度、位置等属性变化;
  • 动画帧与屏幕刷新率同步:Flutter 的AnimationController默认以 60fps(帧 / 秒)更新数值,保证动效无卡顿;
  • 不可变的 Widget 与可变的动画数值:Widget 本身是不可变的,但动画数值可以驱动 Widget 重建,呈现动态效果。

本文所有代码基于:

plaintext

Flutter 3.19.0
Dart 3.3.0

二、入门:用 AnimatedOpacity 实现基础显隐动画

先从最简单的显隐动画入手,AnimatedOpacity是 Flutter 封装好的动画组件,无需手动管理控制器,适合新手入门。

2.1 完整代码实现

dart

import 'package:flutter/material.dart';

void main() => runApp(const AnimationDemoApp());

class AnimationDemoApp extends StatelessWidget {
  const AnimationDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter动画实战',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const BasicFadeAnimationPage(),
    );
  }
}

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

  @override
  State<BasicFadeAnimationPage> createState() => _BasicFadeAnimationPageState();
}

class _BasicFadeAnimationPageState extends State<BasicFadeAnimationPage> {
  // 控制组件是否显示
  bool _isVisible = false;

  // 切换显隐状态
  void _toggleVisibility() {
    setState(() {
      _isVisible = !_isVisible;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('基础显隐动画')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 核心:AnimatedOpacity实现透明度动画
            AnimatedOpacity(
              // 透明度值:显示时1,隐藏时0
              opacity: _isVisible ? 1.0 : 0.0,
              // 动画时长:300毫秒
              duration: const Duration(milliseconds: 300),
              // 动画曲线:控制速度变化(easeInOut是先慢后快再慢)
              curve: Curves.easeInOut,
              // 动画结束后的回调(可选)
              onEnd: () {
                debugPrint('显隐动画完成!当前状态:${_isVisible ? "显示" : "隐藏"}');
              },
              // 子组件:要执行动画的内容
              child: Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(
                  color: Colors.blueAccent,
                  borderRadius: BorderRadius.circular(16),
                ),
                alignment: Alignment.center,
                child: const Text(
                  '动画演示',
                  style: TextStyle(color: Colors.white, fontSize: 20),
                ),
              ),
            ),
            const SizedBox(height: 40),
            // 触发按钮
            ElevatedButton(
              onPressed: _toggleVisibility,
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
              ),
              child: Text(_isVisible ? '隐藏' : '显示'),
            )
          ],
        ),
      ),
    );
  }
}

2.2 核心代码解析

  1. opacity 参数:动画的核心驱动值,范围 0(完全透明)到 1(完全不透明),通过_isVisible布尔值控制;
  2. duration:动画执行时长,单位毫秒,300ms 是符合用户体验的 “黄金时长”(太短太突兀,太长显拖沓);
  3. curve 动画曲线Curves.easeInOut是最常用的曲线,对应 “慢 - 快 - 慢” 的速度变化,让动画更自然;
    • 常用曲线参考:
      • Curves.linear:匀速运动(机械感强,少用);
      • Curves.bounceOut:回弹效果(适合弹性动效);
      • Curves.decelerate:减速运动(适合入场动画);
  4. onEnd 回调:动画执行完成后的操作,可用于状态重置、日志打印等。

2.3 效果演示

点击按钮后,蓝色容器会在 300ms 内从完全透明渐变到不透明(或反之),过程丝滑无卡顿,相比直接setState切换显示隐藏,体验提升显著。

三、进阶:手动管理 AnimationController 实现多属性联动动画

AnimatedOpacity这类封装组件虽然简单,但灵活性不足。真正掌握 Flutter 动画,必须理解AnimationControllerAnimation的核心用法。我们实现一个 “缩放 + 旋转 + 透明度” 的联动动画。

3.1 核心概念铺垫

  • AnimationController:动画的 “总开关”,控制动画的启动、暂停、反向、重置,生成从 0 到 1 的基础数值;
  • Animation:基于AnimationController的数值,通过Tween映射到目标范围(比如 0 到 200 的尺寸、0 到 π 的角度);
  • AnimatedBuilder:动画重建的 “优化器”,只重建需要动效的部分,而非整个页面。

3.2 完整代码实现

dart

import 'package:flutter/material.dart';

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

  @override
  State<AdvancedAnimationPage> createState() => _AdvancedAnimationPageState();
}

// 混入SingleTickerProviderStateMixin:提供动画帧回调
class _AdvancedAnimationPageState extends State<AdvancedAnimationPage>
    with SingleTickerProviderStateMixin {
  // 1. 声明动画控制器
  late AnimationController _controller;
  // 2. 声明多个动画数值(缩放、旋转、透明度)
  late Animation<double> _scaleAnim;
  late Animation<double> _rotateAnim;
  late Animation<double> _opacityAnim;

  @override
  void initState() {
    super.initState();
    // 初始化控制器:时长1秒,绑定当前页面的Ticker
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );

    // 3. 定义数值映射(Tween)
    // 缩放动画:从0.5倍到1倍
    _scaleAnim = Tween<double>(begin: 0.5, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOutBack),
    );
    // 旋转动画:从0弧度到2π(360度)
    _rotateAnim = Tween<double>(begin: 0, end: 2 * 3.14159).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    );
    // 透明度动画:从0到1
    _opacityAnim = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: const Interval(0, 0.5)), // 前500ms执行
    );

    // 监听动画状态(可选)
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        debugPrint('动画执行完成');
        // 动画完成后反向播放
        // _controller.reverse();
      }
    });
  }

  // 核心:释放控制器资源(必做!避免内存泄漏)
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // 启动动画
  void _startAnimation() {
    // 重置后向前播放
    _controller.reset();
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('多属性联动动画')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 4. 使用AnimatedBuilder构建动画组件
            AnimatedBuilder(
              animation: _controller, // 绑定控制器
              builder: (context, child) {
                // 只有这里的内容会随动画重建
                return Transform(
                  // 组合缩放和旋转
                  transform: Matrix4.identity()
                    ..scale(_scaleAnim.value) // 缩放
                    ..rotateZ(_rotateAnim.value), // 绕Z轴旋转
                  alignment: Alignment.center, // 旋转/缩放中心
                  child: Opacity(
                    opacity: _opacityAnim.value,
                    child: child, // 子组件复用,避免重建
                  ),
                );
              },
              // 静态子组件:只构建一次,提升性能
              child: Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(
                  color: Colors.purpleAccent,
                  borderRadius: BorderRadius.circular(16),
                ),
                alignment: Alignment.center,
                child: const Text(
                  '联动动画',
                  style: TextStyle(color: Colors.white, fontSize: 20),
                ),
              ),
            ),
            const SizedBox(height: 40),
            ElevatedButton(
              onPressed: _startAnimation,
              child: const Text('启动动画'),
            )
          ],
        ),
      ),
    );
  }
}

3.3 深度代码解析

  1. SingleTickerProviderStateMixin
    • Ticker是 Flutter 的帧回调工具,vsync: this将控制器与页面生命周期绑定,页面不可见时暂停动画,避免资源浪费;
    • 如果有多个控制器,需使用TickerProviderStateMixin
  2. Tween 数值映射
    • Tween<double>(begin: 0.5, end: 1.0)将控制器的 0-1 数值映射到 0.5-1.0(缩放倍数);
    • CurvedAnimation为每个动画添加独立的曲线,比如缩放用Curves.easeOutBack(回弹效果),旋转用Curves.linear(匀速);
  3. Interval 区间动画
    • Interval(0, 0.5)表示透明度动画只在总时长的前 50%(0-500ms)执行,后 50% 保持 1.0,实现 “先显隐,后旋转缩放” 的时序效果;
  4. AnimatedBuilder 性能优化
    • child参数传入静态组件,builder中直接复用,避免每次动画帧都重建容器;
    • 只有TransformOpacity部分会随动画重建,大幅减少 CPU 开销;
  5. 资源释放
    • 重写dispose方法调用_controller.dispose(),是动画开发的 “必做项”,否则会导致内存泄漏。

3.4 效果演示

点击按钮后,紫色容器会:

  • 0-500ms:透明度从 0 到 1,同时缩放从 0.5 到 1(带回弹),旋转匀速执行;
  • 500-1000ms:透明度保持 1,缩放继续回弹到 1,旋转完成 360 度;整个过程多个属性联动,动效丰富且丝滑。

四、高阶:自定义交错动画组件(可复用)

实际开发中,我们需要给列表、网格等多个组件添加 “逐个入场” 的交错动画。下面封装一个通用的StaggeredAnimationWidget,支持自定义动画延迟和属性。

4.1 自定义交错动画组件实现

dart

import 'package:flutter/material.dart';

// 通用交错动画组件
class StaggeredAnimationWidget extends StatefulWidget {
  // 子组件列表
  final List<Widget> children;
  // 每个组件的动画延迟(毫秒)
  final int delayStep;
  // 单个组件动画时长
  final int duration;

  const StaggeredAnimationWidget({
    super.key,
    required this.children,
    this.delayStep = 100, // 默认每个延迟100ms
    this.duration = 300, // 默认时长300ms
  });

  @override
  State<StaggeredAnimationWidget> createState() =>
      _StaggeredAnimationWidgetState();
}

class _StaggeredAnimationWidgetState extends State<StaggeredAnimationWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  // 存储每个子组件的动画
  late List<Animation<double>> _animations;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      // 总时长 = 基础时长 + (子组件数-1)*延迟步长
      duration: Duration(
        milliseconds: widget.duration +
            (widget.children.length - 1) * widget.delayStep,
      ),
    );

    // 为每个子组件创建动画
    _animations = List.generate(widget.children.length, (index) {
      // 计算每个组件的动画区间
      final start = index * widget.delayStep / _controller.duration!.inMilliseconds;
      final end = start + widget.duration / _controller.duration!.inMilliseconds;

      // 位移动画:从下方50px到原位置 + 透明度动画
      return Tween<double>(begin: 1.0, end: 0.0).animate(
        CurvedAnimation(
          parent: _controller,
          curve: Interval(
            start.clamp(0.0, 1.0), // 防止越界
            end.clamp(0.0, 1.0),
            curve: Curves.easeOut,
          ),
        ),
      );
    });

    // 组件挂载后自动启动动画
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _controller.forward();
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: List.generate(widget.children.length, (index) {
        return AnimatedBuilder(
          animation: _animations[index],
          builder: (context, child) {
            return Transform.translate(
              // Y轴位移:从50px到0
              offset: Offset(0, 50 * _animations[index].value),
              child: Opacity(
                // 透明度:从0到1
                opacity: 1 - _animations[index].value,
                child: child,
              ),
            );
          },
          child: widget.children[index],
        );
      }),
    );
  }
}

4.2 使用自定义交错动画组件

dart

// 页面级使用示例
class StaggeredAnimationDemoPage extends StatelessWidget {
  const StaggeredAnimationDemoPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 模拟列表数据
    final List<String> items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];

    return Scaffold(
      appBar: AppBar(title: const Text('自定义交错动画')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: StaggeredAnimationWidget(
          delayStep: 150, // 每个item延迟150ms
          duration: 400, // 单个item动画400ms
          children: items
              .map((item) => Container(
                    margin: const EdgeInsets.only(bottom: 12),
                    padding: const EdgeInsets.all(16),
                    decoration: BoxDecoration(
                      color: Colors.greenAccent[100],
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Text(
                      item,
                      style: const TextStyle(fontSize: 18),
                    ),
                  ))
              .toList(),
        ),
      ),
    );
  }
}

4.3 核心逻辑拆解

  1. 动画区间计算
    • 每个子组件的动画起始时间 = index * delayStep,保证逐个入场;
    • Interval(start, end)精准控制每个组件的动画执行时段;
  2. 位移动画 + 透明度联动
    • Transform.translate实现 Y 轴向下位移(50px)到原位置的渐变;
    • 透明度从 0 到 1,结合位移实现 “从下往上渐显” 的经典入场效果;
  3. 自动启动动画
    • WidgetsBinding.instance.addPostFrameCallback确保组件挂载后再启动动画,避免动画提前执行;
  4. 可配置化设计
    • delayStepduration作为参数,支持不同场景下的动画节奏调整。

4.4 效果演示

页面加载后,5 个列表项会依次从下方 50px 处向上滑动并渐显,每个 item 延迟 150ms 启动,400ms 完成动画,整体呈现错落有致的入场效果,比一次性全部显示更有层次感。

五、动画开发避坑指南

  1. 避免过度动画
    • 单个交互的动效总数不超过 2 个,时长控制在 200-500ms;
    • 高频操作(如列表滚动)禁用复杂动画,避免卡顿;
  2. 资源释放
    • 所有AnimationController必须在dispose中释放,否则会导致内存泄漏;
  3. 性能优化
    • 优先使用AnimatedBuilder而非直接在build中使用动画数值;
    • 静态内容通过child参数传入AnimatedBuilder,避免重复重建;
  4. 适配不同设备
    • 动画时长可根据设备性能动态调整(比如低端机缩短时长);
  5. 测试动画效果
    • 使用 Flutter DevTools 的 “Animation” 面板,可视化调试动画曲线和时长。

六、总结

Flutter 动画的学习路径是 “封装组件→手动控制器→自定义组件”,核心是理解 “数值驱动变化” 的底层逻辑:

  1. 简单动效用AnimatedOpacityAnimatedContainer等封装组件,快速实现;
  2. 复杂联动动效用AnimationController+AnimatedBuilder,灵活控制;
  3. 通用动效封装为自定义组件,提升复用性和代码整洁度。

动画不是 “炫技”,而是服务于用户体验 —— 恰到好处的动效能引导用户注意力、强化交互反馈,而过度动画只会干扰用户。希望本文的代码案例和原理解析,能让你从 “会用” Flutter 动画到 “用好” Flutter 动画,写出既美观又高性能的动效代码。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐