Flutter 显式动画

Flutter 中的动画系统基于类型化的 Animation 对象。 Widgets 既可以通过读取当前值和监听状态变化直接合并动画到 build 函数,也可以作为传递给其他 widgets 的更精细动画的基础。

渲染动画

创建 AnimationController 的同时,也赋予了一个 vsync 参数。 vsync 的存在防止后台动画消耗不必要的资源。您可以通过添加 SingleTickerProviderStateMixin 到类定义,将有状态的对象用作 vsync

因为addListener() 函数调用 setState(),所以每次 Animation 生成一个新的数字,当前帧就被标记为 dirty,使得 build() 再次被调用。在 build() 函数中,container 会改变大小,因为它的高和宽都读取 animation.value,而不是固定编码值。当 State 对象销毁时要清除控制器以防止内存溢出。

class _AnimateContentState extends State<AnimateContent>
    with SingleTickerProviderStateMixin {
  bool flag = false;
  late AnimationController _controller;
  late Animation<double> animation;

  @override
  void initState() {
    // TODO: implement dispose
    super.initState();
    _controller =
        AnimationController(duration: const Duration(seconds: 1), vsync: this);
    //animate() 方法会返回一个 Animation
    animation = Tween<double>(begin: 0, end: 300).animate(_controller)
      ..addListener(() {
        setState(() {});
      });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 10),
          width: animation.value,
          height: animation.value,
          child: const FlutterLogo(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller.forward();
        },
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }
}

使用 Animated­Widget 进行简化

  • 如何使用 AnimatedWidget 帮助类(代替 addListener() 和 setState())创建动画 widget。
  • 利用 AnimatedWidget 创建一个可以运行重复使用动画的 widget。如需区分 widget 过渡,可以使用 AnimatedBuilder,你可以在 使用 AnimatedBuilder 进行重构 部分查阅更多信息。
  • Flutter API 中的 AnimatedWidgetAnimatedBuilder, RotationTransition,ScaleTransition, SizeTransition, SlideTransition

AnimatedWidget 基本类可以从动画代码中区分出核心 widget 代码。 AnimatedWidget 不需要保持 State 对象来 hold 动画。可以添加下面的 AnimatedLogo 类:

class AnimatedLogo extends AnimatedWidget {
  const AnimatedLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 10),
        height: animation.value,
        width: animation.value,
        child: const FlutterLogo(),
      ),
    );
  }
}

在绘制时,AnimatedLogo 会读取 animation 当前值。

class _AnimateContentState extends State<AnimateContent>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> animation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(_controller)
      ..addStatusListener((status) { //监听动画状态
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      })
      ..addStatusListener((status) => print('$status'));
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedLogo(
          animation: animation,
        ),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }
}

使用 AnimatedBuilder 进行重构

  • AnimatedBuilder 知道如何渲染过渡效果
  • AnimatedBuilder 不会渲染 widget,也不会控制动画对象。
  • 使用 AnimatedBuilder 描述一个动画是其他 widget 构建方法的一部分。如果只是单纯需要用可重复使用的动画定义一个 widget,可参考文档:简单使用 AnimatedWidget。
  • Flutter API 中 AnimatedBuildersBottomSheet, ExpansionTile, PopupMenu, ProgressIndicator, RefreshIndicator, Scaffold, SnackBar, TabBar, TextField

LogoWidget 组件

// logo组件
class LogoWidget extends StatelessWidget {
  const LogoWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 10),
      child: const FlutterLogo(),
    );
  }
}

GrowTransition 组件
GrowTransition widget 本身是无状态的,而且拥有定义过渡动画所需的一系列最终变量。 build() 函数创建并返回 AnimatedBuilderAnimatedBuilder 使用(Anonymous builder)方法并将 LogoWidget 对象作为参数。渲染过渡效果实际上是在(Anonymous builder)方法中完成的,该方法创建一个适当大小 Container 强制 LogoWidget 配合。

class GrowTransition extends StatelessWidget {
  const GrowTransition(
      {required this.child, required this.animation, super.key});
  final Widget child;
  final Animation<double> animation;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedBuilder(
        animation: animation,
        builder: (context, child) {
          return SizedBox(
            height: animation.value,
            width: animation.value,
            child: child,
          );
        },
        child: child,
      ),
    );
  }
}

最后, initState() 方法创建了 AnimationControllerTween,然后用 animate() 绑定它们。神奇的是 build() 方法,它返回一个以LogoWidgetchildGrowTransition 对象,和一个驱动过渡的动画对象。

class AnimateContent extends StatefulWidget {
  const AnimateContent({super.key});

  @override
  State<AnimateContent> createState() => _AnimateContentState();
}

class _AnimateContentState extends State<AnimateContent>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> animation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(_controller)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      })
      ..addStatusListener((status) => print('$status'));
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GrowTransition(
          animation: animation,
          child: const LogoWidget(),
        ),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }
}

同步动画

CurvedAnimation对象来控制动画, 修改 AnimatedLogo 来封装Tween 对象,以及其 build() 方法在母动画对象上调用 Tween.evaluate() 来计算所需的尺寸和不透明度值
AnimateLogo 组件

class AnimateLogo extends AnimatedWidget {
  const AnimateLogo({super.key, required Animation<double> animation})
      : super(listenable: animation);
  
  // Make the Tweens static because they don't change.
  static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
  static final _sizeTween = Tween<double>(begin: 0, end: 300);

  @override
  Widget build(BuildContext context) {
    final animation = listenable as Animation<double>;
    return Center(
      child: Opacity(
        opacity: _opacityTween.evaluate(animation),// 用Tween.evaluate()方法来计算所需的值
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 10),
          height: _sizeTween.evaluate(animation),// 用Tween.evaluate()方法来计算所需的值
          width: _sizeTween.evaluate(animation),// 用Tween.evaluate()方法来计算所需的值
          child: const FlutterLogo(),
        ),
      ),
    );
  }
}
class AnimateContent extends StatefulWidget {
  const AnimateContent({super.key});

  @override
  State<AnimateContent> createState() => _AnimateContentState();
}

class _AnimateContentState extends State<AnimateContent>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> animation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    // animation = Tween<double>(begin: 0, end: 300).animate(_controller)
    // 用CurvedAnimation对象来统一控制动画
    animation = CurvedAnimation(parent: _controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      })
      ..addStatusListener((status) => print('$status'));
    _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimateLogo(
          animation: animation,
        ),
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }
}

posted @   angelwgh  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示