隐式动画
欢迎来到隐式动画编码实验,您将学习如何使用 Flutter 小部件,轻松地为特定属性集创建动画。
要最大程度地从本编码实验中受益,您应该具备以下基础知识:
- 如何创建 Flutter 应用。
- 如何使用状态管理小部件(Stateful Widgets)。
本编码实验涵盖以下内容:
- 使用
AnimatedOpacity
创建淡入效果。 - 使用
AnimatedContainer
为大小、颜色和边距的过渡添加动画。 - 隐式动画的概述和使用技术。
完成本编码实验的估计时间:15-30 分钟。
什么是隐式动画?
#借助 Flutter 的动画库,您可以为 UI 中的小部件添加动感并创建视觉效果。库中的一组小部件可以为您管理动画。这些小部件统称为隐式动画或隐式动画小部件,它们的名称来源于它们实现的ImplicitlyAnimatedWidget
类。通过隐式动画,您可以通过设置目标值来为小部件的属性添加动画;每当该目标值发生变化时,小部件就会将该属性从旧值动画到新值。通过这种方式,隐式动画用便利性换取了控制权——它们管理动画效果,而您无需手动操作。
示例:淡入文本效果
#以下示例展示了如何使用一个名为 AnimatedOpacity
的隐式动画小部件为现有 UI 添加淡入效果。该示例开始时没有动画代码——它包含一个 MaterialApp
主屏幕,其中包含:
- 一张猫头鹰的照片。
- 一个“显示详情”按钮,点击后无任何反应。
- 照片中猫头鹰的描述文本。
淡入(初始代码)
#要查看示例,请点击“运行”。
// 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 小部件进行不透明度动画
#本节包含一系列步骤,您可以使用它们将隐式动画添加到淡入初始代码中。完成步骤后,您还可以运行淡入完整版代码,其中已包含所做的更改。这些步骤概述了如何使用 AnimatedOpacity
小部件添加以下动画功能:
- 猫头鹰的描述文本在用户点击“显示详情”之前保持隐藏。
- 当用户点击“显示详情”时,猫头鹰的描述文本会淡入。
1. 选择要添加动画的小部件属性
#要创建淡入效果,您可以使用 AnimatedOpacity
小部件为 opacity
属性添加动画。将 Column
小部件包装在 AnimatedOpacity
小部件中。
@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
小部件在用户点击“显示详情”时完全可见,请使用 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
小部件的以下功能:
- 它会监听其
opacity
属性的状态变化。 - 当
opacity
属性发生变化时,它会为opacity
的新值执行过渡动画。 - 它需要一个
duration
参数来定义值之间的过渡需要多长时间。
示例:形状变换效果
#以下示例展示了如何使用 AnimatedContainer
小部件为多个属性(margin
、borderRadius
和 color
)添加具有不同类型(double
和 Color
)的动画。该示例开始时没有动画代码。它从一个 MaterialApp
主屏幕开始,该屏幕包含:
- 一个配置了
borderRadius
、margin
和color
的Container
小部件。这些属性的设置将在您每次运行示例时重新生成。 - 一个“更改”按钮,点击后无任何反应。
形状变换(初始代码)
#要开始示例,请点击“运行”。
// 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 动画颜色、圆角和边距
#本节包含一系列步骤,您可以使用它们将隐式动画添加到形状变换初始代码中。完成每个步骤后,您还可以运行形状变换完整示例,其中已包含所做的更改。
“形状变换初始代码”为 Container
小部件中的每个属性分配一个随机值。关联的函数会生成相关值:
randomColor()
函数为color
属性生成一个Color
。randomBorderRadius()
函数为borderRadius
属性生成一个double
。randomMargin()
函数为margin
属性生成一个double
。
以下步骤使用 AnimatedContainer
小部件来:
- 每当用户点击“更改”时,过渡到
color
、borderRadius
和margin
的新值。 - 当
color
、borderRadius
和margin
设置时,为它们的新值执行过渡动画。
1. 添加隐式动画
#将 Container
小部件更改为 AnimatedContainer
小部件。
SizedBox(
width: 128,
height: 128,
child: Container(
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
),
),
2. 为动画属性设置初始值
#AnimatedContainer
小部件会在其属性发生变化时在旧值和新值之间进行过渡。为了包含用户点击“更改”时触发的行为,请创建一个 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. 设置时长
#设置动画的时长,该动画用于驱动旧值和新值之间的过渡。
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(),
);
}
使用动画曲线
#前面的示例展示了:
- 隐式动画允许您为特定小部件属性的值之间的过渡添加动画。
duration
参数允许您设置动画完成所需的时间。
隐式动画还允许您控制在设定的 duration
内发生的动画的速率。要定义此速率变化,请将 curve
参数的值设置为一个 Curve
,例如在 Curves
类中声明的。
前面的示例没有为 curve
参数指定值。如果没有指定曲线值,隐式动画将应用线性动画曲线。
在形状变换完整示例中为 curve
参数指定一个值。当您将 easeInOutBack
常量传递给 curve
时,动画会发生变化。
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
小部件的 curve
属性时,请观察 margin
、borderRadius
和 color
的变化速率如何遵循该常量定义的曲线。
整合所有概念
#“形状变换完整示例”会动画 margin
、borderRadius
和 color
属性之间的值过渡。AnimatedContainer
小部件会为任何属性的变化添加动画。这些属性包括您未使用的属性,例如 padding
、transform
,甚至 child
和 alignment
!通过展示隐式动画的其他功能,“形状变换完整示例”在“淡入完整版”示例的基础上进行了扩展。
总结隐式动画:
- 一些隐式动画,如
AnimatedOpacity
小部件,只为单个属性添加动画。另一些,如AnimatedContainer
小部件,可以为多个属性添加动画。 - 隐式动画会在属性值发生变化时,使用提供的
curve
和duration
,为旧值和新值之间的过渡添加动画。 - 如果您未指定
curve
,隐式动画将默认为线性曲线。
下一步是什么?
#恭喜您,您已完成本次编码实验!要了解更多信息,请查看以下建议:
- 尝试动画教程。
- 了解元素动画(hero animations)和交错动画(staggered animations)。
- 查看动画库。
- 探索其他Flutter 学习资源。