隐式动画
欢迎来到隐式动画的 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/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 来添加以下的动画特性:
-
用户点击 Show details 按钮后,显示猫头鹰的描述文字。
-
当用户点击 Show details 按钮时,猫头鹰的描述文字淡入。
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
的初始值设置为 0 ,以便在用户点击 Show details 前隐藏文字:
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。在下面的例子中,动画会以两秒的时长运行:
AnimatedOpacity(
duration: const Duration(seconds: 2),
opacity: opacity,
child: const Column(
4. 为动画设置一个触发器,并选择一个结束值
#当用户点击 Show details 按钮时,将会触发动画。为了做到这点,我们使用 TextButton
的 onPressed()
方法,在调用时改变 opacity
的状态值为 1。
TextButton(
child: const Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => {},
onPressed: () => setState(() {
opacity = 1;
}),
),
淡入 (完成代码)
#下面的示例是修改后的完成版代码— 运行这个示例,然后点击 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/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(),
);
}
小结一下
#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:
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 按钮时触发变更的行为。
change()
方法可以使用 setState()
为 color
、borderRadius
和 margin
状态变量设置新值:
void change() {
setState(() {
color = randomColor();
borderRadius = randomBorderRadius();
margin = randomMargin();
});
}
@override
Widget build(BuildContext context) {
// ...
3. 为动画设置触发器
#每当用户点击 Change 按钮时触发动画,调用 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),
),
),
形状变化 (完成代码)
#下面的示例是修改后的完成版代码—
运行这个示例,然后点击 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
时,观察动画的变化:
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: _duration,
curve: Curves.easeInOutBack,
),
),
现在你已经将 easeInOutBack
作为 curve
的值传递给了 AnimatedContainer
,注意:margin
、borderRadius
和 color
的变化速率遵循 easeInOutBack
所定义的曲线:
小结一下
#形状变化示例
对 margin
、borderRadius
和 color
属性值进行了动画变换。注意:AnimatedContainer
可以对它的任意属性进行动画改变,包括那些你没有使用的属性,比如 padding
、transform
,甚至是 child
和 alignment
!
这个 形状变化示例
建立在 渐变完成代码 的基础上,展现出隐式动画的额外功能。
总结隐式动画的特点:
-
一些隐式动画(比如
AnimatedOpacity
)只能对一个属性值进行动画变换,然而有些(比如AnimatedContainer
)可以同时变换多个属性。 -
隐式动画会在新旧属性值变换时,自动使用提供的
curve
和duration
进行动画变换。 -
如果你没有指定
curve
,隐式动画的曲线会默认使用 线性曲线。
下一步是什么?
#恭喜,你已经完成了这个 codelab!如果你想要了解更多,这里有一些其他文章的推荐:
-
尝试一下 动画教程。
-
学习 hero 动画 和 staggered 动画。
-
查看更多 动画库 的信息。
-
尝试一下其他的 codelab。
除非另有说明,本文档之所提及适用于 Flutter 的最新稳定版本,本页面最后更新时间: 2024-09-02。 查看文档源码 或者 为本页面内容提出建议。