显示动画的本质是「数值流转+信号驱动」

完整流水线如下:

AnimationController0~1基础进度)
    ↓ (节奏调整)
CurvedAnimation0~1带曲线进度)
    ↓ (数值转换)
Tween0~1 → 业务值:如50~300尺寸/红→蓝颜色)
    ↓ (存储结果)
Animation(实时存储业务值)
    ↓ (渲染UIAnimatedBuilder(读取value,局部重建UI

整体总结:控制器生成0~1进度 → 曲线修饰节奏 → Tween转换业务值 → Animation存储值 → AnimatedBuilder渲染UI

一、用「拍电影」比喻所有核心类(一看就懂)

我直接把整套体系换成你能立刻理解的角色:

Flutter 动画类 电影里的角色 一句话作用 解释
AnimationController 导演 + 场记板 控制:开始、暂停、倒放、时长,只输出 0~1 的进度 每 16ms 刷新一帧,输出一个 0~1 的进度
→ 这是唯一动力源
CurvedAnimation 运镜节奏 让动画不呆板:快进、慢放、回弹、缓入缓出(还是 0~1) 把 0~1 的直线进度,掰成好看的曲线进度
→ 还是 0~1,只是节奏变了
Tween 美术设定 / 剧本 把 0~1 转成真正的数值:大小、颜色、位置、圆角 把0~1的值转成实际的拍摄
Animation 实时画面帧 存当前这一帧的最终数值,给 UI 用 Tween调用animate把拍摄记录成实时的帧
AnimatedBuilder 放映机 只刷新动的部分,不卡、不浪费性能

一句话总结全文(最强记忆版)

  • AnimationController 提供 0~1 进度
  • CurvedAnimation 调整节奏
  • Tween 翻译成真实数值
  • Animation 存当前帧
  • AnimatedBuilder 高性能渲染

整条链路:进度 → 节奏 → 翻译 → 存值 → 显示

再一个例子,我们把显示动画比作「制作一杯奶茶」,瞬间理解核心角色:

Flutter动画核心类 奶茶制作中的角色 核心作用
AnimationController 奶茶店店员 掌控全局:启动/暂停/停止动画,决定制作时长
CurvedAnimation 搅拌手法(匀速/慢快/弹性) 修饰过程:让动画节奏更自然
Tween(Animatable) 配方(糖度0→100%、温度20→80℃) 转换数值:把基础进度转成实际业务值
Animation 进度表 记录结果:显示当前动画的实时值
AnimatedBuilder 出杯台 展示成品:根据进度渲染最终UI

显示动画的核心特征是需要开发者显式控制动画生命周期,区别于只需配置参数的隐式动画(如AnimatedContainer),适合实现自定义、多属性联动、精准控制的动效(比如路径动画、弹性缩放、颜色渐变)。


二、核心类展开

1. AnimationController —— 整部动画的“导演”

  • 它只干一件事:按时间输出 0~1
  • 控制动画:forward()reverse()stop()repeat()
  • 必须绑定 vsync: this,跟着屏幕刷新率走
  • 必须 dispose(),否则内存泄漏
_controller = AnimationController(
  vsync: this,
  duration: Duration(seconds: 2),
);

2. CurvedAnimation —— 动画的“节奏师”

  • 输入:0~1
  • 输出:曲线处理后的 0~1
  • 不改变范围,只改变快慢手感
final curve = CurvedAnimation(
  parent: _controller,
  curve: Curves.easeInOut,
);

3. Tween —— 数值“翻译官”

  • 控制器只会输出 0~1,界面看不懂
  • Tween 负责翻译
    0~1 → 你想要的任何值

常用:

  • Tween<double> 大小、透明度
  • ColorTween 颜色
  • SizeTween 尺寸
  • OffsetTween 位置
  • BorderRadiusTween 圆角
Animation<double> _sizeAnim = Tween<double>(
  begin: 50,
  end: 300,
).animate(curve);

4. Animation —— 最终“帧画面”

  • 它不是控制器,不是 Tween
  • 它就是:当前这一帧的结果
  • 你 UI 里用的永远是:animation.value

5. AnimatedBuilder —— 高性能“放映机”

  • 不用 setState 狂刷页面
  • 只刷新动画相关部分
  • 静态内容丢 child 里,完全不重建
AnimatedBuilder(
  animation: _controller,
  child: Text("我不动,不重建"),
  builder: (context, child) {
    return Container(
      width: _sizeAnim.value,
      child: child,
    );
  },
)

6. Listenable.merge —— 多镜头同步拍摄

  • 多个动画(大小、颜色、圆角)一起动
  • 用它合并监听,一个 AnimatedBuilder 搞定
Listenable.merge([
  _scaleAnim,
  _colorAnim,
  _radiusAnim,
])

三、核心类详细版本

1 动力源:AnimationController

AnimationController是整个动画的「发动机」,核心作用是定时生成0~1的进度值并发送信号

  1. 初始化时绑定vsync(垂直同步),保证只在屏幕刷新时计算进度(60帧/秒,约16ms一次),避免后台无效计算;
  2. 通过forward()/reverse()/stop()等方法控制动画播放状态;
  3. 每计算一个新进度值,就触发「值变化信号」,通知所有关联对象更新。

基础用法

// 初始化控制器
late AnimationController _controller;


void initState() {
  super.initState();
  _controller = AnimationController(
    vsync: this, // 混入SingleTickerProviderStateMixin
    duration: const Duration(seconds: 2), // 动画时长
    lowerBound: 0.0, // 最小值(默认0)
    upperBound: 1.0, // 最大值(默认1)
  );
  
  // 监听进度变化
  _controller.addListener(() {
    print('当前进度:${_controller.value}');
  });
  
  // 监听状态变化
  _controller.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      _controller.reverse(); // 完成后反向播放
    }
  });
}

