Skip to content

动画

动画让 App 更有活力。Flutter 提供了从简单到复杂的多种动画方案。

动画分类

类型特点适用场景
隐式动画简单,自动管理简单的属性变化动画
显式动画灵活,手动控制复杂的自定义动画
Hero 动画页面间共享元素页面跳转时的过渡动画

一、隐式动画

隐式动画是最简单的动画方式,你只需要设置目标值,框架自动处理动画过程。

AnimatedContainer

dart
class AnimatedBox extends StatefulWidget {
  @override
  State<AnimatedBox> createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _expanded = !_expanded),
      // ─── ★ AnimatedContainer 隐式动画 ──────────────
      child: AnimatedContainer(
        // ─── ★ 动画时长 ──────────────
        duration: Duration(milliseconds: 300),
        // ─── ☆ 动画时长 ──────────────
        // ─── ★ 动画曲线 ──────────────
        curve: Curves.easeInOut,
        // ─── ☆ 动画曲线 ──────────────
        width: _expanded ? 200 : 100,
        height: _expanded ? 200 : 100,
        color: _expanded ? Colors.blue : Colors.red,
        alignment: Alignment.center,
        child: Text('点击切换'),
      ),
      // ─── ☆ AnimatedContainer 隐式动画 ──────────────
    );
  }
}

AnimatedOpacity — 渐入渐出

dart
import 'package:flutter/material.dart';

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

  @override
  State<AnimatedOpacityExample> createState() => _AnimatedOpacityExampleState();
}

class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedOpacity')),
      body: Center(
        // ─── ★ AnimatedOpacity 渐入渐出 ──────────────
        child: AnimatedOpacity(
          opacity: _visible ? 1.0 : 0.0,
          duration: const Duration(milliseconds: 300),
          child: const Text('渐入渐出', style: TextStyle(fontSize: 24)),
        ),
        // ─── ☆ AnimatedOpacity 渐入渐出 ──────────────
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _visible = !_visible),
        child: const Icon(Icons.visibility),
      ),
    );
  }
}

AnimatedSwitcher — 切换动画

dart
import 'package:flutter/material.dart';

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

  @override
  State<AnimatedSwitcherExample> createState() => _AnimatedSwitcherExampleState();
}

class _AnimatedSwitcherExampleState extends State<AnimatedSwitcherExample> {
  bool _showA = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedSwitcher')),
      body: Center(
        child: AnimatedSwitcher(
          duration: const Duration(milliseconds: 300),
          child: _showA
              ? const Text('A', key: ValueKey('a'), style: TextStyle(fontSize: 48))
              : const Text('B', key: ValueKey('b'), style: TextStyle(fontSize: 48)),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _showA = !_showA),
        child: const Icon(Icons.swap_horiz),
      ),
    );
  }
}

其他隐式动画组件

组件动画属性
AnimatedContainer宽、高、颜色、边距等
AnimatedOpacity不透明度
AnimatedSwitcher子组件切换
AnimatedPadding内边距
AnimatedAlign对齐方式
AnimatedPositioned位置(需在 Stack 中)
AnimatedDefaultTextStyle文字样式
AnimatedIcon图标动画
AnimatedCrossFade两个子组件交叉渐变

隐式动画常用属性

属性说明
duration动画时长
curve动画曲线(缓动函数)
onEnd动画结束回调

常用 Curves 缓动曲线

dart
Curves.linear          // 匀速
Curves.easeIn          // 慢启动
Curves.easeOut         // 慢结束
Curves.easeInOut       // 慢启动慢结束
Curves.bounceIn        // 弹入
Curves.bounceOut       // 弹出
Curves.elasticIn       // 弹性入
Curves.elasticOut      // 弹性出
Curves.fastOutSlowIn   // Material 标准曲线

二、显式动画

显式动画使用 AnimationController 手动控制动画过程,灵活度更高。

AnimationController

dart
class FadeInWidget extends StatefulWidget {
  final Widget child;
  const FadeInWidget({super.key, required this.child});

  @override
  State<FadeInWidget> createState() => _FadeInWidgetState();
}

class _FadeInWidgetState extends State<FadeInWidget>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeIn,
    );
    _controller.forward();  // 启动动画
  }

  @override
  void dispose() {
    _controller.dispose();  // 必须释放
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: _animation,
      child: widget.child,
    );
  }
}

