如果我们想要一些东西动画,我们必须改变大小或改变连续帧中对象的位置。例如,在第1帧中,我们的对象位于位置x,在第2帧中,它将位于x + 1的位置,在第3帧中,它位于x + 2的位置,依此类推。

创建动画时的另一个概念是“每秒帧数”或FPS。我们想要每秒更改对象的位置或大小多少次?电影通常每秒使用24帧。这是人类眼睛看起来光滑自然的动画的最小数量。

frc fd6c67063e6372225503b308a4ef5cf6 - Flutter - 了解Flutter中的动画

FPS(图片来源)

为了在Flutter中为小部件设置动画,我们需要以下小部件:

  1. Animation:动画对象由值(类型T)和状态组成。该值类似于当前帧编号。它会告诉您是否在第1,2,3等帧中。根据此值,您可以决定窗口小部件的下一个位置或大小。状态指示动画在概念上是从开始到结束还是从结束回到开始。
  2. AnimationController:要创建动画,首先要创建一个AnimationController。在给定的持续时间内,此小部件线性生成从0.0(下限)到1.0(上限)的值。只要运行应用程序的设备准备好显示新帧(通常,此速率大约为每秒60个值),动画控制器就会生成一个新值。一个AnimationController当不再需要它应该被设置。这减少了泄漏的可能性。当与StatefulWidget一起使用时,通常在State.initState方法中创建AnimationController ,然后将其放置在State.dispose中。方法。请注意,AnimationController继承了Animation类,因此属于Animation类型。
  3. Tween:这个类可用于将1AnimationController1的下界和上界(默认值为0.0到1.0)从开始到结束转换(或映射)到值。除非另有说明,否则吐Tween为double类型。补间的唯一工作是定义一个从输入范围到输出范围的映射。输入范围通常是0.0到1.0,但这不是必需的。
  4. TickerProvider:这是一个生成Ticker对象的工厂。Ticker对象为每个新帧触发一个事件。AnimationController类使用Ticker来逐步调整它控制的动画。我们可以通过使用SingleTickerProviderStateMixin 实现TickerProvider功能,将Ticker功能添加到我们的有状态类中。如果您不确定mixins是什么,请阅读本文。当您只需要一个Ticker对象时(例如,如果类在其整个生命周期内仅创建一个AnimationController),此mixin非常有用。
  5. AnimatedBuilder:很明显,每当我们改变小部件的大小或位置时,我们都想重新构建它。但是我们怎么做到的?这是AnimatedBuilder小部件派上用场的地方。我们给这个小部件提供动画,并告诉它在动画前进时要绘制什么。

好吧,让我们做一些实际的事。

首先,我们将在屏幕中间创建一个圆圈,并使其周期性地变大。

我们的主要功能运行应用程序并显示AnimatedCirclePage

main.dart

import 'package:animation/pages/animated_circle_page.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: AnimatedCirclePage(),
    );
  }
}

AnimatedCirclePage最初显示在页面中间的圆圈:

animated_circle_page.dart

import 'package:flutter/material.dart';

class AnimatedCirclePage extends StatefulWidget {
  @override
  _AnimatedCirclePageState createState() => _AnimatedCirclePageState();
}

class _AnimatedCirclePageState extends State {
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Animated Circle"),
      ),
      body: Center(
        child: Container(
          width: 50,
          height: 50,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.all(
              Radius.circular(25),
            ),
            color: Colors.red,
          ),
        ),
      ),
    );
  }
}
frc 73a846f70287a7ca75343f2a647406b1 - Flutter - 了解Flutter中的动画

现在让我们用动画放大这个圆圈。

在下面的代码中,我们添加了一个类型的成员AnimationController并在initState方法中实例化它。AnimationController需要两个参数:DurationTickerProvider

duration参数指定动画将持续多长时间,在我们的示例中,将需要1秒钟才能完成。

第二个参数被命名vsync为type TickerProvider。由于我们TickerProvider使用以下mixin为我们的类添加了功能:

with SingleTickerProviderStateMixin

该类的当前实例可以vsync作为TickerProvider:传递给参数:

vsync: this

因此我们将:

animated_circle_page.dart

class _AnimatedCirclePageState extends State
    with SingleTickerProviderStateMixin {
  
  AnimationController animationController;

  @override
  void initState() {
    super.initState();
    
    animationController = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this,
    );
    animationController.forward();
  }

