Flutter 动画详解系列——隐式动画

上一篇文章Flutter 动画详解系列概述了下Flutter中的动画类型,及如何选择恰当的动画创建方式,接下来我们来看下最简单的动画,隐式动画。

系统的隐式动画Widget

在 Flutter 中的 Widgets 中有一部已经实现隐式动画Widget。如下图列出部分:

implicit_animation.png

首先我们来看一段未使用动画的代码:

 bool _bigger = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Center(
            child: Container(
              width: _bigger ? 100 : 500,
              height: 100,
              color: Colors.red,
            ),
          ),
          RaisedButton(
            onPressed: () => setState(() {
              _bigger = !_bigger;
            }),
            child: Icon(Icons.star),
          ),
        ],
      ),
    );
  }

未加动画效果时,矩形的形变会显得十分生硬,如果使用AnimatedContainer替换Container,增加一个动画的过渡效果:

  Center(
            child: AnimatedContainer(
              width: _bigger ? 100 : 500,
              height: 100,
              color: Colors.red,
              duration: Duration(seconds: 1),
            ),
          ),

整个过渡过程显得比较自然顺畅,我们通过新旧值之间的值进行动画处理的过程称为插值。每当旧值和新值发生变化时,AnimatedContainer便会处理其属性插值。

同样我们也可以通过插值来修改AnimatedContainer的其它属性,包括decoration的渐变色:

AnimatedContainer(
  decoration: BoxDecoration(
    gradient: RadialGradient(
      colors: [Colors.purple, Colors.transparent],
      stops: [ _bigger ? 0.2 : 0.5, 1.0])
  ),
),

上述代码很简单的演示如何使用 隐式动画Widget 来实现动画效果,非常的方便简单,但这也意味着可灵活性较差,在隐式动画Widget中,我们控制动画效果只能控制动画时长(Duration)和动画的曲线(Curve,具体的曲线效果可以参考 系统自带的曲线效果)。

AnimatedContainer(
  width: _bigger ? 100 : 500,
  child: Image.asset('assets/star.png'),
  duration: Duration(seconds: 1),
  curve: Curves.easeInOutQuint,
),

另外除了系统自带的曲线效果外,我们还可以通过继承Curve来实现自定义的曲线效果,如下实现了正弦曲线。

class SineCurve extends Curve {
final double count;

SineCurve({this.count = 1});

@override
double transformInternal(double t) {
return sin(count * 2 * pi * t) * 0.5 + 0.5;
}
}

小结

在Flutter中,系统已经提供了隐式动画的Widget,这些Widget是普通Widget的动画版本,我们可以通过 durationcurve来控制动画效果。

还有我们不一定需要通过StatefulWidget中使用setState来生成动画效果,我们也可以使用StreamBuilderFutureBuilder来触发动画。

FutureBuilder(
  future: future,
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    double width;
    switch(snapshot.connectionState) {
      case ConnectionState.none:
      case ConnectionState.waiting:
      case ConnectionState.active:
        width = 0;
        break;
      case ConnectionState.done:
        width = 500;
        break;
    }
    return AnimatedContainer(
      width: width,
      height: 100,
      color: Colors.red,
      duration: Duration(seconds: 1),
    );
  }
),

以上,如果Flutter框架提供给你的隐式动画Widget不能满足你的需求,那么进一步的话可以试试使用TweenAnimationBuilder来自定义创建隐式动画。

自定义隐式动画TweenAnimationBuilder

使用TweenAnimationBuilder,该 Widget 使用的时候我们需要传递 duration 参数动画时间、tween 参数动画要设置的值的范围(补间)、重要的还有 builder 参数,builder函数的参数包含context、补间参数tween的类型、还有child,让我们看一个简单的例子,红色的矩形框旋转360度:

TweenAnimationBuilder<double>(
            tween: Tween<double>(begin: 0, end: 2 * pi),
            duration: Duration(seconds: 2),
            builder: (BuildContext context, double angle, Widget child) {
              return Transform.rotate(
                angle: angle,
                child: Container(
                  color: Colors.red,
                  width: 100,
                  height: 100,
                ),
              );
            },
          ),

让我们再来看个例子,使用ColorFilered Wideget做一个图片渲染的效果。

