美文网首页Flutter
Flutter动画简化类

Flutter动画简化类

作者: 就叫汉堡吧 | 来源:发表于2022-03-11 11:49 被阅读0次
    • 概述

      在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<ImplicitlyAnimatedWidget> 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<double> get animation => _animation;
      late Animation<double> _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<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> 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<dynamic> visitor);
      

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

      @override
      void forEachTween(TweenVisitor<dynamic> 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<dynamic>? tween, dynamic targetValue, TweenConstructor<dynamic> 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<dynamic> tween, dynamic targetValue) {
        return targetValue != (tween.end ?? tween.begin);
      }
      

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

      再看 _updateTween方法:

      void _updateTween(Tween<dynamic>? 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<T extends ImplicitlyAnimatedWidget> extends ImplicitlyAnimatedWidgetState<T> {
        @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动画简化类

        本文链接:https://www.haomeiwen.com/subject/yyblrrtx.html