如何减少AnimationController的模板代码。Flutter钩子与扩展状态类

88 阅读4分钟

AnimationController 是Flutter中动画的大本营。你可以用它来前进倒退重复动画,以及更多。

但是设置一个AnimationController ,需要很多的仪式。

// 1. declare a StatefulWidget
class RotatingContainer extends StatefulWidget {
  @override
  _RotatingContainerState createState() => _RotatingContainerState();
}

class _RotatingContainerState extends State<RotatingContainer>
    // 2. add SingleTickerProviderStateMixin
    with SingleTickerProviderStateMixin {
  // 3. declare the animation controller
  late final AnimationController _animationController;
  
  @override
  void initState() {
    super.initState();
    // 4. initialize the animation controller
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 750),
    );
  }

  @override
  void dispose() {
    // 5. dispose when done
    _animationController.dispose();
    super.dispose();
  }

  // build method, etc.
}

这都是在我们写任何动画代码之前。如果我们想用它来显示一个旋转的盒子,我们可以在initState() 里面调用_animationController.repeat(); ,并在build() 方法中加入这段代码。

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _animationController,
    child: Container(color: Colors.red),
    builder: (context, child) {
      return Transform(
        alignment: Alignment.center,
        transform: Matrix4.rotationZ(2 * pi * _animationController.value),
        child: child,
      );
    },
  );
}

注意:红色的Container 是作为一个子节点传递给AnimatedBuilder ,并在构建器内重复使用。参见"为什么TweenAnimationBuilder和AnimatedBuilder有一个子参数?"以获得对这种技术的解释。

如果你有很多明确的动画,就很难重用initState()dispose() 中的代码,而且在各部件之间复制粘贴所有的模板也容易出错。

因此,让我们来探索两种减少模板代码的方法。

[

赞助

Andrea的代码对每个人都是免费的。帮助我保持这种方式,看看这个赞助商。

Open-Source Backend Server for Flutter Developers

**面向Flutter开发者的开源后端服务器。**Appwrite是一个安全的、自我托管的解决方案,它为开发者提供了一套易于使用的REST API来管理他们的核心后台需求。你可以用Appwrite构建任何东西点击这里了解更多。

](appwrite.io/?utm_source…)

1.使用 Flutter 钩子

flutter_hooks包使与AnimationController 的工作变得非常简单。

// Note: we are extending `HookWidget`
class RotatingContainer extends HookWidget {
  @override
  Widget build(BuildContext context) {
    // All the steps above are replaced by a single line:
    final controller = useAnimationController(duration: Duration(seconds: 2))
      ..repeat(); // start the animation
    return AnimatedBuilder(
      animation: controller,
      child: Container(color: Colors.red),
      builder: (context, child) {
        return Transform(
          alignment: Alignment.center,
          transform: Matrix4.rotationZ(2 * pi * controller.value),
          child: child,
        );
      },
    );
  }
}

所有获取TickerProvider 、初始化和处置AnimationController 的魔法都发生在 useAnimationController()方法。

请务必阅读 flutter_hooks文档,以了解如何正确使用它。

Flutter 钩子是这种用例的一个很好的解决方案。如果您已经在您的项目中使用了它们,这就不难理解了。👍

但如果您不想让flutter_hooks成为一个额外的依赖项(例如因为您正在编写一个包),还有其他方法来减少模板代码。

2.扩展State类

你可以创建一个AnimationControllerState 的子类State<T> 来包含所有的AnimationController 逻辑。

import 'package:flutter/material.dart';

abstract class AnimationControllerState<T extends StatefulWidget>
    extends State<T> with SingleTickerProviderStateMixin {
  AnimationControllerState(this.animationDuration);
  final Duration animationDuration;
  late final animationController = AnimationController(
    vsync: this,
    duration: animationDuration
  );

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

这里是原来的例子,更新后使用了这个。

class RotatingContainer extends StatefulWidget {
  @override
  _RotatingContainerState createState() =>
      _RotatingContainerState(Duration(seconds: 2));
}

class _RotatingContainerState
    extends AnimationControllerState<RotatingContainer> {
  // add a constructor so we can pass the duration to the parent class
  _RotatingContainerState(Duration duration) : super(duration);

  // TODO: initState & build methods
}

在这种情况下,我们仍然需要一个StatefulWidget 。但所有的初始化和处置逻辑都在AnimationControllerState ,它现在是_RotatingContainerState 的父类。

我们可以像这样完成这个例子。

@override
void initState() {
  super.initState();
  // animationController is defined in the parent class, so we can use it here
  animationController.repeat();
}

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: animationController,
    child: Container(color: Colors.red),
    builder: (context, child) {
      return Transform(
        alignment: Alignment.center,
        transform: Matrix4.rotationZ(2 * pi * animationController.value),
        child: child,
      );
    },
  );
}

这比flutter_hooks的方法多了一点代码,但我们不需要额外的包依赖。

如果你不介意为你应用中的每个显式动画创建一个StatefulWidget ,这是一个很好的解决方案。许多需要显式动画的用例都需要一些状态变量,所以我觉得这在实践中可以接受。

useAnimationController() 只是你可以使用的众多钩子中的一个。您可以使用flutter_hooks来管理状态和更多。请确保了解各种钩子以充分利用这个包。

差点忘了,这是上面的例子中正在运行的应用程序。

使用AnimationController旋转方形

总结

我们已经探索了两种方法来减少AnimationController boilerplate。

  1. 使用flutter_hooks包中的useAnimationController()
  2. 实现AnimationControllerState 并在需要时重复使用它

现在轮到您了

您准备好从您的Flutter 应用程序中删除大量不必要的代码了吗?

  • 在整个项目中搜索AnimationController
  • 用您喜欢的方法(useAnimationController()AnimationControllerState )替换所有的模板代码
  • 测试一切,做一个PR,看看你节省了多少代码

不客气!😀