Flutter 动画

时间: 2019-11-27阅读: 528标签: 动画

Flutter动画中用到的基本概念

Flutter动画中有4个比较重要的角色:Animation、Controller、Curve、Tween,先来了解一下这四个角色

1.1 Animation

Animation是Flutter动画库中的核心类,用于插入指导动画的值

Animation对象知道动画当前的状态(比如开始还是停止),可以使用addListener和addStatusListener监听动画状态改变。

animation.addListener((){
   //调用setState来刷新界面
   setState(() {
   });
 });
 animation.addStatusListener((status){
   debugPrint('status $status');
   switch (status){
     //动画一开始就停止了
     case AnimationStatus.dismissed:
       break;
     //动画从头到尾都在播放
     case AnimationStatus.forward:
       break;
     //动画从结束到开始倒着播放
     case AnimationStatus.reverse:
       break;
     //动画播放完停止
     case AnimationStatus.completed:
       break;
   }
 });
  1. addListener: 每一帧都会调用,调用之后一般使用setState来刷新界面
  2. addStatusListener:监听动画当前的状态 如动画开始、结束、正向或反向

在Flutter中,Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,它拥有其当前值和状态(完成或停止)。其中一个比较常用的Animation类是 Animation<double> ,还可以生成除double之外的其他类型值,如: Animation<Color> 或 Animation<Size> 。

1.2 AnimationController

用来管理Animation,它继承自Animation, 是个特殊的Animation ,屏幕每刷新一帧,它都会生成一个新值,需要一个vsync参数, vsync的存在可以防止后台动画消耗不必要的资源 。

vsync的值怎么获得,可以让stateful对象扩展使用TickerProviderStateMixin比如:

class AnimationDemoHome extends StatefulWidget {
  @override
  _AnimationDemoHomeState createState() => _AnimationDemoHomeState();
}

class _AnimationDemoHomeState extends State<AnimationDemoHome> with TickerProviderStateMixin{...}

AnimationController在默认情况下,在给定的时间段内,AnimationController会生成0.0到1.0的数字。

它可以控制动画,比如使用 .forward() 方法可以启动一个动画, .stop() 可以结束一个动画, .reverse() 启动反向动画。

AnimationController({
  double value,
  this.duration,
  this.reverseDuration,
  this.debugLabel,
  this.lowerBound = 0.0,
  this.upperBound = 1.0,
  this.animationBehavior = AnimationBehavior.normal,
  @required TickerProvider vsync,
})

看一下AnimationController的构造方法,有一个必须的参数TickerProvider,就是前面给定的TickerProviderStateMixin

在StatefulWidget中创建一个AnimationController对象

  animationController = AnimationController(
//      lowerBound: 32.0,
//      upperBound: 100.0,
      duration: Duration(milliseconds: 2000),
      vsync: this
    );

1.3 CurvedAnimation

定义动画曲线,运动过程,比如匀速,先加速在减速等等

CurvedAnimation({
   @required this.parent,
   @required this.curve,
   this.reverseCurve,
 })

它有两个必要的参数parent和curve。parent就是前面的AnimationController对象,curve就是动画运行的曲线,相当于Android属性动画中的插值器curve都有哪些取值呢

curve曲线动画过程

linear|匀速的

decelerate|匀减速

ease|先加速后减速

easeIn|开始慢后面快

easeout|开始快后面慢

easeInOut|先慢在快在慢

上面是常用的一些曲线,还有很多中曲线运动的方式可以去curve.dart源码中去看,源码注释中有mp4的链接,可以清楚的看到动画运动的视频。

abstract class Curve {
  const Curve();

  double transform(double t) {
    assert(t >= 0.0 && t <= 1.0);
    if (t == 0.0 || t == 1.0) {
      return t;
    }
    return transformInternal(t);
  }

  @protected
  double transformInternal(double t) {
    throw UnimplementedError();
  }
  ...
}

如果系统提供的运动曲线仍然无法满足我们的需求,那就可以继承Curve来自己实现一个。上面的代码可以看到Curve是一个抽象类,继承它并重写transform方法即可。比如我们可以自己在里面实现一个sin或者cos函数的曲线。例如

class ShakeCurve extends Curve {
  @override
  double transform(double t) {
    return math.sin(t * math.PI * 2);
  }
}

创建一个CurvedAnimation对象

CurvedAnimation curvedAnimation =
CurvedAnimation(parent: animationController,curve: Curves.bounceOut);

1.4 Tween:

给动画对象插入一个范围值

默认情况下,AnimationController对象的范围从0.0到1.0,如果我们想要更大的范围,就需要使用到Tween了。比如

Tween tween = Tween(begin: 32.0,end: 100.0);

