欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Flutter Animation动画开发之——AnimatedWidget

程序员文章站 2022-03-09 20:21:14
...

Flutter Animation动画开发之——最简单的动画入门这篇文章中我们介绍了创建一个动画的基本流程,其中每创建一个动画都要通过addListener来监听动画的每一帧,然后调用setState来刷新UI。我们今天要介绍的AnimatedWidget内部帮我们实现了这一步骤,其内部会监听动画然后更新UI。

AnimatedWidget示例

首先我们将需要执行动画的控件单独抽出来,继承AnimatedWidget自定义成一个动画控件,构造方法中的animation参数从外部接收一个Animation动画,记得传给父类构造方法的listenable参数,将动画的监听和更新UI交由父类处理

class MyAnimatedWidget extends AnimatedWidget {
  MyAnimatedWidget({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);

  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return Center(
      // 这里显示一个绿色方块,随着动画的执行会不断变大
      child: Container(
        color: Colors.red,
        width: animation.value,
        height: animation.value,
      ),
    );
  }
}

然后在布局中使用该自定义的动画控件,传入动画参数,该动画无需再通过addListener监听变化来更新UI

class AnimationRoute extends StatefulWidget {
  @override
  AnimationRouteState createState() => AnimationRouteState();
}

class AnimationRouteState extends State<AnimationRoute> with SingleTickerProviderStateMixin {

  Animation<double> animation;
  AnimationController controller;

  initState() {
    super.initState();
    // Controller设置动画时长
    // vsync设置一个TickerProvider,当前State 混合了SingleTickerProviderStateMixin就是一个TickerProvider
    controller = AnimationController(
        duration: Duration(seconds: 5),
        vsync: this //
    );
    animation=CurvedAnimation(parent: controller, curve: Curves.bounceInOut);
    // Tween设置动画的区间值,animate()方法传入一个Animation,AnimationController继承Animation
    // 这里无需再通过addListener监听动画来更新UI
    animation = new Tween(begin: 50.0, end: 250.0).animate(animation);
    //启动动画(正向执行)
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return MyAnimatedWidget(animation: animation);
  }

  @override
  void dispose() {
    // 释放资源
    controller.dispose();
    super.dispose();
  }
}

AnimatedWidget源码解析

代码如下所示,主要包括AnimatedWidget和_AnimatedState这两个类,代码比较简单,我们主要看listenable这个变量,AnimatedWidget的核心就是通过监听listenable来更新UI的,listenable其实就是我们传入的动画,下面做简要说明

我们先看下AnimatedWidget的源码

  • listenable不能为空,所以自定义的子类必须将动画传给listenable
  • AnimatedWidget是一个有状态的控件,其状态由_AnimatedState管理

再看下_AnimatedState的源码

  • 在initState()方法中调用listenable.addListener(_handleChange)监听动画
  • 在_handleChange()方法中处理动画监听,调用setState()方法通知更新UI
  • 在build()方法中返回widget.build(),也就是自定义AnimatedWidget中重写build方法中自定义的UI
  • 在dispose()方法中调用listenable.removeListener(_handleChange)移除动画监听
abstract class AnimatedWidget extends StatefulWidget {
  /// Creates a widget that rebuilds when the given listenable changes.
  ///
  /// The [listenable] argument is required.
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

  /// The [Listenable] to which this widget is listening.
  ///
  /// Commonly an [Animation] or a [ChangeNotifier].
  final Listenable listenable;

  /// Override this method to build widgets that depend on the state of the
  /// listenable (e.g., the current value of the animation).
  @protected
  Widget build(BuildContext context);

  /// Subclasses typically do not override this method.
  @override
  _AnimatedState createState() => _AnimatedState();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
  }
}

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }

  void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

  @override
  Widget build(BuildContext context) => widget.build(context);
}