调用该forward方法将启动动画并生成从0.0(下限)到1.0(上限)的值。但是我们如何消耗生成的价值呢?

使用AnimatedBuilder小部件!

animated_circle_page.dart

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Animated Circle"),
      ),
      body: AnimatedBuilder(
        animation: animationController,
        builder: (BuildContext context, Widget child) {
          final size = 100 * (animationController.value+1);
          return Center(
            child: Container(
              width: size,
              height: size,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.all(
                  Radius.circular(size/2),
                ),
                color: Colors.red,
              ),
            ),
          );
        },
      ),
    );
  }

AnimationBuilder的构造函数有三个参数:

  1. animation:我们在这里提供动画对象。请记住,AnimationController继承自Animation类。因此,我们的AnimationController属于Animation类型,可以传递给此参数。
  2. child:此可选参数是一个小部件,在动画期间不会更改,只创建一次(以提高性能)。它总是可以在构建器函数(下一个参数)中重用。
  3. builder:这是为动画的每个刻度调用的函数。在这里,我们可以决定在动画的下一帧中绘制什么。我们可以通过animation.value属性访问当前帧号。

一旦我们调用该animationController.forward()方法,我们的动画就会启动,并且将为动画的每个帧调用AnimatedBuilder的构建器方法。在每个帧中,值animationController.value将逐渐从0.0增加到1.0。我们可以利用这个值并根据它改变圆的宽度和高度:

final size = 100 * (animationController.value+1);

如您所见,当动画值为0.0时,圆的大小将为100,当值增加到1.0时,大小将更改为200.因此,我们必须看到以下动画,其中圆的大小从100变为100到200:

frc 8359ac196d21e90a4c13a256271e83a0 - Flutter - 了解Flutter中的动画

扩大圈

在上面的示例中,我们为所有动画值添加了1。实际上,我们需要将动画的范围从[0.0 … 1.0]更改为[1.0 … 2.0]。你还记得这Tween堂课对什么有用吗?它用于修改动画值。所以我们可以在这里使用Tween类将动画值从[0.0 … 1.0]映射到[1.0 … 2.0]。我们开始做吧:

animated_circle_page.dart

void initState() {
  super.initState();
  animationController = AnimationController(
    duration: Duration(
      seconds: 1,
    ),
    vsync: this,
  );
  animation = Tween(begin: 1.0, end: 2.0).animate(animationController);
  animationController.forward();
}

映射动画值实际上发生在以下行中:

animation = Tween(begin:1.0, end:2.0).animate(animationController);

我们创建一个实例Tween并指定beginend值。然后我们调用该animate方法并将动画对象传递给它。animate函数会返回一个新的animation对象,其值是从beginend。然后在AnimatedBuilder对象中,我们只需将animation属性设置为我们的新animation对象:

body: AnimatedBuilder(
  animation: animation,

并将圆的大小设置为:

final size = 100 * (animation.value);

反转动画

现在让我们做一些有趣的事情。一旦我们的动画完成,我们将反转动画(这次它的值从1变为0)。这将使圆圈再次变小。

我们怎么知道我们的动画是完整的?

通过监听AnimationStatus

我们可以为动画添加一个监听器,这样每次状态改变时,我们都会收到通知。

animated_circle_page.dart

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      duration: Duration(
        seconds: 1,
      ),
      vsync: this,
    );
    animation = Tween(begin: 1.0, end: 2.0).animate(animationController);

    animationController.addStatusListener(animationStatusListener);
    animationController.forward();
  }

  void animationStatusListener(AnimationStatus status) {
    if (status == AnimationStatus.completed) {
      animationController.reverse();
    } else if (status == AnimationStatus.dismissed) {
      animationController.forward();
    }
  }

只有在状态发生变化时才会调用我们的侦听器函数。动画有四种可能的状态:

  1. dismissed动画在开始时停止
  2. forward动画从头到尾运行
  3. reverse动画从头到尾向后运行
  4. completed动画在结束时停止

在上面的代码中,我们检查了状态。如果是completed,那意味着我们刚刚到达动画的末尾,所以我们调用reverse函数,向后播放动画。当状态变为dismissed,表示动画已经到达开头,所以我们forward再次打电话!这个循环将永远持续下去!

生成的动画是一个连续大小的圆圈:

frc 020b146c62e3f2afab111cc0b5be050d - Flutter - 了解Flutter中的动画

