隐式动画
欢迎来到隐式动画的 codelab,在这里您将学到:如何使用 Flutter widgets 轻松地对一组特定属性创建动画。
为了充分理解该 codelab,您应该具备以下基本知识:
-
如何 创建一个 Flutter 应用。
-
如何使用 stateful widgets。
该 codelab 包括以下内容:
-
使用
AnimatedOpacity
来创建一个淡入效果。 -
使用
AnimatedContainer
让尺寸、颜色和边距产生动画变换。 -
隐式动画及其使用方法的概述。
完成该 codelab 的时间约为:15-30 分钟。
什么是隐式动画?
通过使用 Flutter 的 动画库,您可以为 UI 中的组件添加运动和创建视觉效果。
您可以使用库中的一套组件来管理动画,这些组件统称为隐式动画或隐式动画组件,其名称源于它们都实现了 ImplicitlyAnimatedWidget 类。
使用隐式动画,您可以通过设置一个目标值,驱动 widget 的属性进行动画变换;每当目标值发生变化时,属性会从旧值逐渐更新到新值。通过这种方式,隐式动画内部实现了动画控制,从而能够方便地使用— 隐式动画组件会管理动画效果,用户不需要再进行额外的处理。
示例:淡入文字效果
下面的示例展示了如何使用名为 AnimatedOpacity 的隐式动画 widget,为已存在的 UI 添加淡入效果。
这个示例开始没有动画效果— 它包含一个由 Material App 组成的主页面,有以下内容:
-
一张猫头鹰的照片。
-
一个点击时什么也不做的 Show details 按钮。
-
照片中猫头鹰的描述文字。
淡入 (初始代码)
点击 Run 按钮来运行这个示例:
{$ begin main.dart $}
import 'package:flutter/material.dart';
const owlUrl =
'https://raw.githubusercontent.com/flutter/website/main/src/assets/images/docs/owl.jpg';
class FadeInDemo extends StatefulWidget {
const FadeInDemo({Key? key}) : super(key: key);
@override
State<FadeInDemo> createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Image.network(owlUrl),
TextButton(
child: const Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => {}),
const Column(
children: [
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
)
]);
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
{$ end main.dart $}
使用 AnimatedOpacity widget 进行透明度动画
这部分包含在 淡入初始代码 中添加一个隐式动画一系列步骤。完成这些步骤后,您还可以运行 淡入完成代码,该代码已经实现了淡入效果。这些步骤概述了如何使用 AnimatedOpacity
widget 来添加以下的动画特性:
-
用户点击 Show details 按钮后,显示猫头鹰的描述文字。
-
当用户点击 Show details 按钮时,猫头鹰的描述文字淡入。
1. 选择要进行动画的 widget 属性
想要创建淡入效果,您可以使用 AnimatedOpacity
widget 对 opacity
属性进行动画。将 Column
widget 换成 AnimatedOpacity
widget:
@@ -2,6 +2,8 @@
|
|
2
2
|
// Use of this source code is governed by a BSD-style license
|
3
3
|
// that can be found in the LICENSE file.
|
4
|
+
// ignore_for_file: missing_required_argument
|
5
|
+
|
4
6
|
import 'package:flutter/material.dart';
|
5
7
|
const owlUrl =
|
@@ -25,12 +27,14 @@
|
|
25
27
|
style: TextStyle(color: Colors.blueAccent),
|
26
28
|
),
|
27
29
|
onPressed: () => {}),
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
AnimatedOpacity(
|
31
|
+
child: const Column(
|
32
|
+
children: [
|
33
|
+
Text('Type: Owl'),
|
34
|
+
Text('Age: 39'),
|
35
|
+
Text('Employment: None'),
|
36
|
+
],
|
37
|
+
),
|
34
38
|
)
|
35
39
|
]);
|
36
40
|
}
|
2. 为动画属性初始化一个状态变量
将 opacity
的初始值设置为 0 ,以便在用户点击 Show details 前隐藏文字:
@@ -2,8 +2,6 @@
|
|
2
2
|
// Use of this source code is governed by a BSD-style license
|
3
3
|
// that can be found in the LICENSE file.
|
4
|
-
// ignore_for_file: missing_required_argument
|
5
|
-
|
6
4
|
import 'package:flutter/material.dart';
|
7
5
|
const owlUrl =
|
@@ -17,6 +15,8 @@
|
|
17
15
|
}
|
18
16
|
class _FadeInDemoState extends State<FadeInDemo> {
|
17
|
+
double opacity = 0.0;
|
18
|
+
|
19
19
|
@override
|
20
20
|
Widget build(BuildContext context) {
|
21
21
|
return Column(children: <Widget>[
|
@@ -28,6 +28,8 @@
|
|
28
28
|
),
|
29
29
|
onPressed: () => {}),
|
30
30
|
AnimatedOpacity(
|
31
|
+
duration: const Duration(seconds: 3),
|
32
|
+
opacity: opacity,
|
31
33
|
child: const Column(
|
32
34
|
children: [
|
33
35
|
Text('Type: Owl'),
|
3. 为动画设置一个时长
除了 opacity
参数以外,AnimatedOpacity
还需要为动画设置 duration。在下面的例子中,动画会以两秒的时长运行:
@@ -28,7 +28,7 @@
|
|
28
28
|
),
|
29
29
|
onPressed: () => {}),
|
30
30
|
AnimatedOpacity(
|
31
|
-
duration: const Duration(seconds:
|
31
|
+
duration: const Duration(seconds: 2),
|
32
32
|
opacity: opacity,
|
33
33
|
child: const Column(
|
34
34
|
children: [
|
4. 为动画设置一个触发器,并选择一个结束值
当用户点击 Show details 按钮时,将会触发动画。为了做到这点,我们使用 TextButton
的 onPressed()
方法,在调用时改变 opacity
的状态值为 1。
@@ -22,11 +22,14 @@
|
|
22
22
|
return Column(children: <Widget>[
|
23
23
|
Image.network(owlUrl),
|
24
24
|
TextButton(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
child: const Text(
|
26
|
+
'Show Details',
|
27
|
+
style: TextStyle(color: Colors.blueAccent),
|
28
|
+
),
|
29
|
+
onPressed: () => setState(() {
|
30
|
+
opacity = 1;
|
31
|
+
}),
|
32
|
+
),
|
30
33
|
AnimatedOpacity(
|
31
34
|
duration: const Duration(seconds: 2),
|
32
35
|
opacity: opacity,
|
淡入 (完成代码)
下面的示例是修改后的完成版代码— 运行这个示例,然后点击 Show details 按钮就可以触发动画。
{$ begin main.dart $}
import 'package:flutter/material.dart';
const owlUrl =
'https://raw.githubusercontent.com/flutter/website/main/src/assets/images/docs/owl.jpg';
class FadeInDemo extends StatefulWidget {
const FadeInDemo({Key? key}) : super(key: key);
@override
State<FadeInDemo> createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
double opacity = 0.0;
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Image.network(owlUrl),
TextButton(
child: const Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => setState(() {
opacity = 1;
}),
),
AnimatedOpacity(
duration: const Duration(seconds: 2),
opacity: opacity,
child: const Column(
children: [
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
)
]);
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
{$ end main.dart $}
小结一下
淡入文字效果 的例子展现了 AnimatedOpacity
的特性:
-
AnimatedOpacity
会监听其opacity
属性的状态变化。 -
当
opacity
属性改变时,AnimatedOpacity
会自动将opacity
变化到新值,同时使 widget 进行动画跟随变换。 -
AnimatedOpacity
需要一个duration
参数来确定新旧opacity
进行动画变换的时长。
示例:形状变化效果
下面的示例将展示如何使用 AnimatedContainer widget
让多个不同类型(double
和 Color
)的属性(margin
、borderRadius
和 color
)同时进行动画变换。
这个示例开始没有动画效果— 它以一个由 Material App 组成的主页面开始,有以下内容:
-
一个有
margin
、borderRadius
、和color
属性的Container
,这些属性每次运行时的值都不同。 -
一个点击时什么都不做的 Change 按钮。
形状变化 (初始代码)
点击 Run 按钮来运行这个示例:
{$ begin main.dart $}
import 'dart:math';
import 'package:flutter/material.dart';
double randomBorderRadius() {
return Random().nextDouble() * 64;
}
double randomMargin() {
return Random().nextDouble() * 64;
}
Color randomColor() {
return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}
class AnimatedContainerDemo extends StatefulWidget {
const AnimatedContainerDemo({Key? key}) : super(key: key);
@override
State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
initState() {
super.initState();
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: Container(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
),
),
ElevatedButton(
child: const Text('change'),
onPressed: () => {},
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
{$ end main.dart $}
使用 AnimatedContainer 将 color、borderRadius、和 margin 进行动画变换
这部分包含在 形状变化初始代码 中添加一个隐式动画的一系列步骤。完成这些步骤后,您还可以运行 形状变化完成代码,该代码已经实现了淡入效果。
在 形状变化初始代码 中每个 Container
widget 的属性(color
、borderRadius
和 margin
)都由一个相关的函数赋值(分别是 randomColor()
、randomBorderRadius()
和 randomMargin()
)。您可以使用 AnimatedContainer
widget 重构这段代码,来完成以下的效果:
-
每当用户点击 Change 按钮时,
color
、borderRadius
和margin
都会生成一个新值。 -
每当
color
、borderRadius
和margin
被设置时,都会进行动画变换到新的值。
1. 添加一个隐式动画
将 Container
widget 换成 AnimatedContainer
widget:
@@ -2,6 +2,8 @@
|
|
2
2
|
// Use of this source code is governed by a BSD-style license
|
3
3
|
// that can be found in the LICENSE file.
|
4
|
+
// ignore_for_file: missing_required_argument
|
5
|
+
|
4
6
|
import 'dart:math';
|
5
7
|
import 'package:flutter/material.dart';
|
@@ -47,7 +49,7 @@
|
|
47
49
|
SizedBox(
|
48
50
|
width: 128,
|
49
51
|
height: 128,
|
50
|
-
child:
|
52
|
+
child: AnimatedContainer(
|
51
53
|
margin: EdgeInsets.all(margin),
|
52
54
|
decoration: BoxDecoration(
|
53
55
|
color: color,
|
2. 为动画属性设置初始值
当属性的新旧值发生变化时,AnimatedContainer
会自动在新旧值之间产生动画效果。通过创建一个 change()
方法,我们将定义当用户点击 Change 按钮时触发变更的行为。
change()
方法可以使用 setState()
为 color
、borderRadius
和 margin
状态变量设置新值:
@@ -40,6 +40,14 @@
|
|
40
40
|
margin = randomMargin();
|
41
41
|
}
|
42
|
+
void change() {
|
43
|
+
setState(() {
|
44
|
+
color = randomColor();
|
45
|
+
borderRadius = randomBorderRadius();
|
46
|
+
margin = randomMargin();
|
47
|
+
});
|
48
|
+
}
|
49
|
+
|
42
50
|
@override
|
43
51
|
Widget build(BuildContext context) {
|
44
52
|
return Scaffold(
|
3. 为动画设置触发器
每当用户点击 Change 按钮时触发动画,调用 onPressed()
处理器的 change()
方法:
@@ -67,7 +67,7 @@
|
|
67
67
|
),
|
68
68
|
ElevatedButton(
|
69
69
|
child: const Text('change'),
|
70
|
-
onPressed: () =>
|
70
|
+
onPressed: () => change(),
|
71
71
|
),
|
72
72
|
],
|
73
73
|
),
|
4. 设置时长
在最后,设置新旧值之间变换的时长参数 duration
:
@@ -2,12 +2,12 @@
|
|
2
2
|
// Use of this source code is governed by a BSD-style license
|
3
3
|
// that can be found in the LICENSE file.
|
4
|
-
// ignore_for_file: missing_required_argument
|
5
|
-
|
6
4
|
import 'dart:math';
|
7
5
|
import 'package:flutter/material.dart';
|
6
|
+
const _duration = Duration(milliseconds: 400);
|
7
|
+
|
8
8
|
double randomBorderRadius() {
|
9
9
|
return Random().nextDouble() * 64;
|
10
10
|
}
|
@@ -63,6 +63,7 @@
|
|
63
63
|
color: color,
|
64
64
|
borderRadius: BorderRadius.circular(borderRadius),
|
65
65
|
),
|
66
|
+
duration: _duration,
|
66
67
|
),
|
67
68
|
),
|
68
69
|
ElevatedButton(
|
形状变化 (完成代码)
下面的示例是修改后的完成版代码—
运行这个示例,然后点击 Change 按钮就可以触发动画。注意:每次您点击 Change 按钮,形状的 margin
、borderRadius
和 color
都会进行动画变化到新的值。
{$ begin main.dart $}
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 {
const AnimatedContainerDemo({Key? key}) : super(key: key);
@override
State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
initState() {
super.initState();
color = randomColor();
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,
),
),
ElevatedButton(
child: const Text('change'),
onPressed: () => change(),
),
],
),
),
);
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
{$ end main.dart $}
使用动画曲线
前面的示例展示出,如何让您通过隐式动画对特定的 widget 属性值进行动画变化,以及如何通过 duration
参数设置动画完成所需的时间。隐式动画还允许您在 duration
时长内控制动画的 速率 变化。用来定义这种速率变化的参数是 curve。
前面的例子中没有指定 curve
,所以隐式动画默认使用 线性动画曲线。在 形状变化完成代码 中添加一个 curve
参数,然后当您将常量 easeInOutBack 传递给 curve
时,观察动画的变化:
@@ -64,6 +64,7 @@
|
|
64
64
|
borderRadius: BorderRadius.circular(borderRadius),
|
65
65
|
),
|
66
66
|
duration: _duration,
|
67
|
+
curve: Curves.easeInOutBack,
|
67
68
|
),
|
68
69
|
),
|
69
70
|
ElevatedButton(
|
现在您已经将 easeInOutBack
作为 curve
的值传递给了 AnimatedContainer
,注意:margin
、borderRadius
和 color
的变化速率遵循 easeInOutBack
所定义的曲线:
小结一下
形状变化完成代码 示例对 margin
、borderRadius
和 color
属性值进行了动画变换。注意:AnimatedContainer
可以对它的任意属性进行动画改变,包括那些您没有使用的属性,比如 padding
、transform
,甚至是 child
和 alignment
!
这个 形状变化完成代码 的示例建立在 渐变完成代码 的基础上,展现出隐式动画的额外功能:
-
一些隐式动画(比如
AnimatedOpacity
)只能对一个属性值进行动画变换,然而有些(比如AnimatedContainer
)可以同时变换多个属性。 -
隐式动画会在新旧属性值变换时,自动使用提供的
curve
和duration
进行动画变换。 -
如果您没有指定
curve
,隐式动画的曲线会默认使用 线性曲线。
下一个是什么?
恭喜,您已经完成了这个 codelab!如果您想要了解更多,这里有一些其他文章的推荐:
-
尝试一下 动画教程。
-
学习 hero 动画 和 staggered 动画。
-
查看更多 动画库 的信息。
-
尝试一下其他的 codelab。