持续创作,加速成长!这是我参加「日新方案 6 月更文挑战」的第1天,点击检查活动概况

前言

又到了一年一度的 520,咱们都有没有被虐狗呢?赶忙学会下面这个小动画,去赢取女神的芳心吧。

动画作用展现如下:

Flutter 实现爱心小动画并部署 Github Page

这个动画的作用和完成思路主要来自 B 站上的这个视频, 【Bilibili】【HTML+CSS】8分钟完成砰砰的爱心动画,快学起来送给喜爱的人吧!我运用 Flutter 来复刻了一下。

爱心动画

咱们来分析一下这个作用:

  1. 有 9 个竖直放置的圆角矩形,色彩是对称的,而且排列出一个心形的概括。
  2. 每个矩形都有从没有高度展开到终究的高度再缩短到没有高度的动画,从左到右每个矩形顺次履行这个动画,构成终究作用。

完成静态作用

首要咱们不考虑动画,先将第 1 步中的静态作用完成。这一步比较简单,就直接贴代码,一些关键的当地加了注释。

// 主页
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("爱心小动画"),
        ),
        body: Container(
          height: double.infinity,
          color: Colors.black87,
          child: LoveWidget(),
        )
    );
  }
}
// 封装整个动画组件
class LoveWidget extends StatelessWidget {
  const LoveWidget({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: 300,
      margin: EdgeInsets.only(top: 100),
      alignment: Alignment.center,
      child: Container(
        height: 200,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [ // 包含 9 个 LoveItem, 对应组成心形的 9 个圆角矩形
            LoveItem(
              color: Colors.red,
              height: 60.0,
              translateY: -30,
            ),
            LoveItem(
              color: Colors.blueAccent,
              height: 125.0,
              translateY: -60,
            ),
            LoveItem(
              color: Colors.amber,
              height: 160.0,
              translateY: -75.0,
            ),
            LoveItem(
              color: Colors.deepPurpleAccent,
              height: 180.0,
              translateY: -60.0,
            ),
            LoveItem(
              color: Colors.orange,
              height: 200.0,
              translateY: -45.0,
            ),
            LoveItem(
              color: Colors.deepPurpleAccent,
              height: 180.0,
              translateY: -60,
            ),
            LoveItem(
              color: Colors.amber,
              height: 160.0,
              translateY: -75.0,
            ),
            LoveItem(
              color: Colors.blueAccent,
              height: 125.0,
              translateY: -60,
            ),
            LoveItem(
              color: Colors.red,
              height: 60.0,
              translateY: -30,
            ),
          ],
        ),
      ),
    );
  }
}
// 笼统封装每个圆角矩形,能够指定高度、色彩、Y 轴方向的偏移(经过偏移来形成心形概括)
class LoveItem extends StatefulWidget {
  final double height;
  final Color color;
  final double translateY;
  const LoveItem({
    Key? key,
    required this.color,
    required this.height,
    required this.translateY,
  }) : super(key: key);
  @override
  _LoveItemState createState() => _LoveItemState();
}
class _LoveItemState extends State<LoveItem> {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: widget.height,
      width: 20,
      margin: EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: widget.color,
        borderRadius: BorderRadius.circular(10),
      ),
      transform: Matrix4.translationValues(0.0, widget.translateY, 0.0),
    );
  }
}

完成后作用是这样的,一个心形的概括已经出来了。

Flutter 实现爱心小动画并部署 Github Page

完成动画作用

接下来咱们来考虑动画部分,因为对 Flutter 动画的常识没有特别把握,是在做这个项目中刚开端学习的,所以运用的是最粗糙的完成,这部分代码还有很多的优化空间。咱们看看完成的思路即可。

  1. _LoveItemState 中创立 heightAnimtranslateYAnim 两个 Animation 目标,来分别对 height 和 translateY 这两个特点做动画,泛型类型都是 double。创立一个 AnimationController 来控制动画。
late Animation<double> heightAnim;
late Animation<double> translateYAnim;
late AnimationController controller;
  1. initState() 中初始化上面三个目标,heightAnimtranslateYAnim 都是 Tween 目标,Tween 表明在指定规模内生成一系列连续的动画中心值,默认规模是 [0, 1],这儿咱们指定规模的最小值为 0,最大值为 Widget 传入的 height 或 translateY。然后经过 animate 方法来指定详细的动画,这儿咱们运用 CurvedAnimation 而且自定义了一个 Curve。Curve 的意义是曲线,对应的是动画体系中插值器的概念,经过设置不同的「时刻-动画中心值」曲线,发生不同的动画作用,咱们自定义的曲线作用如图所示。初始化 AnimationController 目标设置动画的时长为 4s,而且开端重复播映。记得在 dispose() 中开释 controller 的资源。
  @override
  void initState() {
    super.initState();
    controller = AnimationController(vsync: this,
      duration: Duration(seconds: 4)
    );
    heightAnim = Tween(begin: 0.0, end: widget.height)
    .animate(CurvedAnimation(parent: controller, curve: CustomCurve()))
    ..addListener(() {
      setState(() {});
    });
    translateYAnim = Tween(begin: 0.0, end: widget.translateY)
        .animate(CurvedAnimation(parent: controller, curve: CustomCurve()))
      ..addListener(() {
        setState(() {});
      });
    controller.repeat();
  }
  @override
  void dispose() {
    super.dispose();
    controller.dispose();
  }

Flutter 实现爱心小动画并部署 Github Page

  1. build() 华夏来运用 widget.heightwidget.translateY 的当地替换为 heightAnim.valuetranslateYAnim.value
  @override
  Widget build(BuildContext context) {
    return Container(
      height: heightAnim.value,
      width: 20,
      margin: EdgeInsets.all(8),
      decoration: BoxDecoration(
        color: widget.color,
        borderRadius: BorderRadius.circular(10),
      ),
      transform: Matrix4.translationValues(0.0, transformYAnim.value, 0.0),
    );
  }

这样咱们就能够完成如下的动画作用:

Flutter 实现爱心小动画并部署 Github Page

咱们再给从左到右每个 Item 顺次添加一些推迟,来到达动画交替进行的作用。

  1. LoveItem 添加一个 delay 的特点,表明动画开端的推迟时刻,能够为空,为空时表明没有推迟,当即开端动画。
class LoveItem extends StatefulWidget {
  final double height;
  final Color color;
  final double translateY;
  // 动画开端的推迟时刻
  Duration? delay;
}
  1. _LoveItemStateinitState 中,参加 delay 的逻辑,假如为空,当即开端动画,假如不为空,则延后相应的时刻再开端动画。
  @override
  void initState() {
    super.initState();
    // 初始化 controller 和 animation 逻辑
    ...
    // delay 动画开端的逻辑
    if (widget.delay != null) {
      Future.delayed(widget.delay!).then((value) => {
        controller.repeat()
      });
    } else {
      controller.repeat();
    }
  }
  1. LoveWidget 创立 9 个 LoveItem 时,第一个不指定 delay 特点,当即履行动画,尔后每个 Item 顺次添加 200ms 的延时。
[
LoveItem(
  color: Colors.red,
  height: 60.0,
  translateY: -30,
),
LoveItem(
  color: Colors.blueAccent,
  height: 125.0,
  translateY: -60,
  delay: Duration(milliseconds: 200),
),
LoveItem(
  color: Colors.amber,
  height: 160.0,
  translateY: -75.0,
  delay: Duration(milliseconds: 400),
),
LoveItem(
  color: Colors.deepPurpleAccent,
  height: 180.0,
  translateY: -60.0,
  delay: Duration(milliseconds: 600),
),
LoveItem(
  color: Colors.orange,
  height: 200.0,
  translateY: -45.0,
  delay: Duration(milliseconds: 800),
),
LoveItem(
  color: Colors.deepPurpleAccent,
  height: 180.0,
  translateY: -60,
  delay: Duration(milliseconds: 1000),
),
LoveItem(
  color: Colors.amber,
  height: 160.0,
  translateY: -75.0,
  delay: Duration(milliseconds: 1200),
),
LoveItem(
  color: Colors.blueAccent,
  height: 125.0,
  translateY: -60,
  delay: Duration(milliseconds: 1400),
),
LoveItem(
  color: Colors.red,
  height: 60.0,
  translateY: -30,
  delay: Duration(milliseconds: 1600),
),
]

这样就完成了最开端所展现的作用,咱们的开发部分也就完毕了。

Web 打包

已然想给女神展现,那 Web 页面无疑是最方便的一个途径,所以我期望将这个产品打包成一个 Web 页面,Flutter 优异的跨渠道特性为这个想法提供了极为快捷的支撑,只需要运用以下指令

flutter build web

就能够构建 Web 产品了。产品能够在项意图 build/web/ 目录下找到。

Github Project Pages

