Flutter 漏斗加载动画效果
老孟Flutter
共 11753字,需浏览 24分钟
· 2021-08-21
漏斗加载动画效果是Loading动画系列中的一个,github地址:https://github.com/LaoMengFlutter/flutter-do
Loading动画效果如下
其中漏斗加载动画效果如下
下面我们看看漏斗加载动画效果是如何实现的?动画效果实现的思路是绘制一个静止的效果,其中可变的效果使用参数控制,回到我们的漏斗加载动画,先绘制一个中间状态,效果如下:
绘制这样一个自定义UI需要使用 「CustomPaint」,先绘制外面的边框,
//酒瓶
var _path = Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width / 2 + _middleWidth, size.height / 2)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..lineTo(size.width / 2 - _middleWidth, size.height / 2)
..close();
canvas.drawPath(_path, _paint);
绘制上半部分三角形
//上部三角形
_paint.style = PaintingStyle.fill;
double _offsetX = progress * (size.width / 2 - _middleWidth);
var _topTrianglePath = Path()
..moveTo(_offsetX, progress * size.height / 2)
..lineTo(size.width - _offsetX, progress * size.height / 2)
..lineTo(size.width / 2 + _middleWidth, size.height / 2)
..lineTo(size.width / 2 - _middleWidth, size.height / 2)
..close();
canvas.drawPath(_topTrianglePath, _paint);
绘制下半部分三角形
//底部三角形
var _bottomTrianglePath = Path()
..moveTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width - _offsetX, size.height - progress * size.height / 2)
..lineTo(_offsetX, size.height - progress * size.height / 2)
..close();
canvas.drawPath(_bottomTrianglePath, _paint);
在绘制一条直线
//垂直线条
_paint.style = PaintingStyle.stroke;
var _linePath = Path()
..moveTo(size.width / 2, size.height / 2)
..lineTo(size.width / 2, size.height - progress * size.height / 2)
..close();
canvas.drawPath(_linePath, _paint);
让其从上面向下面流入,其实就是上面的三角形越来越小,下面的越来越大,设置一个参数 「progress」,
class _PouringHourGlassPainter extends CustomPainter {
final double progress;
final Color color;
late Paint _paint;
double _middleWidth = 2;
_PouringHourGlassPainter(this.progress, this.color) {
_paint = Paint()
..color = color
..strokeWidth = 2
..style = PaintingStyle.stroke;
}
@override
void paint(Canvas canvas, Size size) {
//酒瓶
var _path = Path()
..moveTo(0, 0)
..lineTo(size.width, 0)
..lineTo(size.width / 2 + _middleWidth, size.height / 2)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..lineTo(size.width / 2 - _middleWidth, size.height / 2)
..close();
canvas.drawPath(_path, _paint);
//上部三角形
_paint.style = PaintingStyle.fill;
double _offsetX = progress * (size.width / 2 - _middleWidth);
var _topTrianglePath = Path()
..moveTo(_offsetX, progress * size.height / 2)
..lineTo(size.width - _offsetX, progress * size.height / 2)
..lineTo(size.width / 2 + _middleWidth, size.height / 2)
..lineTo(size.width / 2 - _middleWidth, size.height / 2)
..close();
canvas.drawPath(_topTrianglePath, _paint);
//底部三角形
var _bottomTrianglePath = Path()
..moveTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width - _offsetX, size.height - progress * size.height / 2)
..lineTo(_offsetX, size.height - progress * size.height / 2)
..close();
canvas.drawPath(_bottomTrianglePath, _paint);
//垂直线条
_paint.style = PaintingStyle.stroke;
var _linePath = Path()
..moveTo(size.width / 2, size.height / 2)
..lineTo(size.width / 2, size.height - progress * size.height / 2)
..close();
canvas.drawPath(_linePath, _paint);
}
@override
bool shouldRepaint(covariant _PouringHourGlassPainter old) {
return color != old.color || progress != old.progress;
}
}
加上动画控制
class PouringHourGlassLoading extends StatefulWidget {
final Color color;
final Duration duration;
final Curve curve;
const PouringHourGlassLoading(
{Key? key,
this.color = Colors.white,
this.duration = const Duration(milliseconds: 2500),
this.curve = Curves.linear})
: super(key: key);
@override
_PouringHourGlassLoadingState createState() =>
_PouringHourGlassLoadingState();
}
class _PouringHourGlassLoadingState extends State<PouringHourGlassLoading>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
@override
void initState() {
_controller =
AnimationController(vsync: this, duration: widget.duration)
..repeat();
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Interval(0.0, 0.6,curve: widget.curve)));
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: _PouringHourGlassPainter(_animation.value, widget.color),
);
});
}
}
在给其加上一个旋转
class PouringHourGlassLoading extends StatefulWidget {
final Color color;
final Duration duration;
final Curve curve;
const PouringHourGlassLoading(
{Key? key,
this.color = Colors.white,
this.duration = const Duration(milliseconds: 2500),
this.curve = Curves.linear})
: super(key: key);
@override
_PouringHourGlassLoadingState createState() =>
_PouringHourGlassLoadingState();
}
class _PouringHourGlassLoadingState extends State<PouringHourGlassLoading>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation, _rotateAnimation;
@override
void initState() {
_controller = AnimationController(vsync: this, duration: widget.duration)
..repeat();
_animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
parent: _controller, curve: Interval(0.0, 0.6, curve: widget.curve)));
_rotateAnimation = Tween(begin: 0.0, end: pi).animate(CurvedAnimation(
parent: _controller, curve: Interval(0.6, 1.0, curve: widget.curve)));
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _rotateAnimation.value,
child: CustomPaint(
painter: _PouringHourGlassPainter(_animation.value, widget.color),
),
);
});
}
}
到这里,我们就完成了,如果你有比较酷炫的加载动画效果想要实现,可以将效果发给我,我来实现,或者已经实现的动画效果想要分享给大家,也可以发给我,我会加到github中。
评论
用 Shader 实现旗帜飘扬动画效果
我觉得对于刚入门 3D 编程的朋友来说,如果能够完成代码创建模型数据->创建材质->编写Shader动画这一系列,想必会有满满的成就感。今天就用 Cocos Creator 的 utils.MeshUtils.createMesh 接口,带大家感受一下这个流程。这个流程不仅可以用于新手学
COCOS
2
全新 SOTA backbone | 2024年了,再见ViT系列Backbone,实数难得,不知道效果如何?
点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达在构建用于精确匹配的深度固定长度表示时,确定指纹上的密集特征点,特别是在像素 Level 上,具有重大意义。为了探索指纹匹配的可解释性,作者提出了一种多阶段可解释的指纹匹配网络,名为通过视觉 Transformer 进行指纹匹配的
小白学视觉
10
文献分享 | 体外冲击波+本体感觉训练治疗「运动创伤性踝关节炎」患者的效果分析
■ 近年来,国内患有运动创伤性踝关节炎患者的数量在逐年增加,患者主要为青壮年。在日常生活与工作中,不健康运动方式或者运动过度,都会对踝关节健康造成危害。■ 运动创伤性踝关节炎在临床上还被称为足球踝,该病症大多数是运动员踝关节出现急性损伤后,过早负重,韧带因为尚未修复
乐普医疗AI
0
性能优化——图片压缩、加载和格式选择
大厂技术 高级前端 Node进阶点击上方 程序员成长指北,关注公众号回复1,加入高级Node交流群前言相信大家都听说过 "258 原则(https://blog.csdn.net/weixin_42139375/article/details/8
程序员成长指北
10
Cocos Creator 3.8 中实现割绳子游戏物理效果
游戏录屏 引言 近日,笔者在某个砍树游戏的广告中看到一个非常有趣的割绳子小游戏,据说这个砍树游戏也是用Cocos引擎开发的,出于好奇,研究了下如何在Cocos Creator 3.8 中简单地实现一下割绳子游戏效果。 其实割...
COCOS
0
【每日一练】306—HTML、CSS和JS实现一款简易风时钟效果
作者 | 杨小爱 写在前面 关于时钟的效果,我们前面刚刚写过一期,《【每日一练】304—用 HTML、CSS和JavaScript 实现一款时钟效果》那是一个圆形钟表的效果,今天我们再来写一款,这个风格比较简易,就是纯数字时钟,...
web前端开发
0
提升LLM效果时最具挑战性的问题和值得研究的方向
提升LLM效果时最具挑战性的问题和值得研究的方向作者:swtheking原文地址:https://zhuanlan.zhihu.com/p/682824684AGI的本质还是期待能获得一个无所不能的大语言模型,在这一年里我也是在这个领域探索和成长。在真...
DayNightStudy
0
Flutter 3.13 之后如何监听 App 生命周期事件
在 Flutter 中,您可以监听多个生命周期事件来处理应用程序的不同状态,但今天我们将讨论 didChangeAppLifecycleState 事件。每当应用程序的生命周期状态发生变化时,就会触发此事件。可能的状态有 resumed 、 inact...
前端全栈开发者
0