在Widget属性发生变化时会执行过渡动画的组件统称为”动画过渡组件“
动画过渡组件最明显的一个特征就是它会在内部自管理AnimationController
我们知道,为了方便使用者可以自定义动画的曲线、执行时长、方向等,在前面介绍过的动画封装方法中,通常都需要使用者自己提供一个AnimationController对象来自定义这些属性值。但是,如此一来,使用者就必须得手动管理AnimationController,这又会增加使用的复杂性。因此,如果也能将AnimationController进行封装,则会大大提高动画组件的易用性。
1. Flutter预置的动画过渡组件
组件名 | 功能 |
---|---|
AnimatedPadding | 在padding发生变化时会执行过渡动画到新状态 |
AnimatedPositioned | 配合Stack一起使用,当定位状态发生变化时会执行过渡动画到新的状态。 |
AnimatedOpacity | 在透明度opacity发生变化时执行过渡动画到新状态 |
AnimatedAlign | 当alignment发生变化时会执行过渡动画到新的状态。 |
AnimatedContainer | 当Container属性发生变化时会执行过渡动画到新的状态。 |
AnimatedDefaultTextStyle | 当字体样式发生变化时,子组件中继承了该样式的文本组件会动态过渡到新样式。 |
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. 自定义动画过渡组件
要实现一个AnimatedDecoratedBox,它可以在decoration属性发生变化时,从旧状态变成新状态的过程可以执行一个过渡动画
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来实现按钮点击后背景色从蓝色过渡到红色的效果
...
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();
}
}
上面的代码虽然实现了我们期望的功能,但是代码却比较复杂。稍加思考后,我们就可以发现,AnimationController的管理以及Tween更新部分的代码都是可以抽象出来的,如果我们这些通用逻辑封装成基类,那么要实现动画过渡组件只需要继承这些基类,然后定制自身不同的代码(比如动画每一帧的构建方法)即可,这样将会简化代码。
为了方便开发者来实现动画过渡组件的封装,Flutter提供了一个ImplicitlyAnimatedWidget抽象类,它继承自StatefulWidget,同时提供了一个对应的ImplicitlyAnimatedWidgetState类,AnimationController的管理就在ImplicitlyAnimatedWidgetState类中。开发者如果要封装动画,只需要分别继承ImplicitlyAnimatedWidget和ImplicitlyAnimatedWidgetState类即可
需要分两步实现:
1.继承ImplicitlyAnimatedWidget类。
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三个属性在ImplicitlyAnimatedWidget中已定义。 可以看到AnimatedDecoratedBox类和普通继承自StatefulWidget的类没有什么不同。
- State类继承自AnimatedWidgetBaseState(该类继承自ImplicitlyAnimatedWidgetState类)。
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;
}
}
可以看到我们实现了build和forEachTween两个方法。在动画执行过程中,每一帧都会调用build方法(调用逻辑在ImplicitlyAnimatedWidgetState中),所以在build方法中我们需要构建每一帧的DecoratedBox状态,因此得算出每一帧的decoration 状态,这个我们可以通过_decoration.evaluate(animation) 来算出,其中animation是ImplicitlyAnimatedWidgetState基类中定义的对象,_decoration是我们自定义的一个DecorationTween类型的对象。
那么现在的问题就是它是在什么时候被赋值的呢?要回答这个问题,我们就得搞清楚什么时候需要对_decoration赋值。我们知道_decoration是一个Tween,而Tween的主要职责就是定义动画的起始状态(begin)和终止状态(end)。对于AnimatedDecoratedBox来说,decoration的终止状态就是用户传给它的值,而起始状态是不确定的,有以下两种情况:
- AnimatedDecoratedBox首次build,此时直接将其decoration值置为起始状态,即_decoration值为DecorationTween(begin: decoration) 。
- AnimatedDecoratedBox的decoration更新时,则起始状态为_decoration.animate(animation),即_decoration值为DecorationTween(begin: _decoration.animate(animation),end:decoration)。
现在forEachTween的作用就很明显了,它正是用于来更新Tween的初始值的,在上述两种情况下会被调用,而开发者只需重写此方法,并在此方法中更新Tween的起始状态值即可。而一些更新的逻辑被屏蔽在了visitor回调,我们只需要调用它并给它传递正确的参数即可,visitor方法签名如下
Tween<T> visitor(
Tween<T> tween, //当前的tween,第一次调用为null
T targetValue, // 终止状态
TweenConstructor<T> constructor,//Tween构造器,在上述三种情况下会被调用以更新tween
);
可以看到,通过继承ImplicitlyAnimatedWidget和ImplicitlyAnimatedWidgetState类可以快速的实现动画过渡组件的封装,这和我们纯手工实现相比,代码简化了很多
https://book.flutterchina.club/chapter9/animated_widgets.html