Flutter -如何创建炫酷粒子时钟效果!

使用Flutter Widgets 添加图层

到现在为止,所有内容都使用单个CustomPainter小部件(即widget,下文也一样)绘制。时钟看起来不错,但是很难告诉你时间。而且,背景是单色,看上去很无聊。

Flutter非常适合构建复杂的布局。毕竟,它是一个用于用户界面的工具包。将一堆小部件彼此堆叠,您只需将它们包装在Stack widget中。粒子时钟的最后一个场景小部件负责构建3个主要层:

  • 1、Background:一个带有CustomPaint小部件的堆栈,该小部件绘制不同颜色和绘画样式的随机形状,以及一个应用了模糊效果的BackdropFilter

  • 2、Clock Face: 带有2个CustomPaint 小部件的堆栈,

  • a. 时钟标记-绘制时钟标记。每分钟标记,每5分钟标记具有额外的可见性。

  • b. 秒针-绘制两秒的针弧。

  • 3、 Particle FX: 一个CustomPaint小部件,用于绘制所有粒子。

@override

Widget build(BuildContext context) {

return AnimatedContainer(

duration: Duration(milliseconds: 1500),

curve: Curves.easeOut,

color: _bgColor,

child: ClipRect(

child: Stack(

children: [

_buildBgBlurFx(),

_buildClockFace(),

CustomPaint(

painter: ClockFxPainter(fx: _fx),

child: Container(),

),

],

),

),

);

}

即使底层代码很复杂,Flutter仍可以通过小部件组合来管理布局。

时钟绘图层和覆盖层。

这是与上述相同的图片,但没有覆盖层。

与时间同步的动画

我很早就想到,如果动画与时钟的滴答声同步发生,那就太酷了。最终版本中的解决方案非常简单。没有到达到想象中的目标。最初,我把它变成了一个非常复杂的问题,并尝试了各种怪异的技巧使它起作用。

@override

void tick(Duration duration) {

var secFrac = DateTime.now().millisecond / 1000;

var vecSpeed = duration.compareTo(easingDelayDuration) > 0

? max(.2, Curves.easeInOutSine.transform(1 - secFrac))

: 1;

particles.asMap().forEach((i, p) {

// Movement

p.x -= p.vx * vecSpeed;

p.y -= p.vy * vecSpeed;

// etc…

}

}

以上代码在每个动画刻度上运行。通过结合使用DateTime.now()(以毫秒为单位)和Curves,我们得到一个介于01之间的值。max函数确保该数字保持在0.2以上,以始终保持粒子随着每个刻度移动。

然后,在计算粒子的新xy位置时,将vecSpeed编号与运动矢量结合使用。

调色板和清晰度

在图形用户界面中随机分配颜色时,通常会让人感到厌烦。当然,这是有充分的理由,因为它通常会使GUI的访问性降低。在保持易读性的同时将随机颜色应用于GUI并不是一个容易解决的问题。幸运的是,Flutter有一些工具可以使我们更轻松。

首先,我使用了ColourLovers API来获取其用户最喜欢的一些调色板。简而言之,许多调色板的颜色之间的对比度很差。我根据WCAG Contrast指南,创建了一个过滤调色板阵列的脚本来解决了这一问题。过滤后,该列表仅包含调色板,其中至少存在一种对比度大于或等于4.5的颜色组合。

然后,在Flutter中,我们仅需使用Color类的computeLuminance方法即可找到良好的匹配项。

/// Gets a random palette from a list of palettes and sorts its’

/// colors by luminance.

///

/// Given if [dark] or not, this method makes sure the luminance

/// of the background color is valid.

static Palette getPalette(List palettes, bool dark) {

Palette result;

while (result == null) {

Palette palette = Rnd.getItem(palettes);

List colors = Rnd.shuffle(palette.components);

var luminance = colors[0].computeLuminance();

if (dark ? luminance <= .1 : luminance >= .1) {

var lumDiff = colors

.sublist(1)

.asMap()

.map(

(i, color) => MapEntry(

i,

[i, (luminance - color.computeLuminance()).abs()],

),

)

.values

.toList();

lumDiff.sort((List a, List b) {

return a[1].compareTo(b[1]);

});

List sortedColors =

lumDiff.map((d) => colors[d[0] + 1]).toList();

result = Palette(

components: [colors[0]] + sortedColors,

);

}

}

return result;

}

该代码返回一个Palette,仅包含一个颜色列表。通过颜色之间的亮度差异调色板进行排序。

然后,此方法的调用者可以确保组件的第一项最后一项具有足够好的对比度。

一小部分,可能有许多不同颜色变化。请注意,就亮度而言,强调色始终总是与背景色最远的一种。

最后的润色

大部分魔术发生在编写此代码的最后几个小时。使发出的粒子从中心淡入,而不是从无处突然弹出。这使整体外观更加平滑。我对弧/速度标记进行了相同的操作,并一次将它们限制为仅几个粒子,以减少视觉复杂性。

最初,我不确定如何避免沿钟针方向发射噪声粒子,但知道必须这样做。在放弃寻找数学解之后,我用了一些蛮力的代码解决了这个问题(当然,数学解方案是有的,只是我没有耐心寻找到它)。

// Find a random angle while avoiding clutter at the hour & minute hands.

var am = _getMinuteRadians();

var ah = _getHourRadians() % (pi * 2);

var d = pi / 18;

// Probably not the most efficient solution right here.

do {

angle = Rnd.ratio * pi * 2;

} while (_isBetween(angle, am - d, am + d) || _isBetween(angle, ah - d, ah + d));

有效!这样一来,所有噪音颗粒都从针中移开,就更容易分辨时间了。

最后

有时令人沮丧(谢谢,数学!😅)。但最后,我对结果感到满意。我特别喜欢不断变化的色彩和有机的、不可预测的动画。

Flutter非常适合此类事情。创造力需要实验,这就是Flutter令人瞩目的地方。在这个项目的开始,我不知道它最终会变成这样。记住,我最初的想法是建立一个数字时钟。但是由于一些幸运的错误,以及对不同想法的成千上万次小迭代,它的变化比最初想象的要好。

最终演示视频地址:https://youtu.be/VPbcVhKIzIo

Google Flutter全球市场总监 Martin Aguinis发推文说,他们在86个国家/地区收到了850份独特的作品。在所有这些中,Google专家评审团选出了我的获得大奖(一台装有Apple iMac Pro的苹果,价值约10,000美元)。我从来没有认为自己是一个优秀的程序员,所以当Martin向我伸手时,我真的很惊讶!我现在仍然不敢相信自己赢了。

感谢Google和Flutter团队使这一挑战得以实现,也感谢所有为我加油并在Twitter上对我表示支持的人

结语

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!以下是目录截图:

由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

再附一部分Android架构面试视频讲解:

的程序员,所以当Martin向我伸手时,我真的很惊讶!我现在仍然不敢相信自己赢了。

感谢Google和Flutter团队使这一挑战得以实现,也感谢所有为我加油并在Twitter上对我表示支持的人

结语

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!以下是目录截图:

[外链图片转存中…(img-Ny7ez7n8-1718870977082)]

由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

再附一部分Android架构面试视频讲解:

[外链图片转存中…(img-iYDIQ1wb-1718870977083)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值