AnimationController 常用方法

方法说明
forward()从头到尾播放
reverse()从尾到头播放
repeat()循环播放
stop()停止
reset()重置到起点
animateTo(target)动画到指定值
animateBack(target)反向动画到指定值

Tween — 值范围

dart
// 数值范围
final tween = Tween<double>(begin: 0, end: 1);
final value = tween.transform(_controller.value);

// 颜色范围
final colorTween = ColorTween(begin: Colors.red, end: Colors.blue);
final color = colorTween.transform(_controller.value);

// 使用 animate 方法
final animation = Tween<double>(begin: 0, end: 200).animate(_controller);

Transition 组件

组件动画效果
FadeTransition渐变透明度
SlideTransition滑动位置
ScaleTransition缩放
RotationTransition旋转
SizeTransition尺寸
PositionedTransition位置变化

常见动画模式

渐变 + 位移:

dart
SlideTransition(
  position: Tween<Offset>(
    begin: Offset(0, 0.5),  // 从下方开始
    end: Offset.zero,
  ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut)),
  child: FadeTransition(
    opacity: _controller,
    child: widget.child,
  ),
)

交错动画(Staggered Animation):

dart
// 多个动画按时间顺序执行
final fadeIn = Tween<double>(begin: 0, end: 1).animate(
  CurvedAnimation(
    parent: _controller,
    curve: Interval(0.0, 0.5),  // 前半段时间
  ),
);

final slideUp = Tween<Offset>(begin: Offset(0, 0.5), end: Offset.zero).animate(
  CurvedAnimation(
    parent: _controller,
    curve: Interval(0.3, 1.0),  // 后 70% 时间
  ),
);

三、Hero — 共享元素动画

Hero 动画让一个元素在页面跳转时"飞"过去,非常适合图片详情页等场景。

基本用法

dart
import 'package:flutter/material.dart';

// 页面 A:列表页
class HeroListPage extends StatelessWidget {
  const HeroListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('相册')),
      body: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => const HeroDetailPage()),
          );
        },
        child: Hero(
          tag: 'photo_123',  // 唯一标识
          child: Container(
            width: 100, height: 100,
            color: Colors.blue,
            child: const Icon(Icons.image, color: Colors.white, size: 48),
          ),
        ),
      ),
    );
  }
}

// 页面 B:详情页
class HeroDetailPage extends StatelessWidget {
  const HeroDetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('详情')),
      body: Center(
        child: Hero(
          tag: 'photo_123',  // 相同的 tag
          child: Container(
            width: 200, height: 200,
            color: Colors.blue,
            child: const Icon(Icons.image, color: Colors.white, size: 96),
          ),
        ),
      ),
    );
  }
}

完整示例

dart
// 列表页
class PhotoListPage extends StatelessWidget {
  final List<String> photos = ['photo1', 'photo2', 'photo3'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('相册')),
      body: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
        itemCount: photos.length,
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => PhotoDetailPage(photoId: photos[index]),
              ),
            ),
            child: Hero(
              tag: 'photo_${photos[index]}',
              child: Image.asset('assets/${photos[index]}.jpg', fit: BoxFit.cover),
            ),
          );
        },
      ),
    );
  }
}

// 详情页
class PhotoDetailPage extends StatelessWidget {
  final String photoId;
  const PhotoDetailPage({super.key, required this.photoId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Hero(
          tag: 'photo_$photoId',
          child: Image.asset('assets/$photoId.jpg'),
        ),
      ),
    );
  }
}

Hero 动画自定义

dart
Hero(
  tag: 'photo_123',
  flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
    return ScaleTransition(
      scale: animation,
      child: Image.asset('assets/photo.jpg'),
    );
  },
  child: Image.asset('assets/photo.jpg'),
)

动画选择指南

需求方案
简单属性变化(大小、颜色、透明度)隐式动画(AnimatedContainer 等)
需要精确控制动画过程显式动画(AnimationController)
页面间元素过渡Hero 动画
复杂的多步动画交错动画(Interval)
循环动画AnimationController.repeat()

建议

  • 优先使用隐式动画——代码少,不易出错
  • 需要精确控制时才用显式动画
  • 动画时长一般 200~500ms,太短看不清,太长感觉卡
  • 使用 Curves.easeInOut 等曲线让动画更自然

下一步

基于 Flutter 官方文档整理