• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

implicit_animations.dart阅读

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

这个文件定义了Flutter很多的基础动画,主要是基于Container的各个属性的动画封装,它的实现原理根据定义的时间曲线获取对应的补间值, 然后不断的更新widget的配置信息(如果有变动), 在vsync扫描时就会不断的更新每一帧的界面
连在一起看就成了动画效果, 实现它需要有时间函数曲线, 补间值, vsync同步信号

1.视图更新的同步信号(Vsync)

  • Vsync信息由flutter engine提供, 当前的widget只需要with一个 TickerProviderStateMixin 类, 就能监听到系统的vsync信号, 以下面的 SingleTickerProvider 为栗子, 它直接控制和监听 SchedulerBinding.instance 来实现vsync的回调事件转发给AnimationController

    mixin SingleTickerProviderStateMixin<T extends StatefulWidget> on State<T> implements TickerProvider {
      //创建ticker,这里创建了一个tiker,而 `TickerProvider` 是个抽象类,所以vsync肯定和它有关
    @override
    Ticker createTicker(TickerCallback onTick) { ...
    _ticker = Ticker(onTick, debugLabel: kDebugMode ? 'created by $this' : null);
    @override
    void didChangeDependencies() { ...
    _ticker.muted = !TickerMode.of(context);
    }
    class Ticker {
    //Ticker的初始化只有一个参数,没有继承其他的内
    Ticker(this._onTick, { this.debugLabel }) {
    //控制ticker的事件队列,确保次序正确
    TickerFuture _future;
    //决定当前ticker是否只是监听vsync不执行callback,避免视图不现实的内存开销
    set muted(bool value) {
    isMuted ...
    unscheduleTick();
    shouldScheduleTick ...
    scheduleTick();
    //开启一个定时刷新,如果当前能执行动画(!isMetued)则执行它的callback事件
    TickerFuture start() {
    ...
    if (shouldScheduleTick) {
    scheduleTick();
    ...
    return _future;
    }
    void stop({ bool canceled = false }) { ...
    unscheduleTick();
    if (canceled) {
    localFuture._cancel(this);
    } else {
    localFuture._complete();
    }
    }
    //记录animationId,避免重复schedule
    @protected
    bool get scheduled => _animationId != null;
    //执行tick的三要素
    @protected
    bool get shouldScheduleTick => !muted && isActive && !scheduled;
    //
    void _tick(Duration timeStamp) {..
    _onTick(timeStamp - _startTime);
    if (shouldScheduleTick)
    scheduleTick(rescheduling: true);
    }
    //最终将tick事件发送给 `SchedulerBinding.instance` ,持续定于window的每一帧
    @protected
    void scheduleTick({ bool rescheduling = false }) { ...
    _animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
    }
    @protected
    void unscheduleTick() { ...
    SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId);
    }
    //用于接管另外一个ticker,释放原来的ticker,使用自己的ticker事件
    void absorbTicker(Ticker originalTicker) {...
    if (shouldScheduleTick)
    scheduleTick();
    originalTicker._future = null; // so that it doesn't get disposed when we dispose of originalTicker
    originalTicker.unscheduleTick();
    }
    originalTicker.dispose();
    }

2. 动画的时间曲线

  • 通过Widget初始化时构造一个AnimationController, 它接收一个总的时常, 同是提供了ticker构造工厂函数, 而ticker可以监听vsync信号,
  • 通过监听vsync事件和frameCallback的duration, 再结合动画的时间区间, 计算出每一帧绘制时的时间, 这样就生成一个动画的时间曲线
  • 除了生成时间曲线以外, 它还提供了动画控制的能力, 比如重复开始, 反转, 每执行一个操作它都会去从新生成一个当前的时间值, 确保动画的进度按照代码设定的逻辑执行

    class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo>
        with SingleTickerProviderStateMixin {
    AnimationController _animationController;
    @override
    void initState() {
    super.initState();
    _animationController = AnimationController(
    vsync: this,
    duration: Duration(seconds: 2),
    )..repeat();
    }
    //在AnimationController创建的时候,会创建ticker
    class AnimationController ...
    AnimationController({ ...
    _ticker = vsync.createTicker(_tick);
    ...
    }
    void _tick(Duration elapsed) { ...
    //事件比例计算,也就是当前的动画进度
    _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound) as double;
    notifyListeners();
    _checkStatusChanged();
    }
    //重复执行
    TickerFuture repeat({ double min, double max, bool reverse = false, Duration period }) {
    ...
    stop();
    return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period, _directionSetter));
    }
    //开始执行动画
    TickerFuture forward({ double from }) { ...
    _direction = _AnimationDirection.forward;
    return _animateToInternal(upperBound);
    }
    TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear }) {...
    stop(); ...
    return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
    }
    TickerFuture _startSimulation(Simulation simulation) { ...
    _value = simulation.x(0.0).clamp(lowerBound, upperBound) as double;
    final TickerFuture result = _ticker.start();
    _checkStatusChanged();
    return result;
    }
    //可以看到AnimationController对动画的控制最终都会传递给 `Ticker` ,它主要负责时间进度的计算,计算完毕后根据代码设定的逻辑通知ticker是否需要监听下一帧的回调事件。
  • 以下为Flutter定义的Curve曲线,用于适配X,Y之间的比例关系

    ParametricCurve (curves.dart)
        _BottomSheetSuspendedCurve (scaffold.dart)
    _BottomSheetSuspendedCurve (bottom_sheet.dart)
    Curve2D (curves.dart)
    CatmullRomSpline (curves.dart)
    Curve (curves.dart)
    FlippedCurve (curves.dart)
    _Linear (curves.dart)
    ElasticInOutCurve (curves.dart)
    SawTooth (curves.dart)
    Cubic (curves.dart)
    ElasticOutCurve (curves.dart)
    Interval (curves.dart)
    ElasticInCurve (curves.dart)
    _BounceInOutCurve (curves.dart)
    CatmullRomCurve (curves.dart)
    _BounceOutCurve (curves.dart)
    Threshold (curves.dart)
    _DecelerateCurve (curves.dart)
    _BounceInCurve (curves.dart)

3. 补间值

  • 它代表的是动画在每一帧渲染时, Widget(RenderObject的最终提交渲染信息)的属性的值, 简单的动画一般只有开始和结束2个值,需要由时间曲线函数动画的起始值共同作用来完成
  • 动画过程也是线性的, 随着时间的推移, 按线性规律逐步完成的, 为了动画过渡的更加完美, 需要额外对补间值进行加工, 在保证时间因子是线性移动的前提下, 通过引入Curve函数, 每个时间节点的 属性值按照重新计算, 这样我们的补间值就变得丰富多样。
  • 按照这个规律我们可以把线性的补间值变成各种变化规律的补间值
    在flutter仓库的源码中定义了很多的动画

  • 补间值转换, 在flutter的动画设计中可以把它看作是一个以时间为 X 轴, 动画进度为 Y 的函数, 函数的具体实现都封装在 transform方法中

  • 基于此规律我们可以定义自己Curve, 生成自己的曲线函数, 这样就能实时的调整动画的补间值, 以下为Flutter目前现有的一些Curve,基本上满足了绝大部分应用场景。

  • 补间值的具体类, 基本上满足了绝大部分的场景, 直接就能使用

    Animatable (tween.dart)
        Tween (tween.dart)
    AlignmentTween (tweens.dart)
    FractionalOffsetTween (tweens.dart)
    AlignmentGeometryTween (tweens.dart)
    RelativeRectTween (transitions.dart)
    Matrix4Tween (implicit_animations.dart)
    BoxConstraintsTween (implicit_animations.dart)
    EdgeInsetsTween (implicit_animations.dart)
    DecorationTween (implicit_animations.dart)
    BorderTween (implicit_animations.dart)
    BorderRadiusTween (implicit_animations.dart)
    TextStyleTween (implicit_animations.dart)
    EdgeInsetsGeometryTween (implicit_animations.dart)
    ThemeDataTween (theme.dart)
    _FontWeightTween (sliding_segmented_control.dart)
    ShapeBorderTween (material.dart)
    _InputBorderTween (input_decorator.dart)
    MaterialPointArcTween (arc.dart)
    ReverseTween (tween.dart)
    StepTween (tween.dart)
    SizeTween (tween.dart)
    ColorTween (tween.dart)
    ConstantTween (tween.dart)
    IntTween (tween.dart)
    RectTween (tween.dart)
    MaterialRectCenterArcTween (arc.dart)
    MaterialRectArcTween (arc.dart)

示例Demo

  • 下面以最常用的 AnimatedContainer 为例, 通过它来构造一个动画, 下面是一个简易的Demo,
    通过点击手势来触发ValueListenableBuilder重新构建, 切换动画的的初始值

AnimatedContainer示例

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class AnimatedContainerDemo extends StatelessWidget {
  final CheckStatusListener statusListener = CheckStatusListener(true);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        statusListener.value = !statusListener.value;
      },
      child: ValueListenableBuilder<bool>(
          valueListenable: statusListener,
          builder: (BuildContext context, bool selected, Widget child) {
            return Center(
              child: AnimatedContainer(
                width: selected ? 200.0 : 100.0,
                height: selected ? 100.0 : 200.0,
                color: selected ? Colors.red : Colors.blue,
                alignment: selected
                    ? Alignment.center
                    : AlignmentDirectional.topCenter,
                duration: Duration(seconds: 2),
                curve: Curves.fastOutSlowIn,
                child: FlutterLogo(size: 75),
              ),
            );
          }),
    );
  }
}

