Flutter 动画实战:隐式动画、显式动画与自定义动画控制器

Flutter 动画实战:隐式动画、显式动画与自定义动画控制器

动画是提升 Flutter 应用交互体验的核心手段,能够让界面过渡更流畅、操作反馈更直观。Flutter 提供了完善的动画体系,按实现复杂度可分为隐式动画、显式动画和自定义动画三大类,分别适配不同的业务场景。本文将从动画核心原理入手,通过具体实战案例,详细解析三种动画的实现逻辑、使用场景及进阶技巧,帮助开发者快速掌握 Flutter 动画开发能力。

作者:爱吃大芒果

个人主页 爱吃大芒果

本文所属专栏 Flutter

更多专栏

Ascend C 算子开发教程(进阶)
鸿蒙集成
从0到1自学C++

一、动画核心原理:理解 Flutter 动画的底层逻辑

在深入实战前,需先掌握 Flutter 动画的核心概念,明确其底层工作机制,为后续开发奠定基础。

1. 核心概念解析

  • Animation :动画的核心抽象类,用于存储动画的当前值和状态(如是否正在播放、是否完成),本身不执行动画,仅提供值的变化监听。常见实现类为 Animation<T>(泛型支持多种数据类型)。

  • AnimationController:动画控制器,负责控制动画的生命周期(启动、暂停、反转、重置),生成从 0.0 到 1.0 的线性变化值,是驱动动画的核心动力。

  • Curve:动画曲线,定义动画值变化的速率(如匀速、加速、减速),可通过自定义曲线实现个性化动画效果(如弹性、弹跳)。

  • Tween:值映射器,用于将 AnimationController 生成的 0.0-1.0 范围值,映射到实际需要的业务值范围(如从 50 到 200 的尺寸变化、从红色到蓝色的颜色变化)。

  • AnimatedWidgetAnimatedBuilder:动画widget封装,用于监听 Animation 值变化并重建 UI,避免手动添加监听器的冗余代码。

2. 动画执行流程

Flutter 动画的核心执行流程可概括为:① 初始化 AnimationController,设置动画时长;② 通过 Tween 映射目标值范围;③ 绑定 Curve 定义变化速率;④ 通过 AnimatedWidget 或 AnimatedBuilder 监听值变化并重建 UI;⑤ 调用控制器方法(如 forward())启动动画。

二、隐式动画:无需控制器的极简动画实现

隐式动画(Implicit Animations)是 Flutter 封装的"开箱即用"动画,内部已自动实现 AnimationController、Tween 等核心逻辑,无需手动管理控制器,仅需指定目标值和动画时长即可实现动画效果。适合简单的属性过渡动画(如尺寸、颜色、透明度变化)。

1. 核心特点与适用场景

特点:API 简洁,开发成本低;内部自动管理动画生命周期;仅支持预定义的可动画属性。适用场景:简单的属性过渡(如按钮点击后的缩放、页面切换时的淡入淡出、文本颜色变化)。

2. 常用隐式动画组件实战

Flutter 提供了多个封装好的隐式动画组件,以下是最常用的 4 个组件的实战案例:

(1)AnimatedContainer:容器属性动画

支持容器相关属性的动画过渡(如宽高、颜色、边框、圆角、padding 等),核心代码如下:

dart 复制代码
import 'package:flutter/material.dart';

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

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  // 控制动画状态的变量
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedContainer 实战')),
      body: Center(
        child: GestureDetector(
          // 点击触发动画
          onTap: () => setState(() => _isExpanded = !_isExpanded),
          child: AnimatedContainer(
            // 动画时长(必须指定)
            duration: const Duration(milliseconds: 500),
            // 动画曲线(可选,默认匀速)
            curve: Curves.easeInOut,
            // 动态变化的属性
            width: _isExpanded ? 300 : 150,
            height: _isExpanded ? 300 : 150,
            color: _isExpanded ? Colors.blue : Colors.red,
            borderRadius: BorderRadius.circular(_isExpanded ? 50 : 8),
            alignment: Alignment.center,
            // 子组件
            child: const Text(
              '点击缩放',
              style: TextStyle(color: Colors.white, fontSize: 20),
            ),
          ),
        ),
      ),
    );
  }
}