// 务必释放资源

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

2 节奏调节器:CurvedAnimation

让动画摆脱匀速的「机械感」,通过曲线修饰进度变化节奏:

// 给控制器添加弹性曲线
CurvedAnimation _curve = CurvedAnimation(
  parent: _controller,
  curve: Curves.bounceOut, // 弹性结束
);

Flutter内置了丰富的曲线(如Curves.easeInOut先慢后快再慢、Curves.elasticIn弹性进入),也支持自定义曲线。

3 数值转换器:Tween

Tween是「规则手册」,负责把控制器输出的0~1标准化值,转换成业务需要的数值(尺寸、颜色、位置等),核心特点:

  • 无状态:仅定义映射规则,不存储实时值;
  • 需绑定:必须通过animate()绑定到AnimationController/CurvedAnimation,生成有状态的Animation对象;
  • 多类型:Flutter内置ColorTweenSizeTweenOffsetTween等常用子类。

基础用法

// 尺寸补间:0~1 → 50~300
Animation<double> _sizeAnim = Tween<double>(
  begin: 50.0,
  end: 300.0,
).animate(_curve);

// 颜色补间:0~1 → 红→蓝
Animation<Color?> _colorAnim = ColorTween(
  begin: Colors.red,
  end: Colors.blue,
).animate(_curve);

进阶玩法

  1. 自定义Tween:处理特殊类型值
class TextStyleTween extends Tween<TextStyle> {
  TextStyleTween({required TextStyle begin, required TextStyle end}) 
    : super(begin: begin, end: end);

  
  TextStyle lerp(double t) {
    return TextStyle(
      fontSize: lerpDouble(begin!.fontSize, end!.fontSize, t),
      color: Color.lerp(begin!.color, end!.color, t),
    );
  }
}
  1. 链式Tween:多步数值转换
// 0~1 → 0~100 → 50~150
Animation<double> _chainAnim = Tween<double>(begin: 0, end: 1)
  .chain(Tween<double>(begin: 0, end: 100))
  .chain(Tween<double>(begin: 50, end: 150))
  .animate(_controller);

