Flutter 动画实现指南
前言
动画是提升用户体验的一个重要方式,一个恰当组件的动画或页面切换的动画可以缓解因等待数据加载的情绪问题,也能增加用户的好感。
Animation、AnimationController与Listener
示例
在 Flutter 中,动画(Animation)是指一种随着时间推移逐步改变的视觉效果。为了控制动画的变化和执行,我们需要用到三个重要的类:Animation、AnimationController 和 Listener。
- Animation:Animation 是一个抽象类,它定义了动画的运行过程和动画的值如何计算。具体的动画效果可以通过继承 Animation 类来实现。例如,Flutter 中自带的一些动画类包括 Tween、Curve、Interval 等。Animation 知道当前动画的状态(比如,动画是否开始、停止、前进或者后退,以及动画的当前值),但却不知道这些状态究竟应用在哪个组件对象上。Animation 仅仅是用来提供动画数据,而不负责动画的渲染
- AnimationController:AnimationController 用于控制动画的开始、结束和状态的管理。它可以设置动画的时间、速度、是否重复等等。当我们创建一个 AnimationController 对象时,需要指定动画的时间长度和 TickerProvider 对象。TickerProvider 负责提供时钟信号,以便 AnimationController 知道何时更新动画。
- 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 时,需要注意以下几点:
- AnimationController 的 dispose 方法需要在 Widget 生命周期结束时调用,以便释放资源。通常可以在 State 对象的 dispose 方法中调用 dispose 方法,例如在 StatefulWidget 中重写 dispose 方法,并在 dispose 方法中调用 AnimationController 的 dispose 方法。
- AnimationController 的 forward、reverse、reset 等方法需要谨慎使用。如果在调用这些方法时没有正确处理动画的状态,可能会导致动画播放不正确,例如动画重复播放、动画播放速度过快等问题。
- 在使用 Listener 监听动画时,需要注意监听的动画值是否正确。通常可以在监听器中打印动画值,以便检查动画值是否正确。
- 使用 AnimationController 的 animateTo、animateBy 等方法时,需要注意设置动画的持续时间、曲线等参数,以控制动画的播放效果。
- 在使用 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 时,需要注意以下几点:
- AnimatedWidget 和 AnimatedBuilder 的作用相似,都可以用于构建动画。但是,它们的使用方式略有不同。AnimatedWidget 是一个 StatelessWidget,通过监听 Animation 的值来自动更新 Widget 的状态,因此使用 AnimatedWidget 可以让代码更加简洁。而 AnimatedBuilder 则是一个 Widget Builder,需要手动在 builder 方法中创建需要更新的 Widget。
- 在创建 AnimationController 时,需要将 AnimationController 的 vsync 参数设置为 TickerProvider,以便在 Widget 状态改变时同步更新动画。通常,可以使用 State 对象来作为 TickerProvider,例如使用 StatefulWidget 创建一个 State 对象,并将其作为 vsync 参数传递给 AnimationController。
- AnimatedWidget 和 AnimatedBuilder 中的 Animation 需要设置监听器,以便在动画值改变时更新 Widget 状态。在监听器中,通常需要调用 setState 方法,以便告诉 Flutter 框架更新 Widget 状态。
- 在使用 AnimatedWidget 和 AnimatedBuilder 时,需要注意内存的使用。如果动画比较复杂或者需要持续运行,可能会消耗大量内存。因此,应该尽量避免在 Widget 树中创建过多的 AnimatedWidget 或 AnimatedBuilder。
- 当使用 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('https://example.com/avatar.jpg'),
  ),
)
在目标页面上设置与源页面相同 tag 的 Hero Widget:
Scaffold(
  body: Center(
    child: Hero(
      tag: 'avatar', // 与源页面相同的 tag
      child: CircleAvatar(
        backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
      ),
    ),
  ),
)
在上面的代码中,我们在源页面上创建了一个 Hero Widget,并设置了 tag 为 'avatar'。在目标页面中,我们也创建了一个与源页面相同 tag 的 Hero Widget,并在 Scaffold 中使用它来实现元素的过渡动画。
注意点
在使用 Hero 动画时,需要注意以下几点:
- Hero Widget 的 tag 必须在源页面和目标页面中保持一致。如果 tag 不一致,Flutter 无法匹配源页面和目标页面的 Hero Widget,从而无法执行过渡动画。
- Hero Widget 的内容应该尽可能相似。如果两个 Hero Widget 的内容差异过大,可能会导致过渡动画效果不佳。例如,在源页面中使用的是一个圆形的头像,而在目标页面中使用的是一个方形的头像,这会导致在过渡时头像的形状会发生变化。
- Hero Widget 可以包含子 Widget。如果源页面和目标页面的 Hero Widget 中包含子 Widget,那么这些子 Widget 的布局和位置也应该尽可能相似,以确保过渡动画效果平滑。
- Hero Widget 应该尽可能放置在页面层级较高的位置。如果 Hero Widget 被覆盖在其他 Widget 之下,可能会导致过渡动画效果不佳。因此,通常将 Hero Widget 放置在页面的最上层,以确保过渡动画效果正常。
- 如果 Hero Widget 中包含的是网络图片等资源,建议提前加载这些资源,以免在过渡时出现加载延迟的情况。可以使用 Flutter 中提供的 Image.network 或者 CachedNetworkImage 等 Widget 来加载网络图片。
转载自:https://juejin.cn/post/7211284290067038264




