Skip to main content

动画

动画基本原理以及Flutter动画简介

动画原理

在任何系统的 UI 框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变 UI 外观;由 于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将 UI 的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率 FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下, 对于人眼来说,动画帧率超过 16 FPS ,就基本能看了,超过 32 FPS 就会感觉相对平滑,而超过 32 FPS ,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变 UI 输出,所以在一个时间段内 连续的改变 UI 输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧 率是重要的性能指标,而在 Flutter 中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率 是基本是持平的。

动画简介

FLutter 中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画、和 Hero 动画。

隐式动画

通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以 叫隐式动画,FLutter 中提供的 AnimatedContainerAnimatedPaddingAnimatedPositionedAnimatedOpacityAnimatedDefaultTextStyleAnimatedSwitcher 都属于隐式动画。 隐式动画中可以通过 duration 配置动画时长、可以通过 Curve (曲线) 来配置动画过程。

AnimatedContainer

AnimatedContainer 的属性和 Container 属性基本是一样的,当 AnimatedContainer 属性改变的时候就会触发动画。

import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag=true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.animation),
onPressed: () {
setState(() {
flag=!flag;
});
},
),
appBar: AppBar(
title: const Text("AnimatedContainer Demo"),
),
body: Center(
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // 动画时长500 ms
width: flag?100:300,
height:flag?100:300,
color: Colors.blue,
),
),
);
}
}

AnimatedPadding 以及 curve 属性

import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.animation),
onPressed: () {
setState(() {
flag = !flag;
});
},
), // FloatingActionButton
appBar: AppBar(
title: const Text("AnimatedContainer Demo"),
), // AppBar
body: AnimatedPadding(
duration: const Duration(milliseconds: 2000), // 动画时长500 ms
curve: Curves.bounceInOut,
padding: EdgeInsets.fromLTRB(10, flag ? 10 : 500, 0, 0),
child: Container(
width: 100,
height: 100,
color: Colors.red,
), // Container
), // AnimatedPadding
); // Scaffold
}
}

Curves曲线值:

曲线名动画过程
linear匀速的
decelerate匀减速
ease开始加速,后面减速
easeIn开始慢,后面快
easeOut开始快,后面慢
easeInOut开始慢,然后加速,最后再减速
更多曲线https://docs.flutter.io/flutter/animation/Curves-class.html 官方文档打不开也可以参考教程目录中提供的gif截图

AnimatedPositioned

import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
); // MaterialApp
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("AnimatedPositioned Demo"),
), // AppBar
body: Stack(
children: [
AnimatedPositioned(
curve: Curves.easeInOut,
duration: const Duration(seconds: 1),
top: flag ? 10 : 500,
left: flag ? 10 : 300,
child: Container(
width: 60,
height: 60,
color: Colors.blue,
), // Container
), // AnimatedPositioned
Align(
alignment: const Alignment(0, 0.8),
child: ElevatedButton(
child: const Text("Transform"),
onPressed: () {
setState(() {
flag = !flag;
});
},
),
), // Align
],
), // Stack
); // Scaffold
}
}

AnimatedOpacity

import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
); // MaterialApp
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
flag=!flag;
});
},
child: const Icon(Icons.opacity),
), // FloatingActionButton
appBar: AppBar(
title: const Text("AnimatedPositioned Demo"),
), // AppBar
body: Center(
child: AnimatedOpacity(
curve: Curves.linear,
duration: const Duration(seconds: 1),
opacity: flag?1:0,
child: Container(
width: 300,
height: 300,
color: Colors.blue,
), // Container
), // AnimatedOpacity
), // Center
);
}
}

AnimatedDefaultTextStyle

import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.opacity),
), // FloatingActionButton
appBar: AppBar(
title: const Text("AnimatedPositioned Demo"),
), // AppBar
body: Center(
child: Container(
alignment: Alignment.center,
width: 300,
height: 300,
color: Colors.blue,
child: AnimatedDefaultTextStyle(
duration: const Duration(seconds: 1),
style: TextStyle(
fontSize: flag?20:50,
), // TextStyle
child: const Text("你好Flutte"),
), // AnimatedDefaultTextStyle
), // Container
), // Center
);
}
}

AnimatedSwitcher 以及 transitionBuilder

上面讲的 AnimatedContainerAnimatedPaddingAnimatedPositionedAnimatedOpacityAnimatedDefaultTextStyle 都是在属性改变的时候执行动画,AnimatedSwitcher 则是在子元素改变的时候执行动画。 相比上面的动画组件 AnimatedSwitcher 多了 transitionBuilder 参数,可以在 transitionBuilder 中自定义动画。

示例1:载页面前显示一个loading动画,加载完毕后显示对于的内容
import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.opacity),
), // FloatingActionButton
appBar: AppBar(
title: const Text("AnimatedPositioned Demo"),
), // AppBar
body: Center(
child: Container(
alignment: Alignment.center,
width: 300,
height: 180,
color: Colors.yellow,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 1000),
child: flag
? const CircularProgressIndicator()
: Image.network(
"https://www.au-sonpo.co.jp/common/img/id0026.jpeg",
fit: BoxFit.contain,
),
), // AnimatedSwitcher
), // Container
), // Center
); // Scaffold
}
}
示例2:通过transitionBuilder自定义动画效果
import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.opacity),
), // FloatingActionButton
appBar: AppBar(
title: const Text("AnimatedPositioned Demo"),
), // AppBar
body: Center(
child: Container(
alignment: Alignment.center,
width: 300,
height: 180,
color: Colors.yellow,
child: AnimatedSwitcher(
transitionBuilder: ((child, animation) {
return ScaleTransition(
scale: animation,
child: FadeTransition(
opacity: animation,
child:child,
), // FadeTransition
); // ScaleTransition
}),
duration: const Duration(milliseconds: 400),
child: flag
? const CircularProgressIndicator()
: Image.network(
"https://www.au-sonpo.co.jp/common/img/id0026.jpeg",
fit: BoxFit.contain,
), // network
), // AnimatedSwitcher
), // Container
), // Center
); // Scaffold
}
}
示例3:通过transitionBuilder改变子元素执行动画
import 'dart:isolate';
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.opacity),
), // FloatingActionButton
appBar: AppBar(
title: const Text("AnimatedPositioned Demo"),
), // AppBar
body: Center(
child: Container(
alignment: Alignment.center,
width: 300,
height: 180,
color: Colors.yellow,
child: AnimatedSwitcher(
transitionBuilder: ((child, animation) {
return ScaleTransition(
scale: animation,
child: FadeTransition(
opacity: animation,
child:child ,
),
);
}),
duration: const Duration(milliseconds: 400),
child: Text(
key:UniqueKey(),flag ? "你好Flutter" : "你好艾斯",
style: const TextStyle(fontSize: 30),
), // Text
), // AnimatedSwitcher
), // Container
), // Center
);
}
}

显式动画

常见的显式动画有 RotationTransitionFadeTransitionScaleTransitionSlideTransitionAnimatedIcon 。在显示动画中开发者需要创建一个 AnimationController ,通过 AnimationController 控制动画的开始、暂停、重置、跳转、倒播等。

RotationTransition、 AnimationController

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
); // MaterialApp
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂
直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保
持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
); // AnimationController
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RotationTransition(
turns: _controller,
child: const FlutterLogo(
size: 100,
), // FlutterLogo
), // RotationTransition
const SizedBox(height: 40,),
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Wrap(
spacing: 10,
alignment: WrapAlignment.center,
children: [
ElevatedButton(
onPressed: (){
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: (){
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
ElevatedButton(
onPressed: (){
_controller.stop(); //停止播放
},
child: const Text("Stop")
),
ElevatedButton(
onPressed: (){
_controller.reset(); //重置
},
child: const Text("rest")
),
ElevatedButton(
onPressed: (){
_controller.repeat(); //重复播放
},
child: const Text("repeat")
)
],
), // Wrap
) // Padding
],
), // Column
); // Scaffold
}
}

lowerBound upperBound