4 高性能渲染:AnimatedBuilder

AnimatedBuilder是显示动画的「最佳拍档」,核心价值是局部重建UI,避免整个页面随动画重绘:

  • 只重建builder回调内的组件,静态内容可通过child参数复用;
  • 底层监听动画信号,自动处理重建逻辑,无需手动调用setState

基础用法

AnimatedBuilder(
  animation: _controller,
  // 静态子组件:只创建一次,不随动画重建
  child: const Text('静态文本', style: TextStyle(color: Colors.white)),
  builder: (context, child) {
    return Container(
      width: _sizeAnim.value,
      height: 100,
      color: _colorAnim.value,
      child: child, // 复用静态子组件
    );
  },
)

多动画联动:Listenable.merge

当需要同时监听多个动画时,Listenable.merge可将多个「信号源」合并为一个,简化监听逻辑:

AnimatedBuilder(
  // 合并缩放、颜色、圆角动画
  animation: Listenable.merge([_sizeAnim, _colorAnim, _radiusAnim]),
  builder: (context, child) {
    return Container(
      width: _sizeAnim.value,
      height: _sizeAnim.value,
      color: _colorAnim.value,
      borderRadius: _radiusAnim.value,
      child: child,
    );
  },
)

Listenable.merge的核心逻辑是「信号转发」:任意子动画触发信号,都会通知合并后的总信号源,只需一个AnimatedBuilder即可监听所有变化。

四、完整实战示例:多属性联动动画

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: ExplicitAnimationDemo()));

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

  
  State<ExplicitAnimationDemo> createState() => _ExplicitAnimationDemoState();
}

class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnim;
  late Animation<Color?> _colorAnim;
  late Animation<BorderRadius?> _radiusAnim;
  late Listenable _mergedAnim;

  
  void initState() {
    super.initState();
    // 1. 初始化控制器
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );

    // 2. 添加曲线修饰
    final curve = CurvedAnimation(parent: _controller, curve: Curves.bounceOut);

    // 3. 定义Tween动画
    _scaleAnim = Tween<double>(begin: 0.8, end: 1.5).animate(curve);
    _colorAnim = ColorTween(begin: Colors.blue, end: Colors.orange).animate(curve);
    _radiusAnim = BorderRadiusTween(
      begin: BorderRadius.circular(10),
      end: BorderRadius.circular(50),
    ).animate(curve);

    // 4. 合并多动画
    _mergedAnim = Listenable.merge([_scaleAnim, _colorAnim, _radiusAnim]);

    // 5. 循环播放动画
    _controller.repeat(reverse: true);
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter显示动画实战')),
      body: Center(
        child: AnimatedBuilder(
          animation: _mergedAnim,
          child: const Text(
            '多属性联动',
            style: TextStyle(color: Colors.white, fontSize: 20),
          ),
          builder: (context, child) {
            return Transform.scale(
              scale: _scaleAnim.value,
              child: Container(
                width: 200,
                height: 200,
                color: _colorAnim.value,
                borderRadius: _radiusAnim.value,
                child: child, // 重复利用child,不用每次都重建
              ),
            );
          },
        ),
      ),
    );
  }
}

核心总结

  1. 核心流水线:控制器生成0~1进度 → 曲线修饰节奏 → Tween转换业务值 → Animation存储值 → AnimatedBuilder渲染UI;
  2. 性能关键:用AnimatedBuilder实现局部重建,静态内容通过child复用,多动画用Listenable.merge合并监听;
  3. 避坑指南:务必释放AnimationController,避免在builder内创建新对象,Tween需绑定后才能读取value

掌握显示动画的核心逻辑后,你可以轻松实现任意自定义动效——从简单的缩放渐变,到复杂的路径动画、物理动效,Flutter显示动画的灵活性足以支撑所有场景的需求。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