前言:前几天在掘金上看到一篇文章,用html+css编写了一个剑气加载的动效。前端能做的东西,我Flutter大前端岂能罢休?于是小弟班门弄斧,用Flutter编写了这个剑气动效。相关掘金文章:juejin.cn/post/7001779766852321287

效果图

知识点

  • Animation【动效】

  • Clipper/Canvas【路径裁剪/画布】

  • Matrix4【矩阵转化】

剑气形状

我们仔细看一道剑气,它的形状是一轮非常细小的弯弯的月牙;在Flutter中,我们可以通过Clipper路径来裁剪出来,或者也可以通过canvas绘制出来。

  1. 先看canvas如何进行绘制的

class MyPainter extends CustomPainter {Color paintColor;MyPainter(this.paintColor);Paint _paint = Paint()..strokeCap = StrokeCap.round..isAntiAlias = true..strokeJoin = StrokeJoin.bevel..strokeWidth = 1.0;@overridevoid paint(Canvas canvas, Size size) {_paint..color = this.paintColor;Path path = new Path();// 获取视图的大小double w = size.width;double h = size.height;// 月牙上边界的高度double topH = h * 0.92;// 以区域中点开始绘制path.moveTo(0, h / 2);// 贝塞尔曲线连接pathpath.cubicTo(0, topH * 3 / 4, w / 4, topH, w / 2, topH);path.cubicTo((3 * w) / 4, topH, w, topH * 3 / 4, w, h / 2);path.cubicTo(w, h * 3 / 4, 3 * w / 4, h, w / 2, h);path.cubicTo(w / 4, h, 0, h * 3 / 4, 0, h / 2);canvas.drawPath(path, _paint);}@overridebool shouldRepaint(covariant CustomPainter oldDelegate) => false; // 一次性画好,不需要更新,返回false
}
  1. Clipper也上代码,跟canvas两种选其一即可,我用的是canvas

class SwordPath extends CustomClipper<Path> {@overridegetClip(Size size) {print(size);// 获取视图的大小double w = size.width;double h = size.height;// 月牙上边界的高度double topH = h * 0.92;Path path = new Path();// 以区域中点开始绘制path.moveTo(0, h / 2);// 贝塞尔曲线连接pathpath.cubicTo(0, topH * 3 / 4, w / 4, topH, w / 2, topH);path.cubicTo((3 * w) / 4, topH, w, topH * 3 / 4, w, h / 2);path.cubicTo(w, h * 3 / 4, 3 * w / 4, h, w / 2, h);path.cubicTo(w / 4, h, 0, h * 3 / 4, 0, h / 2);return path;}@overridebool shouldReclip(covariant CustomClipper oldClipper) => false;
}
  1. 生成月牙控件

CustomPaint(painter: MyPainter(widget.loadColor),size: Size(200, 200),
),

让剑气旋转起来

我们需要剑气一直不停的循环转动,所以需要用到动画,让剑气围绕中心的转动起来。注意这里只是单纯的平面旋转,也就是我们说的2D变换。这里我们用到的是Transform.rotate控件,通过animation.value传入旋转的角度,从而实现360度的旋转。

class _SwordLoadingState extends State<SwordLoading>with TickerProviderStateMixin {late AnimationController _controller;late Animation<double> _animation;double angle = 0;@overridevoid initState() {_controller =AnimationController(vsync: this, duration: Duration(milliseconds: 800));// pi * 2:360°旋转_animation = Tween(begin: 0.0, end: pi * 2).animate(_controller);_controller.repeat(); // 循环播放动画super.initState();}@overrideWidget build(BuildContext context) {return Transform.rotate(alignment: Alignment.center,angle: _animation.value,child: CustomPaint(painter: MyPainter(widget.loadColor),size: Size(widget.size, widget.size),),);}
}

让剑气有角度的、更犀利的转动

  • 我们仔细看单独一条剑气,其实是在一个三维的模型中,把与Z轴垂直的剑气 向Y轴、X轴进行了一定角度的偏移。

  • 相当于在这个3D空间内,剑气不在某一个平面了,而是斜在这个空间内,然后 再绕着圆心去旋转。