class CheckStatusListener extends ValueNotifier<bool>
    implements ValueListenable<bool> {
  CheckStatusListener(bool isSelected) : super(isSelected);
}

AnimatedContainer代码实现

  • 从构造方法就可以看出它支持设置多种不同的补间值

    class AnimatedContainer extends ImplicitlyAnimatedWidget {
    AnimatedContainer({
    Key key,
    this.alignment,
    this.padding,
    Color color,
    Decoration decoration,
    this.foregroundDecoration,
    double width,
    double height,
    BoxConstraints constraints,
    this.margin,
    this.transform,
    this.child,
    Curve curve = Curves.linear,
    @required Duration duration,
    VoidCallback onEnd,
    })
    ...
    class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {
    AlignmentGeometryTween _alignment;
    EdgeInsetsGeometryTween _padding;
    DecorationTween _decoration;
    DecorationTween _foregroundDecoration;
    BoxConstraintsTween _constraints;
    EdgeInsetsGeometryTween _margin;
    Matrix4Tween _transform;
    //它的父类也是一个空壳,只是包装了setState方法,这个方法会触发这个widget及children重建,慎用,不要将复杂过多的逻辑用这种方案时间,一般使用与小部件的widget,继续向上查找它的Ancestor
    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 */ });
    }
    }
    //父类实现了tickerProvider协议,可以通过Tiker发送监听屏幕刷新事件,同时也持有了`AnimationController`用于控制基本的动画行为
    abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> {
    @protected
    AnimationController get controller => _controller;
    Animation<double> _animation;
    @override
    void initState() {
    super.initState();
    _controller = AnimationController( ...
    _controller.addStatusListener((AnimationStatus status) { ...
    _updateCurve(); //这里将 Curve函数和时间关联在一起,它和Curve将Tween关联在一起是一样的,因为最终都是通过3个数的乘积的到最终补间值
    _constructTweens(); //初始化设置子类的Tween
    didUpdateTweens(); //供子类在视图更新时刷新Tween
    }
    @override
    void didUpdateWidget(T oldWidget) {
    super.didUpdateWidget(oldWidget);
    ...
    _updateCurve();
    _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自带的curve函数,更新动画的时间进度,间接修改补间值
    void _updateCurve() { ...
    _animation = CurvedAnimation(parent: _controller, curve: widget.curve);
    //自动更新补间动画值
    void _updateTween(Tween<dynamic> tween, dynamic targetValue) {
    ..begin = tween.evaluate(_animation)
    ..end = targetValue;
    }
    //充分利多态的特性,实现子类Tween的构造,并执行
    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;
    }
    //子类通过重写这个方法
    //1. 子类传递 `Tween<T> tween, T targetValue,`给父类,举个栗子,Tween<T> tween就是子类的 AlginmentTween,targetValue就是Aliginment.topLeft
    //2. 父类调用`constructor`方法,子类生成对应的AlginmentTween,这样子类就持有了AlginmentTween,在子类的build方法中获取父类的`animation`,就能实时的改变Contianer相关的属性了(此处的tween回传给父类主要是为了检测动画进度)
    //typedef TweenVisitor<T> = Tween<T> Function(Tween<T> tween, T targetValue, TweenConstructor<T> constructor);
    @protected
    void forEachTween(TweenVisitor<dynamic> visitor);
    //用于hook子视图在widgetupdate时更新补间值
    @protected
    void didUpdateTweens() { }
    }
  • 子类_AnimatedContainerState在每次构建中更新当前的补间值

    class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> {...
      @override
    Widget build(BuildContext context) {
    return Container(
    child: widget.child,
    alignment: _alignment?.evaluate(animation),
    padding: _padding?.evaluate(animation),
    decoration: _decoration?.evaluate(animation),
    foregroundDecoration: _foregroundDecoration?.evaluate(animation),
    constraints: _constraints?.evaluate(animation),
    margin: _margin?.evaluate(animation),
    transform: _transform?.evaluate(animation),
    );
    }
  • 创建Tween的具体实现在父类初始化是触发forEachTween来创建子类的Tween

    ``` dart
    abstract class ImplicitlyAnimatedWidgetState<T extends ImplicitlyAnimatedWidget> extends State<T> with SingleTickerProviderStateMixin<T> { ...
    void initState() { ...
    _constructTweens(); -> forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
    void didUpdateWidget(T oldWidget) {...
    forEachTween((Tween<dynamic> tween, dynamic targetValue, TweenConstructor<dynamic> constructor) {
    子类重写`forEachTween`,传入当前的`_alignmentTween`(父类动画进度检查用)和`widget.alignmentValue`
    class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> { ...
    @override
    void forEachTween(TweenVisitor<dynamic> visitor) {
    _alignment = visitor(_alignment, widget.alignment, (dynamic value) =>
    AlignmentGeometryTween(begin: value as AlignmentGeometry)) as AlignmentGeometryTween;//用于父类构造通过Tween开启动画用
    ...
    }
    ```

小结

  • 理解AnimatedContainer动画的封装主要是要了解forEachTween的执行过程,它是一个嵌套函数,同时实现了父子协同工作的方式, AnimationController通过持有Ticker,可以很方便的注册和取消系统下一帧的回调,实时的计算当前的动画进度
  • 动画的三要素:

    • Vsync信号,视图的刷新周期
    • Tween: 动画的补间值
    • TimeFunction: 时间函数,动画在某个时刻内它的动画进度值
  • 复杂的动画都是基于动画的 补间值和动画时间函数计算得来的.


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Dart中的final和const 及 ??= 用法发布时间:2022-07-13
下一篇:
Flutter(Dart)发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap