之前三篇文章的動畫, 我刻意的將各種動畫做成了獨立式的, 即使是略微復雜的動畫, 也分解成了獨立式肯污。 實際使用時颁独, 可能一個動畫會有一系列小的動畫片段組成彩届, 每個動畫之間可能有時間順序關系。
- 可以監(jiān)聽
AnimationController
的狀態(tài)以便啟動下一段動畫誓酒。 - 可以通過設置每個小動畫片段的時間間隔樟蠕, 來控制當前哪一個動畫運作起來。
其中設置小動畫片段的時間間隔靠柑, 關鍵在于Interval
部件的使用坯墨,以及為每個動畫設置一個取值范圍Tween
,使用同一個AnimationController
控制總體的動畫狀態(tài)病往。
先看一個示例動畫捣染。
TimeLineAnimation
動畫分解
從示例中可以看到這個動畫大概可以分解成2個大動作:
- 頭像組件從上部掉下來
- 頭像原地旋轉縮放,該動作又被拆分成了三個小動作停巷, 并且循環(huán)
- 頭像旋轉一圈
- 頭像縮小一點
- 頭像放大一點
代碼
import 'package:flutter/material.dart';
class MyTimeLineAnimationDemo extends StatelessWidget {
buildBackButton(BuildContext context) {
return Positioned(
left: 0.0,
top: 0.0,
right: 0.0,
child: Container(
padding: EdgeInsets.only(top: 32),
alignment: Alignment.topLeft,
child: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
MainPage(),
buildBackButton(context),
IconLayer(),
],
),
);
}
}
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.grey, Colors.black],
),
),
);
}
}
class IconLayer extends StatefulWidget {
@override
_IconLayerState createState() => _IconLayerState();
}
class _IconLayerState extends State<IconLayer> with TickerProviderStateMixin {
AnimationController posController;
Animation<double> posAnimation;
Duration posDuration;
AnimationController nController;
Duration nDuration;
Animation<double> rotationAnimation;
Animation<double> scaleDownAnimation;
Animation<double> scaleUpAnimation;
@override
void initState() {
super.initState();
// 掉落的動畫控制
posDuration = Duration(milliseconds: 300);
posController = AnimationController(vsync: this, duration: posDuration)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
// 稍微延遲一下再啟動后面的動畫耍攘, 免得太突兀了
Future.delayed(Duration(milliseconds: 500), () {
nController.repeat();
});
}
});
posAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: posController, curve: Curves.linearToEaseOut));
//旋轉和縮放的動畫控制
nDuration = Duration(milliseconds: 3000);
nController = AnimationController(vsync: this, duration: nDuration);
rotationAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: nController, curve: Interval(0.0, 0.7)));
scaleDownAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
CurvedAnimation(parent: nController, curve: Interval(0.7, 0.85)));
scaleUpAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
CurvedAnimation(parent: nController, curve: Interval(0.85, 1.0)));
//啟動動畫
posController.forward();
}
@override
void dispose() {
posController?.dispose();
nController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height - 120;
return AnimatedPositioned(
right: 10,
top: height * posAnimation.value,
duration: posDuration,
child: Container(
width: 80,
height: 80,
margin: EdgeInsets.all(8.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 1.5),
),
child: RotationTransition(
turns: rotationAnimation,
child: ScaleTransition(
scale: scaleDownAnimation,
child: ScaleTransition(
scale: scaleUpAnimation,
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/head.jpg'),
),
),
),
),
),
);
}
}
分析
主界面用Stack
串聯(lián)了三部分組成, 分別是
- MainPage畔勤, 就是一個簡單的漸變背景蕾各,
- 回退(back button)按鈕,
- 動畫圖標
第一段掉落的動畫庆揪, 采用的是AnimatedPositioned
組件來實施式曲, AnimatedPositioned
組件只能在Stack
組件中使用。
第二段原地旋轉縮放的動畫缸榛, 可以看到控制器只有一個:nController
吝羞, 但Animation
有三個, 分別是:
- rotationAnimation
- scaleDownAnimation
- scaleUpAnimation
通過Interval部件將三個Animation
分解成了三個時間段内颗, 分別是
- Interval(0.0, 0.7)
- Interval(0.7, 0.85)
- Interval(0.85, 1.0)
意思是在動畫開始到0.7的時間段內旋轉動畫在起作用钧排, 0.7-0.85的時間段內, 縮小均澳, 0.85-結束的時間段內恨溜, 放大。
第一段和第二段動畫之間的銜接找前, 靠的是監(jiān)聽第一段動畫的狀態(tài)糟袁,當監(jiān)聽到第一段動畫完成后, 延遲一小段時間躺盛, 然后第二段動畫重復(repeat)项戴。
if (status == AnimationStatus.completed) {
// 稍微延遲一下再啟動后面的動畫, 免得太突兀了
Future.delayed(Duration(milliseconds: 500), () {
nController.repeat();
});
}