隐式动画
欢迎来到隐式动画的 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 按钮来运行这个示例:
// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
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({super.key});
@override
State<FadeInDemo> createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
@override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
return Column(children: <Widget>[
Image.network(owlUrl, height: height * 0.8),
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({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
使用 AnimatedOpacity widget 进行透明度动画
这部分包含在 淡入初始代码 中添加一个隐式动画一系列步骤。完成这些步骤后,你还可以运行 淡入完成代码,该代码已经实现了淡入效果。这些步骤概述了如何使用 AnimatedOpacity
widget 来添加以下的动画特性:
-
用户点击 Show details 按钮后,显示猫头鹰的描述文字。
-
当用户点击 Show details 按钮时,猫头鹰的描述文字淡入。
1. 选择要进行动画的 widget 属性
想要创建淡入效果,你可以使用 AnimatedOpacity
widget 对 opacity
属性进行动画。将 Column
widget 换成 AnimatedOpacity
widget:
@@ -27,12 +27,14 @@
|
|
27
27
|
),
|
28
28
|
onPressed: () => {},
|
29
29
|
),
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
AnimatedOpacity(
|
31
|
+
child: const Column(
|
32
|
+
children: [
|
33
|
+
Text('Type: Owl'),
|
34
|
+
Text('Age: 39'),
|
35
|
+
Text('Employment: None'),
|
36
|
+
],
|
37
|
+
),
|
36
38
|
)
|
37
39
|
]);
|
38
40
|
}
|
2. 为动画属性初始化一个状态变量
将 opacity
的初始值设置为 0 ,以便在用户点击 Show details 前隐藏文字:
@@ -15,6 +15,8 @@
|
|
15
15
|
}
|
16
16
|
class _FadeInDemoState extends State<FadeInDemo> {
|
17
|
+
double opacity = 0;
|
18
|
+
|
17
19
|
@override
|
18
20
|
Widget build(BuildContext context) {
|
19
21
|
double height = MediaQuery.of(context).size.height;
|
@@ -28,6 +30,7 @@
|
|
28
30
|
onPressed: () => {},
|
29
31
|
),
|
30
32
|
AnimatedOpacity(
|
33
|
+
opacity: opacity,
|
31
34
|
child: const Column(
|
32
35
|
children: [
|
33
36
|
Text('Type: Owl'),
|
3. 为动画设置一个时长
除了 opacity
参数以外,AnimatedOpacity
还需要为动画设置 duration。在下面的例子中,动画会以两秒的时长运行:
@@ -30,6 +30,7 @@
|
|
30
30
|
onPressed: () => {},
|
31
31
|
),
|
32
32
|
AnimatedOpacity(
|
33
|
+
duration: const Duration(seconds: 2),
|
33
34
|
opacity: opacity,
|
34
35
|
child: const Column(
|
35
36
|
children: [
|
4. 为动画设置一个触发器,并选择一个结束值
当用户点击 Show details 按钮时,将会触发动画。为了做到这点,我们使用 TextButton
的 onPressed()
方法,在调用时改变 opacity
的状态值为 1。
@@ -27,7 +27,9 @@
|
|
27
27
|
'Show Details',
|
28
28
|
style: TextStyle(color: Colors.blueAccent),
|
29
29
|
),
|
30
|
-
onPressed: () => {
|
30
|
+
onPressed: () => setState(() {
|
31
|
+
opacity = 1;
|
32
|
+
}),
|
31
33
|
),
|
32
34
|
AnimatedOpacity(
|
33
35
|
duration: const Duration(seconds: 2),
|
淡入 (完成代码)
下面的示例是修改后的完成版代码— 运行这个示例,然后点击 Show details 按钮就可以触发动画。
// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
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({super.key});
@override
State<FadeInDemo> createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
double opacity = 0;
@override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
return Column(children: <Widget>[
Image.network(owlUrl, height: height * 0.8),
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({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
小结一下
The Fade-in text effect example demonstrates the following features
of the AnimatedOpacity
widget.
-
AnimatedOpacity
会监听其opacity
属性的状态变化。 -
当
opacity
属性改变时,AnimatedOpacity
会自动将opacity
变化到新值,同时使 widget 进行动画跟随变换。 -
AnimatedOpacity
需要一个duration
参数来确定新旧opacity
进行动画变换的时长。
示例:形状变化效果
下面的示例将展示如何使用 AnimatedContainer
widget
让多个不同类型(double
和 Color
)的属性(margin
、borderRadius
和 color
)同时进行动画变换。
这个示例开始没有动画效果—
它以一个由 Material App 组成的主页面开始,有以下内容:
-
一个有
margin
、borderRadius
、和color
属性的Container
,这些属性每次运行时的值都不同。 -
一个点击时什么都不做的 Change 按钮。
形状变化 (初始代码)
点击 Run 按钮来运行这个示例:
// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
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({super.key});
@override
State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
void 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({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
使用 AnimatedContainer 将 color、borderRadius、和 margin 进行动画变换
这部分包含在 形状变化初始代码 中添加一个隐式动画的一系列步骤。完成这些步骤后,你还可以运行 形状变化示例,该代码已经实现了淡入效果。
在 形状变化初始代码 中每个 Container
widget 的属性都由一个相关的函数赋值来完成以下的效果:
-
randomColor()
函数为color
属性生成新的Color
。 -
randomBorderRadius()
函数为borderRadius
属性生成新的double
。 -
randomMargin()
函数为margin
属性生成新的double
。
以下步骤会使用 AnimatedContainer
来达到:
-
每当用户点击 Change 按钮时,
color
、borderRadius
和margin
都会渐变到新的值。 -
每当
color
、borderRadius
和margin
被设置时,都会进行动画变换到新的值。
1. 添加一个隐式动画
将 Container
widget 换成 AnimatedContainer
widget:
@@ -47,7 +47,7 @@
|
|
47
47
|
SizedBox(
|
48
48
|
width: 128,
|
49
49
|
height: 128,
|
50
|
-
child:
|
50
|
+
child: AnimatedContainer(
|
51
51
|
margin: EdgeInsets.all(margin),
|
52
52
|
decoration: BoxDecoration(
|
53
53
|
color: color,
|
2. 为动画属性设置初始值
当属性的新旧值发生变化时,AnimatedContainer
会自动在新旧值之间产生动画效果。通过创建一个 change()
方法,我们将定义当用户点击 Change 按钮时触发变更的行为。
change()
方法可以使用 setState()
为 color
、borderRadius
和 margin
状态变量设置新值:
@@ -38,6 +38,14 @@
|
|
38
38
|
margin = randomMargin();
|
39
39
|
}
|
40
|
+
void change() {
|
41
|
+
setState(() {
|
42
|
+
color = randomColor();
|
43
|
+
borderRadius = randomBorderRadius();
|
44
|
+
margin = randomMargin();
|
45
|
+
});
|
46
|
+
}
|
47
|
+
|
40
48
|
@override
|
41
49
|
Widget build(BuildContext context) {
|
42
50
|
return Scaffold(
|
3. 为动画设置触发器
每当用户点击 Change 按钮时触发动画,调用 onPressed()
处理器的 change()
方法:
@@ -65,7 +65,7 @@
|
|
65
65
|
),
|
66
66
|
ElevatedButton(
|
67
67
|
child: const Text('Change'),
|
68
|
-
onPressed: () =>
|
68
|
+
onPressed: () => change(),
|
69
69
|
),
|
70
70
|
],
|
71
71
|
),
|
4. 设置时长
在最后,设置新旧值之间变换的时长参数 duration
:
@@ -6,6 +6,8 @@
|
|
6
6
|
import 'package:flutter/material.dart';
|
7
|
+
const _duration = Duration(milliseconds: 400);
|
8
|
+
|
7
9
|
double randomBorderRadius() {
|
8
10
|
return Random().nextDouble() * 64;
|
9
11
|
}
|
@@ -61,6 +63,7 @@
|
|
61
63
|
color: color,
|
62
64
|
borderRadius: BorderRadius.circular(borderRadius),
|
63
65
|
),
|
66
|
+
duration: _duration,
|
64
67
|
),
|
65
68
|
),
|
66
69
|
ElevatedButton(
|
形状变化 (完成代码)
下面的示例是修改后的完成版代码—
运行这个示例,然后点击 Change 按钮就可以触发动画。注意:每次你点击 Change 按钮,形状的 margin
、borderRadius
和 color
都会进行动画变化到新的值。
// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
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({super.key});
@override
State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
late Color color;
late double borderRadius;
late double margin;
@override
void 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({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AnimatedContainerDemo(),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
使用动画曲线
The preceding examples show how:
-
如何让你通过隐式动画对特定的 widget 属性值进行动画变化。
-
如何通过
duration
参数设置动画完成所需的时间。
隐式动画还允许你在 duration
时长内控制动画的 速率 变化。用来定义这种速率变化的参数是 Curve
,或者 Curves
这些已经预定义的曲线。
前面的例子中没有指定 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。