animated_widgets源码阅读

平移动画/补间动画:TranslationAnimatedWidget

TranslationAnimatedWidget是一个继承于StatefulWidget的widget。

1、widget

包含成员变量如下:

  final List<Offset> _values;
  final Duration duration;
  final Duration delay;
  final bool enabled;
  final Curve curve;
  final Widget child;
  final Function(bool) animationFinished;
  • duration:动画持续的时间
  • delay:动画开始前的延迟时间
  • enabled:true为正向执行动画,false为反向执行动画。对应于forward/reverse
  • curve:动画执行的曲线,常见的有:ease、easeIn、linear等。更多类型可参考官方文档
  • child:执行动画的widget
  • animationFinished:动画执行完毕的回调方法
  • _values:widget偏移量数组,放的内容是Offset对象

注意:在flutter中,变量或函数前面加_表示这是个私有的变量或函数,外部不能访问。

TranslationAnimatedWidget中包含一个tween方法和一个构造方法,并赋予了某些初始值。
代码:

TranslationAnimatedWidget.tween({
    Duration duration = const Duration(milliseconds: 500),
    Duration delay = const Duration(),
    Offset translationDisabled = const Offset(0, 200),
    Offset translationEnabled = const Offset(0, 0),
    bool enabled = true,
    Function(bool) animationFinished,
    Curve curve = Curves.linear,
    @required Widget child,
  }) : this(
          duration: duration,
          enabled: enabled,
          curve: curve,
          delay: delay,
          child: child,
          animationFinished: animationFinished,
          values: [translationDisabled, translationEnabled],
        );

TranslationAnimatedWidget({
    this.duration = const Duration(milliseconds: 500),
    this.delay = const Duration(),
    List<Offset> values = const [const Offset(0, 0), const Offset(0, 200)],
    this.enabled = true,
    this.curve = Curves.linear,
    this.animationFinished,
    @required this.child,
  })  : this._values = values,
        assert(values.length > 1);

此外,在TranslationAnimatedWidget中还声明了一个对外的values,即_values,还有一个方法来判断两次的动画是否相等。

bool isAnimationEqual(TranslationAnimatedWidget other) =>
      listEquals(values, other.values) &&
      duration == other.duration &&
      curve == other.curve &&
      delay == other.delay;

最后就是重写createState方法。

2、state部分

先看代码:

class _State extends State<TranslationAnimatedWidget>
    with TickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _translationXAnim;
  Animation<double> _translationYAnim;

  @override
  void didUpdateWidget(TranslationAnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isAnimationEqual(oldWidget)) {
      if (widget.enabled != oldWidget.enabled) {
        _updateAnimationState();
      }
    } else {
      _createAnimations();
      if (widget.enabled != oldWidget.enabled) {
        _updateAnimationState();
      }
    }
  }

  void _updateAnimationState() async {
    if (widget.enabled ?? false) {
      await Future.delayed(widget.delay);
      _animationController.forward();
    } else {
      _animationController.reverse();
    }
  }

  void _createAnimations() {
    _animationController?.dispose();
    _animationController = AnimationController(
      duration: widget.duration,
      vsync: this,
    )..addStatusListener((status) {
        if (widget.animationFinished != null) {
          widget.animationFinished(widget.enabled);
        }
      });

    _translationXAnim = chainTweens(
      widget.values.map((it) => it.dx).toList(),
    ).animate(
      CurvedAnimation(parent: _animationController, curve: widget.curve),
    )..addListener(() {
        setState(() {});
      });

    _translationYAnim = chainTweens(
      widget.values.map((it) => it.dy).toList(),
    ).animate(
      CurvedAnimation(parent: _animationController, curve: widget.curve),
    )..addListener(() {
        setState(() {});
      });

    _updateAnimationState();
  }

  @override
  void initState() {
    _createAnimations();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Transform.translate(
      offset: Offset(_translationXAnim.value, _translationYAnim.value),
      child: widget.child,
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

看到了这么个东西TickerProviderStateMixin,这是个啥?先继续往下看,稍候解释。

首先声明了三个变量:_animationController_translationXAnim_translationYAnim,看名字就可以理解,分别是控制动画的控制器、动画过程中x的值和y的值,因为位移动画是一个平面上的动画,只涉及到了横纵坐标。

再往下是重写的一个方法:didUpdateWidget,该方法在组件的状态改变的时候就会被调用。在这个方法中做了些判断:首先是调用在上面定义的方法来判断状态改变前后的两个widget是否相等,相等时判断两个widget的enabled是否一样,不一样则说明发生了改变,调用另一个函数_updateAnimationState()。如果不想等,则通过_createAnimations()重新创建,然后再比较enabled的值。逻辑比较简单。

下面是刷新动画状态的方法:_updateAnimationState。改方法是一个异步函数,首先判断了当前widget的enabled,在第一部分也说过为true则正向执行动画,即_animationController.forward();;为false则反向执行动画,即_animationController.reverse();。这里有一个??的操作符,这个操作符的作用是给前面的变量赋予一个初始值,如果该变量为null的话。还有一个在_animationController.forward();之前做了一个延时,而反向则没有延时,立即执行。

接着就是_createAnimations方法,顾名思义就是创建动画。在这里面对上面生命的变量进行了初始化,并赋值。先看第一行,调用了dispose方法。这么做是为了避免重复创建。可以看到在_animationController后面跟着一个?,这是为了防止_animationController为null的情况,若为null,则不执行后面的操作。

下面是创建的具体代码。看到了一个陌生的参数:vsync,并赋值为this。官网上有相关说明:

存在vsync时会防止屏幕外动画消耗不必要的资源。vsync对象会绑定动画的定时器到一个可视的widget,所以当widget不显示时,动画定时器将会暂停,当widget再次显示时,动画定时器重新恢复执行,这样就可以避免动画相关UI不在当前屏幕时消耗资源。 如果要使用自定义的State对象作为vsync时,请包含TickerProviderStateMixin。
这里就回答上面的疑问。实际上还有另外一个关键词:SingleTickerProviderStateMixin,这个跟TickerProviderStateMixin有什么关系呢?这个官网上没有找到,不过在《Flutter实战》这本书中有一句话,是这么说的:如果有多个AnimationController,则应该使用TickerProviderStateMixin。链接如下: 动画基本结构及状态监听

后面有一个奇怪的符号:..,这是级联操作符,改操作符可以对同一对象执行一系列操作,能够节省中间步骤和临时变量,让代码更高效。这部分代码等价于下面的代码:

_animationController.addStatusListener((status) {
      if (widget.animationFinished != null) {
        widget.animationFinished(widget.enabled);
      }
    });

可以看到这是给_animationController添加了一个状态的监听,若widget.animationFinished不为null,则将当前状态通过回调函数传递回去。

再往下则是对另外两个边路进行了初始化,通过调用chainTweens方法返回一个TweenSequence对象。TweenSequence<T>是一个继承与Animatable<T>的类,跟Tween的意义是相近的。不过TweenSequence可以实现多组补间动画,而tween只能实现一组。
方法实现如下:

TweenSequence chainTweens(List<double> values) {
  if (values.length < 2) {
    return TweenSequence<double>([]);
  }

  List<TweenSequenceItem<double>> items = List<TweenSequenceItem<double>>();

  var lastValue = values[0];
  for (int i = 1; i < values.length; ++i) {
    double newValue = values[i];
    items.add(TweenSequenceItem<double>(
        tween: Tween(begin: lastValue, end: newValue), weight: 1));
    lastValue = newValue;
  }

  return TweenSequence<double>(items);
}

传入一个double类型的数组,若数组个数小于2,则返回一个空的double类型的TweenSequence对象。主体内容是一个for循环,在循环中创建一个TweenSequenceItem对象并添加到数组中,最后将数组传入TweenSequence并返回。TweenSequenceItem中包含一个Tween对象和weight,tween提供补间动画的初始值,weight标识该动画的权重,在整个动画持续时间内,当前的tween所占的时间比。
此外,还调用了animate()方法,传入了一个控制器对象,并添加了监听,在监听中刷新当前的state。最后调用_updateAnimationState()方法。

最后就是重写了三个系统的方法,在initState()中调用_createAnimations(),在build方法中调用系统的平移动画方法,并将参数传递进去,在dispose()中释放_animationController

其他动画

其他的动画的实现方式跟上述基本一致,主要的不同之处就是build方法中调用的系统动画方法的不同,所以不再单独进行描述。
唯一不同的是CustomAnimatedWidget,这个是在build里面返回了一个builder函数,在函数中传递了一个从0到1的percent,需要在执行动画的widget中标明显示的内容。


终于结束了,菜鸟,勿喷。😀

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342