核心逻辑:通过setState 改变 _isExpanded 状态,AnimatedContainer 会自动监听属性变化,在指定时长内完成从旧值到新值的动画过渡。

(2)AnimatedOpacity:透明度动画

用于实现组件的淡入淡出效果,核心代码如下:

dart 复制代码
class AnimatedOpacityDemo extends StatefulWidget {
  const AnimatedOpacityDemo({super.key});

  @override
  State<AnimatedOpacityDemo> createState() => _AnimatedOpacityDemoState();
}

class _AnimatedOpacityDemoState extends State<AnimatedOpacityDemo> {
  double _opacity = 1.0; // 初始透明度(1.0 完全不透明)

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedOpacity 实战')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedOpacity(
              opacity: _opacity,
              duration: const Duration(seconds: 1),
              curve: Curves.fadeInOut,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.green,
              ),
            ),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: () => setState(() {
                _opacity = _opacity == 1.0 ? 0.0 : 1.0; // 切换透明度
              }),
              child: const Text('切换淡入淡出'),
            ),
          ],
        ),
      ),
    );
  }
}
(3)AnimatedPadding:内边距动画

实现内边距的平滑过渡,常用于组件的伸缩效果,核心代码如下:

dart 复制代码
class AnimatedPaddingDemo extends StatefulWidget {
  const AnimatedPaddingDemo({super.key});

  @override
  State<AnimatedPaddingDemo> createState() => _AnimatedPaddingDemoState();
}

class _AnimatedPaddingDemoState extends State<AnimatedPaddingDemo> {
  EdgeInsetsGeometry _padding = const EdgeInsets.all(16);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedPadding 实战')),
      body: Center(
        child: GestureDetector(
          onTap: () => setState(() {
            _padding = _padding == const EdgeInsets.all(16) 
                ? const EdgeInsets.all(60) 
                : const EdgeInsets.all(16);
          }),
          child: AnimatedPadding(
            padding: _padding,
            duration: const Duration(milliseconds: 300),
            curve: Curves.bounceInOut,
            child: Container(
              width: double.infinity,
              height: 100,
              color: Colors.orange,
              child: const Center(child: Text('点击调整内边距')),
            ),
          ),
        ),
      ),
    );
  }
}
(4)AnimatedDefaultTextStyle:文本样式动画

实现文本样式(颜色、字体大小、字体粗细等)的平滑过渡,核心代码如下:

dart 复制代码
class AnimatedTextStyleDemo extends StatefulWidget {
  const AnimatedTextStyleDemo({super.key});

  @override
  State<AnimatedTextStyleDemo> createState() => _AnimatedTextStyleDemoState();
}

class _AnimatedTextStyleDemoState extends State<AnimatedTextStyleDemo> {
  bool _isHighlighted = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedDefaultTextStyle 实战')),
      body: Center(
        child: GestureDetector(
          onTap: () => setState(() => _isHighlighted = !_isHighlighted),
          child: AnimatedDefaultTextStyle(
            style: _isHighlighted
                ? const TextStyle(
                    fontSize: 32,
                    color: Colors.red,
                    fontWeight: FontWeight.bold,
                  )
                : const TextStyle(
                    fontSize: 20,
                    color: Colors.black,
                    fontWeight: FontWeight.normal,
                  ),
            duration: const Duration(milliseconds: 400),
            curve: Curves.ease,
            child: const Text('文本样式动画'),
          ),
        ),
      ),
    );
  }
}

三、显式动画:手动控制的灵活动画实现

显式动画(Explicit Animations)需要手动创建和管理 AnimationController,能够精确控制动画的启动、暂停、反转、重置等状态,支持更复杂的动画效果(如序列动画、并行动画)。适合需要自定义动画逻辑的场景。

1. 核心特点与适用场景

特点:灵活性高,可精确控制动画生命周期;支持自定义 Tween 和 Curve;需手动管理控制器的初始化与销毁。适用场景:复杂动画效果(如旋转+缩放组合动画、序列动画、根据业务逻辑触发的动画)。

2. 常用显式动画组件实战

Flutter 中常用的显式动画组件为 AnimatedWidgetAnimatedBuilder,以下是具体实战案例:

(1)AnimatedWidget:动画组件封装

AnimatedWidget 是抽象类,需继承后实现 build 方法,内部自动监听 Animation 值变化并重建 UI。适合将动画逻辑与 UI 组件封装在一起,核心代码如下(实现"旋转+缩放"组合动画):

dart 复制代码
class RotateScaleAnimation extends AnimatedWidget {
  // 接收 Animation 对象
  const RotateScaleAnimation({
    super.key,
    required Animation<double> animation,
  }) : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable as Animation<double>;
    return Transform.rotate(
      angle: animation.value * 2.0 * 3.14159, // 旋转角度(0-2π,即一圈)
      child: Transform.scale(
        scale: animation.value * 2.0, // 缩放比例(0-2倍)
        child: Container(
          width: 100,
          height: 100,
          color: Colors.purple,
        ),
      ),
    );
  }
}

// 页面使用
class AnimatedWidgetDemo extends StatefulWidget {
  const AnimatedWidgetDemo({super.key});

  @override
  State<AnimatedWidgetDemo> createState() => _AnimatedWidgetDemoState();
}

class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo>
    with SingleTickerProviderStateMixin { // 提供动画帧回调
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    // 1. 初始化控制器
    _controller = AnimationController(
      vsync: this, // 绑定页面生命周期,避免动画泄漏
      duration: const Duration(seconds: 2),
    );

    // 2. 定义动画曲线和值映射
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.bounceOut, // 弹跳曲线
    );

    // 3. 启动动画(循环播放)
    _controller.repeat(reverse: true); // reverse: true 表示动画完成后反向播放
  }

  @override
  void dispose() {
    // 4. 销毁控制器,避免内存泄漏
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedWidget 实战')),
      body: Center(
        child: RotateScaleAnimation(animation: _animation),
      ),
    );
  }
}

注意:使用 AnimationController 时,State 类需混入 SingleTickerProviderStateMixin(单控制器)或 TickerProviderStateMixin(多控制器),用于提供动画帧回调,避免动画在页面销毁后继续执行。

(2)AnimatedBuilder:动画逻辑与 UI 分离

AnimatedBuilder 无需继承,直接通过构造函数传入 Animation 和 builder 函数,实现动画逻辑与 UI 组件的分离,复用性更强。核心代码如下(实现"平移+透明度"组合动画):

dart 复制代码
class AnimatedBuilderDemo extends StatefulWidget {
  const AnimatedBuilderDemo({super.key});

  @override
  State<AnimatedBuilderDemo> createState() => _AnimatedBuilderDemoState();
}

class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _translateAnimation;
  late Animation<double> _opacityAnimation;

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

    // 2. 定义多个动画(平移和透明度)
    _translateAnimation = Tween<double>(begin: -100, end: 100).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );

    _opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.fadeIn),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedBuilder 实战')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 2. 使用 AnimatedBuilder 构建动画组件
            AnimatedBuilder(
              animation: _controller, // 监听控制器
              builder: (context, child) {
                return Transform.translate(
                  offset: Offset(_translateAnimation.value, 0), // 水平平移
                  child: Opacity(
                    opacity: _opacityAnimation.value, // 透明度
                    child: child, // 传入固定子组件,避免重复构建
                  ),
                );
              },
              // 固定子组件,仅构建一次
              child: Container(
                width: 150,
                height: 150,
                color: Colors.teal,
                child: const Center(child: Text('平移+淡入')),
              ),
            ),
            const SizedBox(height: 30),
            // 3. 控制动画的按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () => _controller.forward(), // 正向播放
                  child: const Text('开始'),
                ),
                ElevatedButton(
                  onPressed: () => _controller.reverse(), // 反向播放
                  child: const Text('反转'),
                ),
                ElevatedButton(
                  onPressed: () => _controller.reset(), // 重置
                  child: const Text('重置'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

核心优势:AnimatedBuilder 将动画逻辑封装在 builder 函数中,子组件通过 child 参数传入,可避免动画帧刷新时重复构建固定子组件,提升性能。

四、自定义动画控制器:高级动画效果实现

通过自定义 AnimationController 的行为、结合多个 Tween 和 Curve,可实现更复杂的高级动画效果(如序列动画、并行动画、弹性动画)。以下是两个典型的自定义动画实战案例:

1. 序列动画:按顺序执行多个动画

序列动画指多个动画按先后顺序执行(如先缩放、再旋转、最后平移),可通过 AnimationControlleraddStatusListener 监听动画状态变化,触发下一个动画,核心代码如下:

dart 复制代码
class SequenceAnimationDemo extends StatefulWidget {
  const SequenceAnimationDemo({super.key});

  @override
  State<SequenceAnimationDemo> createState() => _SequenceAnimationDemoState();
}

class _SequenceAnimationDemoState extends State<SequenceAnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _rotateAnimation;
  late Animation<double> _translateAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 3),
    );

    // 1. 定义三个动画(缩放、旋转、平移)
    _scaleAnimation = Tween<double>(begin: 1.0, end: 2.0).animate(
      CurvedAnimation(parent: _controller, curve: const Interval(0.0, 0.3)), // 0-0.3 秒执行缩放
    );

    _rotateAnimation = Tween<double>(begin: 0, end: 2 * 3.14159).animate(
      CurvedAnimation(parent: _controller, curve: const Interval(0.3, 0.6)), // 0.3-0.6 秒执行旋转
    );

    _translateAnimation = Tween<double>(begin: 0, end: 100).animate(
      CurvedAnimation(parent: _controller, curve: const Interval(0.6, 1.0)), // 0.6-1.0 秒执行平移
    );

    // 2. 监听动画完成状态,循环播放
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reset();
        _controller.forward();
      }
    });

    // 启动动画
    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('序列动画实战')),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Transform.translate(
              offset: Offset(_translateAnimation.value, 0),
              child: Transform.rotate(
                angle: _rotateAnimation.value,
                child: Transform.scale(
                  scale: _scaleAnimation.value,
                  child: child,
                ),
              ),
            );
          },
          child: Container(
            width: 100,
            height: 100,
            color: Colors.pink,
          ),
        ),
      ),
    );
  }
}

核心逻辑:通过Interval 曲线指定每个动画的执行时间段(范围 0.0-1.0,对应整个动画时长),实现多个动画的顺序执行。

2. 弹性动画:自定义曲线实现弹跳效果

通过自定义 Curve 或使用 Flutter 内置的弹性曲线(如 Curves.elasticInOut),实现类似弹簧的弹性动画效果,核心代码如下:

dart 复制代码
class ElasticAnimationDemo extends StatefulWidget {
  const ElasticAnimationDemo({super.key});

  @override
  State<ElasticAnimationDemo> createState() => _ElasticAnimationDemoState();
}

class _ElasticAnimationDemoState extends State<ElasticAnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );

    // 1. 使用内置弹性曲线
    _animation = Tween<double>(begin: 0, end: 300).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.elasticOut, // 弹性曲线(先超出目标值再回弹)
      ),
    );

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('弹性动画实战')),
      body: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return Align(
            alignment: Alignment.bottomCenter,
            child: Container(
              width: 50,
              height: _animation.value,
              color: Colors.yellow,
            ),
          );
        },
      ),
    );
  }
}

自定义曲线扩展:若内置曲线无法满足需求,可通过继承 Curve 实现自定义曲线,示例如下:

dart 复制代码
// 自定义曲线:先加速后减速,最后轻微回弹
class CustomCurve extends Curve {
  @override
  double transform(double t) {
    // t 范围 0.0-1.0
    if (t < 0.8) {
      return Curves.easeInOut.transform(t / 0.8); // 前 80% 时间执行 easeInOut
    } else {
      final double remaining = (t - 0.8) / 0.2; // 后 20% 时间
      return 1.0 - (remaining * remaining) * 0.1; // 轻微回弹
    }
  }
}

// 使用自定义曲线
_animation = Tween<double>(begin: 0, end: 300).animate(
  CurvedAnimation(
    parent: _controller,
    curve: CustomCurve(),
  ),
);

五、动画性能优化与最佳实践

动画的流畅性直接影响用户体验,需注意以下性能优化要点,避免动画卡顿:

1. 减少重建范围

  • 使用 AnimatedBuilder 时,将固定子组件通过 child 参数传入,避免动画帧刷新时重复构建;

  • 避免在动画的 build 方法中创建新对象(如 TextStyleContainer),应提前缓存;

  • 使用RepaintBoundary 包裹动画组件,将动画组件的重绘范围限制在自身内部,避免影响其他组件。

2. 合理选择动画时长与曲线

  • 短期动画(如点击反馈)时长建议 100-300ms,长期动画(如页面过渡)时长建议 300-500ms;

  • 优先使用 Flutter 内置曲线(如 Curves.easeInOutCurves.fadeIn),自定义曲线需保证计算简洁;

  • 避免过度使用弹性曲线,过多弹性效果会导致界面杂乱。

3. 正确管理控制器生命周期

  • dispose 方法中必须调用 _controller.dispose(),避免内存泄漏;

  • 使用 SingleTickerProviderStateMixin(单控制器)或 TickerProviderStateMixin(多控制器),确保动画与页面生命周期绑定;

  • 避免在页面销毁后仍尝试控制动画(如 forward()reverse())。

4. 复杂动画使用硬件加速

对于复杂的动画效果(如大量粒子动画、3D 旋转),可通过 RepaintBoundary 结合硬件加速提升性能,示例如下:

dart 复制代码
RepaintBoundary(
  child: Transform(
    transform: Matrix4.rotationY(_animation.value),
    alignment: Alignment.center,
    child: Container(
      width: 200,
      height: 200,
      color: Colors.blue,
    ),
  ),
)

六、三种动画方案对比与选型建议

对比维度 隐式动画 显式动画 自定义动画控制器
易用性 ★★★★★(极高,无需控制器) ★★★☆(需手动管理控制器) ★★☆(需自定义逻辑)
灵活性 ★★☆(仅支持预定义属性) ★★★★(支持组合动画) ★★★★★(完全自定义)
适用场景 简单属性过渡(缩放、淡入、颜色变化) 组合动画、需控制生命周期的动画 序列动画、弹性动画、复杂自定义效果
开发成本
性能开销 低(内部优化) 中(需注意重建范围) 高(复杂逻辑可能卡顿)

选型建议

  • 简单场景优先选隐式动画:如按钮点击缩放、文本颜色变化,快速实现且无需关注底层逻辑;

  • 中等复杂度场景选显式动画:如组合动画、需要手动控制启动/暂停的动画,兼顾灵活性与开发效率;

  • 复杂自定义场景选自定义动画控制器:如序列动画、弹性动画、游戏动画,完全掌控动画效果。

七、结语

Flutter 动画体系通过隐式动画、显式动画和自定义动画控制器,覆盖了从简单到复杂的全场景动画需求。开发者在实际开发中,应根据业务场景的复杂度选择合适的动画方案:简单场景用隐式动画提升开发效率,复杂场景用显式动画或自定义动画控制器保证效果灵活性。同时,需重视动画性能优化,通过减少重建范围、合理管理控制器生命周期等手段,确保动画流畅运行。

最终建议:先掌握隐式动画和显式动画的基础用法,再逐步深入自定义动画控制器,通过大量实战案例积累经验,最终能够根据需求灵活设计出流畅、美观的动画效果。

相关推荐
雾岛听蓝5 小时前
C++ 类和对象(二):默认成员函数详解
开发语言·c++
shuaijie05185 小时前
在Vue.js中实现列表的拖动功能,使用第三方库如vuedraggable(基于Sortable.js)
android·javascript·vue.js
郝学胜-神的一滴5 小时前
OpenGL中的glDrawArrays函数详解:从基础到实践
开发语言·c++·程序人生·算法·游戏程序·图形渲染
李白你好5 小时前
Bypass_Webshell webshell编码工具 支持 jsp net php asp编码免杀
开发语言·php
feifeigo1235 小时前
C#中实现控件拖动功能
开发语言·c#
曹牧5 小时前
C#:List<string>类型的集合转换成用逗号分隔的字符串
开发语言·c#·list
fengfuyao9855 小时前
基于C# WinForm的收银管理系统实现
开发语言·c#
05大叔5 小时前
苍穹外买Day05
java·开发语言
代码or搬砖5 小时前
Java集合-List讲解
java·开发语言·list