现在让我们做一些更有趣的事情。我们将围绕屏幕中心旋转这个圆圈!

rotating_circle_page.dart使用以下代码创建一个名为的新页面:

import 'package:flutter/material.dart';

class RotatingCirclePage extends StatefulWidget {
  @override
  _RotatingCirclePageState createState() => _RotatingCirclePageState();
}

class _RotatingCirclePageState extends State
    with SingleTickerProviderStateMixin {

  Widget _buildCircle(radius) {
    return Container(
      width: radius * 2,
      height: radius * 2,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.all(
          Radius.circular(radius),
        ),
        color: Colors.red,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Rotating Circle"),
      ),
      body: Align(
        alignment: Alignment(0, -0.1),
        child: _buildCircle(30.0),
      ),
    );
  }
}

为了使代码更具可读性,我创建了一个辅助函数_buildCircle,用于绘制具有给定半径的红色圆圈。而不是将圆圈居中,我将它对齐在页面中心的上方。(如果您不熟悉,请观看谷歌的这个简短视频以熟悉Align小部件)。结果是:

frc c01da33c0a2e940e0010f137256e67ec - Flutter - 了解Flutter中的动画

现在让我们像钟摆一样为这个圆圈制作动画:

animated_circle_page.dart

import 'package:flutter/material.dart';
import 'dart:math' as math;

class RotatingCirclePage extends StatefulWidget {
  @override
  _RotatingCirclePageState createState() => _RotatingCirclePageState();
}

class _RotatingCirclePageState extends State
    with SingleTickerProviderStateMixin {
  AnimationController animationController;
  Animation animation;

  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      duration: Duration(
        seconds: 2,
      ),
      vsync: this,
    );
    animation = CurvedAnimation(
      parent: animationController,
      curve: Curves.fastOutSlowIn,
    );
    animationController.addStatusListener(animationStatusListener);
    animationController.forward();
  }

  void animationStatusListener(AnimationStatus status) {
    if (status == AnimationStatus.completed) {
      animationController.reverse();
    } else if (status == AnimationStatus.dismissed) {
      animationController.forward();
    }
  }

  Widget _buildCircle(radius) {
    return Container(
      width: radius * 2,
      height: radius * 2,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.all(
          Radius.circular(radius),
        ),
        color: Colors.red,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Rotating Circle"),
      ),
      body: Align(
        alignment: Alignment(0, -0.1),
        child: AnimatedBuilder(
          child: _buildCircle(30.0),
          animation: animationController,
          builder: (BuildContext context, Widget child) {
            return Transform.rotate(
              child: child,
              angle: math.pi * 2 * animation.value,
              origin: Offset(0, 30),
            );
          },
        ),
      ),
    );
  }
}

让我解释一下上面代码的重要部分。

animation = CurvedAnimation(
  parent: animationController,
  curve: Curves.fastOutSlowIn,
);

默认情况下,AnimationController在给定的持续时间内线性生成从0.0到1.0的数字,因此动画无任何速度播放。如果我们想要改变动画的速度和样式,我们可以将它包装在CurvedAnimation小部件中:

当您想要将非线性曲线应用于动画对象时,CurvedAnimation非常有用,特别是当您想要动画前进时的曲线与后退时的曲线时。

请注意,可以首先在CurvedAnimation小部件中包装动画,然后使用Tween小部件转换其下限和上限,如下所示:

animation = Tween(begin: 5.0, end: 10.0).animate(
  CurvedAnimation(
    parent: animationController,
    curve: Curves.fastOutSlowIn,
  ),
);

在这里,我使用了曲线。fastOutSlowIn曲线,但您可以使用其他值并查看它们如何影响动画的速度和速度。

现在让我解释一下构建方法:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Rotating Circle"),
    ),
    body: Align(
      alignment: Alignment(0, -0.1),
      child: AnimatedBuilder(
        child: _buildCircle(30.0),
        animation: animationController,
        builder: (BuildContext context, Widget child) {
          return Transform.rotate(
            child: child,
            angle: math.pi * 2 * animation.value,
            origin: Offset(0, 30),
          );
        },
      ),
    ),
  );
}

