为了表述方便,本书约定,将在Widget属性发生变化时会执行过渡动画的组件统称为动画过渡组件,而动画过渡组件最明显的一个特征就是它会在内部自管理AnimationController

  • 我们知道,为了方便使用者可以自定义
    • 动画的曲线
    • 执行时长
    • 方向等,
  • 在前面介绍过的动画封装方法中,通常都需要使用者自己提供一个AnimationController对象来自定义这些属性值。
  • 但是,如此一来,使用者就必须得手动管理AnimationController,这又会增加使用的复杂性
  • 因此,如果也能将AnimationController进行封装,则会大大提高动画组件的易用性。

自定义动画过渡组件

我们要实现一个AnimatedDecoratedBox,它可以在decoration属性发生变化时,从旧状态变成新状态的过程可以执行一个过渡动画。根据前面所学的知识,我们实现了一个AnimatedDecoratedBox组件:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. debugShowCheckedModeBanner: true,
  8. title: 'AnimatedSwitcher',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("AnimatedSwitcher")),
  14. body: AnimatedSwitcherCounterRoute(),
  15. ));
  16. }
  17. }
  18. class AnimatedSwitcherCounterRoute extends StatefulWidget {
  19. const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);
  20. @override
  21. _AnimatedSwitcherCounterRouteState createState() =>
  22. _AnimatedSwitcherCounterRouteState();
  23. }
  24. class _AnimatedSwitcherCounterRouteState
  25. extends State<AnimatedSwitcherCounterRoute> {
  26. Color _decorationColor = Colors.blue;
  27. var duration = Duration(seconds: 5);
  28. @override
  29. Widget build(BuildContext context) {
  30. return Center(
  31. child: Column(
  32. mainAxisAlignment: MainAxisAlignment.center,
  33. children: <Widget>[
  34. AnimatedDecoratedBox(
  35. duration: duration,
  36. decoration: BoxDecoration(color: _decorationColor),
  37. child: FlatButton(
  38. onPressed: () {
  39. setState(() {
  40. _decorationColor = Colors.red;
  41. });
  42. },
  43. child: Text(
  44. "AnimatedDecoratedBox",
  45. style: TextStyle(color: Colors.white),
  46. ),
  47. ),
  48. )
  49. ],
  50. ),
  51. );
  52. }
  53. }
  54. class AnimatedDecoratedBox extends StatefulWidget {
  55. AnimatedDecoratedBox({
  56. Key key,
  57. @required this.decoration,
  58. @required this.duration,
  59. @required this.child,
  60. this.curve = Curves.linear,
  61. this.reverseDuration,
  62. });
  63. final BoxDecoration decoration;
  64. final Widget child;
  65. final Duration duration;
  66. final Curve curve;
  67. final Duration reverseDuration;
  68. @override
  69. _AnimatedDecoratedBox1State createState() => _AnimatedDecoratedBox1State();
  70. }
  71. class _AnimatedDecoratedBox1State extends State<AnimatedDecoratedBox>
  72. with SingleTickerProviderStateMixin {
  73. @protected
  74. AnimationController get controller => _controller;
  75. AnimationController _controller;
  76. Animation<double> get animation => _animation;
  77. Animation<double> _animation;
  78. DecorationTween _tween;
  79. @override
  80. void initState() {
  81. super.initState();
  82. _controller = AnimationController(
  83. duration: widget.duration,
  84. reverseDuration: widget.reverseDuration,
  85. vsync: this,
  86. );
  87. _tween = DecorationTween(begin: widget.decoration);
  88. _updateCurve();
  89. }
  90. void _updateCurve() {
  91. if (widget.curve != null)
  92. _animation = CurvedAnimation(parent: _controller, curve: widget.curve);
  93. else
  94. _animation = _controller;
  95. }
  96. @override
  97. void didUpdateWidget(AnimatedDecoratedBox oldWidget) {
  98. super.didUpdateWidget(oldWidget);
  99. if (widget.curve != oldWidget.curve) _updateCurve();
  100. _controller.duration = widget.duration;
  101. _controller.reverseDuration = widget.reverseDuration;
  102. if (widget.decoration != (_tween.end ?? _tween.begin)) {
  103. _tween
  104. ..begin = _tween.evaluate(_animation)
  105. ..end = widget.decoration;
  106. _controller
  107. ..value = 0.0
  108. ..forward();
  109. }
  110. }
  111. @override
  112. void dispose() {
  113. _controller.dispose();
  114. super.dispose();
  115. }
  116. @override
  117. Widget build(BuildContext context) {
  118. return AnimatedBuilder(
  119. animation: _animation,
  120. builder: (context, child) {
  121. return DecoratedBox(
  122. decoration: _tween.evaluate(_animation),
  123. child: child,
  124. );
  125. },
  126. child: widget.child,
  127. );
  128. }
  129. }