  • 而观者的视图,永远与Z轴垂直【或者说:X轴和Y轴共同组成的平面上】,所以就会产生剑气 从外到里进行旋转 的感觉。

下图纯手工绘制,不要笑我~~~

综上,可以确定这个过程是一个3D的变换,很明显我们Transform.rotate这种2D的widget已经不满足需求了,这个时候Matrix4大佬上场了,我们通过Matrix4.identity()..rotate的方法,传入我们的3D转化,在通过rotateZ进行旋转,简直完美。代码如下

AnimatedBuilder(animation: _animation,builder: (context, _) => Transform(transform: Matrix4.identity()..rotate(v.Vector3(0, -8, 12), pi)..rotateZ(_animation.value),alignment: Alignment.center,child: CustomPaint(painter: MyPainter(widget.loadColor),size: Size(widget.size, widget.size),),),
),

这里多说一句,要完成矩阵变换,Matrix4必不可少,可以着重学习下。

让剑气一起动起来

完成一个剑气的旋转之后,我们回到预览效果,无非就是3个剑气堆叠在一起,通过偏移角度去区分。Flutter堆叠效果直接用Stack实现,完整代码如下:

import 'package:flutter/material.dart';
import 'dart:math';
import 'package:vector_math/vector_math_64.dart' as v;class SwordLoading extends StatefulWidget {const SwordLoading({Key? key, this.loadColor = Colors.black, this.size = 88}): super(key: key);final Color loadColor;final double size;@override_SwordLoadingState createState() => _SwordLoadingState();
}class _SwordLoadingState extends State<SwordLoading>with TickerProviderStateMixin {late AnimationController _controller;late Animation<double> _animation;double angle = 0;@overridevoid initState() {_controller =AnimationController(vsync: this, duration: Duration(milliseconds: 800));_animation = Tween(begin: 0.0, end: pi * 2).animate(_controller);_controller.repeat();super.initState();}@overrideWidget build(BuildContext context) {return Stack(children: [AnimatedBuilder(animation: _animation,builder: (context, _) => Transform(transform: Matrix4.identity()..rotate(v.Vector3(0, -8, 12), pi)..rotateZ(_animation.value),alignment: Alignment.center,child: CustomPaint(painter: MyPainter(widget.loadColor),size: Size(widget.size, widget.size),),),),AnimatedBuilder(animation: _animation,builder: (context, _) => Transform(transform: Matrix4.identity()..rotate(v.Vector3(-12, 8, 8), pi)..rotateZ(_animation.value),alignment: Alignment.center,child: CustomPaint(painter: MyPainter(widget.loadColor),size: Size(widget.size, widget.size),),),),AnimatedBuilder(animation: _animation,builder: (context, _) => Transform(transform: Matrix4.identity()..rotate(v.Vector3(-8, -8, 6), pi)..rotateZ(_animation.value),alignment: Alignment.center,child: CustomPaint(painter: MyPainter(widget.loadColor),size: Size(widget.size, widget.size),),),),],);}
}class MyPainter extends CustomPainter {Color paintColor;MyPainter(this.paintColor);Paint _paint = Paint()..strokeCap = StrokeCap.round..isAntiAlias = true..strokeJoin = StrokeJoin.bevel..strokeWidth = 1.0;@overridevoid paint(Canvas canvas, Size size) {_paint..color = this.paintColor;Path path = new Path();// 获取视图的大小double w = size.width;double h = size.height;// 月牙上边界的高度double topH = h * 0.92;// 以区域中点开始绘制path.moveTo(0, h / 2);// 贝塞尔曲线连接pathpath.cubicTo(0, topH * 3 / 4, w / 4, topH, w / 2, topH);path.cubicTo((3 * w) / 4, topH, w, topH * 3 / 4, w, h / 2);path.cubicTo(w, h * 3 / 4, 3 * w / 4, h, w / 2, h);path.cubicTo(w / 4, h, 0, h * 3 / 4, 0, h / 2);canvas.drawPath(path, _paint);}@overridebool shouldRepaint(covariant CustomPainter oldDelegate) =>false; // 一次性画好,不需要更新,返回false
}

业务端调用

SwordLoading(loadColor: Colors.black,size: 128),

写在最后

花了我整个周六下午的时间,很开心用Flutter实现了加载动画,说说感受吧。

  1. 在编写的过程中,对比html+css的方式,Flutter的实现难度其实更大,而且剑气必须使用canvas绘制出来。

  2. 如果你也懂前端,你可以深刻体会声明式和命令式UI在编写布局和动画所带来的强烈差异,从而加深Flutter万物皆对象的思想。*【因为万物皆对象,所以所有控件和动画,都是可以显示声明的对象,而不是像前端那样通过解析xml命令来显示】

  3. 2D/3D变换,我建议Flutter学者们,一定要深入学习,这种空间思维对我们实现特效是不可获取的能力。

转自:掘金  Karl_wei

https://juejin.cn/post/7002977635206692901

PS:如果觉得我的分享不错,欢迎大家随手点赞、转发、在看。

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。

Flutter 实现剑气加载相关推荐

  1. Android 炫酷自定义 View - 剑气加载

    效果图 原理分析 这个效果仔细看,就是有三个类似月牙形状的元素进行循环转动,我们只需要拆解出一个月牙来做效果即可,最后再将三个月牙组合起来就可以达到最终效果了 月牙 先画一个圆: 再画个大一丢丢的: ...

  2. Flutter中ListView加载图片数据的优化

    题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天. ** 你可能需要 CSDN 网易云课堂教程 掘金 EDU学院教程 知乎 Flutter系列文章 在使用ListView ...

  3. Flutter Image 图片加载

    题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精. 目前在西瓜视频上免费刊登 Flutter 系列教程,每日更新,欢迎关注接收提醒 [x1]微信公众号的每日提醒 随时随记 每日积累 随心 ...

  4. Flutter 实现风车加载动画组件

    前言 Flutter 官方提供了诸如 CircularProgressIndicator和 LinearProgressIndicator两种常见的加载指示组件,但是说实话,实在太普通,比如下面这个C ...

  5. Flutter 本地图片加载不出来

    pubspec.yaml 中添加 项目根目录/assets/images/ 中是存在该图片的 但图片无论怎么刷新都加载不出来,根据文档反复对了很多次,实在找到不到代码在哪里出了问题. 重点来了-重新启 ...

  6. flutter webview 无法加载网页错误提示:ERR_NAME_NOT_RESOLVED 解决方法

    问题: 之前用flutter写了一个app,里面包括一个网页显示页面,当时测试的时候一切正常.但是今天重新运行发现app的其他功能正常但是无法加载网页,错误提示如下: 网页无法打开 位于 https: ...

  7. 2020大厂面试集合,GitHub,百度,flutter下拉加载

    由于Fragment的生命周期与Activity的生命周期有着牵扯,所以把两者的图放到一起作为对比理解. [图片上传失败-(image-a13b49-1601037655916)] 接下来就不同情况下 ...

  8. 下血本买的!Flutter中网络图片加载和缓存源码分析,看看这篇文章吧!

    目录 想要成为一名优秀的Android开发,你需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样. PagerAdapter 介绍 ViwePager 缓存策略 ViewPager 布局处 ...

  9. flutter加载本地html标签,Flutter中如何加载并预览本地的html文件的方法

    直接进入主题,大概步骤如下 在 assets 创建需要访问 html 文件,如下 这里创建一个files文件夹,专门来放这些静态 html 文件. 在 pubspec.yaml 中配置访问位置 ass ...

最新文章

  1. redis 之 sds (二) char []
  2. 【MySQL】按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
  3. rabbitmq 如何删除队列中的消息
  4. 智能手机的开放与封闭刍议
  5. vue实战案例:用学过的知识做一个小demo
  6. windbg-内存破坏实例分析
  7. 洛谷 P1031 均分纸牌【交叉模拟】
  8. 在线文档有哪些技术难点
  9. SQL服务器名称的更改
  10. mysql自动跑sql发邮件_SQL server 表数据改变触发发送邮件的方法
  11. Python 音频: sounddevice 使用 左声道/右声道/立体声 --- 播放,录音
  12. html5实现直接下载文件
  13. 【产品】 产品设计:ID设计和MD设计详解
  14. Netty自定义数据包
  15. 初探强化学习(7)基于模型的强化学习的应用综述
  16. 一个形式良好的XML文档
  17. aliPay支付宝APP支付操作流程
  18. 初试 Kubernetes 集群使用 CephFS 文件存储
  19. 查询订单表中同一父订单下的所有子订单(蜜芽)
  20. P1838 三子棋I

热门文章

  1. 强烈推荐:20款优秀的数据可视化工具
  2. 黄鱼车今后会少 下沙公交小巴向出租车转型
  3. UBUNTU环境下编译的openwrt
  4. 网络嗅探与欺骗----函数sniff()、ARP的原理、ARP欺骗、AARP欺骗的中间人攻击
  5. FATFS文件系统复制文件
  6. #186-[栈]法力水晶
  7. linux按目录名查找目录_如何在Linux中查找目录?
  8. ## DNF地狱猫官方版本公告
  9. 012-数据结构-树形结构-哈希树[hashtree]、字典树[trietree]、后缀树
  10. MongoDB 文档的高级查询操作