TweenAnimationBuilder(
  tween: ColorTween(begin: Colors.white, end: Colors.red),
  duration: Duration(seconds: 2),
  builder: (_, Color color, __) {
    return ColorFiltered(
      child: Image.asset('assets/sun.png'),
      colorFilter: ColorFilter.mode(color, BlendMode.modulate),
    );
  },
)

通过Tween补间参数设置了从白色到红色的过渡,由颜色和图片的混合,另外如何补间参数可变的,所以如果补间参数是不变的话可以将参数声明为静态常量来使用。

static final colorTween = ColorTween(begin: Colors.white, end: Colors.red);

Center(
          child: TweenAnimationBuilder<Color>(
            tween: colorTween,
            duration: Duration(seconds: 2),
            builder: (_, Color color, __) {
              return ColorFiltered(
                child: Image.asset('assets/sun.png'),
                colorFilter: ColorFilter.mode(color, BlendMode.modulate),
              );
            },
          ),
        ),

动态修改 Tween 参数

上面的例子中我们并没有调用setState,仅仅展示了动画从Tween的初始值到终值的简单动画效果,除此之外,我们还可以通过动态的修改Tween来实现动画效果:

class OngoingAnimationByModifyingEndTweenValue extends StatefulWidget {
  @override
  _OngoingAnimationState createState() => _OngoingAnimationState();
}

class _OngoingAnimationState extends State<OngoingAnimationByModifyingEndTweenValue> {
  double _newValue = .4;
  Color _newColor = Colors.white;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        starsBackground,
        Column(
          children: <Widget>[
            Center(
              child: TweenAnimationBuilder(
                tween: ColorTween(begin: Colors.white, end: _newColor),
                duration: Duration(seconds: 2),
                builder: (_, Color color, __) {
                  return ColorFiltered(
                    child: Image.asset('assets/sun.png'),
                    colorFilter: ColorFilter.mode(color, BlendMode.modulate),
                  );
                },
              ),
            ),
            Slider.adaptive(
              value: _newValue,
              onChanged: (double value) {
                setState(() {
                  _newValue = value;
                  _newColor = Color.lerp(Colors.white, Colors.red, value);
                });
              },
            ),
          ],
        ),
      ],
    );
  }
}

首先我们声明一个_newColor 作为 Tween 的终值,通过滑动 Slider Widget,我们改变_newColor。调用setState每次的滑动都会使得动画更新。

另外,需要明确的一点事,TweenAnimationBuilder 的动画属性值是从当前值向最新的终值转变的。如上面的例子,当我们拖动 Slider 时,颜色变化相对于之前的颜色,而不是每次从最初的白色开始动画渐变。

TweenAnimationBuilder 总会将当前颜色到终值颜色平滑的动画过渡。所以如果改变的不是终值颜色而是开始颜色,动画效果是不会有区别的。

补充说明

除了上诉介绍的TweenAnimationBuilder参数外,我们还需要注意的参数还有:

  • curve,动画曲线,在上一篇文章Flutter 动画详解系列有过介绍。
  • 动画完成的回调 onEnd :,我们可以在动画完成时完成指定的操作,如动画完成后显示另一个Widget。
  • child参数,child参数的设置其实也是一个潜在的性能优化项,正确的设置child,对动画性能的提升也是一大帮助。例子中虽然颜色发生了变化,但图片本身保持不变,但是当前代码是每次build都会重新创建Image Widget。针对此类的优化方式,我们可以提前创建图片,将图片作为参数传入,这样 Flutter 就知道每帧渲染时变化的是颜色,而不是图片本身。 当然因为例子本身简单,所以此优化不会有明显的效果,但当实现复杂动画效果时,慎重考虑child的实现,将会对你的动画性能带来一定的帮助。

总结

OK,以上就是对 Flutter 中的隐式动画的介绍了,包括系统自带的AnimatedFooTweenAnimationBuilder都有了一定的涉及。包括如何不通过使用StatefulWidget来实现动画效果、如何改变tween的终值产生顺滑的动画效果、如何提升TweenAnimationBuilder的动画性能时,我们可以设置tween参数时可以考虑设置为静态常量,设置child参数时可以考虑提前创建好child,作为参数传递。

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