隐式动画
欢迎来到隐式动画 Codelab,您将学习如何使用 Flutter 小组件,这些小组件可以轻松为特定属性集创建动画。
为了充分利用此 Codelab,您应该具备以下基本知识
- 如何 制作 Flutter 应用。
- 如何使用 有状态小组件。
此 Codelab 涵盖以下内容
- 使用
AnimatedOpacity
创建淡入效果。 - 使用
AnimatedContainer
设置大小、颜色和边距的过渡动画。 - 隐式动画和使用它们的技巧概述。
完成此代码实验室的估计时间:15-30 分钟。
什么是隐式动画?
使用 Flutter 的动画库,您可以为 UI 中的小部件添加动作并创建视觉效果。库中的一组小部件可为您管理动画。这些小部件统称为隐式动画或隐式动画小部件,其名称源自它们实现的ImplicitlyAnimatedWidget类。使用隐式动画,您可以通过设置目标值来对小部件属性进行动画处理;每当该目标值发生更改时,小部件都会将属性从旧值动画处理到新值。通过这种方式,隐式动画以便利性换取控制权——它们管理动画效果,这样您就不必管理了。
示例:淡入文本效果
以下示例展示了如何使用名为AnimatedOpacity的隐式动画小部件,为现有 UI 添加淡入效果。此示例一开始没有任何动画代码——它包含一个Material App主屏幕,其中包含
- 一张猫头鹰照片。
- 一个显示详细信息按钮,单击后不执行任何操作。
- 照片中猫头鹰的描述文本。
淡入(初始代码)
要查看示例,请单击运行
// 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 小部件设置不透明度动画
本部分包含可用于向淡入初始代码添加隐式动画的步骤列表。完成这些步骤后,您还可以运行淡入完成代码,其中已进行了更改。这些步骤概述了如何使用AnimatedOpacity
小部件添加以下动画功能
- 猫头鹰的描述文本在用户单击显示详细信息之前保持隐藏。
- 当用户单击显示详细信息时,猫头鹰的描述文本淡入显示。
1. 选择要设置动画的小部件属性
要创建淡入效果,您可以使用AnimatedOpacity
小部件设置opacity
属性。将Column
小部件包装在AnimatedOpacity
小部件中
@@ -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
的起始值设置为零
@@ -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('类型:猫头鹰'),
|
3. 设置动画持续时间
除了 opacity
参数,AnimatedOpacity
还需要一个 duration 用于动画。对于此示例,你可以从 2 秒开始
@@ -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. 设置动画触发器并选择结束值
配置动画,在用户点击显示详情时触发。为此,使用 TextButton
的 onPressed()
处理程序更改 opacity
状态。若要使 FadeInDemo
小组件在用户点击显示详情时完全可见,请使用 onPressed()
处理程序将 opacity
设置为 1
@@ -27,7 +27,9 @@
|
|
27
27
|
'显示详情',
|
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),
|
淡入(完成)
这是您已完成更改的示例。运行此示例,然后单击显示详情以触发动画。
// 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(),
);
}
将所有内容放在一起
淡入文本效果示例演示了 AnimatedOpacity
小组件的以下功能。
- 它侦听其
opacity
属性的状态更改。 - 当
opacity
属性更改时,它会为opacity
的新值设置过渡动画。 - 它需要一个
duration
参数来定义值之间的过渡需要多长时间。
示例:变形效果
以下示例展示了如何使用 AnimatedContainer
小组件为多种不同类型的属性(margin
、borderRadius
和 color
)设置动画(double
和 Color
)。该示例最初不包含任何动画代码。它从包含 Material App 主屏幕开始,其中包含
- 一个
Container
小部件,配置了borderRadius
、margin
和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';
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 动画化颜色、borderRadius 和边距
本部分包含一个步骤列表,你可以使用它向 形状变化启动代码 添加一个隐式动画。完成每一步后,你还可以运行 完整的形状变化示例,其中已经进行了更改。
形状变化启动代码 为 Container
小部件中的每个属性分配一个随机值。相关函数生成相关值
randomColor()
函数为color
属性生成一个Color
randomBorderRadius()
函数为borderRadius
属性生成一个double
。randomMargin()
函数为margin
属性生成一个double
。
以下步骤使用 AnimatedContainer
小部件
- 在用户点击更改时,过渡到
color
、borderRadius
和margin
的新值。 - 在设置
color
、borderRadius
和margin
的新值时,动画化到这些新值的过渡。
1. 添加一个隐式动画
将 Container
小组件更改为 AnimatedContainer
小组件
@@ -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()
方法可以使用 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. 设置动画触发器
要设置动画以便在用户按下更改时触发,请在 onPressed()
处理程序中调用 change()
方法
@@ -65,7 +65,7 @@
|
|
65
65
|
),
|
66
66
|
ElevatedButton(
|
67
67
|
child: const Text('更改'),
|
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(
|
形状转换(完成)
以下是您已完成更改的示例。运行代码并单击更改以触发动画。每次单击更改时,形状都会针对 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(),
);
}
使用动画曲线
前面的示例展示了如何
- 隐式动画允许您为特定小部件属性的值之间的转换设置动画。
duration
参数允许您设置动画完成所需的时间。
隐式动画还允许您控制在设置的 duration
期间发生的动画速率的变化。要定义此速率变化,请将 curve
参数的值设置为 Curve
,例如在 Curves
类中声明的曲线。
前面的示例未为 curve
参数指定值。如果没有指定曲线值,隐式动画将应用 线性动画曲线。
在 完整的形状转换示例 中为 curve
参数指定一个值。当您传递 curve
的 easeInOutBack
常量时,动画会发生变化,
@@ -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(
|
将 Curves.easeInOutBack
常量传递给 AnimatedContainer
小部件的 curve
属性时,请观察 margin
、borderRadius
和 color
的变化速率如何遵循该常量定义的曲线。
将所有内容组合在一起
完整的形状转换示例会对 margin
、borderRadius
和 color
属性的值之间的过渡进行动画处理。AnimatedContainer
小部件会对其任何属性的更改进行动画处理。其中包括您未使用的属性,例如 padding
、transform
,甚至 child
和 alignment
!通过展示隐式动画的其他功能,完整的形状转换示例建立在完整的淡入示例的基础上。
总结隐式动画
- 某些隐式动画(如
AnimatedOpacity
小部件)仅对一个属性进行动画处理。其他动画(如AnimatedContainer
小部件)可以对多个属性进行动画处理。 - 隐式动画使用提供的
curve
和duration
在属性的旧值和新值之间对过渡进行动画处理。 - 如果您未指定
curve
,隐式动画会默认使用线性曲线。
接下来是什么?
恭喜,您已完成代码实验室!要了解更多信息,请查看以下建议