交織動畫
有些時候我們可能會需要一些復(fù)雜的動畫谍失,這些動畫可能由一個動畫序列或重疊的動畫組成,要實現(xiàn)這種效果拄查,使用交織動畫(Stagger Animation)會非常簡單跟畅。要創(chuàng)建交織動畫,需要使用多個動畫對象(Animation
)晌块。一個AnimationController
控制所有的動畫對象爱沟。給每一個動畫對象指定時間間隔。
所有動畫都由同一個AnimationController
驅(qū)動匆背,無論動畫需要持續(xù)多長時間呼伸,控制器的值必須在0.0到1.0之間,而每個動畫的間隔也必須介于0.0和1.0之間钝尸。對于在間隔中設(shè)置動畫的每個屬性括享,需要分別創(chuàng)建一個Tween
用于指定該屬性的開始值和結(jié)束值。也就是說0.0到1.0代表整個動畫過程珍促,也可以給不同動畫指定不同的起始點和終止點來決定它們的開始時間和終止時間铃辖。代碼示例:
class StaggerAnimationDemo extends StatefulWidget {
@override
_StaggerAnimationDemoState createState() => _StaggerAnimationDemoState();
}
class _StaggerAnimationDemoState extends State<StaggerAnimationDemo>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> _opacityAnimation;
Animation<double> _sizeAnimation;
Animation<Color> _colorAnimation;
Animation<double> _radiansAnimation;
@override
void initState() {
super.initState();
// 創(chuàng)建AnimationController
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
// Tween
_opacityAnimation =
CurvedAnimation(parent: _controller, curve: Curves.easeIn);
_sizeAnimation = Tween(
begin: 50.0,
end: 150.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
0.6, // 間隔,前60%的動畫時間
curve: Curves.ease,
),
),
);
_colorAnimation = ColorTween(
begin: Colors.orange,
end: Colors.purple,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.0,
0.6, // 間隔猪叙,前60%的動畫時間
curve: Curves.ease,
),
),
);
_radiansAnimation = Tween(begin: 0.0, end: 2 * pi).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
0.6,
1.0, //間隔娇斩,后40%的動畫時間
curve: Curves.ease,
),
),
);
// 監(jiān)聽動畫狀態(tài)改變
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('StaggerAnimation Demo'),
),
body: Center(
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Opacity(
opacity: _opacityAnimation.value ?? 0.0,
child: Transform(
transform: Matrix4.rotationZ(_radiansAnimation.value),
alignment: Alignment.center,
child: Container(
width: _sizeAnimation.value,
height: _sizeAnimation.value,
color: _colorAnimation.value,
),
),
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () {
if (_controller.isAnimating) {
_controller.stop();
print(_controller.status);
} else if (_controller.status == AnimationStatus.forward) {
_controller.forward();
} else if (_controller.status == AnimationStatus.reverse) {
_controller.reverse();
} else {
_controller.forward();
}
},
),
);
}
@override
void dispose() {
// 釋放動畫資源
_controller.dispose();
super.dispose();
}
}
Hero動畫
移動端開發(fā)會經(jīng)常遇到類似這樣的需求:點擊一個頭像,顯示頭像的大圖穴翩,并且從原來圖像的Rect到大圖的Rect犬第。點擊一個商品的圖片,可以展示商品的大圖芒帕,并且從原來圖像的Rect到大圖的Rect瓶殃。這種跨頁面共享的動畫被稱之為享元動畫(Shared Element Transition)。
在Flutter中副签,有一個專門的Widget可以來實現(xiàn)這種動畫效果:Hero
,實現(xiàn)Hero
動畫基矮,需要如下步驟:
1.在第一個Page1中淆储,定義一個起始的Hero Widget,被稱之為source hero家浇,并且綁定一個tag本砰;
2.在第二個Page2中,定義一個終點的Hero Widget钢悲,被稱之為destination hero点额,并且綁定相同的tag舔株;
3.可以通過Navigator來實現(xiàn)第一個頁面Page1到第二個頁面Page2的跳轉(zhuǎn)過程;
Flutter會設(shè)置Tween
來界定Hero
從起點到終端的大小和位置还棱,并且在圖層上執(zhí)行動畫效果载慈。代碼示例:
class HeroAnimationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 16 / 9,
),
children: List.generate(20, (index) {
final imageURL = "https://picsum.photos/500/500?random=$index";
return GestureDetector(
onTap: () {
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: ImageDetailPage(imageURL),
);
}),
);
},
child: Hero(
tag: imageURL,
child: Image.network(
imageURL,
fit: BoxFit.cover,
)),
);
}),
),
);
}
}
class ImageDetailPage extends StatelessWidget {
final String _imageURL;
ImageDetailPage(this._imageURL);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: GestureDetector(
onTap: () {
Navigator.of(context).pop();
},
child: Hero(
tag: _imageURL,
child: Image.network(_imageURL),
),
),
),
);
}
}