隐式动画
欢迎来到隐式动画 Codelab,你将学习如何使用 Flutter widget 轻松创建特定属性的动画。
要充分利用本 Codelab,你应该具备以下基本知识:
- 如何制作 Flutter 应用。
- 如何使用有状态 widget。
本 Codelab 涵盖以下内容:
- 使用
AnimatedOpacity
创建淡入效果。 - 使用
AnimatedContainer
动画大小、颜色和 margin 的过渡。 - 隐式动画概述及其使用技巧。
完成本 Codelab 估计时间:15-30 分钟。
什么是隐式动画?
#借助 Flutter 的动画库,你可以为 UI 中的 widget 添加运动和创建视觉效果。库中的一个 widget 集为你管理动画。这些 widget 统称为隐式动画或隐式动画 widget,其名称来源于它们实现的 ImplicitlyAnimatedWidget 类。通过隐式动画,你可以通过设置目标值来动画 widget 属性;每当该目标值改变时,widget 就会将属性从旧值动画到新值。通过这种方式,隐式动画以控制换取便利——它们管理动画效果,因此你无需自行处理。
示例:文本淡入效果
#以下示例展示了如何使用名为 AnimatedOpacity 的隐式动画 widget 为现有 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/content/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) {
return ListView(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({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 添加以下动画功能:
- 猫头鹰的描述文本在用户点击“显示详情”之前保持隐藏。
- 当用户点击“显示详情”时,猫头鹰的描述文本淡入。
1. 选择要动画的 widget 属性
#要创建淡入效果,你可以使用 AnimatedOpacity
widget 动画 opacity
属性。将 Column
widget 包装在 AnimatedOpacity
widget 中
@override
Widget build(BuildContext context) {
return ListView(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'),
],
),
AnimatedOpacity(
child: const Column(
children: [
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
),
]);
}
2. 初始化动画属性的状态变量
#要在用户点击“显示详情”之前隐藏文本,请将 opacity
的起始值设置为零
class _FadeInDemoState extends State<FadeInDemo> {
double opacity = 0;
@override
Widget build(BuildContext context) {
return ListView(children: <Widget>[
// ...
AnimatedOpacity(
opacity: opacity,
child: const Column(
3. 设置动画持续时间
#除了 opacity
参数外,AnimatedOpacity
还需要一个duration 参数用于动画。对于本示例,你可以从 2 秒开始
AnimatedOpacity(
duration: const Duration(seconds: 2),
opacity: opacity,
child: const Column(
4. 设置动画触发器并选择结束值
#配置动画以在用户点击“显示详情”时触发。为此,请使用 TextButton
的 onPressed()
处理程序更改 opacity
状态。要使 FadeInDemo
widget 在用户点击“显示详情”时完全可见,请使用 onPressed()
处理程序将 opacity
设置为 1
TextButton(
child: const Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => {},
onPressed: () => setState(() {
opacity = 1;
}),
),
淡入(完整代码)
#这是你完成更改后的示例。运行此示例,然后点击“显示详情”以触发动画。
// 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/content/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) {
return ListView(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({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: FadeInDemo(),
),
),
);
}
}
void main() {
runApp(
const MyApp(),
);
}
整合所有概念
#文本淡入效果示例演示了 AnimatedOpacity
widget 的以下特性。
- 它监听其
opacity
属性的状态变化。 - 当
opacity
属性改变时,它会将过渡动画到opacity
的新值。 - 它需要一个
duration
参数来定义值之间过渡应花费的时间。
示例:形状变换效果
#以下示例展示了如何使用 AnimatedContainer
widget 动画多个属性 (margin
、borderRadius
和 color
),这些属性具有不同类型 (double
和 Color
)。该示例开始时没有动画代码。它从一个 Material App 主屏幕开始,其中包含:
- 一个配置了
borderRadius
、margin
和color
的Container
widget。这些属性在每次运行示例时都会重新生成。 - 一个“改变”按钮,点击后不执行任何操作。
形状变换(入门代码)
#要启动示例,点击“运行”。
// 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 和 margin
#本节包含一个步骤列表,你可以使用这些步骤将隐式动画添加到形状变换入门代码中。完成每个步骤后,你还可以运行已进行更改的形状变换完整示例。
形状变换入门代码为 Container
widget 中的每个属性分配一个随机值。相关函数生成相应的值:
randomColor()
函数为color
属性生成一个Color
。randomBorderRadius()
函数为borderRadius
属性生成一个double
。randomMargin()
函数为margin
属性生成一个double
。
以下步骤使用 AnimatedContainer
widget 来:
- 每当用户点击“改变”时,过渡到
color
、borderRadius
和margin
的新值。 - 每当设置
color
、borderRadius
和margin
时,动画过渡到新值。
1. 添加隐式动画
#将 Container
widget 更改为 AnimatedContainer
widget
SizedBox(
width: 128,
height: 128,
child: Container(
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
),
),
2. 设置动画属性的起始值
#当 AnimatedContainer
widget 的属性改变时,它会在其旧值和新值之间进行过渡。为了包含用户点击“改变”时触发的行为,创建一个 change()
方法。change()
方法可以使用 setState()
方法为 color
、borderRadius
和 margin
状态变量设置新值
void change() {
setState(() {
color = randomColor();
borderRadius = randomBorderRadius();
margin = randomMargin();
});
}
@override
Widget build(BuildContext context) {
// ...
3. 设置动画触发器
#要将动画设置为在用户按下“改变”时触发,请在 onPressed()
处理程序中调用 change()
方法
ElevatedButton(
child: const Text('Change'),
onPressed: () => {},
onPressed: () => change(),
),
4. 设置持续时间
#设置驱动旧值和新值之间过渡的动画的 duration
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: const Duration(milliseconds: 400),
),
),
形状变换(完整代码)
#这是你完成更改后的示例。运行代码并点击“改变”以触发动画。每次点击“改变”时,形状都会动画到其 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(),
);
}
使用动画曲线
#前面的示例展示了:
- 隐式动画允许你动画特定 widget 属性值之间的过渡。
duration
参数允许你设置动画完成所需的时间。
隐式动画还允许你控制在设定的 duration
期间发生的动画速率变化。要定义这种速率变化,将 curve
参数的值设置为 Curve
,例如在 Curves
类中声明的曲线。
前面的示例没有为 curve
参数指定值。如果没有指定曲线值,隐式动画会应用线性动画曲线。
在形状变换完整示例中为 curve
参数指定一个值。当你为 curve
传递 easeInOutBack
常量时,动画会改变:
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: _duration,
curve: Curves.easeInOutBack,
),
),
当你将 Curves.easeInOutBack
常量传递给 AnimatedContainer
widget 的 curve
属性时,观察 margin
、borderRadius
和 color
的变化速率如何遵循该常量定义的曲线。
整合所有概念
#形状变换完整示例动画了 margin
、borderRadius
和 color
属性值之间的过渡。AnimatedContainer
widget 会动画其任何属性的变化。这些属性包括你没有使用的属性,例如 padding
、transform
,甚至 child
和 alignment
!通过展示隐式动画的额外功能,形状变换完整示例建立在淡入完整示例的基础上。
总结隐式动画:
- 一些隐式动画,如
AnimatedOpacity
widget,只动画一个属性。其他动画,如AnimatedContainer
widget,可以动画许多属性。 - 隐式动画在属性改变时,使用提供的
curve
和duration
动画旧值和新值之间的过渡。 - 如果你没有指定
curve
,隐式动画默认为线性曲线。
下一步?
#恭喜你,你已经完成了 Codelab!要了解更多信息,请查看以下建议: