Flutter动画简化类

  • 概述

    在Flutter的使用过程中,我们总是会调用addListener方法添加每一帧的回调监听,并且动画都是体现在UI变化上的,所以在监听中我们通常要调用setState方法重新刷新UI,从而使得控件对应属性重新引用animation.value的值,这块逻辑通常都是必须的,因为你基本上不可能应用动画但是不让他刷新UI,这样动画就失去了意义。

    另外,动画的构建过程也是大同小异,比如,我们都至少需要一个AnimationController,都可能需要Tween和Curve,都需要duration等。

    Flutter框架中有这样的类帮我们封装了这部分逻辑,我们来看一下它们的源码。

  • ImplicitlyAnimatedWidget

    凡是需要封装刷新逻辑的,通常需要继承这个类,为什么要继承呢?我这里用的是“通常”,而不是必须,其实如果要追究的话,所有的东西其实你都可以自己封装,之所以继承这个类是因为方便,不要“重复造轮子”,我们只是需要知其所以然。

    先看一下一些经常使用的子类:

    [TweenAnimationBuilder], which animates any property expressed by
    a [Tween] to a specified target value.
    [AnimatedAlign], which is an implicitly animated version of [Align].
    [AnimatedContainer], which is an implicitly animated version of
    [Container].
    [AnimatedDefaultTextStyle], which is an implicitly animated version of
    [DefaultTextStyle].
    [AnimatedScale], which is an implicitly animated version of [Transform.scale].
    [AnimatedRotation], which is an implicitly animated version of [Transform.rotate].
    [AnimatedSlide], which implicitly animates the position of a widget relative to its normal position.
    [AnimatedOpacity], which is an implicitly animated version of [Opacity].
    [AnimatedPadding], which is an implicitly animated version of [Padding].
    [AnimatedPhysicalModel], which is an implicitly animated version of
    [PhysicalModel].
    [AnimatedPositioned], which is an implicitly animated version of
    [Positioned].
    [AnimatedPositionedDirectional], which is an implicitly animated version
    of [PositionedDirectional].
    [AnimatedTheme], which is an implicitly animated version of [Theme].
    [AnimatedCrossFade], which cross-fades between two given children and
    animates itself between their sizes.
    [AnimatedSize], which automatically transitions its size over a given
    duration.
    [AnimatedSwitcher], which fades from one widget to another.
    

    这个类有一些基本的属性:

    const ImplicitlyAnimatedWidget({
      Key? key,
      this.curve = Curves.linear,
      required this.duration,
      this.onEnd,
    })
    

    最重要的还是它的createState方法限制了State类型必须是ImplicitlyAnimatedWidgetState:

    @override
    ImplicitlyAnimatedWidgetState createState();
    

    作为规范,ImplicitlyAnimatedWidgetState要求Widget必须是ImplicitlyAnimatedWidget类型。

    我们来看ImplicitlyAnimatedWidgetState中封装了那些内容。

  • ImplicitlyAnimatedWidgetState

    @protected
    AnimationController get controller => _controller;
    late final AnimationController _controller = AnimationController(
      duration: widget.duration,
      debugLabel: kDebugMode ? widget.toStringShort() : null,
      vsync: this,
    );
    
    /// The animation driving this widget's implicit animations.
    Animation get animation => _animation;
    late Animation _animation = _createCurve();
    
    CurvedAnimation _createCurve() {
      return CurvedAnimation(parent: _controller, curve: widget.curve);
    }
    

    首先我们看到,它内部持有一个AnimationController还有一个CurvedAnimation,可能有人会问,那这样岂不是强制把AnimationController绑定在CurvedAnimation上了吗,如果我不需要Curve,我想要直接使用AnimationController或者把绑定在Tween上呢?其实很简单就能说通,即时你不使用Curve,系统的屏幕刷新频率是固定的,在你不使用任何包装类的情况下它默认速度其实就是线性变化的,这和你指定Curve.linear是完全一样的效果,而通过继承ImplicitlyAnimatedWidget,它默认指定的curve属性就是Curve.linear,这也是ImplicitlyAnimatedWidget的另一个作用,就是初始化一些必须的值。

    其次ImplicitlyAnimatedWidgetState依赖了SingleTickerProviderStateMixin,所以我们自己的State也省去了vsync这一步。

    接下来我们看initState方法:

    @override
    void initState() {
      super.initState();
      _controller.addStatusListener((AnimationStatus status) {
        switch (status) {
          case AnimationStatus.completed:
            widget.onEnd?.call();
            break;
          case AnimationStatus.dismissed:
          case AnimationStatus.forward:
          case AnimationStatus.reverse:
        }
      });
      _constructTweens();
      didUpdateTweens();
    }
    

    可以看到,initState中添加了一个StatusListener,用来处理动画结束时的回调,ImplicitlyAnimatedWidget构造中只提供了动画结束时的回调接口设置入口,所以默认只能监听动画结束,但是ImplicitlyAnimatedWidgetState提供了controller方法来获取_controller,所以有需要的话你完全可以设置其他的。

    接着会调用_constructTweens方法:

    bool _constructTweens() {
      bool shouldStartAnimation = false;
      forEachTween((Tween? tween, dynamic targetValue, TweenConstructor constructor) {
        if (targetValue != null) {
          tween ??= constructor(targetValue);
          if (_shouldAnimateTween(tween, targetValue))
            shouldStartAnimation = true;
        } else {
          tween = null;
        }
        return tween;
      });
      return shouldStartAnimation;
    }
    

    这个方法稍微有些深度,我们来看forEachTween方法:

    @protected
    void forEachTween(TweenVisitor visitor);
    

    这个方法在哪实现的呢?我们上面说到,ImplicitlyAnimatedWidgetState有很多子类,我们以_AnimatedPaddingState为例看看它的forEachTween方法:

    @override
    void forEachTween(TweenVisitor visitor) {
      _padding = visitor(_padding, widget.padding, (dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?;
    }
    

    这个_padding是 _AnimatedPaddingState要用来应用到Widget的变化属性值,显然它是根据visitor函数生成的,而这里的visitor我们知道是在调用 _constructTweens方法中调用forEachTween时传入的,回到 _constructTweens方法,tween就是这里的 _padding,targetValue就是这里的widget.padding,constructor就是(dynamic value) => EdgeInsetsGeometryTween(begin: value as EdgeInsetsGeometry)) as EdgeInsetsGeometryTween?。所以我们可以整理一下逻辑:外面State要变化的属性是需要自定义的,因此它的Tween的构造方法也是需要自定义的,目标属性值是它的widget指定的,但是创建逻辑是由ImplicitlyAnimatedWidgetState封装好的。由此我们也可以知道,forEachTween方法是用来创建变化属性的Tween的。

    didUpdateTweens方法是用来更新Tween的,没有默认实现,只是提供了一个可以在构造完Tween 之后做一些事情的接口,你可以按照需求实现自己的逻辑。

    接下来看一下didUpdateWidget方法:

    @override
    void didUpdateWidget(T oldWidget) {
      super.didUpdateWidget(oldWidget);
      if (widget.curve != oldWidget.curve) {
        (_animation as CurvedAnimation).dispose();
        _animation = _createCurve();
      }
      _controller.duration = widget.duration;
      if (_constructTweens()) {
        forEachTween((Tween? tween, dynamic targetValue, TweenConstructor constructor) {
          _updateTween(tween, targetValue);
          return tween;
        });
        _controller
          ..value = 0.0
          ..forward();
        didUpdateTweens();
      }
    }
    

    这个方法只有在widget.canUpdate方法返回true的时候(也即是runtimeType或Widget.key有变化的时候)才会被系统调用,所以在这个方法里做一些对动画配置可能发生变化的更新操作。

    前面的就不说了,说一下 _constructTweens的返回值,前面可以看到 _constructTweens的返回值是shouldStartAnimation,它是否能返回true是根据 _shouldAnimateTween方法决定的:

    bool _shouldAnimateTween(Tween tween, dynamic targetValue) {
      return targetValue != (tween.end ?? tween.begin);
    }
    

    可见,只要当前动画进度值不是在两个端点就会返回true。

    再看 _updateTween方法:

    void _updateTween(Tween? tween, dynamic targetValue) {
      if (tween == null)
        return;
      tween
        ..begin = tween.evaluate(_animation)
        ..end = targetValue;
    }
    

    这个方法会使Widget配置发生变化后还能从之前动画执行的当前进度值继续变化。而这里也会调用 _controller的forward方法使动画继续。

    所以,结合_shouldAnimateTween方法,整体就是动画当前进度值正在进行中的时候才会调用forward使动画继续,如果在起点或者终点的话是不会自动开启的,同样在端点的时候Tween也不需要根据动画的进度值去设置起点属性值,这也能看出框架设计者的细节之处。

    当然,ImplicitlyAnimatedWidgetState也封装了动画的释放工作:

    @override
    void dispose() {
      (_animation as CurvedAnimation).dispose();
      _controller.dispose();
      super.dispose();
    }
    
  • AnimatedWidgetBaseState

    现在还有一个点没有提到,就是刷新UI的逻辑,它就在ImplicitlyAnimatedWidgetState的另一个子类AnimatedWidgetBaseState中。

    abstract class AnimatedWidgetBaseState extends ImplicitlyAnimatedWidgetState {
      @override
      void initState() {
        super.initState();
        controller.addListener(_handleAnimationChanged);
      }
    
      void _handleAnimationChanged() {
        setState(() { /* The animation ticked. Rebuild with new animation value */ });
      }
    }
    

    这个类非常简单,不言而喻,不再赘述。

  • AnimatedWidget

    有人说我想要完全自定义的AnimationController和Tween,只是Listener调用State部分封装就好了,那么有没有这样的封装类呢?答案是有的,他就是AnimatedWidget。

    AnimatedWidget的State是_AnimatedState:

    @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.
      });
    }
    

    可以看到只是把刷新UI回调这一步给封装了。

  • AnimatedBuilder

    在上面的例子中,调用setState方法都会引起整个组件树的构建,出于性能考虑,有了AnimatedBuilder。

    class AnimatedBuilder extends AnimatedWidget {
      /// Creates an animated builder.
      ///
      /// The [animation] and [builder] arguments must not be null.
      const AnimatedBuilder({
        Key? key,
        required Listenable animation,
        required this.builder,
        this.child,
      }) : assert(animation != null),
           assert(builder != null),
           super(key: key, listenable: animation);
    
      /// Called every time the animation changes value.
      final TransitionBuilder builder;
    
      final Widget? child;
    
      @override
      Widget build(BuildContext context) {
        return builder(context, child);
      }
    }
    

    可以看到,AnimatedBuilder是继承自AnimatedWidget的,所以它也可以自动刷新,只不过多了个builder函数参数,build方法会返回它的返回值,这就把构建范围缩小到了应用动画的组件范围,调用setState时就不会调用更上层组件的build方法了,极大的节省了渲染效率。

  • 总结

    经过源码的阅读,我们知道了Flutter是怎样通过封装简化我们的动画使用步骤的,继承自ImplicitlyAnimatedWidget保证了我们的State必须是ImplicitlyAnimatedWidgetState类型的,为什么不是AnimatedWidgetBaseState呢?我想是为了灵活性,因为ImplicitlyAnimatedWidgetState里封装的逻辑都是必须要有的通用逻辑,而刷新UI可能不需要(虽然我想不出应用场景...),当然我们如果想要应用动画通常继承AnimatedWidgetBaseState就好。另外,ImplicitlyAnimatedWidget也会保证ImplicitlyAnimatedWidgetState中一些必须初始化的属性一定有值,比如curve。

    总之,ImplicitlyAnimatedWidgetState中涵盖了AniamtionController、CurveAnimation、Tween所有的可能需要的动画配置,我们只需要传递对应的参数就好;而AnimatedWidgetBaseState中封装了动画执行过程中刷新UI的逻辑。

你可能感兴趣的:(Flutter动画简化类)