Flutter - 动画
Flutter - 动画基础
Section titled “Flutter - 动画基础”动画通过添加运动和视觉反馈来增强用户体验,使应用感觉更具交互性和精致。虽然动画可能很复杂,但 Flutter 提供了一个强大而直观的动画框架。
动画本质上是通过在特定持续时间内显示一系列帧来创建的运动幻觉。关键方面包括:
- 起始值和结束值(Start and End Values): 动画在定义的起始值和结束值之间进行插值(interpolate)(例如,将透明度从 1.0(完全可见)动画到 0.0(不可见))。
- 插值(Tweening): 计算起始值和结束值之间的中间值的过程。可以是线性的(恒定速度)或非线性的(使用 Curves 控制加速、减速、弹跳等)。
- 持续时间(Duration): 动画完成的总时间,影响其感知速度。
- 控制(Control): 启动、停止、反向或重复动画的能力。
- Ticker: 为每一帧提供一个信号(一个 ‘tick’),将动画与屏幕刷新率同步。
- Animation 对象(Animation Object): 在 Flutter 中,动画系统主要通过
Animation对象随时间生成变化的值(如数字、颜色、大小)。它不直接渲染;widgets 在构建过程中使用这些值来更新其属性。
Flutter 的动画系统围绕几个核心类展开:
Animation
Section titled “Animation”表示一个类型为 T 且随时间变化的值。它知道其当前值和状态(例如,运行中、已完成、已取消)。常见类型包括 Animation<double>、Animation<Color>、Animation<Size>。
AnimationController
Section titled “AnimationController”一个特殊的 Animation<double>,用于管理动画过程。它在给定的持续时间内生成从 0.0 到 1.0 的值,并提供控制动画的方法(例如,forward() 前进、reverse() 反向、stop() 停止、repeat() 重复)。它需要一个 TickerProvider(vsync)。
// Inside a State class that mixes in TickerProviderStateMixinlate AnimationController _controller;
@overridevoid initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, // 'this' provides the Ticker );}
@overridevoid dispose() { _controller.dispose(); // IMPORTANT: Dispose the controller! super.dispose();}vsync 防止屏幕外的动画消耗资源。它通常是通过将 TickerProviderStateMixin(用于单个 controller)或 SingleTickerProviderStateMixin(用于多个 controllers)混入(mixin)到你的 State 类中来提供的。
定义 AnimationController 的 0.0-1.0 范围与你的动画实际需要的值范围之间的映射关系(例如,将 0.0-1.0 映射到 0-300 像素,或映射到特定颜色)。它是 ‘in-between’ 的缩写。
// Creates an animation that goes from 50.0 to 200.0late Animation<double> _sizeAnimation;
// In initState, after controller is initialized:_sizeAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(_controller);CurvedAnimation
Section titled “CurvedAnimation”将非线性 Curve(如 Curves.easeIn、Curves.bounceOut)应用于现有动画(通常是 AnimationController)。这会修改变化的速率,而不是值范围。
// Apply an ease-in curve to the controllerlate Animation<double> _curvedAnimation;
// In initState, after controller is initialized:_curvedAnimation = CurvedAnimation( parent: _controller, curve: Curves.easeIn,);
// You can then chain a Tween with the CurvedAnimation:_sizeAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(_curvedAnimation);动画工作流程
Section titled “动画工作流程”创建显式动画的典型工作流程包括:
- 设置(在
State中): -
- 混入
TickerProviderStateMixin或SingleTickerProviderStateMixin。
- 混入
-
- 在
initState中创建AnimationController,提供duration和vsync。
- 在
-
- 创建
Tween以定义值范围。
- 创建
-
- 通过使用
.animate()将Tween(以及可选的CurvedAnimation)连接到AnimationController来创建Animation对象。
- 通过使用
-
- 重要: 在
dispose方法中释放AnimationController。
- 重要: 在
- 监听/构建(Listening/Building): 选择以下一种方法来在动画值改变时重建 UI:
-
AnimatedBuilder:(推荐)一个 widget,它监听一个动画,并在动画值改变时重建其builder函数。它通过仅影响其子树来优化重建。
-
AnimatedWidget: 用于创建可重用动画 widgets 的基类(如FadeTransition、SlideTransition)。你扩展它并在其build方法中直接使用动画值。
-
addListener+setState: 手动向Animation或AnimationController添加监听器。在监听器回调函数内部,调用setState(() {})来触发整个 widget 的重建。这不如AnimatedBuilder或AnimatedWidget高效。
- 使用值(Using the Value): 在
build方法中(在AnimatedBuilder的 builder 中,或在AnimatedWidget中,或如果使用addListener则在主build中),使用animation.value访问动画的当前值,并将其应用于 widget 的属性(如width、height、opacity、color、transform)。 - 启动动画(Starting the Animation): 调用
AnimationController上的方法,如_controller.forward()或_controller.repeat()。
工作示例:渐隐和放大 Widget
Section titled “工作示例:渐隐和放大 Widget”让我们创建一个应用,让一个 logo 渐隐出现并放大。
- 创建一个新的 Flutter 项目:
flutter create animation_example。 - 将
lib/main.dart替换为以下代码:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Animation Demo', theme: ThemeData(primarySwatch: Colors.blue), home: const AnimationDemoPage(), ); }}
class AnimationDemoPage extends StatefulWidget { const AnimationDemoPage({Key? key}) : super(key: key);
@override _AnimationDemoPageState createState() => _AnimationDemoPageState();}
// Use SingleTickerProviderStateMixin since we have one AnimationControllerclass _AnimationDemoPageState extends State<AnimationDemoPage> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _opacityAnimation; late Animation<double> _sizeAnimation;
@override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 3), vsync: this, );
// Opacity goes from 0.0 (invisible) to 1.0 (visible) // Use a CurvedAnimation for a non-linear fade (e.g., ease-in) final curvedAnimation = CurvedAnimation( parent: _controller, curve: Curves.easeIn, ); _opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(curvedAnimation);
// Size goes from 50 to 200 // You can animate the controller directly or use the curved animation _sizeAnimation = Tween<double>(begin: 50.0, end: 200.0).animate(curvedAnimation);
// Start the animation automatically _controller.forward();
/* Optional: Add listeners to track status _controller.addStatusListener((status) { if (status == AnimationStatus.completed) { // Maybe reverse it? // _controller.reverse(); } else if (status == AnimationStatus.dismissed) { // Maybe start it again? // _controller.forward(); } }); */ }
@override void dispose() { // IMPORTANT: Dispose the controller when the widget is removed _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Fade & Grow Animation'), ), body: Center( // Use AnimatedBuilder for efficient rebuilding child: AnimatedBuilder( animation: _controller, // Listen to the controller builder: (BuildContext context, Widget? child) { // This builder function runs every time the animation ticks return Opacity( opacity: _opacityAnimation.value, // Use the opacity value child: Container( width: _sizeAnimation.value, // Use the size value height: _sizeAnimation.value, child: child, // Pass the constant child down ), ); }, // Provide a constant child that doesn't need rebuilding itself child: const FlutterLogo(), ), ), floatingActionButton: FloatingActionButton( onPressed: () { // Example: Control the animation if (_controller.isCompleted) { _controller.reverse(); } else { _controller.forward(); } }, child: const Icon(Icons.play_arrow), ), ); }}运行此应用。你会看到 Flutter logo 从小而透明的状态开始,然后在 3 秒内逐渐放大并渐隐出现。浮动操作按钮(Floating Action Button)可用于重播或反向播放动画。
Flutter 还提供了隐式动画(Implicit Animations)(如 AnimatedContainer、AnimatedOpacity 等 widget),它们会自动在指定持续时间内动画化其属性的变化,无需显式使用 controllers 或 tweens,从而简化了常见的动画任务。