flutter中的动画基础知识

生成显示动画类模版快捷方式:输入stanim

动画小技巧

1.因使用动画而造成卡顿的话,可以使用  timeDilat ion  属性来极大地放慢动画。

import 'package:flutter/scheduler.dart' show timeDilation;

///在适当的时候使用timeDilation,如下面的代码

  @override
  void initState() {
    timeDilation = 5.0; //让动画效果放慢5倍
   super.initState();
  }

2.AnimatedBuilder和TweenAnimationBuilder使用优化,把不需要刷新的widget放到child中

AnimatedBuilder(
          animation: _controller,
          builder: (BuildContext context, Widget? child) {
            //动画过程中值发生变化就会刷新
            return Container(
              width: 300,
              height: 200 + 100 * _controller.value,
              color: Colors.blue,
              child: child,
            );
          },
          //动画过程中不执行刷新的子Widget
          child: Center(
              child: Text(
            'Hi',
            style: TextStyle(fontSize: 100),
          )),
        )

1.动画介绍

Flutter中的动画系统基于Animation对象的,它不是一个Widget,这是因为Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,就相当于一个定时器,它用于保存动画的插值和状态,并执行数值的变化。widget可以在build函数中读取Animation对象的当前值, 并且可以监听动画的状态改变。

2.在Flutter中有哪些类型的动画?

在Flutter中动画分为两类:基于tween或基于物理的。

  • 补间(Tween)动画:在补间动画中,定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点;
  • 基于物理的动画:在基于物理的动画中,运动被模拟为与真实世界的行为相似。例如,当你掷球时,它在何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。 类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子上的球放下的方式也是不同;

3.Flutter中常用的动画的API

在Flutter中使用动画有几个常用的API:

  • Animation:是Flutter动画库中的一个核心类,它生成指导动画的值;
  • AnimationController:Animation的一个子类,用来管理Animation;
  • Tween:在正在执行动画的对象所使用的数据范围之间生成值。例如,Tween可生成从红到蓝之间的色值,或者从0到255;
  • AnimatedBuilder:是拆分动画的一个工具类,借助它我们可以将动画和widget进行分离,而AnimatedWidget理解为Animation的助手,使用它可以简化我们对动画的使用;
    • AnimatedBuilder与AnimatedWidget的最大区别是在于对动画的拆分上AnimatedBuilder的模式可以将动画逻辑和widget展示进行拆分,而AnimatedWidget是融合在一起的。
  • Curve:Flutter中可以通过Curve(曲线)来描述动画过程;
  • Hero动画(主动画):Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画
  • 组合动画:有些时候我们可能会需要执行一个动画序列执行一些复杂的动画
  • Ticker:的作用是添加屏幕刷新回调,每次屏幕刷新都会调用TickerCallback;

Animation

在Flutter中,Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,它拥有其当前值和状态(完成或停止)。其中一个比较常用的Animation类是Animation<double>

Flutter中的Animation对象是一个在一段时间内依次生成一个区间之间值的类。Animation对象的输出可以是线性的、曲线的、一个步进函数或者任何其他可以设计的映射。 根据Animation对象的控制方式,动画可以反向运行,甚至可以在中间切换方向。

  • Animation还可以生成除double之外的其他类型值,如:Animation<Color> 或 Animation<Size>
  • Animation对象有状态。可以通过访问其value属性获取动画的当前值;
  • Animation对象本身和UI渲染没有任何关系;

AnimationController

AnimationController是一个特殊的Animation对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内会线性的生成从0.0到1.0的数字。 例如,下面代码创建一个Animation对象:

final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 2000), vsync: this);

AnimationController派生自Animation<double>,因此可以在需要Animation对象的任何地方使用。 但是,AnimationController具有控制动画的其他方法:

  • forward():启动动画;
  • reverse({double from}):倒放动画;
  • reset():重置动画,将其设置到动画的开始位置;
  • stop({ bool canceled = true }):停止动画;

当创建一个AnimationController时,需要传递一个vsync参数,存在vsync时会防止屏幕外动画消耗不必要的资源,可以将stateful对象作为vsync的值。

注意: 在某些情况下,值(position,值动画的当前值)可能会超出AnimationController的0.0-1.0的范围。例如,fling()函数允许您提供速度(velocity)、力量(force)、position(通过Force对象)。位置(position)可以是任何东西,因此可以在0.0到1.0范围之外。 CurvedAnimation生成的值也可以超出0.0到1.0的范围。根据选择的曲线,CurvedAnimation的输出可以具有比输入更大的范围。例如,Curves.elasticIn等弹性曲线会生成大于或小于默认范围的值。

Tween

默认情况下,AnimationController对象值为:double类型,范围是0.0到1.0 。如果我们需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。例如,以下示例,Tween生成从-200.0到0.0的值:

final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);

Tween是一个无状态(stateless)对象,需要begin和end值。Tween的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为0.0到1.0,但这不是必须的。

Tween继承自Animatable<T>,而不是继承自Animation<T>。Animatable与Animation相似,不是必须输出double值。例如,ColorTween指定两种颜色之间的过渡。

final Tween colorTween =
    new ColorTween(begin: Colors.transparent, end: Colors.black54);

Tween对象不存储任何状态。相反,它提供了evaluate(Animation<double> animation)方法将映射函数应用于动画当前值。 Animation对象的当前值可以通过value()方法取到。evaluate函数还执行一些其它处理,例如分别确保在动画值为0.0和1.0时返回开始和结束状态。

Tween.animate

要使用Tween对象,可调用它的animate()方法,传入一个控制器对象。例如,以下代码在500毫秒内生成从0到255的整数值。

final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);

注意animate()返回的是一个Animation,而不是一个Animatable。

Curve

动画过程默认是线性的(匀速),如果需要非线形的,比如:加速的或者先加速后减速等。Flutter中可以通过Curve(曲线)来描述动画过程。以下示例构建了一个控制器、一条曲线和一个Tween:

final AnimationController controller = new AnimationController(
    duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
    new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
Curves曲线动画过程
linear匀速的
decelerate匀减速
ease开始加速,后面减速
easeIn开始慢,后面快
easeOut开始快,后面慢
easeInOut开始慢,然后加速,最后再减速

官方参考:Curves class - animation library - Dart API

Hero动画

Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
          appBar: AppBar(
            title: Text("主页"),
          ),
          body: Route1()),
    );
  }
}

// 路由A
class Route1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.topCenter,
      child: InkWell(
        child: Hero(
          tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同
          child: CircleAvatar(
            backgroundImage: AssetImage(
              "assets/banner.jpeg",
            ),
          ),
        ),
        onTap: () {
          Navigator.push(context, MaterialPageRoute(builder: (_) {
            return Route2();
          }));
        },
      ),
    );
  }
}

class Route2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Hero(
          tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同
          child: Image.asset("assets/banner.jpeg")),
    );
  }
}

组合动画

有些时候我们可能会需要执行一个动画序列执行一些复杂的动画。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: Route(),
    );
  }
}

class Route extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return RouteState();
  }
}

class RouteState extends State<Route> with SingleTickerProviderStateMixin {
  Animation<Color> color;
  Animation<double> width;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      // 动画的时长
      duration: Duration(milliseconds: 2000),
      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin
      vsync: this,
    );

    //高度动画
    width = Tween<double>(
      begin: 100.0,
      end: 300.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          //间隔,前60%的动画时间 1200ms执行高度变化
          0.0, 0.6,
        ),
      ),
    );

    color = ColorTween(
      begin: Colors.green,
      end: Colors.red,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.6, 1.0, //高度变化完成后 800ms 执行颜色编码
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("主页"),
      ),
      body: InkWell(
        ///1、不用显式的去添加帧监听器,再调用setState()
        ///2、缩小动画构建的范围,如果没有builder,setState()将会在父widget上下文调用,导致父widget的build方法重新调用,现在只会导致动画widget的build重新调用
        child: AnimatedBuilder(
            animation: controller,
            builder: (context, child) {
              return Container(
                color: color.value,
                width: width.value,
                height: 100.0,
              );
            }),
        onTap: () {
          controller.forward().whenCompleteOrCancel(() => controller.reverse());
        },
      ),
    );
  }
}

Ticker

Ticker的作用是添加屏幕刷新回调,每次屏幕刷新都会调用TickerCallback。使用Ticker来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源。因为Flutter中屏幕刷新时会通知Ticker,锁屏后屏幕会停止刷新,所以Ticker就不会再触发。最简单的做法为将SingleTickerProviderStateMixin添加到State的定义中。

4.实现一个从小放大的动画

在下面的实例中我们为一个logo添加了一个从小放大的动画:

zoom.gif

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

void main() => runApp(LogoApp());

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;
  AnimationStatus animationState;
  double animationValue;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    // #docregion addListener
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      ..addListener(() {
        // #enddocregion addListener
        setState(() {
          animationValue = animation.value;
        });
        // #docregion addListener
      })
      ..addStatusListener((AnimationStatus state) {
        setState(() {
          animationState = state;
        });
      });
    // #enddocregion addListener
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top: 50),
      child: Column(
        children: <Widget>[
          GestureDetector(
            onTap: () {
              controller.reset();
              controller.forward();
            },
            child: Text('Start', textDirection: TextDirection.ltr),
          ),
          Text('State:' + animationState.toString(),
              textDirection: TextDirection.ltr),
          Text('Value:' + animationValue.toString(),
              textDirection: TextDirection.ltr),
          Container(
            height: animation.value,
            width: animation.value,
            child: FlutterLogo(),
          ),
        ],
      ),
    );
  }

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

注意,在上述代码中要实现这个动画的关键一步是在addListener()的回调中添加setState的调用这样才能触发页面重新渲染,动画才能有效,另外也可以通过AnimatedWidget来实现,在下文中会讲到。

5.为动画添加监听器

有时我们需要知道动画执行的进度和状态,在Flutter中我们可以通过Animation的addListeneraddStatusListener方法为动画添加监听器:

  • addListener:动画的值发生变化时被调用;
  • addStatusListener:动画状态发生变化时被调用;
枚举值含义
dismissed动画在起始点停止
forward动画正在正向执行
reverse动画正在反向执行
completed动画在终点停止
 @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(controller)
      // #enddocregion print-state
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      })
      // #docregion print-state
      ..addStatusListener((state) => print('$state'));
      ..addListener(() {
        // #enddocregion addListener
        setState(() {
          // The state that has changed here is the animation object’s value.
        });
        // #docregion addListener
      });
    controller.forward();
  }

6.用AnimatedWidget与AnimatedBuilder简化代码

什么是AnimatedWidget?

我们可以将AnimatedWidget理解为Animation的助手,使用它可以简化我们对动画的使用,在实现一个从小放大的动画的学习中我们不难发现,在不使用AnimatedWidget的情况下需要手动调用动画的addListener()并在回调中添加setState才能看到动画效果,AnimatedWidget将为我们简化这一操作。

在下面的重构示例中,LogoApp现在继承自AnimatedWidget而不是StatefulWidgetAnimatedWidget在绘制时使用动画的当前值。LogoApp仍然管理着AnimationControllerTween

// Demonstrate a simple animation with AnimatedWidget

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

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

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

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => new _LogoAppState();
}

class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> animation;

  initState() {
    super.initState();
    controller = new AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
    controller.forward();
  }

  Widget build(BuildContext context) {
    return new AnimatedLogo(animation: animation);
  }

  dispose() {
    controller.dispose();
    super.dispose();
  }
}

void main() {
  runApp(new LogoApp());
}

什么是AnimatedBuilder?

AnimatedBuilder是用于构建动画的通用widget,AnimatedBuilder对于希望将动画作为更大构建函数的一部分包含在内的更复杂的widget时非常有用,其实你可以这样理解:AnimatedBuilder是拆分动画的一个工具类,借助它我们可以将动画和widget进行分离:

在上面的实例中我们的代码存在的一个问题: 更改动画需要更改显示logo的widget。更好的解决方案是将职责分离:

  • 显示logo
  • 定义Animation对象
  • 渲染过渡效果

接下来我们就借助AnimatedBuilder类来完成此分离。AnimatedBuilder是渲染树中的一个独立的类, 与AnimatedWidget类似,AnimatedBuilder自动监听来自Animation对象的通知,不需要手动调用addListener()

我们根据下图的 widget 树来创建我们的代码:

AnimatedBuilder-WidgetTree

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

void main() => runApp(LogoApp());

// #docregion LogoWidget
class LogoWidget extends StatelessWidget {
  // Leave out the height and width so it fills the animating parent
  Widget build(BuildContext context) => Container(
        margin: EdgeInsets.symmetric(vertical: 10),
        child: FlutterLogo(),
      );
}
// #enddocregion LogoWidget

// #docregion GrowTransition
class GrowTransition extends StatelessWidget {
  GrowTransition({this.child, this.animation});

  final Widget child;
  final Animation<double> animation;

  Widget build(BuildContext context) => Center(
        child: AnimatedBuilder(
            animation: animation,
            builder: (context, child) => Container(
                  height: animation.value,
                  width: animation.value,
                  child: child,
                ),
 //动画过程中不执行刷新的子Widget
            child: child),
      );
}
// #enddocregion GrowTransition

class LogoApp extends StatefulWidget {
  _LogoAppState createState() => _LogoAppState();
}

// #docregion print-state
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
  Animation<double> animation;
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller =
        AnimationController(duration: const Duration(seconds: 2), vsync: this);
    animation = Tween<double>(begin: 0, end: 300).animate(controller);
    controller.forward();
  }
  // #enddocregion print-state

  @override
  Widget build(BuildContext context) => GrowTransition(
        child: LogoWidget(),
        animation: animation,
      );

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
  // #docregion print-state
}

参考:Flutter动画全解析(动画四要素、动画组件、隐式动画组件原理等)

Flutter 尺寸缩放、形状、颜色、阴影变换动画_flutter 缩放_xhu_ww的博客-CSDN博客

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值