class Tween<T extends dynamic> extends Animatable<T> Tween继承自Animatable,接收一个begin和一个end值,Tween的职责就是定义从输入范围到输出范围的映射。所以这两个值必须能进行加减乘的运算。

要使用Tween对象,调用其animate()方法,传入一个控制器对象,返回一个Animation对象。例如,

Animation   animation = Tween(begin: 32.0,end: 100.0).animate(curvedAnimation);
Animation   animationColor = ColorTween(begin: Colors.red,end: Colors.green).animate(curvedAnimation);


动画的使用

2.1 Animation动画

动画的四个角色都了解了,下面开始使用这些角色来构建一个动画,动画效果如下图


有一个心形的button,点击的时候放大并且颜色渐变,在点击的时候原路返回

class AnimateDemo1 extends StatefulWidget {
  @override
  _AnimateDemo1State createState() => _AnimateDemo1State();
}

class _AnimateDemo1State extends State<AnimateDemo1> with SingleTickerProviderStateMixin{
  AnimationController animationController;
  Animation animationSize;
  Animation animationColor;
  CurvedAnimation curvedAnimation;

  //Tween sizeTween;
  //Tween colorTween;
  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
        duration: Duration(milliseconds: 1000),
        vsync: this
    );
    //设置插值器  这里使用一个默认的插值器bounceInOut
    curvedAnimation = CurvedAnimation(parent: animationController,curve: Curves.bounceOut);
    animationSize = Tween(begin: 32.0,end: 100.0).animate(curvedAnimation);
    animationColor = ColorTween(begin: Colors.red,end: Colors.green).animate(curvedAnimation);
    animationController.addListener((){
      //刷新界面
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: IconButton(
        icon: Icon(Icons.favorite),
        iconSize: animationSize.value,
        color: animationColor.value,
        //iconSize: sizeTween.evaluate(curvedAnimation),
        //color: colorTween.evaluate(curvedAnimation),
        onPressed: (){
          switch(animationController.status){
            case AnimationStatus.completed:
              animationController.reverse();
              break;
            default:
              animationController.forward();
          }
        },
      ),
    );
  }
}
sizeTween.evaluate(curvedAnimation)

2.2使用AnimatedWidget

2.1中每次写动画都需要在addListener中设置setState来更新UI,有点麻烦,系统给提供了一个AnimatedWidget,它内部封装了addListener和setState的逻辑,我们只需要传给它AnimationController和Animation就行了。

而且我们可以自定义一个Widget继承它,让动画跟原来的视图代码分离

class AnimationDemo2 extends StatefulWidget {
  @override
  _AnimationDemo2State createState() => _AnimationDemo2State();
}

class _AnimationDemo2State extends State<AnimationDemo2> with SingleTickerProviderStateMixin{

  AnimationController animationController;
  Animation animationSize;
  Animation animationColor;
  CurvedAnimation curvedAnimation;
  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
      duration: Duration(milliseconds: 1000),
      vsync: this
    );
    //设置插值器  这里使用一个默认的插值器bounceInOut
    curvedAnimation = CurvedAnimation(parent: animationController,curve: Curves.bounceOut);
    animationSize = Tween(begin: 32.0,end: 100.0).animate(curvedAnimation);
    animationColor = ColorTween(begin: Colors.red,end: Colors.green).animate(curvedAnimation);
  }

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimationHeart(
         animations: [
           animationSize,animationColor
         ],
        controller: animationController,
      ),
    );
  }
}
//动画代码抽离
class AnimationHeart extends AnimatedWidget{
  AnimationController controller;
  List animations;
  AnimationHeart({ this.animations,
    this.controller,}):super(listenable:controller);

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: Icon(Icons.favorite),
      iconSize: animations[0].value,
      color: animations[1].value,
      onPressed: (){
        switch(controller.status){
          case AnimationStatus.completed:
            controller.reverse();
            break;
          default:
            controller.forward();
        }
      },
    );
  }
}

自定义一个AnimationHeart继承自AnimatedWidget,在构造方法中将AnimationController和Animation传过来。其余的跟2.1中一样,最终效果也一样。

2.3使用AnimatedBuilder

Flutter中还可以使用AnimatedBuilder来构建一个动画

class AnimateDemo3 extends StatefulWidget {
  @override
  _AnimateDemo3State createState() => _AnimateDemo3State();
}

class _AnimateDemo3State extends State<AnimateDemo3> with SingleTickerProviderStateMixin{

