🔥 本文由 程序喵正在路上 原創,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,)),)),);}
}