AnimatedWidget簡化了什麼?
首先看一段不使用AnimatedWidget的程式碼:
給animation新增了Listener,動畫執行的每一幀都會回撥這個Listener,在這個Listener回撥中,呼叫setState()
來完成widget的更新(重新整理)。
class ScaleAnimationRoute extends StatefulWidget {
@override
_ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}
//需要繼承TickerProvider,如果有多個AnimationController,則應該使用TickerProviderStateMixin。
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute> with SingleTickerProviderStateMixin{
Animation<double> animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(seconds: 3), vsync: this);
//圖片寬高從0變到300
animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(()=>{});
});
//啟動動畫(正向執行)
controller.forward();
}
@override
Widget build(BuildContext context) {
return new Center(
child: Image.asset("imgs/avatar.png",
width: animation.value,
height: animation.value
),
);
}
dispose() {
//路由銷燬時需要釋放動畫資源
controller.dispose();
super.dispose();
}
}
複製程式碼
這樣是不是很麻煩,還需要手動呼叫setState()
那麼AnimatedWidget就是省略了setState()的步驟,AnimatedWidget
類封裝了呼叫setState()
的細節,來看下面的程式碼:
class AnimatedImage extends AnimatedWidget {
AnimatedImage({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: Image.asset("imgs/avatar.png",
width: animation.value,
height: animation.value
),
);
}
}
class ScaleAnimationRoute1 extends StatefulWidget {
@override
_ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(seconds: 3), vsync: this);
//圖片寬高從0變到300
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
//啟動動畫
controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedImage(animation: animation,);
}
dispose() {
//路由銷燬時需要釋放動畫資源
controller.dispose();
super.dispose();
}
}
複製程式碼
可以看到不再需要新增Listener並手動呼叫setState()
方法了。AnimatedWidget
自己會使用當前 Animation
的 value
來繪製自己。
AnimatedBuilder是幹嘛的?
我們可以看到上面的AnimatedImage
, 用AnimatedWidget
可以從動畫中分離出widget
,而動畫的渲染過程(即設定寬高)仍然在AnimatedWidget
中。也就是animation.value仍然設定在Image的width,height屬性中。
class AnimatedImage extends AnimatedWidget {
AnimatedImage({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: Image.asset("imgs/avatar.png",
width: animation.value,
height: animation.value
),
);
}
}
複製程式碼
而AnimatedBuilder
是在AnimatedWidget
的基礎上將顯示內容和動畫拆分開來,更加方便的為特定的顯示內容新增具體的動畫。看下嘛的例子:Image
和Animation
就完美的分開了。
class GrowTransition extends StatelessWidget {
GrowTransition({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) {
return new Container(
height: animation.value,
width: animation.value,
child: child
);
},
child: child
),
);
}
}
...
Widget build(BuildContext context) {
return GrowTransition(
child: Image.asset("images/avatar.png"),
animation: animation,
);
}
複製程式碼
AnimatedWidget和AnimatedBuilder就完美了嗎?
可以看到上面的程式碼,AnimationController依然暴露在外面,需要呼叫controller.forward()
執行動畫,能不能把AnimationController封裝到動畫widget的內部,答案是肯定的,直接看程式碼:
class AnimatedDecoratedExampleBox extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimatedDecoratedExampleBoxState();
}
}
class _AnimatedDecoratedExampleBoxState
extends State<AnimatedDecoratedExampleBox> {
Color _bgColor = Colors.blue;
var duration = Duration(seconds: 1);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
title: Text("AnimatedSwitcher"),
),
body: Center(
child: AnimatedDecoratedBox(
duration: duration,
decoration: BoxDecoration(color: _bgColor),
child: FlatButton(
onPressed: () {
setState(() {
_bgColor = _bgColor == Colors.blue
? Colors.red
: Colors.blue;
});
},
child: Text(
"AnimatedDecoratedBox",
style: TextStyle(color: Colors.white),
),
),
))));
}
}
class AnimatedDecoratedBox extends StatefulWidget {
AnimatedDecoratedBox({
Key key,
@required this.decoration,
this.child,
this.curve = Curves.linear,
@required this.duration,
this.reverseDuration,
});
final BoxDecoration decoration;
final Widget child;
final Duration duration;
final Curve curve;
final Duration reverseDuration;
@override
_AnimatedDecoratedBoxState createState() => _AnimatedDecoratedBoxState();
}
class _AnimatedDecoratedBoxState extends State<AnimatedDecoratedBox>
with SingleTickerProviderStateMixin {
@protected
AnimationController get controller => _controller;
AnimationController _controller;
Animation<double> get animation => _animation;
Animation<double> _animation;
DecorationTween _tween;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return DecoratedBox(
decoration: _tween.animate(_animation).value,
child: child,
);
},
child: widget.child,
);
}
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
reverseDuration: widget.reverseDuration,
vsync: this,
);
_tween = DecorationTween(begin: widget.decoration);
_updateCurve();
}
void _updateCurve() {
if (widget.curve != null)
_animation = CurvedAnimation(parent: _controller, curve: widget.curve);
else
_animation = _controller;
}
@override
void didUpdateWidget(AnimatedDecoratedBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.curve != oldWidget.curve) _updateCurve();
_controller.duration = widget.duration;
_controller.reverseDuration = widget.reverseDuration;
if (widget.decoration != (_tween.end ?? _tween.begin)) {
_tween
..begin = _tween.evaluate(_animation)
..end = widget.decoration;
_controller
..value = 0.0
..forward();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
複製程式碼
封裝了AnimatedDecoratedBox
這樣一個動畫元件,在內部管理AnimationController
,那它的動畫是何時執行的呢?
AnimatedDecoratedBox(
duration: duration,
decoration: BoxDecoration(color: _decorationColor),
child: FlatButton(
onPressed: () {
setState(() {
_decorationColor = Colors.red;
});
},
child: Text(
"AnimatedDecoratedBox",
style: TextStyle(color: Colors.white),
),
),
)
複製程式碼
外部,點選按鈕時呼叫了setState()
方法,那麼AnimatedDecoratedBox
內部的didUpdateWidget()
方法就會被呼叫,在這個方法裡面判斷新舊屬性不一樣,就執行動畫。
@override
void didUpdateWidget(AnimatedDecoratedBox1 oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.curve != oldWidget.curve)
_updateCurve();
_controller.duration = widget.duration;
_controller.reverseDuration = widget.reverseDuration;
if(widget.decoration!= (_tween.end ?? _tween.begin)){
_tween
..begin = _tween.evaluate(_animation)
..end = widget.decoration;
_controller
..value = 0.0
..forward();
}
}
複製程式碼
關於didUpdateWidget()
何時執行:
didUpdateWidget()
:在widget重新構建時,Flutter framework會呼叫Widget.canUpdate
來檢測Widget樹中同一位置的新舊節點,然後決定是否需要更新,如果Widget.canUpdate
返回true
則會呼叫此回撥。正如之前所述,Widget.canUpdate
會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()
就會被呼叫。如果key或runtime不一樣,整個widget state都會重構,initState()方法會重新執行。詳見我之前的文章:juejin.cn/post/684490…
上面是在didUpdateWidget()
方法中進行手動控制的,flutter提供了兩個類簡化了這種控制:
ImplicitlyAnimatedWidget
和ImplicitlyAnimatedWidgetState
,詳見:book.flutterchina.club/chapter9/an…
AnimatedWidget,AnimatedBuilder同時執行多個動畫
同時執行大小和顏色透明度的動畫
class AnimatedImage extends AnimatedWidget {
AnimatedImage({Key key, Animation<double> animation,this.animation_1, this.animation_2})
: super(key: key, listenable: animation);
final Animation<double> animation_1;
final Animation<double> animation_2;
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: Container(
width: animation_1.value,
height: animation_1.value,
color: Colors.orange.withOpacity(animation_2.value),
),
);
}
}
class ScaleAnimationRoute1 extends StatefulWidget {
@override
_ScaleAnimationRouteState createState() => new _ScaleAnimationRouteState();
}
class _ScaleAnimationRouteState extends State<ScaleAnimationRoute1>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
Animation<double> animation_1;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(seconds: 3), vsync: this);
//圖片寬高從0變到300
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
animation_1 = new Tween(begin: 0.0, end: 1.0).animate(controller);
//啟動動畫
controller.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedImage(animation: controller, animation_1:animation, animation_2: animation_1);
}
dispose() {
//路由銷燬時需要釋放動畫資源
controller.dispose();
super.dispose();
}
}
複製程式碼
交織動畫
有些時候我們可能會需要一些複雜的動畫,這些動畫可能由一個動畫序列或重疊的動畫組成,比如:有一個柱狀圖,需要在高度增長的同時改變顏色,等到增長到最大高度後,我們需要在X軸上平移一段距離。可以發現上述場景在不同階段包含了多種動畫,要實現這種效果,使用交織動畫(Stagger Animation)
待續
通用“動畫切換”元件(AnimatedSwitcher)
待續
自定義路由切換動畫
待續
Hero動畫
待續