AnimationController 用于控制动画,它包含动画的启动 forward()停止 stop() 、反向播放 reverse() 等方法。 AnimationController 会在动画的每一帧,就会生成一个新的值。默认情况 下, AnimationController 在给定的时间段内线性的生成从 0.01.0(默认区间)的数字 ,我们也 可以通过 lowerboundupperBound 来修改 AnimationController 生成数字的区间。

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
lowerBound:3, //第三圈转到第五圈
upperBound: 5
);

_controller.addListener(() {
print(_controller.value);
});
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RotationTransition(
turns: _controller,
child: const FlutterLogo(
size: 100,
),
),
const SizedBox(height: 40,),
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Wrap(
spacing: 10,
alignment: WrapAlignment.center,
children: [
ElevatedButton(
onPressed: (){
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: (){
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
ElevatedButton(
onPressed: (){
_controller.stop(); //停止播放
},
child: const Text("Stop")
),
ElevatedButton(
onPressed: (){
_controller.reset(); //重置
},
child: const Text("rest")
),
ElevatedButton(
onPressed: (){
_controller.repeat(); //重复播放
},
child: const Text("repeat")
)
],
),
)
],
),
);
}
}

FadeTransition

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
);
}

@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FadeTransition(
opacity: _controller,
child: const FlutterLogo(size: 80),
),
const SizedBox(height: 40,),
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
),
)
],
),
);
}
}

ScaleTransition、Tween

示例1:AnimationController控制动画
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ScaleTransition(
scale: _controller,
child: const FlutterLogo(size: 80),
), // ScaleTransition
const SizedBox(
height: 40,
), // SizedBox
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
), // Row
) // Padding
],
), // Column
); // Scaffold
}
}

示例2: AnimationController结合Tween控制动画:
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
); // MaterialApp
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ScaleTransition(
scale: _controller.drive(Tween(begin: 1,end: 2)),
child: const FlutterLogo(size: 80),
), // ScaleTransition
const SizedBox(
height: 40,
), // SizedBox
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
), // Row
) // Padding
],
), // Column
); // Scaffold
}
}
tip

默认情况下, AnimationController 对象值的范围是 [0.0,1.0] 。如果我们需要构建UI的动画值在不同的范围或不同的数据类型,则可以使用 Tween 来添加映射以生成不同的范围或数据类型的值。

SlideTransition

这是一负责平移的显示动画组件,使用时需要通过 position 属性传入一个 Animated 表示位移程度,通常借助 Tween 实现。

示例1: _controller.drive驱动动画

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
);
}

@override
void dispose() {
_controller.dispose();super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SlideTransition(
position: _controller.drive(
Tween(
begin: const Offset(0,0),
end: const Offset(1.2,0) //表示实际的位置向右移动自身宽度的1.2倍
)
),
child: const FlutterLogo(size: 80),
), // SlideTransition

const SizedBox(
height: 40,
),

Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
),
) // Padding
],
), // Column
);
}
}
示例2:Tween.animate 驱动动画

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SlideTransition(
position: Tween(
begin: const Offset(0,0),end: const Offset(1.2,0) //表示实际的位置向右移动自身宽度的1.2倍
).animate(_controller),
child: const FlutterLogo(size: 80),
),
const SizedBox(
height: 40,
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
),
)
],
),
);
}
}
示例3: 链式操作修改动画效果
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 1),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SlideTransition(
position: Tween(
begin: const Offset(0,-1),
end: const Offset(0,0.8) //表示实际的位置向右移动自身宽度的1.2倍
).chain(CurveTween(curve: Curves.bounceIn)).animate(_controller),
child: const FlutterLogo(size: 80),
), // SlideTransition
const SizedBox(
height: 40,
), // SizedBox
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
), // Row
) // Padding
],
), // Column
); // Scaffold
}
}
示例4: 链式操作修改动动画执行时间
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
// TODO: implement initState
_controller = AnimationController(
vsync:this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 3),
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SlideTransition(
position: Tween(
begin: const Offset(0,-1),
end: const Offset(0,0.8) //表示实际的位置向右移动自身宽度的1.2倍
).chain(CurveTween(curve: Curves.bounceIn))
.chain(CurveTween(curve: const Interval(0.8,1.0))) //最后的百分之20的
时间完成动画
.animate(_controller),
child: const FlutterLogo(size: 80),
), // SlideTransition
const SizedBox(
height: 40,
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
_controller.forward(); //正序播放一次
},
child: const Text("Forward")
),
ElevatedButton(
onPressed: () {
_controller.reverse(); //倒序播放一次
},
child: const Text("Reverse")
),
],
), // Row
) // Padding
],
),
);
}
}

AnimatedIcon

AnimatedIcon 顾名思义,是一个用于提供动画图标的组件,它的名字虽然是以 Animated 开头,但是他是一个显式动画组件,需要通过 progress 属性传入动画控制器,另外需要由 Icon 属性传入动画图标数据

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
// TODO: implement initState
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
);
}

@override
void dispose() {
// TODO: implement dispose
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_controller.forward();
},
), // FloatingActionButton
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child:
AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _controller,size:40
), // AnimatedIcon
), // Center
);
}
}

交错动画

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool flag = true;

@override
void initState() {
_controller = AnimationController(
vsync:
this, //Vsync 机制可以理解为是显卡与显示器的通信桥梁,显卡在渲染每一帧之前会等待垂直同步信号,只有显示器完成了一次刷新时,发出垂直同步信号,显卡才会渲染下一帧,确保刷新率和帧率保持同步,以达到供需平衡的效果,防止卡顿现象。
duration: const Duration(seconds: 6),
)..repeat(reverse: true);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
flag ? _controller.forward() : _controller.reverse();
flag = !flag;
},
), // floatingActionButton
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SlidingBox(
controller: _controller,
color: Colors.blue[200],
curve: const Interval(0, 0.2),
),
SlidingBox(
controller: _controller,
color: Colors.blue[400],
curve: const Interval(0.2, 0.4),
),
SlidingBox(
controller: _controller,
color: Colors.blue[600],
curve: const Interval(0.4, 0.6),
),
SlidingBox(
controller: _controller,
color: Colors.blue[800],
curve: const Interval(0.6, 0.8),
),
SlidingBox(
controller: _controller,
color: Colors.blue[900],
curve: const Interval(0.8, 1.0),
),
],
), // Column
), // Center
); // Scaffold
}
}

class SlidingBox extends StatelessWidget {
final AnimationController controller;
final Color? color;
final Curve curve;

const SlidingBox(
{super.key,
required this.controller,
required this.color,
required this.curve}
);

@override
Widget build(BuildContext context) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 0),
end: const Offset(0.3, 0)
)
.chain(
CurveTween(
curve: Curves.bounceInOut
)
)
.chain(
CurveTween(
curve: curve
)
)
.animate(controller),
child: Container(
width: 220,
height: 60,
color: color,
),
);
}
}

自定义动画

TweenAnimationBuilder自定义隐式动画

每当Tween的end发生变化的时候就会触发动画。

示例1:大小变化

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
setState(() {
flag = !flag;
});
},
), // FloatingActionButton
body: Center(
child: TweenAnimationBuilder(
tween: Tween(begin: 100.0, end: flag ? 100.0 : 200.0),
duration: const Duration(seconds: 1),
builder: ((context, value, child) {
return Icon(
Icons.star,
size: value as double,
);
})
), // TweenAnimationBuilder
), // Center
);
}
}
示例2:透明度变化
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
bool flag = true;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
setState(() {
flag = !flag;
});
},
), // FloatingActionButton
body: Center(
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: flag ? 0.2 : 1.0),
duration: const Duration(seconds: 1),
builder: ((context, value, child) {
return Opacity(
opacity: value as double,
child: Container(
color: Colors.red,
width: 200,
height: 200,
), // Container
); // Opacity
})
), // TweenAnimationBuilder
), // Center
); // Scaffold
}
}

AnimatedBuilder 自定义显式动画

示例1:透明度动画
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller =AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
)..repeat(reverse: true);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Opacity(
opacity: _controller.value, //从0到1的变化
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: const Text("我是一个text组件"),
), // Container
); // Opacity
},
), // AnimatedBuilder
), // Center
); // Scaffold
}
}
示例2:自定义变化范围
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@overrideWidget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
)..repeat(reverse: true);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Opacity(
opacity: Tween(begin: 0.5,end: 1.0).animate(_controller).value, // 从0.5到1的变化
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: const Text("我是一个text组件"),
), // Container
); // Opacity
},
), // AnimatedBuilder
), // Center
); // Scaffold
}
}
示例3:位置变化
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
)..repeat(reverse: true);
}

@override
Widget build(BuildContext context) {
Animation x = Tween(begin: -100.0, end: 100.0)
.chain(CurveTween(curve: Curves.easeIn))
.chain(CurveTween(curve: const Interval(0.2, 0.8)))
.animate(_controller);
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
), // AppBar
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Container(
width: 200,height: 200,
color: Colors.red,
transform: Matrix4.translationValues(x.value, 0, 0),
child: const Text("我是一个text组件"),
);
},
), // AnimatedBuilder
), // Center
); // Scaffold
}
}
示例4:chilid优化

import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;

@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1)
)..repeat(reverse: true);
}

@overrideWidget build(BuildContext context) {
Animation x = Tween(begin: -100.0, end: 100.0)
.chain(CurveTween(curve: Curves.easeIn))
.chain(CurveTween(curve: const Interval(0.2, 0.4)))
.animate(_controller);
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return Container(
width: 200,
height: 200,
color: Colors.red,
transform: Matrix4.translationValues(x.value, 0, 0),
child: child,
); // Container
},
child: const Text("我是一个text组件"),
), // AnimatedBuilder
), // Center
); // Scaffold
}
}

Hero 动画

Hero 动画的使用

微信朋友圈点击小图片的时候会有一个动画效果到大图预览,这个动画效果就可以使用 Hero 动画实现。

Hero 指的是可以在路由(页面)之间“飞行”的 widget ,简单来说 Hero 动画就是在路由切换时,有一个共享的 `widget`` 可以在新旧路由间切换。

home.dart

import 'package:flutter/material.dart';
import '../../res/listData.dart';

class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
List<Widget> _getListData() {
var tempList = listData.map((value) {
return GestureDetector(
onTap: () {
Navigator.pushNamed(context, "/hero",arguments: {
"imageUrl": value['imageUrl'],
"description":value['description'],
});
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: const Color.fromRGBO(233, 233, 233, 0.9),
width: 1
)
), // BoxDecoration
child: Column(
children: <Widget>[
Hero(
tag: value['imageUrl'],
child: Image.network(value['imageUrl'])
),
const SizedBox(height: 12),
Text(
value['title'],
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
)
],
) // Column
), // Container
); // GestureDetector
});
// ('xxx','xxx')
return tempList.toList();
}

@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisSpacing: 10.0, //水平子 Widget 之间间距
mainAxisSpacing: 10.0, //垂直子 Widget 之间间距
padding: const EdgeInsets.all(10),
crossAxisCount: 2, //一行的 Widget 数量
// childAspectRatio:0.7, //宽度和高度的比例
children: _getListData(),
);
}
}
hero.dart
import 'package:flutter/material.dart';
class HeroPage extends StatefulWidget {
final Map arguments;
const HeroPage({super.key, required this.arguments});

@override
State<HeroPage> createState() => _HeroPageState();
}

class _HeroPageState extends State<HeroPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("详情页面"),
),
body: ListView(
children: [
Hero(
tag: widget.arguments["imageUrl"],
child: Image.network(widget.arguments["imageUrl"])
), // Hero
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.all(5),
child: Text(widget.arguments["description"],
style: const TextStyle(fontSize: 22)
), // Text
)
],
),
);
}
}

配置 Hero 动画的执行时间

  1. 引入 scheduler.dart
import 'package:flutter/scheduler.dart';
  1. 设置动画时间
void initState() {
super.initState();
timeDilation=5.0;
}