动画基本原理以及Flutter动画简介
一丶动画基本原理
在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由 于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将UI 的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率 FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下, 对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32 FPS,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内 连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧 率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率 是基本是持平的。
Flutter动画简介
FLutter中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画、和 Hero 动画。
隐式动画
通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以 叫隐式动画,FLutter中提供的 AnimatedContainer、AnimatedPadding、AnimatedPositioned、 AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher都属于隐式动画。
隐式动画中可以通过duration 配置动画时长、可以通过Curve (曲线)来配置动画过程
AnimatedContainer
AnimatedContainer的属性和Container属性基本是一样的,当AnimatedContainer属性改变的时候就 会触发动画。
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: AnimatedContainer(
duration: const Duration(seconds: 1),
transform: Matrix4.rotationZ(flag ? 0 : 0.25),
width: flag ? 100 : 200,
height: flag ? 100 : 200,
color: Colors.red,
),
),
);
}
}
实现一个简单的侧边栏
// 如果需要实现遮住头部导航栏的效果,就需要使用Stack组件包裹住Scaffold组件
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Stack(
children: [
ListView(
children: const [
ListTile(
title: Text('我是一个列表'),
),
],
),
Positioned(
top: 0,
left: 0,
bottom: 0,
child: Center(
child: AnimatedContainer(
duration: const Duration(seconds: 1),
transform: flag
? Matrix4.translationValues(-200, 0, 0)
: Matrix4.translationValues(0, 0, 0),
width: 200,
height: double.infinity,
color: Colors.red,
),
),
)
],
),
);
}
}

