(Flutter)交错动画【译】

交错动画

你将学习到什么:

  • 交错动画由序列或重叠的动画组成。
  • 要创建交错动画,使用多个动画对象。
  • 一个AnimationController控制所有动画。
  • 每个动画对象在间隔期间指定动画。
  • 对于要设置动画的每个属性,请创建一个Tween。

Terminology: 如果补间或补间的概念对您来说是新的,请参阅Flutter教程中的动画

交错的动画是一个直截了当的概念:视觉变化发生在一系列操作中,而不是一次性发生。 动画可能是纯粹顺序的,在下一个动画之后会发生一次更改,或者可能部分或完全重叠。 它也可能有间隙,没有发生变化。

本指南介绍了如何在Flutter中构建交错动画。

Examples

本指南介绍了basic_staggered_animation示例。您还可以参考更复杂的示例staggered_pic_selection。
basic_staggered_animation

显示单个窗口小部件的一系列连续和重叠动画。 点击屏幕会开始一个动画,可以改变不透明度,大小,形状,颜色和填充。

staggered_pic_selection

显示从以三种尺寸之一显示的图像列表中删除图像。此示例使用两个动画控制器: 一个用于图像选择/取消选择,另一个用于图像删除。 选择/取消选择动画是错开的。 (要查看此效果,您可能需要增加timeDilation 值。) 选择一个最大的图像,它会缩小,因为它在蓝色圆圈内显示一个复选标记。 接下来,选择一个最小的图像,当复选标记消失时,大图像会扩展。 在大图像完成展开之前,小图像会缩小以显示其复选标记。 这种交错行为类似于您在Google相册中看到的行为。

以下视频演示了basic_staggered_animation执行的动画:


StaggerDemo.gif

在视频中,您会看到单个小部件的以下动画,该小部件以带有略微圆角的边框蓝色方块开始。 该方块按以下顺序运行更改:

  1. 淡入
  2. 扩大
  3. 向上移动时变得更高
  4. 转变为有边界的圆圈
  5. 将颜色更改为橙​​色

向前跑之后,动画反向运行。

Flutter新手?
本页假定您知道如何使用Flutter的小部件创建布局。 有关更多信息,请参阅在Flutter中构建布局.

交错动画的基本结构

重点是什么?

  • 所有动画都由同一个AnimationController驱动。
  • 无论动画实时持续多长时间,控制器的值必须介于0.0和1.0之间。
  • 每个动画的间隔介于0.0和1.0之间。
  • 对于在间隔中设置动画的每个属性,请创建一个Tween。 Tween指定该属性的开始值和结束值。
  • Tween生成一个由控制器管理的Animation对象。

下图显示了basic_staggered_animation示例中使用的间隔。 您可能会注意到以下特征:

  • 不透明度在时间轴的前10%期间发生变化。
  • 不透明度的变化与宽度的变化之间存在微小的差距。
  • 在最后25%的时间线中没有任何动画。
  • 增加填充使小部件看起来向上。
  • 将边框半径增加到0.5,将带圆角的方形转换为圆形。
  • 填充和边界半径变化发生在相同的精确间隔期间,但它们不必。
StaggeredAnimationIntervals.png

要设置动画:

  • 创建一个管理所有动画的AnimationController。
  • 为每个动画属性创建一个Tween。
    • Tween定义了一系列值。
    • Tween的animate方法需要parent控制器,并为该属性生成一个Animation。
  • 在动画curve属性上指定间隔。

当控制动画的值更改时,新动画的值会更改,从而触发UI更新。

以下代码为width属性创建补间。 它构建一个CurvedAnimation ,指定一个缓和的曲线。 有关其他可用的预定义动画曲线,请参阅曲线

width = Tween<double>(
  begin: 50.0,
  end: 150.0,
).animate(
  CurvedAnimation(
    parent: controller,
    curve: Interval(
      0.125, 0.250,
      curve: Curves.ease,
    ),
  ),
),

beginend 的值不必是双倍的。下面的代码使用BorderRadius.circular()borderRadius属性(控制方块角的圆度)构建补间。

borderRadius = BorderRadiusTween(
  begin: BorderRadius.circular(4.0),
  end: BorderRadius.circular(75.0),
).animate(
  CurvedAnimation(
    parent: controller,
    curve: Interval(
      0.375, 0.500,
      curve: Curves.ease,
    ),
  ),
),

完成交错的动画

与所有交互式小部件一样,完整的动画由小部件对组成:无状态小部件和有状态小部件。

无状态窗口小部件指定补间,定义Animation对象,并提供build()函数,负责构建窗口小部件树的动画部分。

有状态小部件创建控制器,播放动画,并构建小部件树的非动画部分。 在屏幕中的任何位置检测到点击时,动画开始。

basic_staggered_animation’s main.dart的完整代码

无状态小部件:StaggerAnimation

在无状态小部件StaggerAnimation中, build()函数实例化一个AnimatedBuilder - 一个用于构建动画的通用小部件。 AnimatedBuilder构建一个小部件并使用Tweens的当前值配置它。 该示例创建一个名为_buildAnimation()的函数(执行实际的UI更新),并将其分配给其builder属性。 AnimatedBuilder监听来自动画控制器的通知,在值发生变化时将小部件树标记为脏。 对于动画的每个刻度,值都会更新,从而调用_buildAnimation()

class StaggerAnimation extends StatelessWidget {
  StaggerAnimation({ Key key, this.controller }) :

    // Each animation defined here transforms its value during the subset
    // of the controller's duration defined by the animation's interval.
    // For example the opacity animation transforms its value during
    // the first 10% of the controller's duration.

    opacity = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.0, 0.100,
          curve: Curves.ease,
        ),
      ),
    ),

    // ... Other tween definitions ...

    super(key: key);

  final Animation<double> controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  final Animation<EdgeInsets> padding;
  final Animation<BorderRadius> borderRadius;
  final Animation<Color> color;

  // This function is called each time the controller "ticks" a new frame.
  // When it runs, all of the animation's values will have been
  // updated to reflect the controller's current value.
  Widget _buildAnimation(BuildContext context, Widget child) {
    return Container(
      padding: padding.value,
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: height.value,
          decoration: BoxDecoration(
            color: color.value,
            border: Border.all(
              color: Colors.indigo[300],
              width: 3.0,
            ),
            borderRadius: borderRadius.value,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: _buildAnimation,
      animation: controller,
    );
  }
}

有状态小部件:StaggerDemo

有状态小部件StaggerDemo创建了AnimationController(规定他们的对象),指定持续时间为2000毫秒。 它播放动画,并构建小部件树的非动画部分。 在屏幕中检测到点击时动画开始。 动画向前,然后向后。

class StaggerDemo extends StatefulWidget {
  @override
  _StaggerDemoState createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin {
  AnimationController _controller;

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

    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this
    );
  }

  // ...Boilerplate...

  Future<Null> _playAnimation() async {
    try {
      await _controller.forward().orCancel;
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // the animation got canceled, probably because we were disposed
    }
  }

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0; // 1.0 is normal animation speed.
    return Scaffold(
      appBar: AppBar(
        title: const Text('Staggered Animation'),
      ),
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: Center(
          child: Container(
            width: 300.0,
            height: 300.0,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.1),
              border: Border.all(
                color:  Colors.black.withOpacity(0.5),
              ),
            ),
            child: StaggerAnimation(
              controller: _controller.view
            ),
          ),
        ),
      ),
    );
  }
}

资源

编写动画时,以下资源可能会有所帮助:

Animations landing page

列出Flutter动画的可用文档。 如果补间对您来说不熟悉,请查看动画教程

Flutter API documentation

所有Flutter库的参考文档。 特别是,请参阅动画库文档。

Flutter Gallery

演示应用程序展示了许多材料组件和其他Flutter功能。 Shrine demo实现了英雄动画。

Material motion spec

描述材料应用的动作。

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

推荐阅读更多精彩内容

  • 天空灰的发白 就像兔子的尾巴 冲出象牙之口 想象没有办法舒展 龙川之水 流淌着工业的源头 仪器与价值的比重 颠覆嘈...
    读叔阅读 416评论 0 1
  • 清晨,开车途中,听起了《蒋勋七讲》中的第一讲,每天留出18分钟来读诗。 于是,我听到了这首月圆夜,蒋勋读的诗: 《...
    蜕变的林林阅读 243评论 0 1
  • 什么时候都不会太迟 老了,也能和伴侣一起爬山,一起唠嗑。 想要出发永远不会太迟。 三五好友,不用一壶酒,不用...
    二十二秋初阅读 366评论 0 0