点击前效果如图所示,点击后截取了过渡过程的一帧如图所示: 动画过渡组件 - 图1动画过渡组件 - 图2

点击后,按钮背景色会从蓝色向红色过渡,图是过渡过程中的一帧,有点偏紫色,整个过渡动画结束后背景会变为红色

if判断语句可以简写

  1. if (widget.curve != null)
  2. _animation = CurvedAnimation(parent: _controller, curve: widget.curve);
  3. else
  4. _animation = _controller;

ImplicitlyAnimatedWidget

上面的代码虽然实现了我们期望的功能,但是代码却比较复杂。稍加思考后,我们就可以发现,AnimationController的管理以及Tween更新部分的代码都是可以抽象出来的,如果我们这些通用逻辑封装成基类,那么要实现动画过渡组件只需要继承这些基类,然后定制自身不同的代码(比如动画每一帧的构建方法)即可,这样将会简化代码。
为了方便开发者来实现动画过渡组件的封装,Flutter提供了一个ImplicitlyAnimatedWidget抽象类,它继承自StatefulWidget,同时提供了一个对应的ImplicitlyAnimatedWidgetState类,AnimationController的管理就在ImplicitlyAnimatedWidgetState类中。
开发者如果要封装动画,只需要分别继承ImplicitlyAnimatedWidget和ImplicitlyAnimatedWidgetState类即可,下面我们演示一下具体如何实现。

我们需要分两步实现:

  1. 继承ImplicitlyAnimatedWidget类。

    1. class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
    2. AnimatedDecoratedBox({
    3. Key key,
    4. @required this.decoration,
    5. this.child,
    6. Curve curve = Curves.linear, //动画曲线
    7. @required Duration duration, // 正向动画执行时长
    8. Duration reverseDuration, // 反向动画执行时长
    9. }) : super(
    10. key: key,
    11. curve: curve,
    12. duration: duration,
    13. reverseDuration: reverseDuration,
    14. );
    15. final BoxDecoration decoration;
    16. final Widget child;
    17. @override
    18. _AnimatedDecoratedBoxState createState() {
    19. return _AnimatedDecoratedBoxState();
    20. }
    21. }

    其中curve、duration、reverseDuration三个属性在ImplicitlyAnimatedWidget中已定义。 可以看到AnimatedDecoratedBox类和普通继承自StatefulWidget的类没有什么不同

  2. State类继承自AnimatedWidgetBaseState(该类继承自ImplicitlyAnimatedWidgetState类)。

    1. class _AnimatedDecoratedBoxState
    2. extends AnimatedWidgetBaseState<AnimatedDecoratedBox> {
    3. DecorationTween _decoration; //定义一个Tween
    4. @override
    5. Widget build(BuildContext context) {
    6. return DecoratedBox(
    7. decoration: _decoration.evaluate(animation),
    8. child: widget.child,
    9. );
    10. }
    11. @override
    12. void forEachTween(visitor) {
    13. // 在需要更新Tween时,基类会调用此方法
    14. _decoration = visitor(_decoration, widget.decoration,
    15. (value) => DecorationTween(begin: value));
    16. }
    17. }

    可以看到我们实现了build和forEachTween两个方法
    在动画执行过程中,每一帧都会调用build方法(调用逻辑在ImplicitlyAnimatedWidgetState中)
    所以在build方法中我们需要构建每一帧的DecoratedBox状态,因此得算出每一帧的decoration状态,这个我们可以通过_decoration.evaluate(animation)来算出,

  • 其中animation是ImplicitlyAnimatedWidgetState基类中定义的对象
  • _decoration是我们自定义的一个DecorationTween类型的对象