AnimatedPadding以及curve属性
Curves曲线值:
| 曲线名 | 动画过程 |
|---|---|
| linear | 匀速的 |
| decelerate | 匀减速 |
| ease | 开始加速,后面减速 |
| easeIn | 开始慢,后面快 |
| easeOut | 开始快,后面慢 |
| easeInOut | 开始慢,然后加速,最后再减速 |
| fastLinearToSlowEaseIn | 开始快,后面慢 |
| bounceIn | 动画结束时有一个小的反弹效果 |
| bounceOut | 动画开始时有一个小的反弹效果 |
| bounceInOut | 动画开始和结束时都有一个小的反弹效果 |
| elasticIn | 有弹性的进入 |
| elasticOut | 有弹性的退出 |
| elasticInOut | 有弹性的进入和退出 |
| cubic | 自定义曲线,需要提供一个CurveTween对象,该对象需要传入一个curve属性,该属性是一个Curve对象,Curve对象需要传入一个double类型的值,该值的范围是0-1,0表示动画开始,1表示动画结束,中间的值表示动画过程中的某个点。 |
| 更多曲线 | https://docs.flutter.io/flutter/animation/Curves-class.html 官方文档打不开也可以参考教程目录中提供的gif截图 |
AnimatedOpacity
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: AnimatedOpacity(
duration: const Duration(seconds: 3),
curve: Curves.elasticInOut,
opacity: flag ? 1.0 : 0.1,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
))),
);
}
}
AnimatedDefaultTextStyle
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: AnimatedOpacity(
duration: const Duration(seconds: 3),
curve: Curves.elasticInOut,
opacity: flag ? 1.0 : 0.5,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: AnimatedDefaultTextStyle(
duration: const Duration(seconds: 3),
curve: Curves.elasticInOut,
style: TextStyle(
fontSize: flag ? 20 : 40,
color: flag ? Colors.red : Colors.green,
),
child: const Text('Hello World'),
),
))),
);
}
}
AnimatedSwitcher 以及transitionBuilder
上面讲的AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、 AnimatedDefaultTextStyle都是在属性改变的时候执行动画,AnimatedSwitcher则是在子元素改变的 时候执行动画。
相比上面的动画组件AnimatedSwitcher多了transitionBuilder参数,可以在transitionBuilder中自定义 动画
示例1:载页面前显示一个loading动画,加载完毕后显示对于的内容
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Container(
alignment: Alignment.center,
width: 200,
height: 200,
color: Colors.red,
child: AnimatedSwitcher( // 当子组件发生变化时,会执行动画
duration: const Duration(seconds: 1),
child: flag
? const CircularProgressIndicator()
: Image.network(
'https://www.itying.com/images/flutter/1.png',
fit: BoxFit.cover,
),
),
)),
);
}
}
示例2:通过transitionBuilder自定义动画效果
import 'package:flutter/material.dart';
import 'package:flutter_application_1/tabs.dart';
import 'home.dart';
import 'router/routers.dart';
import 'search.dart';
import 'form.dart';
import 'news.dart';
// import 'listData.dart';
void main() {
runApp( const MyApp());
}
class MyApp extends StatelessWidget {
// ignore: use_key_in_widget_constructors
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// home: HomePage(),
// home: const Main(),
initialRoute: '/',
// 配置onGenerateRoute 固定写法
onGenerateRoute: onGenerateRoute,
);
}
}
class Main extends StatefulWidget {
const Main({ Key? key }) : super(key: key);
@override
// ignore: library_private_types_in_public_api
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
@override
Widget build(BuildContext context) {
return const TabsPage();
}
}
// 文字需要加key,否则会报错
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
bool flag = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Container(
alignment: Alignment.center,
width: 200,
height: 200,
color: Colors.red,
child: AnimatedSwitcher( // 当子组件发生变化时,会执行动画
transitionBuilder: (child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
},
duration: const Duration(seconds: 1),
// child: flag
// ? const CircularProgressIndicator()
// : Image.network(
// 'https://www.itying.com/images/flutter/1.png',
// fit: BoxFit.cover,
// ),
child: Text(
key: UniqueKey(),
flag ? '我若成佛' : '天下无魔',
style: const TextStyle(fontSize: 30),
)
),
)),
);
}
}
显式动画
常见的显式动画有RotationTransition、FadeTransition、ScaleTransition、SlideTransition、 AnimatedIcon。在显示动画中开发者需要创建一个AnimationController,通过AnimationController 控制动画的开始、暂停、重置、跳转、倒播等。
1、RotationTransition、 AnimationController
示例1:AnimationController普通用法
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RotationTransition(
turns: _controller,
child: const FlutterLogo(size: 200.0),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
示例2: FadeTransition lowerBound upperBound
AnimationController 用于控制动画,它包含动画的启动forward() 、停止stop() 、反向播放 reverse() 等方法。AnimationController 会在动画的每一帧,就会生成一个新的值。默认情况 下, AnimationController 在给定的时间段内线性的生成从 0.0 到1.0(默认区间)的数字 ,我们也 可以通过 lowerbound 和 upperBound 来修改AnimationController 生成数字的区间。
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
lowerBound: 0.5,
upperBound: 1.0
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FadeTransition(
// turns: _controller,
opacity: _controller,
child: const FlutterLogo(size: 200.0),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
2、ScaleTransition、Tween
示例1:AnimationController控制动画
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
)..repeat(reverse: true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ScaleTransition(
// turns: _controller,
// opacity: _controller,
scale: _controller,
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
AnimationController结合Tween控制动画:
默认情况下, AnimationController 对象值的范围是[0.0,1.0]。如果我们需要构建UI的动画值在不同 的范围或不同的数据类型,则可以使用Tween 来添加映射以生成不同的范围或数据类型的值
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
)..repeat(reverse: true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ScaleTransition(
// turns: _controller,
// opacity: _controller,
scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
SlideTransition
这是一负责平移的显示动画组件,使用时需要通过position属性传入一个Animated表示位移程度,通常 借助Tween实现。
示例1: _controller.drive驱动动画
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SlideTransition(
// turns: _controller,
// opacity: _controller,
// scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
position: _controller.drive(
Tween(begin: const Offset(0, 0), end: const Offset(1, 1))
),
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
示例2:Tween.animate 驱动动画
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SlideTransition(
// turns: _controller,
// opacity: _controller,
// scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
position: Tween(begin: const Offset(0, 0), end: const Offset(1, 1)).animate(_controller),
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
示例3: 链式操作修改动画效果
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SlideTransition(
// turns: _controller,
// opacity: _controller,
// scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
position:
Tween(
begin: const Offset(0,-1),
end: const Offset(0,0.8) //表示实际的位置向右移动自身宽度的1.2倍
).chain(CurveTween(curve: Curves.bounceIn)).animate(_controller),
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
示例4: 链式操作修改动动画执行时间
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SlideTransition(
// turns: _controller,
// opacity: _controller,
// scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
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.0, 0.5,curve: Curves.linear))),
child: Container(
width: 100,
height: 100,
color: Colors.red,
)
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
AnimatedIcon
AnimatedIcon顾名思义,是一个用于提供动画图标的组件,它的名字虽然是以Animated开头,但是他 是一个显式动画组件,需要通过progress属性传入动画控制器,另外需要由Icon属性传入动画图标数据
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedIcon(icon: AnimatedIcons.menu_close,progress:
_controller,size:40),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
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('reset'),
),
ElevatedButton(
onPressed: () {
_controller.repeat(); // 重复动画
},
child: const Text('repeat'),
),
],
)
],
),
),
);
}
}
交错动画
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = true;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this, // 让程序和手机的刷新频率一样
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
if (flag) {
_controller.forward();
} else {
_controller.reverse();
}
});
},
child: const Icon(Icons.add),
),
body: Center(
child: Stack(
children: [
ScaleTransition(
scale: _controller.drive(
Tween(
begin: 0.0,
end: 1.0
).chain(CurveTween(curve: const Interval(0.5, 1.0))
)
),
child: const Icon(Icons.close, size: 40,),
),
ScaleTransition(
scale: _controller.drive(
Tween(
begin: 1.0,
end: 0.0
).chain(CurveTween(curve: const Interval(0, 0.5))
)
),
child: const Icon(Icons.search, size: 40,),
)
],
),
),
);
}
}
封装交错动画
import 'package:flutter/material.dart';
import 'tools/slidingBox.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = false;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 6),
vsync: this, // 让程序和手机的刷新频率一样
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
flag ? _controller.forward() : _controller.reverse();
flag = !flag;
},
child: const Icon(Icons.add),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SlidingBoxPage(
controller: _controller,
color: Colors.blue[200],
curve: const Interval(0, 0.2),
),
SlidingBoxPage(
controller: _controller,
color: Colors.blue[400],
curve: const Interval(0.2, 0.4),
),
SlidingBoxPage(
controller: _controller,
color: Colors.blue[600],
curve: const Interval(0.4, 0.6),
),
SlidingBoxPage(
controller: _controller,
color: Colors.blue[800],
curve: const Interval(0.6, 0.8),
),
SlidingBoxPage(
controller: _controller,
color: Colors.blue[900],
curve: const Interval(0.8, 1),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
class SlidingBoxPage extends StatelessWidget {
final AnimationController controller;
final Color? color;
final Curve curve;
const SlidingBoxPage(
{super.key, required this.controller, 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: 200,
height: 60,
color: color,
),
);
}
}
自定义动画
import 'package:flutter/material.dart';
import 'tools/slidingBox.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = false;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 6),
vsync: this, // 让程序和手机的刷新频率一样
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: TweenAnimationBuilder(
tween: Tween(begin: 40.0, end: flag ? 100.0 : 40.0),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Icon(Icons.star, size: value, color: Colors.red,);
},
)
),
);
}
}
TweenAnimationBuilder自定义隐式动画
自定义透明动画
import 'package:flutter/material.dart';
import 'tools/slidingBox.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = false;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 6),
vsync: this, // 让程序和手机的刷新频率一样
);
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
flag = !flag;
});
},
child: const Icon(Icons.add),
),
body: Center(
child: TweenAnimationBuilder(
tween: Tween(begin: 0.5, end: flag ? 0.5 : 1.0),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Opacity(opacity: value, child: const Icon(Icons.star, size: 100, color: Colors.red,));
},
)
),
);
}
}
TweenAnimationBuilder自定义显示动画
import 'package:flutter/material.dart';
import 'tools/slidingBox.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage>
with SingleTickerProviderStateMixin {
bool flag = false;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this, // 让程序和手机的刷新频率一样
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('搜索'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_controller.repeat(reverse: true);
});
},
child: const Icon(Icons.add),
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child,) {
return Opacity(
// opacity: _controller.value, // 0.0 - 1.0
opacity: Tween(begin: 0.5, end: 1.0).animate(_controller).value, // 0.5 - 1.0
child: const Icon
(Icons.favorite, color: Colors.red, size: 100,)
);
},
),
),
);
}
}
Hero动画
Hero动画的使用
微信朋友圈点击小图片的时候会有一个动画效果到大图预览,这个动画效果就可以使用Hero 动画实现。
Hero 指的是可以在路由(页面)之间“飞行”的 widget,简单来说 Hero 动画就是在路由切换时,有一个共享的widget 可以在新旧路由间切换。
// home.dart
import 'package:flutter/material.dart';
import '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((e) {
return GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/heros', arguments: {
'imageUrl': e['imageUrl'],
});
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.black12,
width: 1.0,
),
),
child: Column(children: [
Hero(tag: e['imageUrl'], child: Image.network(e['imageUrl'])),
const SizedBox(height: 12.0),
Text(
e['title'],
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20.0,
// 溢出显示省略号
overflow: TextOverflow.ellipsis,
),
),
]),
),
);
});
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
crossAxisCount: 2,
padding: const EdgeInsets.all(10.0),
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 GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Hero(
tag: widget.arguments['imageUrl'],
child: Scaffold(
backgroundColor: Colors.black,
body: Center(
child: AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(widget.arguments['imageUrl']),
)),
),
),
);
}
}
配置 Hero 动画的执行时间
Hero +photo_view 实现微信朋友圈图片预览
photo_view插件支持预览图片,可放大、缩小、滑动图片
photo_view 官方地址:https://pub.dev/packages/photo_view
photo_view预览单张图片
配置依赖
dependencies: photo_view: ^0.14.0
引入
import 'package:photo_view/photo_view.dart';
单张图片预览
import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.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 GestureDetector( onTap: () { Navigator.pop(context); }, child: Hero( tag: widget.arguments['imageUrl'], child: Scaffold( backgroundColor: Colors.black, body: Center( child: AspectRatio( aspectRatio: 16 / 9, child: PhotoView( imageProvider: NetworkImage(widget.arguments['imageUrl']), ), )), ), ), ); } }
photo_view多张图片预览
配置依赖
dependencies: photo_view: ^0.14.0
引入
import 'package:photo_view/photo_view.dart';
多张图片的预览
import 'package:flutter/material.dart'; import 'package:flutter_application_1/listData.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'listData.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 GestureDetector( onTap: () { Navigator.pop(context); }, child: Hero( tag: widget.arguments['imageUrl'], child: Scaffold( backgroundColor: Colors.black, body: Center( child: PhotoViewGallery.builder( itemCount: listData.length, builder: (BuildContext context, int index){ return PhotoViewGalleryPageOptions( imageProvider: NetworkImage(listData[index]['imageUrl']), ); } ) ), ), ), ); } }
PhotoViewGallery.builder属性:
| 属性 | 描述 |
|---|---|
| scrollPhysics | BouncingScrollPhysics() 滑动到边界的时候有弹跳的效果 |
| scrollDirection | Axis.horizontal 水平 、Axis.vertical垂直方向 |
| backgroundDecoration | 背景颜色 |
| builder | builder函数根据配置的itemCount渲染函数 |
| itemCount | 数量 |
| pageController | pageController(initialPage:1) |
| onPageChanged | onPageChanged触发的方法 |
更多详情参考教程以及教程源码