欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Flutter】动画学习(二) Animation

程序员文章站 2022-03-09 20:30:08
...

介绍

在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观,由于人眼会产生视觉暂留,最终看到的就是一个“连续”的动画,这和电影的原理是一样的,而UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),指每秒的动画帧数。很明显,帧率越高则动画就会越流畅。

一般情况下,对于人眼来说,动画帧率超过16FPS,就比较流畅了,超过32FPS就会非常的细腻平滑,而超过32FPS基本就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧率是重要的性能指标,而在Flutter中,理想情况下是可以实现60FPS的,这和原生应用动画基本是持平的。

为了方便开发者创建动画,不同的UI系统对动画都进行了一些抽象,比如在Android中可以通过XML来描述一个动画然后设置给View。Flutter中也对动画进行了抽象,主要涉及Tween、Animation、Curve、Controller这些角色。

基础动画

Animation对象是Flutter动画库中的一个核心类,它生成指导动画的值。
Flutter 提供了两种动画,分别是 Tween Animation 补间动画和 Physics-based Animation 基于物理动画;

Animation

Animation可以生成动画过程中的值,生成的值并非单一的 double 也可以是 Size/Color 等;Animation 可以获取状态但无法获取屏幕显示内容。

AnimationController

AnimationController 小菜理解为 Animation 控制器,实际也是一个特殊的 Animation,在屏幕刷新时会生成一个新的值;使用时需要传递 vsync 值,用来防止屏幕外动画,vsync 值可以继承 TickerProviderStateMixin,若当前页面只有一个 controller 也可以用 SingleTickerProviderStateMixin;

AnimationController 有两个常用方法:

  • forward() 方法用来开始动画,即从无到有;
  • reverse() 方法用来反向开始动画,即从有到无;

TweenAnimation

Tween 动画是无状态的,只是在固定时间内均匀生成 begin 和 end 的值,通过 animation.value 来获取;

    AnimationController controller = AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);
    Animation<double> animation =
        Tween(begin: 0.0, end: 1.0).animate(controller);
    animation.addListener(() {
      setState(() {});
    });

CurvedAnimation

CurvedAnimation 的动画过程是非线性的,curve 种类很多,比较符合日常生活的物理过程,例如先快后慢或先增长到一个峰值再降低等;curve 的动画过程也可以自定义函数等;

    AnimationController controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    CurvedAnimation curve =
        CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);

AnimatedWidget

Flutter 很贴心的提供了自带动画属性的 Widget 极大的方便我们使用简单的动画,涵盖 透明度/旋转/缩放/平移 等常用的动画属性,使用时非常方便;但是缺点也相对明显,这些 Widget 属性相对专一,若需要多种动画属性不太适合;

简单介绍几个小菜日常使用的动画组件;

FadeTransition 显隐性
FadeTransition(opacity: animation, child: FlutterLogo(size: 100.0))
ScaleTransition 缩放
ScaleTransition(scale: animation, child: FlutterLogo(size: 100.0))
RotationTransition 旋转
RotationTransition(turns: animation, child: FlutterLogo(size: 100.0))
Transform.scale 缩放
Transform.scale(scale: curve.value, child: FlutterLogo(size: 100.0))
Transform.rotate 旋转
Transform.rotate(angle: curve.value * pi, child: FlutterLogo(size: 100.0))
Transform.translate 平移
Transform.translate(offset: Offset(100.0 * curve.value, 0.0), child: FlutterLogo(size: 100.0))
AnimatedOpacity 透明度
AnimatedOpacity(opacity: animation.value, duration: Duration(milliseconds: 2000), child: FlutterLogo(size: 100.0))

核心代码

class _AnimationPageState extends State<AnimationPage03> with TickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;
  CurvedAnimation curve;
  bool isForward = false;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    curve = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
    animation = Tween(begin: 0.0, end: 1.0).animate(controller);
    animation.addListener(() {
      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: AppBar(
          title: Text('Animation Demo'),
        ),
        body: Stack(children: <Widget>[
          ListView(children: <Widget>[
            Padding(
                padding: EdgeInsets.all(5.0),
                child: Row(children: <Widget>[
                  Expanded(
                      flex: 1, child: Center(child: Text('FadeTransition'))),
                  Expanded(
                      flex: 1, child: Center(child: Text('ScaleTransition')))
                ])),
            Row(children: <Widget>[
              Expanded(
                  flex: 1,
                  child: FadeTransition(
                      opacity: animation, child: FlutterLogo(size: 100.0))),
              Expanded(
                  flex: 1,
                  child: ScaleTransition(
                      scale: animation, child: FlutterLogo(size: 100.0)))
            ]),
            Padding(
                padding: EdgeInsets.all(5.0),
                child: Row(children: <Widget>[
                  Expanded(
                      flex: 1,
                      child: Center(child: Text('RotationTransition'))),
                  Expanded(
                      flex: 1, child: Center(child: Text('AnimatedOpacity')))
                ])),
            Row(children: <Widget>[
              Expanded(
                  flex: 1,
                  child: RotationTransition(
                      turns: animation, child: FlutterLogo(size: 100.0))),
              Expanded(
                  flex: 1,
                  child: AnimatedOpacity(
                      opacity: animation.value,
                      duration: Duration(milliseconds: 2000),
                      child: FlutterLogo(size: 100.0)))
            ]),
            Padding(
                padding: EdgeInsets.all(5.0),
                child: Row(children: <Widget>[
                  Expanded(
                      flex: 1,
                      child: Center(child: Text('Transform.translate'))),
                  Expanded(
                      flex: 1, child: Center(child: Text('Transform.rotate')))
                ])),
            Row(children: <Widget>[
              Expanded(
                  flex: 1,
                  child: Transform.translate(
                      offset: Offset(100.0 * curve.value, 0.0),
                      child: FlutterLogo(size: 100.0))),
              Expanded(
                  flex: 1,
                  child: Transform.rotate(
                      angle: curve.value * pi, child: FlutterLogo(size: 100.0)))
            ]),
            Padding(
                padding: EdgeInsets.all(5.0),
                child: Row(children: <Widget>[
                  Expanded(
                      flex: 1, child: Center(child: Text('Transform.scale'))),
                  Expanded(flex: 1, child: Center(child: Text('Position')))
                ])),
            Row(children: <Widget>[
              Expanded(
                  flex: 1,
                  child: Transform.scale(
                      scale: curve.value, child: FlutterLogo(size: 100.0))),
            ])
          ]),
          posWid()
        ]),
        floatingActionButton: new FloatingActionButton(
            tooltip: 'Animation',
            child: new Icon(Icons.lightbulb_outline),
            onPressed: () {
              isForward ? controller.reverse() : controller.forward();
              isForward = !isForward;
            }));
  }

  Widget posWid() {
    return Positioned(
        bottom: 16 + 314 * animation.value,
        right: 16 + 84 * animation.value,
        child: Container(child: FlutterLogo(size: 100.0)));
  }
}

复合动画

隐性和缩放

  • addStatusListener 用来监听当前动画状态,即开始或结束;
  • addListener 用来坚挺动画过程,可获取实时 value 值;
AnimationController controller;
Animation<double> animation, sizeAnim;

@override
void initState() {
  super.initState();
  controller = AnimationController(
      duration: const Duration(milliseconds: 2000), vsync: this);
  animation = Tween(begin: 0.0, end: 1.0).animate(controller);
  sizeAnim = Tween(begin: 0.0, end: 180.0).animate(controller);
  animation.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      controller.reverse();
    } else if (status == AnimationStatus.dismissed) {
      controller.forward();
    }
  });
  sizeAnim.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      controller.reverse();
    } else if (status == AnimationStatus.dismissed) {
      controller.forward();
    }
  });
}

Widget bodyWid() {
  return Center(
      child: Opacity(
          opacity: animation.value,
          child: FlutterLogo(size: sizeAnim.value)));
}

时间段动画

既然可以监听动画过程和动画状态,整体的动画便可以灵活掌握;小菜接下来尝试一下分时间段动画,前 50% 显隐性处理,后 50% 缩放处理;

AnimationController controller;
Animation<double> animation, sizeAnim;
@override
void initState() {
  super.initState();
  controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
  
  animation = Tween<double>(
    begin: 0.0,
    end: 1.0,
  ).animate(CurvedAnimation(
      parent: controller, curve: Interval(0.0, 0.5, curve: Curves.ease)));
  sizeAnim = Tween<double>(
    begin: 100.0,
    end: 180.0,
  ).animate(CurvedAnimation(
      parent: controller,
      curve: Interval(0.5, 1.0, curve: Curves.fastOutSlowIn)));
}

Widget bodyWid() {
  return Center(
      child: Opacity(
          opacity: animation.value,
          child: FlutterLogo(size: sizeAnim.value)));
}

自定义动画

动画是灵活的,我们可以根据自己的需求自定义动画效果,小菜尝试一个圆环绕一个圆转圈;

AnimationController controller;
Animation<double> animation;

@override
void initState() {
  super.initState();
  controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
  animation = Tween(begin: -1.0, end: 1.0).animate(controller);
}

class AnimationCanvas extends CustomPainter {
  Animation<double> animation;
  AnimationCanvas(this.animation);
  Paint _paint = Paint()
    ..color = Colors.blue
    ..strokeWidth = 4.0
    ..style = PaintingStyle.stroke;

  @override
  void paint(Canvas canvas, Size size) {
    canvas.save();
    canvas.drawCircle(Offset(300, 300.0), 150.0, _paint);
    canvas.restore();
    canvas.save();

    canvas.translate(150 * sin(pi * animation.value), 150 * cos(pi * animation.value));
    canvas.drawCircle(Offset(300, 300.0), 10.0, _paint..color = Colors.red);
    canvas.restore();
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

Hero 动画

Hero 动画是 Flutter 提供的飞入式动画,主要用在页面间切换时动画,且返回时动画按原路返回;使用时设置两个页面间 tag 一致即可,方便简洁;

Widget bodyWid04() {
  return Container(
      child: Padding(
          padding: EdgeInsets.all(10.0),
          child: GestureDetector(
              child: Row(children: <Widget>[
                Hero(tag: 'user_header', child: FlutterLogo(size: 50.0)),
                Padding(
                    padding: EdgeInsets.only(left: 12.0),
                    child: Text('Flutter Ptoto'))
              ]),
              onTap: () {
                Navigator.pushNamed(context, 'animateRoute01');
              })));
}
相关标签: Flutter