Flutter是为了开发人员能够以少且简单的代码来构建精美的UI。所以动画部分也是如此,Flutter提供了多种动画,本文先介绍最基本的隐式动画类型。
隐式动画 Implicit Animations
Flutter提供了一个animation library,通过import package:flutter/animation.dart使用,该库仅依赖Dart核心库和physics.dart库。
该库中的两个核心类: ImplicitlyAnimatedWidgets和AnimatedWidgets。
ImplicitlyAnimatedWidgets
ImplicitlyAnimatedWidgets是继承自StatefulWidget一个抽象类,用于构建Widget,使得该Widget的属性能够产生动画变换。
ImplicitlyAnimatedWidgets(及其子类)每当更改时都会自动为其属性中的更改设置动画。 为此,他们创建并管理自己的内部AnimationController来为动画提供动力。 尽管这些Widgets易于使用,并且不需要您手动管理AnimationController的生命周期,但它们也受到一些限制:
除了动画属性的目标值之外,开发人员只能为动画选择持续时间duration和曲线Curve。 如果您需要对动画进行更多控制(例如,您想将其停在中间的某个位置),请考虑使用AnimatedWidget或其子类之一。 这些Widgets将“Animation”作为自变量来增强动画效果。 这使开发人员可以完全控制动画,但需要您手动管理基础的AnimationController。
FutureBuilder(
future: future,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
double width;
switch(snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
width = 0;
break;
case ConnectionState.done:
width = 500;
break;
}
return AnimatedContainer(
width: width,
child: Image.asset('assets/star.png'),
duration: Duration(seconds: 1),
);
}
),
AnimatedOpacity 不透明度隐式动画Widget的使用
import 'package:flutter/material.dart';
const owl_url = 'https://raw.githubusercontent.com/flutter/website/master/src/images/owl.jpg';
class FadeInDemo extends StatefulWidget {
_FadeInDemoState createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
double opacityLevel = 0.0;
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Image.network(owl_url),
MaterialButton(
child: Text(
'Show details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => setState(() {
opacityLevel = 1.0;
}),
),
AnimatedOpacity(
duration: Duration(seconds: 3),
opacity: opacityLevel,
child: Column(
children: <Widget>[
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
)
]);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
MyApp(),
);
}
AnimatedContainer 使用
import 'dart:math';
import 'package:flutter/material.dart';
const _duration = Duration(milliseconds: 400);
double randomBorderRadius() {
return Random().nextDouble() * 64;
}
double randomMargin() {
return Random().nextDouble() * 64;
}
Color randomColor() {
return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}
class AnimatedContainerDemo extends StatefulWidget {
_AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
Color color;
double borderRadius;
double margin;
@override
void initState() {
super.initState();
color = Colors.deepPurple;
borderRadius = randomBorderRadius();
margin = randomMargin();
}
void change() {
setState(() {
color = randomColor();
borderRadius = randomBorderRadius();
margin = randomMargin();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: _duration,
),
),
MaterialButton(
color: Theme.of(context).primaryColor,
child: Text(
'change',
style: TextStyle(color: Colors.white),
),
onPressed: () => change(),
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
MyApp(),
);
}
TweenAnimationBuilder
常用的隐式动画Widget
常用的隐式动画Widget被命名为AnimatedFoo,Foo对应于没有动画效果的那个Widget。
- TweenAnimationBuilder: 可以对任意属性进行动画,通过使用Tween来表示指定的目标值。
- AnimatedAlign: Align 的隐式动画版本
- AnimatedContainer: Container的隐式动画版本
- AnimatedDefaultTextStyle: DefaultTextStyle的隐式动画版本
- AnimatedOpacity: Opacity的隐式动画版本
- AnimatedPadding: Padding的隐式动画版本
- AnimatedPhysicalModel: PhysicalModel的隐式动画版本
- AnimatedPositioned: Positioned的隐式动画版本
- AnimatedPositionedDirectional: PositionedDirectional的隐式动画版本
- AnimatedTheme: Theme的隐式动画版本
- AnimatedCrossFade: 在两个给定的子Widget之间交叉淡入淡出,并在其大小之间进行动画设置。
- AnimatedSize: 在给定的时间内自动变化其大小
- AnimatedSwitcher: 从一个Widget消失到另一个Widget
使用 TweenAnimationBuilder 创建自定义隐式动画
如果我们需要的一个基础动画效果不在上面的内置范围内,那么可以通过使用 TweenAnimationBuilder 创建自定义隐式动画。
而且使用TweenAnimationBuilder构建一些简单的动画时,可以不需要使用StatefulWidget。
/// This is an EXTREMELY bare-bones illustration of using TweenAnimationBuilder.
/// See the rest of the article for optimizations.
/// Also note that this example is for illusration use only -- an implicit rotation
/// animation can be accomplished with AnimatedContainer.
class SuperBasic extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
starsBackground,
Center(
child: TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 2 * math.pi),
duration: Duration(seconds: 2),
builder: (BuildContext context, double angle, Widget child) {
return Transform.rotate(
angle: angle,
child: Image.asset('assets/Earth.png'),
);
},
),
),
],
);
}
}
TweenAnimationBuilder中的builder参数里,包含了一个Tween<T>中的类型T变量,该值基本上告诉Flutter在给定时刻当前的动画值是多少。该值通过Tween的lerp方法来进行计算得出。
class ColorAnimationWithStaticFinal extends StatelessWidget {
static final colorTween = ColorTween(begin: Colors.white, end: Colors.red);
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
starsBackground,
Center(
child: TweenAnimationBuilder<Color>(
tween: colorTween,
duration: Duration(seconds: 2),
builder: (_, Color color, __) {
return ColorFiltered(
child: Image.asset('assets/sun.png'),
colorFilter: ColorFilter.mode(color, BlendMode.modulate),
);
},
),
),
],
);
}
}
下面的例子里,由于只对ColorFiltered的起始颜色做动画,而图片widget不需要改变,所以通过child参数指定Image,如下:
class ChildParameter extends StatelessWidget {
static final colorTween = ColorTween(begin: Colors.white, end: Colors.red);
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
starsBackground,
Center(
child: TweenAnimationBuilder<Color>(
tween: colorTween,
child: Image.asset('assets/sun.png'),
duration: Duration(seconds: 2),
builder: (_, Color color, Widget myChild) {
return ColorFiltered(
child: myChild,
colorFilter: ColorFilter.mode(color, BlendMode.modulate),
);
},
),
),
],
);
}
}
显式动画
AnimatedWidget
当给定的Listenable值发生改变时,使得这个Widget发生重建。
// Flutter code sample for AnimatedWidget
// This code defines a widget called `Spinner` that spins a green square
// continually. It is built with an [AnimatedWidget].
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() => runApp(MyApp());
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class SpinningContainer extends AnimatedWidget {
const SpinningContainer({Key key, AnimationController controller})
: super(key: key, listenable: controller);
Animation<double> get _progress => listenable;
@override
Widget build(BuildContext context) {
return Transform.rotate(
angle: _progress.value * 2.0 * math.pi,
child: Container(width: 200.0, height: 200.0, color: Colors.green),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SpinningContainer(controller: _controller);
}
}
常用的animated widgets
在framework中有很多animated widgets,它们通常命名为FooTransition,而Foo对应于没有动画效果的Widget。官方文档:Animation and motion widgets
- AnimatedBuilder: 对于复杂的动画用例很有用,并且是AnimatedWidget子类的命名方案的显着例外
- AlignTransition: 这是Align的动画版本。
- DecoratedBoxTransition: 这是DecoratedBox的动画版本。
- DefaultTextStyleTransition: 它是DefaultTextStyle的动画版本。
- PositionedTransition: 这是Positioned的动画版本。
- RelativePositionedTransition: 这是Positioned的动画版本。
- RotationTransition: 可动画化小部件的旋转。
- ScaleTransition: 可动画化小部件的比例。
- SizeTransition: 动画自己的大小。
- SlideTransition: 可动画化小部件相对于其正常位置的位置。
- FadeTransition: 这是不透明度的动画版本。
- AnimatedModalBarrier: 它是ModalBarrier的动画版本。
AnimationController
ImplicitlyAnimatedWidgets和其子类自动管理其内部的AnimationController,而AnimatedWidget和其子类需要手动管理AnimationController的生命周期。
AnimationController是一种特殊的Animation,它在运行应用程序的设备准备显示新帧时提高其动画值(通常,此速率约为每秒60个值)。可以在需要动画的任何地方使用AnimationController。顾名思义,AnimationController还提供了对其Animation的控制:它实现了一些方法,可以随时停止动画,并使其向前和向后运行。
默认情况下,当向前运行时,AnimationController在给定的持续时间内从0.0到1.0线性增加其动画值。对于许多用例,您可能希望该值具有不同的类型,更改动画值的范围或更改动画在值之间的移动方式。这可以通过包装动画来实现:将其包装在Animatable中(请参见下文)会将动画值的范围更改为不同的范围或类型(例如,对Colors或Rects进行动画处理)。此外,可以通过将Curve包装在CurvedAnimation中来将其应用于动画。代替线性增加动画值,弯曲动画会根据提供的曲线更改其值。该框架附带许多内置曲线(请参见“曲线”)。例如,Curves.easeOutCubic在动画开始时迅速增加动画值,然后减慢直到达到目标值:
Animatable 在不同的类型上做动画
Animatable <T>是一个对象,它接受Animation <double>作为输入并产生类型T的值。这些类型的对象可用于转换AnimationController(或其他double类型的Animation)的动画值范围到不同的范围。该新范围甚至不必再为double类型。借助诸如Tween或TweenSequence之类的Animatable(请参见以下部分),AnimationController可用于在给定的时间内将Colors,Rects,Sizes和许多其他类型从一个值平滑过渡到另一个值。
Tween 和 TweenSequences 补间动画
Tween可以将0.0至1.0的double类型的Animation值映射到其他类型的一个范围内,当为补间动画提供动力的动画的动画值从0.0变为1.0时,它将在其开始值和结束值之间生成插值。当补间动画的动画值接近1.0时,补间生成的值通常会越来越接近其最终值。
(https://hellocyc.com/usr/uploads/2020/07/1075814223.gif)
一个 AnimationController可以同时为多个Tween(比如SizeTween,ColorTween)提供支持。
TweenSequences可以在内部定义多段Item,用以分段控制动画
参考视频和博文:
Animation basics with implicit animations
Flutter animation basics with implicit animations
Custom Implicit Animations in Flutter…with TweenAnimationBuilder