🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Flutter学习
🌠 首发时间:2024年5月28日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
- 动画基本原理
- Flutter动画简介
- 隐式动画
- curve
- AnimatedContainer
- AnimatedPadding
- AnimatedPositioned
- AnimatedOpacity
- AnimatedDefaultTextStyle
- AnimatedSwitcher
- 修改 AnimatedSwitcher 的动画效果
- 子组件相同的AnimatedSwitcher
动画基本原理
在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。
我们将UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下,对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32FPS,大多数人基本上就感受不到差别了。
由于动画的每一帧都是要改变UI输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率是基本是持平的。
Flutter动画简介
FLutter 中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画和 Hero 动画。
隐式动画
通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以叫隐式动画,FLutter 中提供的 AnimatedContainer
、AnimatedPadding
、AnimatedPositioned
、
AnimatedOpacity
、AnimatedDefaultTextStyle
、AnimatedSwitcher
都属于隐式动画。
隐式动画中可以通过 duration
来配置动画时长,通过 curve
(曲线)来配置动画过程,这两个属性在上述组件中都是存在的。
curve
在 Flutter
中,Curves
类提供了一系列预定义的曲线,用于控制动画的速度变化。以下是一些常用的 Curves
组件的值及简单解释:
-
Curves.linear:
- 线性曲线,动画以恒定的速度进行,没有加速或减速。
-
Curves.decelerate:
- 减速曲线,动画开始时速度较快,然后逐渐减速。
-
Curves.ease:
- 标准的加速减速曲线,动画开始和结束时速度较慢,中间时速度较快。
-
Curves.easeIn:
- 加速曲线,动画开始时速度较慢,然后逐渐加速。
-
Curves.easeOut:
- 减速曲线,动画开始时速度较快,然后逐渐减速。
-
Curves.easeInOut:
- 加速减速曲线,动画开始和结束时速度较慢,中间时速度较快,类似于
Curves.ease
。
- 加速减速曲线,动画开始和结束时速度较慢,中间时速度较快,类似于
-
Curves.fastOutSlowIn:
- 快出慢入曲线,动画开始时速度较快,然后逐渐减速到结束。
-
Curves.bounceIn:
- 弹簧效果曲线,动画开始时速度为0,然后加速进入动画,到达最大速度后反弹一次。
-
Curves.elasticIn:
- 弹性效果曲线,动画开始时速度为0,然后加速进入动画,到达最大速度后会有一些超过目标值的回弹效果。
这些是常用的一些Curves组件的值,通过选择合适的曲线,可以控制动画的速度变化,从而实现不同的动画效果。
AnimatedContainer
AnimatedContainer
的属性和 Container
属性基本是一样的,不同的地方是,当 AnimatedContainer
属性改变的时候会触发动画。
下面的程序中,我们通过浮动按钮来改变 AnimatedContainer
中 transform
的值,以触发动画。
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedContainer'),),body: Center(child: AnimatedContainer(duration: const Duration(seconds: 1, milliseconds: 100),width: 200,height: 200,transform: flag? Matrix4.translationValues(0, 0, 0): Matrix4.translationValues(-100, 0, 0),color: Colors.blue,),),);}
}
AnimatedPadding
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedPadding'),),body: AnimatedPadding(curve: Curves.linear,padding: EdgeInsets.fromLTRB(10, flag ? 10 : 200, 0, 0),duration: const Duration(seconds: 2),child: Container(width: 100,height: 100,color: Colors.blue,),),);}
}
AnimatedPositioned
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedPositioned'),),body: Stack(children: [ListView(children: const [ListTile(title: Text("我是一个列表"),),ListTile(title: Text("我是一个列表"),),ListTile(title: Text("我是一个列表"),),ListTile(title: Text("我是一个列表"),),ListTile(title: Text("我是一个列表"),),ListTile(title: Text("我是一个列表"),),],),AnimatedPositioned(curve: Curves.linear,right: flag ? 10 : 300,top: flag ? 10 : 560,duration: const Duration(seconds: 1, milliseconds: 500),child: Container(width: 60,height: 60,color: Colors.blue,)),],),);}
}
AnimatedOpacity
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedOpacity'),),body: Center(child: AnimatedOpacity(opacity: flag ? 0.2 : 1,duration: const Duration(seconds: 1),curve: Curves.easeIn,child: Container(width: 200,height: 200,color: Colors.red,),),),);}
}
AnimatedDefaultTextStyle
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedDefaultTextStyle'),),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, color: Colors.white),child: const Text("你好Flutter"),),),),);}
}
AnimatedSwitcher
Flutter
中的 AnimatedSwitcher
是一个用于在切换子元素时执行动画效果的预置组件。它允许你在切换子元素时使用自定义的过渡效果,从而实现平滑的切换体验。
AnimatedSwitcher
通常用于在切换应用程序界面的不同部分或内容时提供动画效果。例如,当用户点击按钮切换页面或者切换视图时,可以使用 AnimatedSwitcher
来实现页面间的过渡动画。
在使用 AnimatedSwitcher
时,你需要提供新旧子元素,并指定一个过渡效果。当新的子元素被提供时,AnimatedSwitcher
将会在旧的子元素和新的子元素之间执行过渡动画。
要使用 AnimatedSwitcher
,通常需要指定以下几个关键属性:
child
:当前的子元素,即将要显示的子元素。duration
:动画的持续时间,用于控制过渡动画的速度。transitionBuilder
:一个回调函数,用于定义子元素切换时的过渡效果。该回调函数接受当前子元素和动画对象,并返回一个 Widget,用于实现过渡效果。
通过这些属性,你可以轻松地在 Flutter
应用程序中添加动画效果,从而提升用户体验并增加应用的交互性。AnimatedSwitcher
是 Flutter
框架中 Material
组件库的一部分,因此可以与其他 Material
风格的组件无缝集成。
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedSwitcher'),),body: Center(child: Container(alignment: Alignment.center,width: 300,height: 220,color: Colors.orange,child: AnimatedSwitcher(//当子元素改变的时候会触发动画duration: const Duration(seconds: 1),child: flag? const CircularProgressIndicator(): Image.network("https://xixi-web-tlias.oss-cn-guangzhou.aliyuncs.com/5.jpg",fit: BoxFit.cover,))),),);}
}
Flutter 中的 CircularProgressIndicator
是一个用于显示循环进度的预置组件。它通常用于在应用程序加载数据、执行长时间任务或执行任何需要显示进度的操作时显示一个圆形的进度条。
CircularProgressIndicator
可以以不同的方式进行定制,包括更改颜色、大小和动画。它是 Flutter 框架中 Material 组件库的一部分,因此在使用 MaterialApp 或 Scaffold 等 Material 风格的组件时很常见。
你可以通过简单的代码来创建一个 CircularProgressIndicator
,然后将其放置在应用程序的合适位置。在加载数据或执行任务时,CircularProgressIndicator
将显示一个圆形进度条,直到任务完成为止。
修改 AnimatedSwitcher 的动画效果
通过 AnimatedSwitcher
中的 transitionBuilder
参数可以修改其动画效果。
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedSwitcher'),),body: Center(child: Container(alignment: Alignment.center,width: 300,height: 220,color: Colors.orange,child: AnimatedSwitcher(transitionBuilder: ((child, animation) {//可以改变动画效果return ScaleTransition(scale: animation,child: FadeTransition(opacity: animation,child: child,),);}),duration: const Duration(seconds: 1),//当子元素改变的时候会触发动画child: flag? const CircularProgressIndicator(): Image.network("https://xixi-web-tlias.oss-cn-guangzhou.aliyuncs.com/5.jpg",fit: BoxFit.cover,))),),);}
}
在上面的代码中,transitionBuilder
参数是用于定义 AnimatedSwitcher
在切换子元素时所使用的过渡效果的回调函数。这个回调函数接受两个参数:
child
:当前子元素(即将要切换的元素)。animation
:表示当前切换的动画。在这里,animation
是一个Animation<double>
类型的对象,它描述了从0到1的动画值。
transitionBuilder
回调函数应该返回一个 Widget
,这个 Widget
将作为子元素在切换时的动画效果。在上面的代码中,回调函数使用了两种过渡效果:
ScaleTransition
:它通过对子元素进行缩放来实现动画效果,缩放的比例由动画值animation
控制,从而实现子元素的逐渐放大或缩小的效果。FadeTransition
:它通过改变子元素的不透明度来实现淡入淡出的效果,不透明度的变化由动画值animation
控制,从而实现子元素的逐渐显现或消失的效果。
通过结合使用 ScaleTransition
和 FadeTransition
,可以实现更加丰富的过渡效果,使切换子元素时更加平滑和动态。
子组件相同的AnimatedSwitcher
我们知道,AnimatedSwitcher
只有在其子元素发生改变时才会触发动画,那么当子元素不变时,比如子元素为一个 Text
组件,我们只修改里面的内容,不改变组件类型,在这种情况下又该如何触发动画呢?
很简单,我们只要设置一下子元素的 key
值为 UniqueKey()
,就可以让 AnimatedSwitcher
认为子元素已经改变。
import 'package:flutter/material.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return const MaterialApp(home: MyHomePage(),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key});State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {bool flag = true;Widget build(BuildContext context) {return Scaffold(floatingActionButton: FloatingActionButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {flag = !flag;});},),appBar: AppBar(title: const Text('AnimatedSwitcher'),),body: Center(child: Container(alignment: Alignment.center,width: 300,height: 220,color: Colors.blue,child: AnimatedSwitcher(transitionBuilder: ((child, animation) {//可以改变动画效果return ScaleTransition(scale: animation,child: FadeTransition(opacity: animation,child: child,),);}),duration: const Duration(seconds: 1),//当子元素改变的时候会触发动画child: Text(key: UniqueKey(),flag ? "凡所过往" : "皆为序章",style: Theme.of(context).textTheme.headlineLarge,)),)),);}
}