假如要想在公网拜访这个网页,咱们还需要服务器来布置这个网站。一贫如洗的我并没有服务器,不过咱们有白嫖的解决方案。Github 支撑为每个库房树立一个 Project Page,通常能够用来作为开源项意图主页,参阅 用Github Pages展现你的项目。咱们能够使用这个功能来保管咱们的网页。

首要先创立一个 Github 项目,并和本地的代码库房相关,main 分支能够用来保管咱们的代码,这儿就不演示了。打开项意图 Settings,在 Pages 一栏能够配置 Project Page。咱们新创立一个 gh-pages 分支,并挑选这个分支作为保管网页产品的分支,点击保存即可。

Flutter 实现爱心小动画并部署 Github Page

在本地代码中,先将上一步中 build/web/ 目录下的 Web 网页构建产品复制出来,然后切到 gh-pages 分支,删去所有代码(如有),将 Web 产品粘贴到项目根目录下,然后 push 到远程库房。稍等几分钟后就能够经过 https://github 用户名.github.io/项目名/ 这个链接来拜访这个网页。假如 github 用户名.github.io 已经映射到个人域名,则能够经过 https://个人域名/项目名/ 来拜访。

Github Actions

想必聪明的读者看到上面的步骤也认识到了一个问题,假如每次发布一个新版别都需要经历这么一圈繁琐的手动操作,那开发的功率必然是大大折扣的,乃至之后这个项目都不想维护了。

这时候就要请出 Github 的另一个神器 —— Github Actions 了,这是 Github 提供的一个 CI/CD 工具链,能够经过自定义 workflow 来做一些主动化的打包、发布等工作,详细能够参阅 GitHub Actions 官方文档。乃至 workflow 里边的一些使命的脚本也不需要自己去完成,Github 提供了一个 Action 使命的社区,里边已经有很多开箱即用的 Action 使命,咱们需要做的便是依照自己的需求将一些使命组合在一起就能够了。

所以咱们能够定义这样的一个 workflow:咱们的代码布置在 main 分支,每当 main 分支上 push 了一个 commit,就开端主动化使命,打包 web 产品,将产品复制并 push 到 gh-pages 分支,布置新版别的网页

在项目根目录下新建 .github/workflows 目录,并在此目录下新建 main.yml 文件,在这个文件里编写 CI 使命流。

咱们上面自定义的 workflow 完成如下:

name: Flutter Web Build To Github Page
# 表明当 main 分支上有 push 时触发
on:
  push:
    branches:
      - main
jobs:
  build-and-deploy:
    # 表明运行在 ubuntu 最新版别的机器上
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@master
      - name: build
        # 表明运用 flutter-action 这个脚本来构建
        uses: subosito/flutter-action@v1
        with:
          channel: 'stable'
      # 依照下面的次序履行打包指令
      - run: flutter pub get
      - run: flutter channel master
      - run: flutter upgrade
      - run: flutter config --enable-web
      # 构建 web 产品
      - run: flutter build web
      - name: deploy
        # 表明运用 actions-gh-page 这个脚本来布置
        uses: peaceiris/actions-gh-pages@v3
        with:
          # 这儿直接这样写就能够,github 会主动生成用在 Actions 的 GITHUB_TOKEN
          github_token: ${{ secrets.GITHUB_TOKEN }}
          # 发布的分支
          PUBLISH_BRANCH: gh-pages
          # 发布的内容,在 build/web 目录下
          publish_dir: ./build/web

关于 actions-gh-pages 脚本中 github_token 的内容,能够参阅 The GITHUB_TOKEN in GitHub Actions: How it Works, Change Permissions, Customizations。

编写好这个 workflow,就能够提交到远端的 main 分支了,此刻咱们切到项意图 Actions Tab 下,就能够看到有 workflow 在运行了。

Flutter 实现爱心小动画并部署 Github Page

上图中 Flutter Web Build To Github Page 这个 workflow 便是咱们自定义的 workflow,一起咱们也能够发现在 gh-pages 分支提交了 web 产品后,将新的产品布置到服务器上其实也是经过一个 workflow 完成的。

到这儿,咱们就完成了整个项意图开发和布置,能够去给女神发链接啦。

你能够在 我的 Github 上找到这个小项意图完整代码。

参阅资料

  1. 【Bilibili】【HTML+CSS】8分钟完成砰砰的爱心动画,快学起来送给喜爱的人吧!
  2. 用Github Pages展现你的项目
  3. GitHub Actions 官方文档
  4. Flutter学习篇(七)—— flutter web ➡ 几分钟打造个人网站