前言

动画是提升用户体会的一个重要办法,一个恰当组件的动画或页面切换的动画能够缓解因等候数据加载的心情问题,也能增加用户的好感。

Animation、AnimationController与Listener

示例

Flutter 中,动画(Animation)是指一种随着时间推移逐渐改动的视觉作用。为了操控动画的改动和履行,咱们需求用到三个重要的类:Animation、AnimationController 和 Listener。

  1. Animation:Animation 是一个抽象类,它界说了动画的运转过程和动画的值怎么核算。详细的动画作用能够经过承继 Animation 类来完成。例如,Flutter 中自带的一些动画类包括 Tween、Curve、Interval 等。Animation 知道当时动画的状况(比如,动画是否开端、中止、行进或许撤退,以及动画的当时值),但却不知道这些状况究竟应用在哪个组件方针上。Animation 仅仅是用来供给动画数据,而不担任动画的烘托
  2. AnimationController:AnimationController 用于操控动画的开端、结束和状况的办理。它能够设置动画的时间、速度、是否重复等等。当咱们创立一个 AnimationController 方针时,需求指定动画的时间长度和 TickerProvider 方针。TickerProvider 担任供给时钟信号,以便 AnimationController 知道何时更新动画。
  3. Listener:Listener 是用来监听动画改动的。它接收一个 Animation 方针,并在每次动画值改动时被调用。Listener 能够用于更新界面上的元素,以反映当时动画的状况。

咱们来举一个详细的例子:完成一个旋转动画。

首要,咱们需求创立一个 AnimationController 方针来办理动画的履行过程和状况。咱们能够设置动画的时间长度、是否重复、速度等参数。

AnimationController _controller = AnimationController(
  duration: Duration(seconds: 2),
  vsync: this, // TickerProvider 方针
);

然后,咱们需求创立一个 Animation 方针来界说动画的值怎么核算。在本例中,咱们能够运用一个 Tween 类来完成旋转动画。

Animation<double> _animation = Tween<double>( begin: 0, end: 1, ).animate(_controller);

接着,咱们需求创立一个 Listener 来监听动画的改动,并依据当时动画的值来更新界面上的元素。在本例中,咱们能够经过设置一个 Transform.rotate 来完成元素的旋转动画。

Listener(
  child: Transform.rotate(
    angle: _animation.value * 2 * pi,
    child: Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
  ),
  onPointerDown: (event) {
    _controller.repeat();
  },
)

最后在页面毁掉的时候,记得开释资源。

@override
void dispose() {
  controller.dispose(); // 开释资源
  super.dispose();
}

在上面的代码中,咱们将 Transform.rotate 放在 Listener 中,并将 Animation 的值乘以 2,以完成元素的旋转动画作用。当用户点击界面上的元素时,咱们经过 _controller.repeat() 来触发动画的重复播映。

留意点

在运用 Animation、AnimationController 和 Listener 时,需求留意以下几点:

  1. AnimationController 的 dispose 办法需求在 Widget 生命周期结束时调用,以便开释资源。一般能够在 State 方针的 dispose 办法中调用 dispose 办法,例如在 StatefulWidget 中重写 dispose 办法,并在 dispose 办法中调用 AnimationController 的 dispose 办法。
  2. AnimationController 的 forward、reverse、reset 等办法需求谨慎运用。假如在调用这些办法时没有正确处理动画的状况,可能会导致动画播映不正确,例如动画重复播映、动画播映速度过快等问题。
  3. 在运用 Listener 监听动画时,需求留意监听的动画值是否正确。一般能够在监听器中打印动画值,以便查看动画值是否正确。
  4. 运用 AnimationController 的 animateTo、animateBy 等办法时,需求留意设置动画的继续时间、曲线等参数,以操控动画的播映作用。
  5. 在运用 Animation 时,需求留意设置动画的值规模。假如动画的值规模超过了实际需求的规模,可能会导致动画播映不正确,例如动画值超过了 1.0 或小于了 0.0 等问题。

AnimatedWidget 与 AnimatedBuilder

示例

1、AnimatedWidget 是一个抽象类,它承继自 StatefulWidget,并且包括一个 Animation 方针。经过承继 AnimatedWidget 类并重写 build 办法,咱们能够方便地依据动画的值来更新界面上的元素。AnimatedWidget 会主动处理动画的监听和界面更新,无需手动增加 Listener

例如,咱们能够创立一个承继 AnimatedWidget 的类来完成旋转动画:

class RotateWidget extends AnimatedWidget {
  RotateWidget({
    Key key,
    Animation<double> animation,
  }) : super(key: key, listenable: animation);
  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return Transform.rotate(
      angle: animation.value * 2 * pi,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    );
  }
}

在上面的代码中,咱们创立了一个 RotateWidget 类,承继自 AnimatedWidget,它包括一个 Animation 类型的参数 animation,用于界说旋转动画。在 build 办法中,咱们直接依据 animation 的值来更新界面上的元素,无需手动增加 Listener。

2、AnimatedBuilder 是另一个常用的动画类,它经过 builder 办法来创立一个 Widget,并接收一个 Animation 方针作为参数。在 builder 办法中,咱们能够依据动画的值来创立和更新 Widget。与 AnimatedWidget 不同的是,AnimatedBuilder 能够自界说 Widget 的构建过程,更加灵活。

