在Widget屬性發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)的組件統(tǒng)稱(chēng)為”動(dòng)畫(huà)過(guò)渡組件“
動(dòng)畫(huà)過(guò)渡組件最明顯的一個(gè)特征就是它會(huì)在內(nèi)部自管理AnimationController
我們知道兔毙,為了方便使用者可以自定義動(dòng)畫(huà)的曲線(xiàn)壮池、執(zhí)行時(shí)長(zhǎng)狠持、方向等匹摇,在前面介紹過(guò)的動(dòng)畫(huà)封裝方法中婆跑,通常都需要使用者自己提供一個(gè)AnimationController對(duì)象來(lái)自定義這些屬性值吧享。但是魏割,如此一來(lái),使用者就必須得手動(dòng)管理AnimationController钢颂,這又會(huì)增加使用的復(fù)雜性钞它。因此,如果也能將AnimationController進(jìn)行封裝殊鞭,則會(huì)大大提高動(dòng)畫(huà)組件的易用性遭垛。
1. Flutter預(yù)置的動(dòng)畫(huà)過(guò)渡組件
組件名 | 功能 |
---|---|
AnimatedPadding | 在padding發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新?tīng)顟B(tài) |
AnimatedPositioned | 配合Stack一起使用,當(dāng)定位狀態(tài)發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新的狀態(tài)操灿。 |
AnimatedOpacity | 在透明度opacity發(fā)生變化時(shí)執(zhí)行過(guò)渡動(dòng)畫(huà)到新?tīng)顟B(tài) |
AnimatedAlign | 當(dāng)alignment發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新的狀態(tài)锯仪。 |
AnimatedContainer | 當(dāng)Container屬性發(fā)生變化時(shí)會(huì)執(zhí)行過(guò)渡動(dòng)畫(huà)到新的狀態(tài)。 |
AnimatedDefaultTextStyle | 當(dāng)字體樣式發(fā)生變化時(shí)趾盐,子組件中繼承了該樣式的文本組件會(huì)動(dòng)態(tài)過(guò)渡到新樣式卵酪。 |
1.1 AnimatedPadding
class MSAnimatedPaddingDemo extends StatefulWidget {
const MSAnimatedPaddingDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedPaddingDemo> createState() => _MSAnimatedPaddingDemoState();
}
class _MSAnimatedPaddingDemoState extends State<MSAnimatedPaddingDemo> {
double _padding = 10;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: ElevatedButton(
child: AnimatedPadding(
padding: EdgeInsets.all(_padding),
duration: Duration(milliseconds: 500),
child: Text(
"AnimatedPadding",
textScaleFactor: 1.5,
),
),
onPressed: () {
setState(() {
if (_padding == 20) {
_padding = 10;
} else {
_padding = 20;
}
});
},
),
),
);
}
}
1.2 AnimatedPositioned
class MSAnimatedPositionedDemo extends StatefulWidget {
const MSAnimatedPositionedDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedPositionedDemo> createState() =>
_MSAnimatedPositionedDemoState();
}
class _MSAnimatedPositionedDemoState extends State<MSAnimatedPositionedDemo> {
double _posValue = 50;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
AnimatedPositioned(
duration: Duration(milliseconds: 500),
top: _posValue,
child: ElevatedButton(
onPressed: () {
setState(() {
if (_posValue == 100) {
_posValue = 50;
} else {
_posValue = 100;
}
});
},
child: Text(
"AnimatedPositioned",
textScaleFactor: 1.5,
)),
),
],
),
);
}
}
1.3 AnimatedOpacity
class MSAnimatedOpacityDemo extends StatefulWidget {
const MSAnimatedOpacityDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedOpacityDemo> createState() => _MSAnimatedOpacityDemoState();
}
class _MSAnimatedOpacityDemoState extends State<MSAnimatedOpacityDemo> {
double _opacity = 0.5;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: AnimatedOpacity(
opacity: _opacity,
duration: Duration(milliseconds: 500),
child: Text(
"AnimatedOpacity",
textScaleFactor: 1.5,
),
),
onPressed: () {
setState(() {
if (_opacity == 0.5) {
_opacity = 0.8;
} else {
_opacity = 0.5;
}
});
},
),
),
);
}
}
1.4 AnimatedAlign
class MSAnimatedAlignDemo extends StatefulWidget {
const MSAnimatedAlignDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedAlignDemo> createState() => _MSAnimatedAlignDemoState();
}
class _MSAnimatedAlignDemoState extends State<MSAnimatedAlignDemo> {
double _alignmentValue = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedAlign(
duration: Duration(milliseconds: 500),
alignment: Alignment(_alignmentValue, _alignmentValue),
child: ElevatedButton(
child: Text(
"AnimatedAlign",
textScaleFactor: 1.5,
),
onPressed: () {
setState(() {
if (_alignmentValue == 0) {
_alignmentValue = 0.8;
} else {
_alignmentValue = 0;
}
});
},
),
),
),
);
}
}
5.5 AnimatedContainer
class MSAnimatedContainerDemo extends StatefulWidget {
const MSAnimatedContainerDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedContainerDemo> createState() =>
_MSAnimatedContainerDemoState();
}
class _MSAnimatedContainerDemoState extends State<MSAnimatedContainerDemo> {
double _sizeValue = 200;
double _paddingValue = 0;
double _alignmentValue = 0;
Color _bgColor = Colors.red;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
color: _bgColor,
width: _sizeValue,
height: _sizeValue,
alignment: Alignment(_alignmentValue, _alignmentValue),
padding: EdgeInsets.all(_paddingValue),
child: ElevatedButton(
child: Text("AnimatedContainer"),
onPressed: () {
setState(() {
_sizeValue = _sizeValue == 200 ? 350 : 200;
_paddingValue = _paddingValue == 0 ? 20 : 0;
_alignmentValue = _alignmentValue == 0 ? -1 : 0;
_bgColor = _bgColor == Colors.red ? Colors.yellow : Colors.red;
});
},
),
),
),
);
}
}
5.6 AnimatedDefaultTextStyle
class MSAnimatedDefaultTextStyleDemo extends StatefulWidget {
const MSAnimatedDefaultTextStyleDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedDefaultTextStyleDemo> createState() =>
_MSAnimatedDefaultTextStyleDemoState();
}
class _MSAnimatedDefaultTextStyleDemoState
extends State<MSAnimatedDefaultTextStyleDemo> {
TextStyle _textStyle =
TextStyle(fontSize: 20, fontWeight: FontWeight.w300, color: Colors.amber);
bool isflag = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
child: AnimatedDefaultTextStyle(
duration: Duration(milliseconds: 500),
style: _textStyle,
child: Text("AnimatedDefaultTextStyle"),
),
onTap: () {
setState(() {
if (isflag) {
_textStyle = TextStyle(
fontSize: 25,
fontWeight: FontWeight.w700,
color: Colors.red,
);
isflag = false;
} else {
_textStyle = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w300,
color: Colors.amber,
);
isflag = true;
}
});
},
),
),
);
}
}
2. 自定義動(dòng)畫(huà)過(guò)渡組件
要實(shí)現(xiàn)一個(gè)AnimatedDecoratedBox,它可以在decoration屬性發(fā)生變化時(shí)谤碳,從舊狀態(tài)變成新?tīng)顟B(tài)的過(guò)程可以執(zhí)行一個(gè)過(guò)渡動(dòng)畫(huà)
MSAnimatedDecoratedBox
// MSAnimatedDecoratedBox
class MSAnimatedDecoratedBox extends StatefulWidget {
const MSAnimatedDecoratedBox(
{Key? key,
required this.decoration,
required this.child,
required this.duration,
required this.curve,
this.reverseDuration})
: super(key: key);
final BoxDecoration decoration;
final Widget child;
final Duration duration;
final Curve curve;
final Duration? reverseDuration;
@override
State<MSAnimatedDecoratedBox> createState() => _MSAnimatedDecoratedBoxState();
}
class _MSAnimatedDecoratedBoxState extends State<MSAnimatedDecoratedBox>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
late DecorationTween _tween;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: widget.duration,
reverseDuration: widget.reverseDuration,
);
_tween = DecorationTween(begin: widget.decoration);
_updateCurve();
super.initState();
}
void _updateCurve() {
_animation = CurvedAnimation(parent: _controller, curve: widget.curve);
}
@override
void didUpdateWidget(covariant MSAnimatedDecoratedBox 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
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return DecoratedBox(
decoration: _tween.animate(_animation).value, child: child);
},
child: widget.child,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
使用MSAnimatedDecoratedBox來(lái)實(shí)現(xiàn)按鈕點(diǎn)擊后背景色從藍(lán)色過(guò)渡到紅色的效果
...
MSAnimatedDecoratedBox(
child: TextButton(
child: Text(
"AnimatedBecoratedBox",
textScaleFactor: 1.5,
),
onPressed: () {
setState(() {
_decorationColor =
_decorationColor == Colors.red ? Colors.amber : Colors.red;
});
},
),
decoration: BoxDecoration(color: _decorationColor),
duration: Duration(milliseconds: 500),
curve: Curves.linear,
),
...
完整代碼
// MSAnimatedBecoratedBoxDemo
class MSAnimatedBecoratedBoxDemo extends StatefulWidget {
const MSAnimatedBecoratedBoxDemo({Key? key}) : super(key: key);
@override
State<MSAnimatedBecoratedBoxDemo> createState() =>
_MSAnimatedBecoratedBoxDemoState();
}
class _MSAnimatedBecoratedBoxDemoState
extends State<MSAnimatedBecoratedBoxDemo> {
bool isflag = false;
var _decorationColor = Colors.blue;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MSAnimatedDecoratedBox(
child: TextButton(
child: Text(
"AnimatedBecoratedBox",
textScaleFactor: 1.5,
style: TextStyle(color: Colors.white),
),
onPressed: () {
setState(() {
_decorationColor = isflag ? Colors.blue : Colors.red;
isflag = !isflag;
});
},
),
decoration: BoxDecoration(color: _decorationColor),
duration: Duration(seconds: 2),
curve: Curves.linear,
),
),
);
}
}
// MSAnimatedDecoratedBox
class MSAnimatedDecoratedBox extends StatefulWidget {
const MSAnimatedDecoratedBox(
{Key? key,
required this.decoration,
required this.child,
required this.duration,
required this.curve,
this.reverseDuration})
: super(key: key);
final BoxDecoration decoration;
final Widget child;
final Duration duration;
final Curve curve;
final Duration? reverseDuration;
@override
State<MSAnimatedDecoratedBox> createState() => _MSAnimatedDecoratedBoxState();
}
class _MSAnimatedDecoratedBoxState extends State<MSAnimatedDecoratedBox>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
late DecorationTween _tween;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: widget.duration,
reverseDuration: widget.reverseDuration,
);
_tween = DecorationTween(begin: widget.decoration);
_updateCurve();
super.initState();
}
void _updateCurve() {
_animation = CurvedAnimation(parent: _controller, curve: widget.curve);
}
@override
void didUpdateWidget(covariant MSAnimatedDecoratedBox 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
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return DecoratedBox(
decoration: _tween.animate(_animation).value, child: child);
},
child: widget.child,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
上面的代碼雖然實(shí)現(xiàn)了我們期望的功能溃卡,但是代碼卻比較復(fù)雜。稍加思考后蜒简,我們就可以發(fā)現(xiàn)瘸羡,AnimationController的管理以及Tween更新部分的代碼都是可以抽象出來(lái)的,如果我們這些通用邏輯封裝成基類(lèi)搓茬,那么要實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡組件只需要繼承這些基類(lèi)犹赖,然后定制自身不同的代碼(比如動(dòng)畫(huà)每一幀的構(gòu)建方法)即可队他,這樣將會(huì)簡(jiǎn)化代碼。
為了方便開(kāi)發(fā)者來(lái)實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡組件的封裝峻村,F(xiàn)lutter提供了一個(gè)ImplicitlyAnimatedWidget抽象類(lèi)麸折,它繼承自StatefulWidget,同時(shí)提供了一個(gè)對(duì)應(yīng)的ImplicitlyAnimatedWidgetState類(lèi)粘昨,AnimationController的管理就在ImplicitlyAnimatedWidgetState類(lèi)中垢啼。開(kāi)發(fā)者如果要封裝動(dòng)畫(huà),只需要分別繼承ImplicitlyAnimatedWidget和ImplicitlyAnimatedWidgetState類(lèi)即可
需要分兩步實(shí)現(xiàn):
1.繼承ImplicitlyAnimatedWidget類(lèi)张肾。
class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
const AnimatedDecoratedBox({
Key? key,
required this.decoration,
required this.child,
Curve curve = Curves.linear,
required Duration duration,
}) : super(
key: key,
curve: curve,
duration: duration,
);
final BoxDecoration decoration;
final Widget child;
@override
_AnimatedDecoratedBoxState createState() {
return _AnimatedDecoratedBoxState();
}
}
其中curve芭析、duration、reverseDuration三個(gè)屬性在ImplicitlyAnimatedWidget中已定義吞瞪。 可以看到AnimatedDecoratedBox類(lèi)和普通繼承自StatefulWidget的類(lèi)沒(méi)有什么不同馁启。
- State類(lèi)繼承自AnimatedWidgetBaseState(該類(lèi)繼承自ImplicitlyAnimatedWidgetState類(lèi))。
class _AnimatedDecoratedBoxState
extends AnimatedWidgetBaseState<AnimatedDecoratedBox> {
late DecorationTween _decoration;
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: _decoration.evaluate(animation),
child: widget.child,
);
}
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_decoration = visitor(
_decoration,
widget.decoration,
(value) => DecorationTween(begin: value),
) as DecorationTween;
}
}
可以看到我們實(shí)現(xiàn)了build和forEachTween兩個(gè)方法芍秆。在動(dòng)畫(huà)執(zhí)行過(guò)程中惯疙,每一幀都會(huì)調(diào)用build方法(調(diào)用邏輯在ImplicitlyAnimatedWidgetState中),所以在build方法中我們需要構(gòu)建每一幀的DecoratedBox狀態(tài)妖啥,因此得算出每一幀的decoration 狀態(tài)螟碎,這個(gè)我們可以通過(guò)_decoration.evaluate(animation) 來(lái)算出,其中animation是ImplicitlyAnimatedWidgetState基類(lèi)中定義的對(duì)象迹栓,_decoration是我們自定義的一個(gè)DecorationTween類(lèi)型的對(duì)象。
那么現(xiàn)在的問(wèn)題就是它是在什么時(shí)候被賦值的呢俭缓?要回答這個(gè)問(wèn)題克伊,我們就得搞清楚什么時(shí)候需要對(duì)_decoration賦值。我們知道_decoration是一個(gè)Tween华坦,而Tween的主要職責(zé)就是定義動(dòng)畫(huà)的起始狀態(tài)(begin)和終止?fàn)顟B(tài)(end)愿吹。對(duì)于AnimatedDecoratedBox來(lái)說(shuō),decoration的終止?fàn)顟B(tài)就是用戶(hù)傳給它的值惜姐,而起始狀態(tài)是不確定的犁跪,有以下兩種情況:
- AnimatedDecoratedBox首次build,此時(shí)直接將其decoration值置為起始狀態(tài)歹袁,即_decoration值為DecorationTween(begin: decoration) 坷衍。
- AnimatedDecoratedBox的decoration更新時(shí),則起始狀態(tài)為_(kāi)decoration.animate(animation)条舔,即_decoration值為DecorationTween(begin: _decoration.animate(animation)枫耳,end:decoration)。
現(xiàn)在forEachTween的作用就很明顯了孟抗,它正是用于來(lái)更新Tween的初始值的迁杨,在上述兩種情況下會(huì)被調(diào)用钻心,而開(kāi)發(fā)者只需重寫(xiě)此方法,并在此方法中更新Tween的起始狀態(tài)值即可铅协。而一些更新的邏輯被屏蔽在了visitor回調(diào)捷沸,我們只需要調(diào)用它并給它傳遞正確的參數(shù)即可,visitor方法簽名如下
Tween<T> visitor(
Tween<T> tween, //當(dāng)前的tween狐史,第一次調(diào)用為null
T targetValue, // 終止?fàn)顟B(tài)
TweenConstructor<T> constructor痒给,//Tween構(gòu)造器,在上述三種情況下會(huì)被調(diào)用以更新tween
);
可以看到预皇,通過(guò)繼承ImplicitlyAnimatedWidget和ImplicitlyAnimatedWidgetState類(lèi)可以快速的實(shí)現(xiàn)動(dòng)畫(huà)過(guò)渡組件的封裝侈玄,這和我們純手工實(shí)現(xiàn)相比,代碼簡(jiǎn)化了很多
https://book.flutterchina.club/chapter9/animated_widgets.html