Flutter动画: Animation动画基础(二)

我的博客

上一节我们简单介绍了一下animation的基本使用方法, 算是对flutter的动画有了个初步了解。

从之前的代码中可以看到,要让某个Widget动起来, 或者说要给Widget添加动画, 最少需要满足3个方面:

  • 设置控制器(controller)和动画数值变化(animation)区间, 并进行绑定,
  • 需要设置监听(listener), 然后在监听里更新数值(setState),
  • 将数值(animation.value)绑定到具体的Widget上, Widget自动根据数值变化更新UI

Flutter给了2个办法简化以上流程。

  • 1- 使用AnimatedWidget创建一个可重用动画的widget,
  • 2- 或者使用AnimatedBuilder从widget中分离出动画过渡。

AnimatedWidget

把需要动起来的Widget改写成AnimatedWidget子类, UI更新的工作就可以丢出去了。改写之前的例子, 让头像Header动起来,如下代码:

import 'package:flutter/material.dart';

class MyAnimatedWidgetDemo extends StatefulWidget {
  @override
  _MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}

class _MyAnimatedWidgetState extends State<MyAnimatedWidgetDemo>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
    );
    animation = Tween(begin: 0.0, end: 1.0).animate(controller);
    controller.repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('继承AnimatedWidget助手类')),
      body: Center(child: AnimatedHeader(animation: animation)),
    );
  }
}

class AnimatedHeader extends AnimatedWidget {
  AnimatedHeader({Key key, Animation animation})
      : super(key: key, listenable: animation);
  
  @override
  Widget build(BuildContext context) {
    final Animation animation = listenable;
    return Container(
          width: 100 * (1 + animation.value),
          height: 100 * (1 + animation.value),
          child: Image.asset('assets/images/head.jpg'),
        );
  }
}

代码中没有给controller添加数值监听, 而是直接将数值变化(animation)直接传递给了动画组件AnimatedHeader。AnimatedHeader自行根据animation更新UI。其实工作原理和之前是一样的。

实际效果也和之前一样。

继承AnimatedWidget

ps:代码中没有对动画状态进行监听, 直接用了repeat方法做循环播放。controller.repeat(reverse: true);

AnimatedBuilder

上面代码存在的一个问题:AnimatedHeader里的child藏的比较深, 无法推广到普遍适用的范畴。 例如如果需要设计好几个相同动画效果的widget,需要一个一个单独再写一次。

更好的解决方案是将职责分离, 使用AnimatedBuilder可以使动画工具化。

分离child

把头像图片的widget先分离出来

class HeaderProfile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Image.asset('assets/images/head.jpg');
  }
}

分离动画(实现类)

需要采用AnimatedBuilder来实现动画功能分离, 同时需要传入需要渲染的child和渲染方式(animation)。

AnimatedBuilder继承自抽象的AnimationWidget ,目的为了构建通用的AnimationWidget 实现类,不用每次使用AnimationWidget 都要创建一个实现类。

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

  final Widget child;
  final Animation<double> animation;

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

调用

需要将某个Widget赋予该动画效果时, 直接使用即可。

class MyAnimatedBuilderDemo extends StatefulWidget {
  @override
  _MyAnimatedBuilderDemoState createState() => _MyAnimatedBuilderDemoState();
}

class _MyAnimatedBuilderDemoState extends State<MyAnimatedBuilderDemo>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation animation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 1000),
    );
    animation = Tween(begin: 100.0, end: 300.0).animate(controller);
    controller.repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('采用AnimatedBuilder助手类')),
      body: Center(
        child: GrowTransition(
          animation: animation,
          child: HeaderProfile(),
        ),
      ),
    );
  }
}

效果如下:


AnimatedBuilder

系统预制的AnimatedBuilder实现类

Flutter中其实已经预制了不少AnimatedBuilder实现类, 如下所示(估计还会增加)。
具体可以直接使用Flutter的api。

  • AlignTransition
  • AnimatedAlign
  • AnimatedContainer
  • AnimatedCrossFade
  • AnimatedDefaultTextStyle
  • AnimatedIcon
  • AnimatedList
  • AnimatedModalBarrier
  • AnimatedOpacity
  • AnimatedPadding
  • AnimatedPositioned
  • AnimatedPositionedDirectional
  • AnimatedSwitcher
  • DecoratedBoxTransition
  • DefaultTextStyleTransition
  • FadeTransition
  • PositionedTransition
  • RelativePositionedTransition
  • RotationTransition
  • ScaleTransition
  • SizeTransition
  • SlideTransition
  • TweenAnimationBuilder

以上实现类大致分为2类(以下结论不一定正确, 没有全部测试过),

  • 显式动画: 以Transition结尾的类,例如OpacityTransitions,PositionedTransition等。 显式动画指的是需要手动设置动画的时间,运动曲线,取值范围的动画。将值传递给动画部件如: RotationTransition,最后使用一个AnimationController 控制动画的开始和结束。
  • 隐式动画: 以Animated开头的类, 例如AnimatedContainer, AnimatedOpacity等, 通过设置动画的起始值和最终值来触发。当使用 setState 方法改变部件的动画属性值时,框架会自动计算出一个从旧值过渡到新值的动画。

下面我们来拉几个实现类做例子

我们用两种方法做一个点击头像慢慢消隐/显现的动画

FadeTransition

import 'package:flutter/material.dart';

class MyFadeTransitionDemo extends StatefulWidget {
  @override
  _MyFadeTransitionDemoState createState() => _MyFadeTransitionDemoState();
}

class _MyFadeTransitionDemoState extends State<MyFadeTransitionDemo>
    with SingleTickerProviderStateMixin {
  Duration duration = Duration(milliseconds: 1000);
  AnimationController controller;
  Animation<double> opacityAnimation;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(vsync: this, duration: duration);
    opacityAnimation = Tween<double>(begin: 0.2, end: 1.0).animate(controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('点击消隐-FadeTransition组件')),
      body: Center(
        child: FadeTransition(
          opacity: opacityAnimation,
          child: InkWell(
              onTap: () {
                if (controller.status == AnimationStatus.completed) {
                  controller.reverse();
                }
                if (controller.status == AnimationStatus.dismissed) {
                  controller.forward();
                }
              },
              child: Image.asset('assets/images/head.jpg')),
        ),
      ),
    );
  }
}

实现效果如下:


FadeTransition

AnimatedOpacity

上代码

import 'package:flutter/material.dart';

class MyAnimatedOpacityDemo extends StatefulWidget {
  @override
  _MyAnimatedOpacityDemoState createState() => _MyAnimatedOpacityDemoState();
}

class _MyAnimatedOpacityDemoState extends State<MyAnimatedOpacityDemo> {
  bool isActive = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('点击消隐-AnimatedOpacity组件')),
      body: Center(
        child: AnimatedOpacity(
          opacity: isActive ? 0.2 : 1.0,
          duration: Duration(milliseconds: 1000),
          child: InkWell(
              onTap: () {
                setState(() {
                  isActive = !isActive;
                });
              },
              child: Image.asset('assets/images/head.jpg')),
        ),
      ),
    );
  }
}

效果和上面是一样的。

AnimatedOpacity

要想构建漂亮的用户界面, 动画必不可少, 一些icon的点缀性小动画能极大增强用户体验。

Flutter的动画入门难度比Android低多了, 后面的章节我们再来练习几个动画实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270