我们已将该child属性设置AnimatedBuilder为圆圈。为什么?因为我们希望它只创建一次,而不是每一帧!(为了提高性能,我们不需要重建不随时间变化的动画部分。这里我们的圆圈大小在动画期间保持不变,所以我们只构建它一次并将它分配给孩子AnimatedBuilderbuilder每次绘制新帧时,该子项都可以在方法中重复使用。

builder我们动画的每一帧都会调用的方法中,我们习惯Transform.rotate了旋转圆圈。如果我们不指定origin参数,圆圈将围绕其自身的中心旋转(在这种情况下,由于圆圈围绕其自身的中心旋转,我们将看不到任何旋转!)。出于这个原因,我们将旋转中心设置为偏移(0,30)表示的点,该点是距离窗口小部件中心的x距离为0,y距离为30的点。请查看以下内容图片。圆圈现在将围绕标有X的原点旋转:

frc 00dc374dafe3bd7c269c24eef0ef28d4 - Flutter - 了解Flutter中的动画

旋转角度已设置为:

angle: math.pi * 2 * animation.value,

由于动画值从0变为1,旋转角度将从0变为,这等于完整的360°旋转。

重要说明:我没有包含_buildCircle(30.0)在Align小部件中。相反,我已经在Align小部件中包装了整个动画,这是我们的AnimatedWidget对象。那是因为我们只想旋转圆圈,而不是旋转它周围的空间!如果我们在Align小部件中包装了AnimatedBuilder的子节点,那么我们的圆圈周围会有一个额外的空间,这会导致我们的计算出错。我的全部观点是以下代码是错误的:

animated_circle_page.dart

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Rotating Circle"),
      ),
      body: AnimatedBuilder(
        child: Align(
          alignment: Alignment(0, -0.1),
          child: _buildCircle(30.0),
        ),
        animation: animationController,
        builder: (BuildContext context, Widget child) {
          return Transform.rotate(
            child: child,
            angle: math.pi * 2 * animation.value,
            origin: Offset(0, 30),
          );
        },
      ),
    );
  }

与上一个动画一样,当动画完成时,我们将其反转,使其从2π旋转回0 度。结果是以下动画:

frc 556f40432cc0048eb370d2c5da306487 - Flutter - 了解Flutter中的动画

圆周围原点旋转

在上一篇文章中,我们学习了如何绘制弯曲的虚线。我告诉过你关于创建以下动画的信息:

frc 90b9652ec12057c2d8135aeb71709be5 - Flutter - 了解Flutter中的动画

我将在github上的本文代码中包含上述动画的源代码。但我建议你自己创作作为家庭作业!请注意,我没有在弯曲的路径上移动碟子。我正在使用Transform.translate小部件在两条独立的直线上设置动画。Transform.translate可用于在绘制对象之前dxdy之前偏移对象。

摘要,回顾和最终说明

而已。你可以在这里停止阅读!我只想强调以下注释,我从Flutter提供的关于动画的三篇文章中抓取了这些文章(阅读这些文章很好):

  1. 动画概述
  2. 动画教程
  3. 动画简介

要创建动画,首先要创建一个AnimationController。除了作为动画本身,还AnimationController可以控制动画。例如,您可以告诉控制器向前播放动画或停止动画。

AnimationController是一个特殊Animation对象,只要硬件准备好新帧,它就会生成一个新值。默认情况下,AnimationController在给定的持续时间内线性生成从0.0到1.0的数字。

AnimationController派生自Animation,因此它可以在需要Animation对象的任何地方使用。但是,AnimationController还有其他控制动画的方法。例如,您使用该.forward()方法启动动画。数字的生成与屏幕刷新有关,因此通常每秒生成60个数字。

的[Tween(https://api.flutter.dev/flutter/animation/Tween-class.html)抽象类中的类型值的范围0.0-1.0映射名义上的双精度值(例如Color,或另一种双)。这是一个Animatable。要设置超过0.0到1.0间隔的动画,可以使用a [Tween](https://api.flutter.dev/flutter/animation/Tween-class.html),它在其开始值和结束值之间进行插值。许多类型都有特定的Tween子类,它们提供特定于类型的插值。例如,ColorTween在颜色之间插值,RectTween在矩形之间插值。A Tween继承自Animatable而非继承Animation。像动画一样的Animatable不必输出double。例如,ColorTween指定两种颜色之间的进展:

colorTween = ColorTween(begin: Colors.transparent, end: Colors.black54);

可以在Github上找到本文的源代码。

动画菜的源代码可以在这里找到。

谢谢阅读!

转:https://medium.com/@meysam.mahfouzi/understanding-animations-in-flutter-b8ec789d94a4

Flutter – 了解Flutter中的动画