Flutter入门10--动画

Animation

  • 在Flutter中,实现动画的核心类是Animation,Widget可以直接将这些动画合并到自己的build方法中来读取它们当前值或监听它们的状态变化;
  • Animation是一个抽象类,常见的方法如下:
  • addListener方法
    • 每当动画的状态值发生变化时,动画都会通知所有通过 addListener 添加的监听器。
    • 通常,一个正在监听动画的state对象会调用自身的setState方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。
  • addStatusListener方法
    • 当动画的状态发生变化时,会通知所有通过 addStatusListener 添加的监听器。
    • 通常情况下,动画会从 dismissed 状态开始,表示它处于变化区间的开始点。
    • 举例来说,从 0.0 到 1.0 的动画在 dismissed 状态时的值应该是 0.0。
    • 动画进行的下一状态可能是 forward(比如从 0.0 到 1.0)或者 reverse(比如从 1.0 到 0.0)。
    • 最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed状态。

AnimationController

  • AnimationController是Animation的一个子类,通常我们会创建一个 AnimationController实例对象来实现动画效果;
  • AnimationController会生成一系列的值,默认情况下值是0.0到1.0区间的值;
  • AnimationController提供了对动画的控制
    • forward:向前执行动画
    • reverse:方向播放动画
    • stop:停止动画
  • 其构造函数如下:
  AnimationController({
    double value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    @required TickerProvider vsync,
  })
  • value:初始化值;
  • duration:动画执行的时间;
  • reverseDuration:反向动画执行的时间;
  • lowerBound:最小值;
  • upperBound:最大值;
  • vsync:垂直信号,必传参数;

CurvedAnimation

  • CurvedAnimation是Animation的一个子类,它的作用是给Animation增加执行曲线,执行速率;
  • CurvedAnimation可以将AnimationController和Curve结合起来,生成一个新的Animation对象;
  • Curve类型的对象的有一些常量Curves(和Color类型有一些Colors是一样的),可以供我们直接使用,在官网https://api.flutter.dev/flutter/animation/Curves-class.html 中提供了动画效果;

Tween

  • 默认情况下,AnimationController动画生成的值所在区间是0.0到1.0,如果需要使用这个以外的值,或者其他的数据类型,就需要使用Tween;
  • Tween构造函数如下:
Tween({ this.begin, this.end })
  • 传参味初始值与结束值,可确定一段区间;
  • Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值;

案例代码:

  • 需求:目标组件心形图标,由小到大,由大到小 无限循环 ,点击可暂停动画,再次点击在原来的状态基础上继续执行动画;
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
      lowerBound: 0.0,
      upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);

    //3.监听动画值的改变 刷新界面
    _controller.addListener(() {
      setState(() {

      });
    });

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: Center(
          child: Icon(Icons.favorite,color: Colors.red,size: _animationSize.value),
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

AnimatedWidget

  • 上述实现的动画效果,存在一个弊端:
  • 监听动画值的改变,然后调用setState方法,就会导致State类build的方法重新执行,里面包含的所有组件都会重新构建,这样效率十分低下;
  • 可通过AnimatedWidget来解决这个弊端,执行动画时,仅仅让心形组件重建即可,自定义组件SFAnimationIcon继承自AnimatedWidget, 实现如下:
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
      lowerBound: 0.0,
      upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);

    //3.监听动画值的改变 刷新界面
    // _controller.addListener(() {
    //   setState(() {
    //
    //   });
    // });

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("_SFHomePageState build");
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: Center(
          child: SFAnimationIcon(_animationSize),
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

class SFAnimationIcon extends AnimatedWidget {
  final Animation _animationSize;

  SFAnimationIcon(this._animationSize): super(listenable: _animationSize);

  @override
  Widget build(BuildContext context) {
    return Icon(Icons.favorite,color: Colors.red,size: _animationSize.value);
  }
}

AnimatedBuilder

  • AnimatedWidget解决了上述动画的执行效率问题,但其也存在弊端:
    • 我们每次都要新建一个类来继承自AnimatedWidget;
    • 如果我们的动画Widget有子Widget,那么意味着它的子Widget也会重新build;
  • 可通过AnimatedBuilder来解决上面的两个问题,代码如下:
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
        vsync: this,
        duration: Duration(seconds: 2),
        lowerBound: 0.0,
        upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);

    //3.监听动画值的改变 刷新界面
    // _controller.addListener(() {
    //   setState(() {
    //
    //   });
    // });

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("_SFHomePageState build");
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (ctx,child) {
            return Icon(Icons.favorite,color: Colors.red,size: _animationSize.value);
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

交织动画

  • 动画集合了透明度变化、大小变化、颜色变化、旋转动画等;
  • 通过多个Tween生成了多个Animation对象;
  • 代码如下所示:
import 'dart:math';

import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;
  Animation _animationColor;
  Animation _animationOpacity;
  Animation _animationRotation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
        vsync: this,
        duration: Duration(seconds: 2),
        lowerBound: 0.0,
        upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 10.0,end: 200.0).animate(_controller);
    _animationColor = ColorTween(begin: Colors.orange,end: Colors.pink).animate(_controller);
    _animationOpacity = Tween(begin: 0.0,end: 1.0).animate(_controller);
    _animationRotation = Tween(begin: 0.0,end: 2 * pi).animate(_controller);

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("_SFHomePageState build");
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (ctx,child) {
            return Opacity(
              opacity: _animationOpacity.value,
              child: Transform(
                transform: Matrix4.rotationZ(_animationRotation.value),
                alignment: Alignment.center,
                child: Container(
                  width: _animationSize.value,
                  height: _animationSize.value,
                  color: _animationColor.value,
                ),
              ),
            );
          },
        )
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

转场动画

  • 界面切换时的动画效果;
  • 新建一个界面ModalPage,代码如下:

import 'package:flutter/material.dart';

class ModalPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.red,
      appBar: AppBar(
        title: Text("Modal Page"),
      ),
      body: Center(
        child: Text("Modal Page"),
      ),
    );
  }
}
  • 首页代码:
import 'package:Fluter01/day01/pages/ModalPage.dart';
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: Center(
          child: Text("Hello world!!"),
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.pool),
        onPressed: () {
          //iOS Modal界面切换
          // Navigator.of(context).push(MaterialPageRoute(
          //   builder: (ctx) {
          //     return ModalPage();
          //   },
          //   fullscreenDialog: true
          // ));

          Navigator.of(context).push(PageRouteBuilder(
            transitionDuration: Duration(seconds: 3),
            pageBuilder: (ctx,animation1,animation2) {
              return FadeTransition(
                opacity: animation1,
                child: ModalPage()
              );
            }
          ));

        },
      ),
    );
  }
}
  • 通过PageRouteBuilder实现转场动画效果;

Hero动画

  • 需求:图片列表展示图片,当点击一张图片时,切换到图片详情页面,显示点击的图片,切换有动画效果;
  • 图片详情页面,代码如下:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class ImageDetailPage extends StatelessWidget {

  final String imageUrl;

  ImageDetailPage(this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pop();
          },
          child: Hero(
            tag: imageUrl,
            child: Image.network(imageUrl)
          )
        ),
      ),
    );
  }
}
  • 首页代码如下:
import 'package:Fluter01/day01/pages/ImageDetailPage.dart';
import 'package:Fluter01/day01/pages/ModalPage.dart';
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: GridView(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
          childAspectRatio: 16/9
        ),
        children: List.generate(20, (index) {
          final String imageUrl = "https://picsum/photos/500/500?random=$index";
          return GestureDetector(
            onTap: () {
              Navigator.of(context).push(PageRouteBuilder(
                pageBuilder: (ctx,animation1,animation2) {
                  return FadeTransition(opacity: animation1, child: ImageDetailPage(imageUrl));
                }
              ));
            },
            child: Hero(
              tag: imageUrl,
              child: Image.network(
                "https://picsum/photos/500/500?random=$index",
                fit: BoxFit.cover,
              ),
            ),
          );
        }),
      ),
    );
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Animation 在Flutter中,实现动画的核心类是Animation,Widget可以直接将这些动画合并到...
    YanZi_33阅读 221评论 0 0
  • Flutter的动画体系是怎么运作的,各组件之间的关联关系及原理什么,隐式动画、显式动画怎么区分,本文将会进行详细...
    whqfor阅读 1,959评论 0 6
  • 基本的动画概念和类 转自:https://flutterchina.club/tutorials/animatio...
    朱慢慢阅读 382评论 0 0
  • 对于一个前端的App来说,添加适当的动画,可以给用户更好的体验和视觉效果。所以无论是原生的iOS或Android,...
    5e4c664cb3ba阅读 1,492评论 0 7
  • 概述 动画API认识 动画案例练习 其它动画补充 一、动画API认识 动画实际上是我们通过某些方式(某种对象,An...
    IIronMan阅读 355评论 1 3