老孟導讀:此篇文章是 Flutter 動畫系列文章第四篇,本文介紹動畫序列、共享動畫、路由動畫。
動畫序列
Flutter中組合動畫使用Interval
,Interval
繼承自Curve
,用法如下:
Animation _sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
parent: _animationController, curve: Interval(0.5, 1.0)));
表示_sizeAnimation
動畫從0.5(一半)開始到結束,如果動畫時長為6秒,_sizeAnimation
則從第3秒開始。
Interval
中begin
和end
參數值的范圍是0.0到1.0。
下面實現一個先執行顏色變化,在執行大小變化,代碼如下:
class AnimationDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() => _AnimationDemo();
}
class _AnimationDemo extends State<AnimationDemo>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation _colorAnimation;
Animation _sizeAnimation;
@override
void initState() {
_animationController =
AnimationController(duration: Duration(seconds: 5), vsync: this)
..addListener((){setState(() {
});});
_colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(
CurvedAnimation(
parent: _animationController, curve: Interval(0.0, 0.5)));
_sizeAnimation = Tween(begin: 100.0, end: 300.0).animate(CurvedAnimation(
parent: _animationController, curve: Interval(0.5, 1.0)));
//開始動畫
_animationController.forward();
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
height: _sizeAnimation.value,
width: _sizeAnimation.value,
color: _colorAnimation.value),
],
),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
效果如下:
我們也可以設置同時動畫,只需將2個Interval
的值都改為Interval(0.0, 1.0)
。
想象下面的場景,一個紅色的盒子,動畫時長為6秒,前40%的時間大小從100->200,然后保持200不變20%的時間,最后40%的時間大小從200->300,這種效果通過TweenSequence實現,代碼如下:
_animation = TweenSequence([
TweenSequenceItem(
tween: Tween(begin: 100.0, end: 200.0)
.chain(CurveTween(curve: Curves.easeIn)),
weight: 40),
TweenSequenceItem(tween: ConstantTween<double>(200.0), weight: 20),
TweenSequenceItem(tween: Tween(begin: 200.0, end: 300.0), weight: 40),
]).animate(_animationController);
weight
表示每一個Tween的權重。
最終效果如下:
共享動畫
Hero是我們常用的過渡動畫,當用戶點擊一張圖片,切換到另一個頁面時,這個頁面也有此圖,那么使用Hero組件就在合適不過了,先看下Hero的效果圖:
上面效果實現的列表頁面代碼如下:
class HeroDemo extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HeroDemo();
}
class _HeroDemo extends State<HeroDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 5, mainAxisSpacing: 3),
children: List.generate(10, (index) {
if (index == 6) {
return InkWell(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new _Hero1Demo()));
},
child: Hero(
tag: 'hero',
child: Container(
child: Image.asset(
'images/bird.png',
fit: BoxFit.fitWidth,
),
),
),
);
}
return Container(
color: Colors.red,
);
}),
),
);
}
}
第二個頁面代碼如下:
class _Hero1Demo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
alignment: Alignment.topCenter,
child: Hero(
tag: 'hero',
child: Container(
child: Image.asset(
'images/bird.png',
),
),
)),
);
}
}
2個頁面都有Hero控件,且tag
參數一致。
路由動畫
轉場 就是從當前頁面跳轉到另一個頁面,跳轉頁面在 Flutter 中通過 Navigator,跳轉到新頁面如下:
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _TwoPage();
}));
回退到前一個頁面:
Navigator.pop(context);
Flutter 提供了兩個轉場動畫,分別為 MaterialPageRoute 和 CupertinoPageRoute,MaterialPageRoute 根據不同的平台顯示不同的效果,Android效果為從下到上,iOS效果為從左到右。CupertinoPageRoute 不分平台,都是從左到右。
使用 MaterialPageRoute 案例如下:
class NavigationAnimation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: OutlineButton(
child: Text('跳轉'),
onPressed: () {
Navigator.push(context, CupertinoPageRoute(builder: (context) {
return _TwoPage();
}));
},
),
),
);
}
}
class _TwoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.blue,
),
);
}
}
iOS效果:
如果要自定義轉場動畫如何做?
自定義任何組件都是一樣的,如果系統有類似的,直接看源代碼是如何實現的,然后按照它的模版自定義組件。
回到正題,看 MaterialPageRoute 的繼承關系:
PageRoute 的繼承關系:
MaterialPageRoute 和 CupertinoPageRoute 都是繼承PageRoute,所以重點是 PageRoute,PageRoute 是一個抽象類,其子類還有一個 PageRouteBuilder,看其名字就知道這是一個可以自定義動畫效果,PageRouteBuilder源代碼:
pageBuilder
表示跳轉的頁面。
transitionsBuilder
表示頁面的動畫效果,默認值代碼:
Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return child;
}
通過源代碼發現,默認情況下沒有動畫效果。
自定義轉場動畫只需修改transitionsBuilder
即可:
Navigator.push(
context,
PageRouteBuilder(pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return _TwoPage();
}, transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: Tween(begin: Offset(-1, 0), end: Offset(0, 0))
.animate(animation),
child: child,
);
}));
將其封裝,方便使用:
class LeftToRightPageRoute extends PageRouteBuilder {
final Widget newPage;
LeftToRightPageRoute(this.newPage)
: super(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
newPage,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
SlideTransition(
position: Tween(begin: Offset(-1, 0), end: Offset(0, 0))
.animate(animation),
child: child,
),
);
}
使用:
Navigator.push(context, LeftToRightPageRoute(_TwoPage()));
不僅是這些平移動畫,前面所學的旋轉、縮放等動畫直接替換 SlideTransition 即可。
上面的動畫只對新的頁面進行了動畫,如果想實現當前頁面被新頁面從頂部頂出的效果,實現方式如下:
class CustomPageRoute extends PageRouteBuilder {
final Widget currentPage;
final Widget newPage;
CustomPageRoute(this.currentPage, this.newPage)
: super(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) =>
currentPage,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
Stack(
children: <Widget>[
SlideTransition(
position: new Tween<Offset>(
begin: const Offset(0, 0),
end: const Offset(0, -1),
).animate(animation),
child: currentPage,
),
SlideTransition(
position: new Tween<Offset>(
begin: const Offset(0, 1),
end: Offset(0, 0),
).animate(animation),
child: newPage,
)
],
),
);
}
本質就是對兩個頁面做動畫處理,使用:
Navigator.push(context, CustomPageRoute(this, _TwoPage()));
除了自定義路由動畫,在 Flutter 1.17 發布大會上,Flutter 團隊還發布了新的 Animations 軟件包,該軟件包提供了實現新的 Material motion 規范的預構建動畫。
里面提供了一系列動畫,部分效果:
詳情:https://juejin.im/post/6847902223909781511
交流
老孟Flutter博客地址(330個控件用法):http://laomengit.com
歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:
![]() |
![]() |