  AnimationController animationController;
  Animation animationSize;
  Animation animationColor;
  CurvedAnimation curvedAnimation;
  @override
  void initState() {
    super.initState();
    animationController = AnimationController(
        duration: Duration(milliseconds: 1000),
        vsync: this
    );
    //设置插值器  这里使用一个默认的插值器bounceInOut
    curvedAnimation = CurvedAnimation(parent: animationController,curve: Curves.bounceOut);
    animationSize = Tween(begin: 32.0,end: 100.0).animate(curvedAnimation);
    animationColor = ColorTween(begin: Colors.red,end: Colors.green).animate(curvedAnimation);
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animationController,
      builder: (context,child){
        return Center(
          child: IconButton(
            icon: Icon(Icons.favorite),
            iconSize: animationSize.value,
            color: animationColor.value,
            onPressed: (){
              switch(animationController.status){
                case AnimationStatus.completed:
                  animationController.reverse();
                  break;
                default:
                  animationController.forward();
              }
            },
          ),
        );
      },
    );
  }
}

实例化四个动画元素的代码跟前面还是一样,主要是在build代码块中使用AnimatedBuilder构建,传入animation对象。看起来比2.2中的方式也没有简单多少,不过看一下它的构造方法,系统还给提供了一个可选的参数child,让它天然就支持封装。

const AnimatedBuilder({
    Key key,
    @required Listenable animation,
    @required this.builder,
    this.child,
  })
  • 必需要一个Listenable,Animation就是Listenable
  • 必需要一个builder,前面的代码中知道builder中需要传一个context和一个child
  • 可以传一个child。 传入的这个child最终会传入到builder中

上面的例子中我们是直接在builder中创建了一个控件,既然child可以传进来,那么我们可以把一个类型的动画封装一下比如缩放动画,渐变动画等,以后只要把需要此动画的小部件传进来,这个小部件就有这个动画了。

比如下面定义一个可以缩放的小部件。

class ScaleAnimate extends StatelessWidget {
  final Animation animation;
  final Widget child;
  ScaleAnimate({@required this.animation,@required this.child});
  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context,child){
        return SizedBox(
            width: animation.value,
            height: animation.value,
            child: child,
          );
      },
      child: child,
    );
  }
}


Hero动画

Hero动画很简单不过在平时的项目中也经常用到,主要用在路由页面之间切换。比如一个头像点击看大图,或者新闻列表页面,点击看详情,这种共享式的无缝切换。

动画效果如下图


class AnimateDemo4 extends StatefulWidget {
  @override
  _AnimateDemo4State createState() => _AnimateDemo4State();
}

class _AnimateDemo4State extends State<AnimateDemo4> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: InkWell(
        child: Hero(
          tag: "avator",
          child: ClipOval(
            child: Image.network('http://ww1.sinaimg.cn/large/0065oQSqly1fsfq1k9cb5j30sg0y7q61.jpg',width: 100,),
          ),
        ),
        onTap: (){
          Navigator.of(context).push(MaterialPageRoute(builder: (context){
            return Scaffold(
              body: Center(
                child: Hero(
                  tag: "avator",
                  child: Image.network('http://ww1.sinaimg.cn/large/0065oQSqly1fsfq1k9cb5j30sg0y7q61.jpg'),
                ),
              ),
            );
          }));
        },
      ),
    );
  }
}
  • 当前页面的圆形小图和详情页面的大图都使用Hero包裹。
  • 必须使用相同的tag,Flutter Framework通过tag来确定他们之间的关系。


交织动画

有时候我们需要实现一组复杂的动画,比如在0.1-0.2秒缩放,从0.2-0.4秒颜色渐变,从0.4-0.8秒左右移动,这时候使用交织动画可以方便的完成,使用交织动画需要注意下面几点

  • 需要使用多个Animation对象
  • 一个AnimationController控制所有的动画对象
  • 给每一个动画对象指定时间间隔(Interval)


class AnimateDemo5 extends StatefulWidget {
  @override
  _AnimateDemo5State createState() => _AnimateDemo5State();
}

class _AnimateDemo5State extends State<AnimateDemo5> with TickerProviderStateMixin{
  AnimationController _controller;
  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this
    );
  }
    @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: <Widget>[
          SizedBox(height: 30,),
          Center(
            child: StaggerAnimation(controller: _controller,),
          ),
          SizedBox(height: 30,),
          RaisedButton(
            child: Text("点击开始"),
            onPressed: () {
              _play();
            },
            textColor: Theme.of(context).primaryColor,
            splashColor: Colors.grey[400],
          )
        ],
      ),
    );
  }

  void _play() async{
    //先正向执行动画
    await _controller.forward().orCancel;
    //再反向执行动画
    await _controller.reverse().orCancel;
  }
}

class StaggerAnimation extends StatelessWidget {
  final AnimationController controller;
  Animation<double> width,height;
  Animation<EdgeInsets> padding;
  Animation<Color> color;
  Animation<BorderRadius> borderRadius;

