Flutter】动画学习(二) Animation

介绍

在任何系统的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');
              })));
}

猜你喜欢

转载自blog.csdn.net/tianzhilan0/article/details/108075305