那么现在的问题就是它是在什么时候被赋值的呢?要回答这个问题,我们就得搞清楚什么时候需要对_decoration赋值。我们知道_decoration是一个Tween,而Tween的主要职责就是定义动画的起始状态(begin)和终止状态(end)。对于AnimatedDecoratedBox来说,decoration的终止状态就是用户传给它的值,而起始状态是不确定的,有以下两种情况:

  1. AnimatedDecoratedBox首次build,此时直接将其decoration值置为起始状态,即_decoration值为DecorationTween(begin: decoration) 。
  2. AnimatedDecoratedBox的decoration更新时,则起始状态为_decoration.animate(animation),即_decoration值为DecorationTween(begin: _decoration.animate(animation),end:decoration)。

现在forEachTween的作用就很明显了,它正是用于来更新Tween的初始值的,在上述两种情况下会被调用,而开发者只需重写此方法,并在此方法中更新Tween的起始状态值即可。而一些更新的逻辑被屏蔽在了visitor回调,我们只需要调用它并给它传递正确的参数即可,visitor方法签名如下:

  1. Tween visitor(
  2. Tween<dynamic> tween, //当前的tween,第一次调用为null
  3. dynamic targetValue, // 终止状态
  4. TweenConstructor<dynamic> constructor//Tween构造器,在上述三种情况下会被调用以更新tween
  5. );

可以看到,通过继承ImplicitlyAnimatedWidget和ImplicitlyAnimatedWidgetState类可以快速的实现动画过渡组件的封装,这和我们纯手工实现相比,代码简化了很多。

如果读者还有疑惑,建议查看ImplicitlyAnimatedWidgetState的源码并结合本示例代码对比理解。

完整代码

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. debugShowCheckedModeBanner: true,
  8. title: 'AnimatedSwitcher',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("AnimatedSwitcher")),
  14. body: AnimatedSwitcherCounterRoute(),
  15. ));
  16. }
  17. }
  18. class AnimatedSwitcherCounterRoute extends StatefulWidget {
  19. const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);
  20. @override
  21. _AnimatedSwitcherCounterRouteState createState() =>
  22. _AnimatedSwitcherCounterRouteState();
  23. }
  24. class _AnimatedSwitcherCounterRouteState
  25. extends State<AnimatedSwitcherCounterRoute> {
  26. Color _decorationColor = Colors.blue;
  27. var duration = Duration(seconds: 5);
  28. @override
  29. Widget build(BuildContext context) {
  30. return Center(
  31. child: Column(
  32. mainAxisAlignment: MainAxisAlignment.center,
  33. children: <Widget>[
  34. AnimatedDecoratedBox(
  35. duration: duration,
  36. decoration: BoxDecoration(color: _decorationColor),
  37. child: FlatButton(
  38. onPressed: () {
  39. setState(() {
  40. _decorationColor = Colors.red;
  41. });
  42. },
  43. child: Text(
  44. "AnimatedDecoratedBox",
  45. style: TextStyle(color: Colors.white),
  46. ),
  47. ),
  48. )
  49. ],
  50. ),
  51. );
  52. }
  53. }
  54. class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
  55. AnimatedDecoratedBox({
  56. Key key,
  57. @required this.decoration,
  58. this.child,
  59. Curve curve = Curves.linear, //动画曲线
  60. @required Duration duration, // 正向动画执行时长
  61. Duration reverseDuration, // 反向动画执行时长
  62. }) : super(
  63. key: key,
  64. curve: curve,
  65. duration: duration,
  66. // reverseDuration: reverseDuration,
  67. );
  68. final BoxDecoration decoration;
  69. final Widget child;
  70. @override
  71. _AnimatedDecoratedBoxState createState() {
  72. return _AnimatedDecoratedBoxState();
  73. }
  74. }
  75. class _AnimatedDecoratedBoxState
  76. extends AnimatedWidgetBaseState<AnimatedDecoratedBox> {
  77. DecorationTween _decoration; //定义一个Tween
  78. @override
  79. Widget build(BuildContext context) {
  80. return DecoratedBox(
  81. decoration: _decoration.evaluate(animation),
  82. child: widget.child,
  83. );
  84. }
  85. @override
  86. void forEachTween(visitor) {
  87. // 在需要更新Tween时,基类会调用此方法
  88. _decoration = visitor(_decoration, widget.decoration,
  89. (value) => DecorationTween(begin: value));
  90. }
  91. }

动画过渡组件的反向动画

在使用动画过渡组件,我们只需要在改变一些属性值后重新build组件即可,所以要实现状态反向过渡,只需要将前后状态值互换即可实现,这本来是不需要再浪费笔墨的。
但是ImplicitlyAnimatedWidget构造函数中却有一个reverseDuration属性用于设置反向动画的执行时长,这貌似在告诉读者ImplicitlyAnimatedWidget本身也提供了执行反向动画的接口
于是笔者查看了ImplicitlyAnimatedWidgetState源码并未发现有执行反向动画的接口,唯一有用的是它暴露了控制动画的controller。
所以如果要让reverseDuration生效,我们只能先获取controller,然后再通过controller.reverse()来启动反向动画,比如我们在上面示例的基础上实现一个循环的点击背景颜色变换效果,要求从蓝色变为红色时动画执行时间为400ms,从红变蓝为2s,如果要使reverseDuration生效,我们需要这么做:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. debugShowCheckedModeBanner: true,
  8. title: 'AnimatedSwitcher',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("AnimatedSwitcher")),
  14. body: AnimatedSwitcherCounterRoute(),
  15. ));
  16. }
  17. }
  18. class AnimatedSwitcherCounterRoute extends StatefulWidget {
  19. const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);
  20. @override
  21. _AnimatedSwitcherCounterRouteState createState() =>
  22. _AnimatedSwitcherCounterRouteState();
  23. }
  24. class _AnimatedSwitcherCounterRouteState
  25. extends State<AnimatedSwitcherCounterRoute> {
  26. Color _decorationColor = Colors.blue;
  27. var duration = Duration(seconds: 5);
  28. @override
  29. Widget build(BuildContext context) {
  30. return Center(
  31. child: Column(
  32. mainAxisAlignment: MainAxisAlignment.center,
  33. children: <Widget>[
  34. AnimatedDecoratedBox(
  35. duration: Duration(milliseconds: 400),
  36. decoration: BoxDecoration(color: _decorationColor),
  37. reverseDuration: Duration(seconds: 2),
  38. child: Builder(builder: (context) {
  39. return FlatButton(
  40. onPressed: () {
  41. if (_decorationColor == Colors.red) {
  42. ImplicitlyAnimatedWidgetState _state =
  43. context.ancestorStateOfType(
  44. TypeMatcher<ImplicitlyAnimatedWidgetState>());
  45. // 通过controller来启动反向动画
  46. _state.controller.reverse().then((e) {
  47. // 经验证必须调用setState来触发rebuild,否则状态同步会有问题
  48. setState(() {
  49. _decorationColor = Colors.blue;
  50. });
  51. });
  52. } else {
  53. setState(() {
  54. _decorationColor = Colors.red;
  55. });
  56. }
  57. },
  58. child: Text(
  59. "AnimatedDecoratedBox toggle",
  60. style: TextStyle(color: Colors.white),
  61. ),
  62. );
  63. }),
  64. )
  65. ],
  66. ),
  67. );
  68. }
  69. }
  70. class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
  71. AnimatedDecoratedBox({
  72. Key key,
  73. @required this.decoration,
  74. this.child,
  75. Curve curve = Curves.linear, //动画曲线
  76. @required Duration duration, // 正向动画执行时长
  77. Duration reverseDuration, // 反向动画执行时长
  78. }) : super(
  79. key: key,
  80. curve: curve,
  81. duration: duration,
  82. // reverseDuration: reverseDuration,
  83. );
  84. final BoxDecoration decoration;
  85. final Widget child;
  86. @override
  87. _AnimatedDecoratedBoxState createState() {
  88. return _AnimatedDecoratedBoxState();
  89. }
  90. }
  91. class _AnimatedDecoratedBoxState
  92. extends AnimatedWidgetBaseState<AnimatedDecoratedBox> {
  93. DecorationTween _decoration; //定义一个Tween
  94. @override
  95. Widget build(BuildContext context) {
  96. return DecoratedBox(
  97. decoration: _decoration.evaluate(animation),
  98. child: widget.child,
  99. );
  100. }
  101. @override
  102. void forEachTween(visitor) {
  103. // 在需要更新Tween时,基类会调用此方法
  104. _decoration = visitor(_decoration, widget.decoration,
  105. (value) => DecorationTween(begin: value));
  106. }
  107. }

上面的代码实际上是非常糟糕且没必要的,它需要我们了解ImplicitlyAnimatedWidgetState内部实现,并且要手动去启动反向动画。我们完全可以通过如下代码实现相同的效果:

  1. AnimatedDecoratedBox(
  2. duration: Duration(
  3. milliseconds: _decorationColor == Colors.red ? 400 : 2000),
  4. decoration: BoxDecoration(color: _decorationColor),
  5. child: Builder(builder: (context) {
  6. return FlatButton(
  7. onPressed: () {
  8. setState(() {
  9. _decorationColor = _decorationColor == Colors.blue
  10. ? Colors.red
  11. : Colors.blue;
  12. });
  13. },
  14. child: Text(
  15. "AnimatedDecoratedBox toggle",
  16. style: TextStyle(color: Colors.white),
  17. ),
  18. );
  19. }),
  20. )

这样的代码是不是优雅的多!那么现在问题来了,为什么ImplicitlyAnimatedWidgetState要提供一个reverseDuration参数呢?笔者仔细研究了ImplicitlyAnimatedWidgetState的实现,发现唯一的解释就是该参数并非是给ImplicitlyAnimatedWidgetState用的,而是给子类用的!原因正如我们前面说的,要使reverseDuration 有用就必须得获取controller 属性来手动启动反向动画,ImplicitlyAnimatedWidgetState中的controller 属性是一个保护属性,定义如下:

  1. @protected
  2. AnimationController get controller => _controller;

而保护属性原则上只应该在子类中使用,而不应该像上面示例代码一样在外部使用。综上,我们可以得出两条结论:

  1. 使用动画过渡组件时如果需要执行反向动画的场景,应尽量使用状态互换的方法,而不应该通过获取ImplicitlyAnimatedWidgetState中controller的方式。
  2. 如果我们自定义的动画过渡组件用不到reverseDuration ,那么最好就不要暴露此参数,比如我们上面自定义的AnimatedDecoratedBox定义中就可以去除reverseDuration 可选参数,如:
    1. class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
    2. AnimatedDecoratedBox({
    3. Key key,
    4. @required this.decoration,
    5. this.child,
    6. Curve curve = Curves.linear,
    7. @required Duration duration,
    8. }) : super(
    9. key: key,
    10. curve: curve,
    11. duration: duration,
    12. );
    13. }

    Flutter预置的动画过渡组件

    Flutter SDK中也预置了很多动画过渡组件,实现方式和大都和AnimatedDecoratedBox差不多,如表9-1所示:
组件名 功能
AnimatedPadding 在padding发生变化时会执行过渡动画到新状态
AnimatedPositioned 配合Stack一起使用,当定位状态发生变化时会执行过渡动画到新的状态。
AnimatedOpacity 在透明度opacity发生变化时执行过渡动画到新状态
AnimatedAlign 当alignment发生变化时会执行过渡动画到新的状态。
AnimatedContainer 当Container属性发生变化时会执行过渡动画到新的状态。
AnimatedDefaultTextStyle 当字体样式发生变化时,子组件中继承了该样式的文本组件会动态过渡到新样式。