  StaggerAnimation({Key key,this.controller}): super(key:key){
    height = Tween<double>(
        begin: 0,
        end: 200)
        .animate(CurvedAnimation(parent: controller, curve: Interval(0.0,0.4,curve: Curves.ease)));
    width = Tween<double>(
        begin: 50,
        end: 200)
        .animate(CurvedAnimation(parent: controller, curve: Interval(0.0,0.4,curve: Curves.ease)));
    padding = Tween<EdgeInsets>(
      begin:EdgeInsets.only(left: .0),
      end:EdgeInsets.only(left: 100.0),
    ).animate(CurvedAnimation(parent: controller, curve: Interval(0.6, 1.0, curve: Curves.ease)),);
    color = ColorTween(
      begin:Colors.green ,
      end:Colors.red,
    ).animate(CurvedAnimation(parent: controller, curve: Interval(0.0, 0.4, curve: Curves.ease,)));
    borderRadius = BorderRadiusTween(
      begin: BorderRadius.circular(3),
      end: BorderRadius.circular(35),
    ).animate(CurvedAnimation(parent: controller, curve: Interval(0.4, 0.6,curve: Curves.ease,),));
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context,child){
        return Container(
          alignment: Alignment.bottomCenter,
          padding:padding.value ,
          child: Container(
            width: width.value,
            height: height.value,
            decoration: BoxDecoration(
                color: color.value,
                border: Border.all(color: Colors.blue,width: 3),
                borderRadius:borderRadius.value
            ),
          ),
        );
      },
    );
  }
}
  • StaggerAnimation中定义了5个动画,宽,高,颜色,左边距,圆角
  • 使用Interval来定义某个动画执行的时机
  • 最后异步启动动画。

原文 https://chsmy.github.io/2019/11/28/flutter/Flutter-动画/


站长推荐

1.阿里云: 本站目前使用的是阿里云主机,安全/可靠/稳定。点击领取2000元代金券、了解最新阿里云产品的各种优惠活动点击进入

2.腾讯云: 提供云服务器、云数据库、云存储、视频与CDN、域名等服务。腾讯云各类产品的最新活动,优惠券领取点击进入

3.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/6666

设计师10大必备HTML动画工具

HTML 5已经成为Web开发者当中最流行的语言之一,“健壮的”语言有很大的生产优质万维网内容的能力。HTML 5在近三年的增长迅猛,新引入HTML 5的技术更加的优秀。HTML 5技术被现代浏览器所支持

前端动画必知必会:React 和 Vue 都在用的 FLIP 思想实战

拿到了这个需求,第一直觉是怎么做?假设第一行第一个图片移动到了第二行第三列,是不是要计算出第一行的高度,再计算出第二行前两个元素的宽度,然后从初始的坐标点通过 CSS 或者一些动画 API 移动过去?

css3贝塞尔曲线(cubic-bezier)

css3 animation模块,其中animation-timing-function 和 transition-timing-function两个属性来控制动画速度分别提供了ease,liner,ease-in,ease-out

css transition 实现滑入滑出

transition是css最简单的动画。 通常当一个div属性变化时,我们会立即看的变化,从旧样式到新样式是一瞬间的,嗖嗖嗖!!!但是,如果我希望是慢慢的从一种状态,转变成另外一种状态,怎么办? transition可以做到。

js简单动画:匀速动画、缓动动画、多物体动画以及透明度动画

主要实现以下几种简单的动画效果(其实原理基本相同):1.匀速动画:物体的速度固定2.缓动动画:物体速度逐渐变慢3.多物体动画

多段动画整合为一个动画的思路

这是从一个项目中提取的需求。 大致的思路是: 通过获取后端的一系列人员的点位信息,在前端模拟人员的一段时间内的行动过程。我们的开发人员首先想到的思路是,把获取的点位列表信息,每两个取取来,组成一条直线路径

CSS3 动画的实现

什么是CSS3动画?在CSS中,动画是一种让元素逐渐改变样式的效果。 您可以使用@keyframes关键字创建动画,后跟动画的名称。要使动画跨浏览器兼容,您需要使用供应商前缀:

css3中动画有哪些属性?

通过CSS3,我们能够创建动画,这可以在许多网页中取代动画图片、Flash 动画以及 JavaScript。动画是使元素从一种样式逐渐变化为另一种样式的效果。那么在CSS中动画属性有哪些?

css @keyframes属性 语法

@keyframes是CSS的一种规则,可以用来定义CSS动画的一个周期的行为,创建简单的动画。作用:通过 @keyframes 规则,您能够创建动画。

为什么要用css动画替换js动画?

通常我们会通过频繁的操作 DOM的CSS来实现视觉上的动画效果,导致js效率低的两个因素都包括在内了在频繁的操作DOM和CSS时,浏览器会不停的执行重排和重绘,在PC版本的浏览器中,因为浏览器可用的内存比较大

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!

文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全运营推广