关于flutter:Flutter-如何创建炫酷粒子时钟效果

5次阅读

共计 5288 个字符,预计需要花费 14 分钟才能阅读完成。

本文首发于公众号:技术最 TOP

周末发表了一篇文章《这个我的项目也太屌了吧》,给大家举荐了一个炫酷的 Flutter 粒子时钟我的项目,不过没有将具体实现思路和代码,所幸,作者本人写了一篇博客将这个我的项目的背景、实现思路、和所遇到的问题,我感觉对十分有用,因而翻译进去,整顿给大家!原文题目《我是如何创立粒子时钟,并博得了 #FlutterClock 挑战的》

背景

Google 在 2019 年 11 月 18 日发动了 The Flutter Clock Challenge 挑战流动,内容很简略:应用 Flutter UI 工具包设计时钟 。Google 专家小组将依据四个次要规范对参赛作品进行评判: 视觉美感 创意新颖性 代码品质 整体执行力

在这之前,我只应用过 Flutter 一两次,这对于我来说是一个潜在的机会。

最后的想法

挑战开始了大概两周后,我才有了一些想法,但还没有编写任何代码,解决此类问题时,我通常的办法是首先寻找现有的解决方案来找灵感。但这次不行。相同,我在 Figma 上新建了一个文档,并列出了一些想法。它们都是非常简单 数字时钟设计 艰涩的单色组合

很快地,我就对这个设计感到厌倦了,所以我敞开了 Figma,就去下载了Flutter Clock GitHub(https://github.com/flutter/fl…。

这个库蕴含了 2 个我的项目,一个是基于模仿时钟的演示,另一个是基于数字时钟的演示,我在 Figma 上设计都是数字的,所以自然而然地,我启动了根本的数字时钟我的项目。再次不足灵感,在示例我的项目的帮忙下,我搁置了挑战,开始去做其余事儿。

几天之后,在一次晨跑中,我又开始认真的思考这个挑战,一个一般的成年人每天看几次手表?对我来说,让时钟变得乏味起来是真正的挑战。是否能够使“报工夫”成为主动体验?比方:即便您对工夫不感兴趣,看着手表也很乏味。这不仅须要 视觉上令人惊叹的设计 新鲜的动画计划

  • 1、如果每次您看钟时都有不同的外观会怎么?
  • 2、咱们是否激发好奇心,使您渴望下一次设计迭代?或者,当您喜爱的设计永恒隐没时,您会感到惆怅吗?
  • 3、咱们是否能够将背景形态、色彩、动画随机扭转以使它:a. 看起来很酷,b. 足够满足 1 和 2 的随机性,c. 不会扩散你看工夫的注意力?

我以前从未实现过任何艺术,或者基本没有做过 Flutter,所以我着手建造这样的时钟。

粒子与随机性

第一次迭代只是一个时钟。如上所述,我从示例 数字时钟我的项目 开始,而不是从头开始。创立的第一个 Widget 是一个CustomPainter,仅绘制了一个圆圈。很不错,然而从久远来看不是很乏味。

随机性减少了,从色彩开始,而后确定地位和大小。所有逻辑依然在单个 CustomPainterpaint()办法中, 这简直使动画变得不可能,因而须要将一堆逻辑重形成一个简略的粒子系统。我看了 Flutter Vignettes 我的项目,以寻求启发。

https://flutter.gskinner.com/

这时候,制作模仿粒子时钟的想法变得更加显著了。

将粒子变成模仿时钟

提出想法后,我要做的就是编写代码以实现所有指标。数学局部破费了我最多的工夫才最终实现,大多是我多年以前学过的数学,不过好多我都遗记了,角度,弧度,PI 和相似的货色,网上又许多解决方案,然而你将不得不做一些批改以适应您的用例。

以下是获取时针弧度的办法:

/// Gets the radians of the hour hand.
double _getHourRadians() =>
    (time.hour * pi / 6) +
    (time.minute * pi / (6 * 60)) +
    (time.second * pi / (360 * 60));

我在计算中包含了 time.minutetime.second,以使时针在数小时之间平滑地动画。

而后,从弧度取得 2D 静止矢量就很简略了。

// Particle movement vector.
p.vx = sin(-angle);
p.vy = cos(-angle);

当初,p.vxp.vy 领有 无关粒子在每个 动画滴答声中 应该挪动多远的信息,同时放弃在时针的角度里。

除了钟针外,粒子还可能作为乐音产生。而后它将从核心沿随机方向发射。在收回时,还将为所有粒子调配 随机的速度 色彩 大小 绘画款式(填充或笔划)。这使时钟看起来更乏味。

时钟的晚期版本中。这有四分之一标记,有些粒子有速度标记。时钟的第一个版本只是粒子,这里就不花工夫截图演示了。

应用 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: <Widget>[_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<Palette> palettes, bool dark) {
  Palette result;

  while (result == null) {Palette palette = Rnd.getItem(palettes);
    List<Color> 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<num> a, List<num> b) {return a[1].compareTo(b[1]);
      });

      List<Color> 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 上对我示意反对的人

我的项目 Github 地址:https://github.com/miickel/fl…

原文链接:https://ultimatemachine.se/ar…

正文完
 0