表9-1:Flutter预置的动画过渡组件下面我们通过一个示例来感受一下这些预置的动画过渡组件效果:

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(new MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. debugShowCheckedModeBanner: true,
  8. title: 'AnimatedSwitcher',
  9. theme: ThemeData(
  10. primarySwatch: Colors.blue,
  11. ),
  12. home: Scaffold(
  13. appBar: AppBar(title: Text("AnimatedSwitcher")),
  14. body: AnimatedWidgetsTest(),
  15. ));
  16. }
  17. }
  18. class AnimatedWidgetsTest extends StatefulWidget {
  19. @override
  20. _AnimatedWidgetsTestState createState() => _AnimatedWidgetsTestState();
  21. }
  22. class _AnimatedWidgetsTestState extends State<AnimatedWidgetsTest> {
  23. double _padding = 10;
  24. var _align = Alignment.topRight;
  25. double _height = 100;
  26. double _left = 0;
  27. Color _color = Colors.red;
  28. TextStyle _style = TextStyle(color: Colors.black);
  29. Color _decorationColor = Colors.blue;
  30. @override
  31. Widget build(BuildContext context) {
  32. var duration = Duration(seconds: 5);
  33. return SingleChildScrollView(
  34. child: Column(
  35. children: <Widget>[
  36. RaisedButton(
  37. onPressed: () {
  38. setState(() {
  39. _padding = 20;
  40. });
  41. },
  42. child: AnimatedPadding(
  43. duration: duration,
  44. padding: EdgeInsets.all(_padding),
  45. child: Text("AnimatedPadding"),
  46. ),
  47. ),
  48. SizedBox(
  49. height: 50,
  50. child: Stack(
  51. children: <Widget>[
  52. AnimatedPositioned(
  53. duration: duration,
  54. left: _left,
  55. child: RaisedButton(
  56. onPressed: () {
  57. setState(() {
  58. _left = 100;
  59. });
  60. },
  61. child: Text("AnimatedPositioned"),
  62. ),
  63. )
  64. ],
  65. ),
  66. ),
  67. Container(
  68. height: 100,
  69. color: Colors.grey,
  70. child: AnimatedAlign(
  71. duration: duration,
  72. alignment: _align,
  73. child: RaisedButton(
  74. onPressed: () {
  75. setState(() {
  76. _align = Alignment.center;
  77. });
  78. },
  79. child: Text("AnimatedAlign"),
  80. ),
  81. ),
  82. ),
  83. AnimatedContainer(
  84. duration: duration,
  85. height: _height,
  86. color: _color,
  87. child: FlatButton(
  88. onPressed: () {
  89. setState(() {
  90. _height = 150;
  91. _color = Colors.blue;
  92. });
  93. },
  94. child: Text(
  95. "AnimatedContainer",
  96. style: TextStyle(color: Colors.white),
  97. ),
  98. ),
  99. ),
  100. AnimatedDefaultTextStyle(
  101. child: GestureDetector(
  102. child: Text("hello world"),
  103. onTap: () {
  104. setState(() {
  105. _style = TextStyle(
  106. color: Colors.blue,
  107. decorationStyle: TextDecorationStyle.solid,
  108. decorationColor: Colors.blue,
  109. );
  110. });
  111. },
  112. ),
  113. style: _style,
  114. duration: duration,
  115. ),
  116. AnimatedDecoratedBox(
  117. duration: duration,
  118. decoration: BoxDecoration(color: _decorationColor),
  119. child: FlatButton(
  120. onPressed: () {
  121. setState(() {
  122. _decorationColor = Colors.red;
  123. });
  124. },
  125. child: Text(
  126. "AnimatedDecoratedBox",
  127. style: TextStyle(color: Colors.white),
  128. ),
  129. ),
  130. )
  131. ].map((e) {
  132. return Padding(
  133. padding: EdgeInsets.symmetric(vertical: 16),
  134. child: e,
  135. );
  136. }).toList(),
  137. ),
  138. );
  139. }
  140. }
  141. class AnimatedDecoratedBox extends ImplicitlyAnimatedWidget {
  142. AnimatedDecoratedBox({
  143. Key key,
  144. @required this.decoration,
  145. this.child,
  146. Curve curve = Curves.linear, //动画曲线
  147. @required Duration duration, // 正向动画执行时长
  148. Duration reverseDuration, // 反向动画执行时长
  149. }) : super(
  150. key: key,
  151. curve: curve,
  152. duration: duration,
  153. );
  154. final BoxDecoration decoration;
  155. final Widget child;
  156. @override
  157. _AnimatedDecoratedBoxState createState() {
  158. return _AnimatedDecoratedBoxState();
  159. }
  160. }
  161. class _AnimatedDecoratedBoxState
  162. extends AnimatedWidgetBaseState<AnimatedDecoratedBox> {
  163. DecorationTween _decoration; //定义一个Tween
  164. @override
  165. Widget build(BuildContext context) {
  166. return DecoratedBox(
  167. decoration: _decoration.evaluate(animation),
  168. child: widget.child,
  169. );
  170. }
  171. @override
  172. void forEachTween(visitor) {
  173. // 在需要更新Tween时,基类会调用此方法
  174. _decoration = visitor(_decoration, widget.decoration,
  175. (value) => DecorationTween(begin: value));
  176. }
  177. }

运行后效果如图所示:
动画过渡组件 - 图3
读者可以点击一下相应组件来查看一下实际的运行效果。

知识小技巧:遍历增加padding

  1. [].map((e) {
  2. return Padding(
  3. padding: EdgeInsets.symmetric(vertical: 16),
  4. child: e,
  5. );
  6. }).toList()