例如,咱们能够运用 AnimatedBuilder 来完成一个缩放动画:

AnimatedBuilder(
  animation: _controller,
  builder: (BuildContext context, Widget child) {
    return Transform.scale(
      scale: _controller.value,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    );
  },
)

在上面的代码中,咱们创立了一个 AnimatedBuilder,它接收一个 AnimationController 方针作为 animation 参数,并运用 builder 办法来构建一个缩放动画。在 builder 办法中,咱们依据 _controller.value 的值来更新元素的缩放比例。

留意点

在运用 AnimatedWidget 和 AnimatedBuilder 时,需求留意以下几点:

  1. AnimatedWidget 和 AnimatedBuilder 的作用相似,都能够用于构建动画。但是,它们的运用办法略有不同。AnimatedWidget 是一个 StatelessWidget,经过监听 Animation 的值来主动更新 Widget 的状况,因而运用 AnimatedWidget 能够让代码更加简练。而 AnimatedBuilder 则是一个 Widget Builder,需求手动在 builder 办法中创立需求更新的 Widget。
  2. 在创立 AnimationController 时,需求将 AnimationController 的 vsync 参数设置为 TickerProvider,以便在 Widget 状况改动时同步更新动画。一般,能够运用 State 方针来作为 TickerProvider,例如运用 StatefulWidget 创立一个 State 方针,并将其作为 vsync 参数传递给 AnimationController。
  3. AnimatedWidget 和 AnimatedBuilder 中的 Animation 需求设置监听器,以便在动画值改动时更新 Widget 状况。在监听器中,一般需求调用 setState 办法,以便告知 Flutter 结构更新 Widget 状况。
  4. 在运用 AnimatedWidget 和 AnimatedBuilder 时,需求留意内存的运用。假如动画比较复杂或许需求继续运转,可能会消耗很多内存。因而,应该尽量避免在 Widget 树中创立过多的 AnimatedWidget 或 AnimatedBuilder。
  5. 当运用 AnimatedBuilder 时,需求留意 Widget 的创立次数。因为 AnimatedBuilder 是一个 Widget Builder,每次动画值发生改动时都会调用 builder 办法,因而假如 builder 办法中创立的 Widget 比较复杂,可能会导致 Widget 的创立次数过多,影响性能。因而,应该尽可能坚持 builder 办法中创立的 Widget 简单,以进步性能。

hero动画

示例

Hero 动画是 Flutter 中一种常用的过渡动画,它能够完成在两个不同页面之间平滑的元素过渡动画。Hero 动画一般用于展现从一个页面到另一个页面的过渡作用,例如在浏览产品列表页面和产品详情页面之间的过渡动画作用。

运用 Hero 动画需求两个步骤:在源页面上设置 Hero Widget 和 tag,然后在方针页面上设置与源页面相同 tag 的 Hero Widget。Flutter 结构会主动依据 tag 匹配源页面和方针页面的 Hero Widget,并在过渡时主动履行动画。

下面是一个运用 Hero 动画的例子:

在源页面上设置 Hero Widget 和 tag:

Hero(
  tag: 'avatar', // 界说 Hero Widget 的 tag
  child: CircleAvatar(
    backgroundImage: NetworkImage(''),
  ),
)

在方针页面上设置与源页面相同 tag 的 Hero Widget:

Scaffold(
  body: Center(
    child: Hero(
      tag: 'avatar', // 与源页面相同的 tag
      child: CircleAvatar(
        backgroundImage: NetworkImage(''),
      ),
    ),
  ),
)

在上面的代码中,咱们在源页面上创立了一个 Hero Widget,并设置了 tag 为 ‘avatar’。在方针页面中,咱们也创立了一个与源页面相同 tag 的 Hero Widget,并在 Scaffold 中运用它来完成元素的过渡动画。

留意点

在运用 Hero 动画时,需求留意以下几点:

  1. Hero Widget 的 tag 必须在源页面和方针页面中坚持一致。假如 tag 不一致,Flutter 无法匹配源页面和方针页面的 Hero Widget,然后无法履行过渡动画。
  2. Hero Widget 的内容应该尽可能相似。假如两个 Hero Widget 的内容差异过大,可能会导致过渡动画作用不佳。例如,在源页面中运用的是一个圆形的头像,而在方针页面中运用的是一个方形的头像,这会导致在过渡时头像的形状会发生改动。
  3. Hero Widget 能够包括子 Widget。假如源页面和方针页面的 Hero Widget 中包括子 Widget,那么这些子 Widget 的布局和方位也应该尽可能相似,以保证过渡动画作用平滑。
  4. Hero Widget 应该尽可能放置在页面层级较高的方位。假如 Hero Widget 被掩盖在其他 Widget 之下,可能会导致过渡动画作用不佳。因而,一般将 Hero Widget 放置在页面的最上层,以保证过渡动画作用正常。
  5. 假如 Hero Widget 中包括的是网络图片等资源,建议提早加载这些资源,避免在过渡时呈现加载推迟的情况。能够运用 Flutter 中供给的 Image.network 或许 CachedNetworkImage 等 Widget 来加载网络图片。