前言
看過很多文章關於寫Flutter動畫的,但是大多數就是照著文件抄的文章,也有的就是你轉發我的,我轉發你的。很多文章沒有加入自己對知識的理解,不知道為什麼要這做。為此我打算寫一篇自己理解的Flutter動畫。
動畫概述
flutter中動畫分為兩類:基於tween或基於物理的。
補間(Tween)動畫
“介於兩者之間”的簡稱。在補間動畫中,定義了開始點和結束點、時間線以及定義轉換時間和速度的曲線。然後由框架計算如何從開始點過渡到結束點。
基於物理的動畫
在基於物理的動畫中,運動被模擬為與真實世界的行為相似。例如,當你擲球時,它在何處落地,取決於拋球速度有多快、球有多重、距離地面有多遠。 類似地,將連線在彈簧上的球落下(並彈起)與連線到繩子上的球放下的方式也是不同。
基礎概念
1,Animation物件
- 它是Flutter動畫庫中的一個核心類,它生成指導動畫的值。這個值包括顏色,大小等。
- 它知道當前動畫的狀態(開始,停止,反轉,完成),但是它不知道螢幕上渲染的UI是什麼,也就是說它不關心你的動畫物件UI渲染情況。
- 它是一個抽象類,具體功能實現由 其子類完成。
- 一個比較常用的Animation類是Animation<double>,它預設產生的值範圍是(0.0-1.0)
2,CurvedAnimation
它是Animation<double>的子類, 將動畫過程定義為一個非線性曲線,也就是說我們的動畫執行的一個路徑是怎樣的,或者說以一種什麼樣的形式去表現我們的動畫。
3,AnimationController
它是Animation<double>的子類,是一個特殊的Animation物件,在螢幕重新整理的每一幀,就會生成一個新的值。預設情況下,AnimationController在給定的時間段內會線性的生成從0.0到1.0的數字。從類名可以知道其意思:動畫控制器,它可以控制動畫的狀態,監聽動畫的執行狀態,監聽動畫過程中產生的值。
當建立一個AnimationController時,需要傳遞一個vsync
引數,引數的作用就是避免動畫的UI不在當前螢幕時消耗不必要的資源。 通過將SingleTickerProviderStateMixin新增到類定義中,可以將stateful物件作為vsync
的值。舉例:當我們手機接到來電,介面就跳轉到接通電話介面,那麼來電之前正在執行的動畫會暫停。
4,Tween
預設情況下,AnimationController物件產生值的範圍是(0.0,1.0)。如果您需要不同的範圍或不同的資料型別,則可以使用Tween來配置動畫以生成不同的範圍或資料型別的值。
概念總結:
- Animation是動畫的核心抽象類,它常用的類物件是Animation<double>。
- CurvedAnimation和AnimationController都是派生自Animation<double>,前者管理動畫的表現形式,後者管理動畫插值,狀態等。
- 如果預設值滿足不了我們需要,此時我們用Tween自定義插值範圍
案例
放大動畫
1,main.dart,在body裡面放了一個執行動畫的元件,主要程式碼在該元件中
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter動畫"),
),
body:Animation1() ,
);
}
}
複製程式碼
2,Animation1.dart元件檔案
import 'package:flutter/material.dart';
///create by:Administrator
///create at:2019-09-28 14:43
///des:
class Animation1 extends StatefulWidget {
@override
_Animation1State createState() => _Animation1State();
}
class _Animation1State extends State<Animation1>
with SingleTickerProviderStateMixin {
Animation animation;
AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 2000));//建立動畫控制器
animation = Tween(begin: 10.0, end: 200.0).animate(animationController); //自定義插值範圍
animation.addListener((){ //設定插值監聽器,通過setState()重新渲染UI
setState(() {
});
});
animationController.forward(); //啟動動畫
}
@override
Widget build(BuildContext context) {
return Center(
child:Container(
width: animation.value,
height: animation.value,
color: Colors.redAccent,
),
);
}
@override
void dispose() {
super.dispose();
animationController.dispose(); //釋放資源
}
}複製程式碼
3,說明:
- 整個動畫效果就是讓Container長寬從10.0放大到200.0,動畫時長為2秒。
- 通過Tween建立了自定義的插值,範圍是(10.0,200.0),也就是Container在動畫執行過程中改變值的範圍。然後通過animate()方法傳入動畫控制器物件animationController,獲取到Animation物件,前面我們說過,這個物件對動畫有控制權,我們需要通過該物件知道動畫的狀態和插值情況
- 通過設定插值監聽器,呼叫setState()方法重新渲染UI。
- 通過animationController(動畫控制器)啟動動畫。
AnimatedWidget簡化放大動畫
- AnimatedWidget類允許您從
setState()
呼叫中的動畫程式碼中分離出widget程式碼。AnimatedWidget不需要維護一個State物件來儲存動畫。 - AnimatedWidget是一個抽象類繼承自StateFulWidget。
1,Animation2.dart元件檔案
import 'package:flutter/material.dart';
///create by:Administrator
///create at:2019-09-28 14:43
///des:
class Animation2 extends StatefulWidget {
@override
_Animation2tate createState() => _Animation2tate();
}
class _Animation2tate extends State<Animation2>
with SingleTickerProviderStateMixin {
Animation animation;
AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 2000));//建立動畫控制器
animation = Tween(begin: 10.0, end: 200.0).animate(animationController); //自定義插值範圍
animationController.forward(); //啟動動畫
}
@override
Widget build(BuildContext context) {
return Center(
child:CustomWidget(animation: animation),
);
}
@override
void dispose() {
super.dispose();
animationController.dispose(); //釋放資源
}
}
//需要執行動畫的元件封裝在這裡
class CustomWidget extends AnimatedWidget{
CustomWidget({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Container(
width: animation.value,
height: animation.value,
color: Colors.redAccent,
);
}
}複製程式碼
2,說明:
我們把動畫元件中的child換成了CustomWidget,去掉了動畫的監聽和狀態的重新整理。該替換元件是繼承自AnimatedWidget的,它接收一個Animation物件。AnimatedWidget(基類)中會自動呼叫addListener()和setState()方法,完成UI的重新整理,此時動畫執行與之前一樣。
為什麼說是簡化了操作呢,我們可以這麼理解,原先我們需要自己監聽動畫執行管理狀態,此時我們只需要定義出動畫規則,把規則交給AnimatedWidget就行了,其他的我們不用管了,讓它自己去完成動畫的狀態管理與UI重新整理。
另外,我們這樣寫的話,達到了動畫複用的效果,從這個層面上來看也可以理解為簡化了動畫。
感覺從整體上面來看,這個簡化有點牽強附會,我暫時只能這樣理解了,讀者有更好的理解可以下方留言,謝謝
AnimatedBuilder 重構動畫
前面兩種動畫實現中,我們的插值都侵入到了元件的內部,看下面程式碼。
return Container(
width: animation.value,
height: animation.value,
color: Colors.redAccent,
);複製程式碼
如果此時我們要更換這個元件怎麼辦,比如我們想把動畫物件(需要執行動畫的元件)換成一張圖片,又或者是其他的,那我們不得不去修改這個動畫物件,是不是很麻煩,而且複用程度很低。那麼我們有沒有什麼辦法把動畫物件,動畫規則都給分離開來,各司其職。此時就用到了AnimatedBuilder。它通過匿名構造器按照動畫插值重新構建UI,自動管理動畫狀態與重新整理。
另外,這種重新整理是區域性的,它只針對於動畫物件做的重新整理,而不是應用到整個頁面,所以我認為這種重新整理比上面兩種節省資源。
Animation3.dart元件檔案
import 'package:flutter/material.dart';
///create by:Administrator
///create at:2019-09-28 17:29
///des:
class Animation3 extends StatefulWidget {
@override
_Animation3State createState() => _Animation3State();
}
class _Animation3State extends State<Animation3> with SingleTickerProviderStateMixin{
Animation animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this); //動畫控制器
animation = new Tween(begin: 0.0, end: 300.0).animate(controller); //自定義插值範圍
controller.forward();//啟動動畫
}
@override
Widget build(BuildContext context) {
return GrowBuild(child: MyContainer(), animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
//通過AnimatedBuilder實現動畫規則與動畫物件分離
class GrowBuild extends StatelessWidget {
GrowBuild({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) {
return new Center(
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) { //匿名構造器根據動畫插值重新構建child,然後重新渲染
return new Container(
height: animation.value, width: animation.value, child: child);
},
child: child), //將重新渲染後的child回顯
);
}
}
//動畫物件
class MyContainer extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Container(
width: 100.0,
height: 200.0,
color: Colors.redAccent,
);
}
}
複製程式碼
結束語:
以上是個人對Flutter動畫基礎的一點見解,不一定全對。如有錯誤,煩請指正。
另外,讀者在有些地方有自己的見解可以留言,互相探討,謝謝。