Android实现点汇聚成字的动态效果详解

目录
  • 前言
  • 点阵
  • 点阵图形绘制
  • 由点聚集成字的动画实现
  • 总结

前言

在引入 fl_chart 绘制图表的时候,看到插件有下面这样的动效,随机散乱的圆点最后组合成了 Flutter 的 Logo,挺酷炫的。本篇我们来探讨类似的效果怎么实现。

点阵

在讲解代码实现之前,我们先科普一个知识,即点阵。点阵在日常生活中很常见,比如广告屏,停车系统的显示,行业内称之为 LED 显示屏。

LED 显示屏实际上就是由很多 LED 灯组合成的一个显示面板,然后通过显示驱动某些灯亮,某些灯灭就可以实现文字、图形的显示。LED 显示屏的点距足够小时,色彩足够丰富时其实就形成了我们日常的显示屏,比如 OLED 显示屏其实原理也是类似的。之前报道过的大学宿舍楼通过控制每个房间的灯亮灯灭来形成文字的原理也是一样的。

现在来看看 LED显示文字是怎么回事,比如我们要 显示岛上码农的“岛”字,在16x16的点阵上,通过排布得到的就是下面的结果(不同字体的排布会有些差别)。

因为每一行是16个点,我们可以对应为16位二进制数,把黑色的标记为1,灰色的标记为0,每一行就可以得到一个二进制数。比如上面的第一行第8列为1,其他都是0,对应的二进制数就是0000000100000000,对应的16进制数就是0x0100。把其他行也按这种方式计算出来,最终得到的“岛”字对应的是16个16进制数,如下所示。

 [
   0x0100, 0x0200, 0x1FF0, 0x1010,
   0x1210, 0x1150, 0x1020, 0x1000,
   0x1FFC, 0x0204, 0x2224, 0x2224,
   0x3FE4, 0x0004, 0x0028, 0x0010
 ];

又了这个基础,我们就可以用 Flutter 绘制点阵图形。

点阵图形绘制

首先我们绘制一个“LED 面板”,也就是绘制一个有若干个点构成的矩阵,这个比较简单,保持相同的间距,逐行绘制相同的圆即可,比如我们绘制一个16x16的点阵,实现代码如下所示。

var paint = Paint()..color = Colors.grey;
final dotCount = 16;
final fontSize = 100.0;
var radius = fontSize / dotCount;
var startPos =
    Offset(size.width / 2 - fontSize, size.height / 2 - 2 * fontSize);
for (int i = 0; i < dotCount; ++i) {
  var position = startPos + Offset(0.0, radius * i * 2);
  for (int j = 0; j < dotCount; ++j) {
    var dotPosition = startPos + Offset(radius * 2 * j, position.dy);
    canvas.drawCircle(dotPosition, radius, paint);
  }
}

绘制出来的效果如下:

接下来是点亮对应的位置来绘制文字了。上面我们讲过了,每一行是一个16进制数,那么我们只需要判断每一行的16进制数的第几个 bit是1就可以了,如果是1就点亮,否则不点亮。点亮的效果用不同的颜色就可以了。 怎么判断16进制数的第几个 bit 是不是1呢,这个就要用到位运算技巧了。实际上,我们可以用一个第 N 个 bit 是1,其他 bit 都是0的数与要判断的数进行“位与”运算,如果结果不为0,说明要判断的数的第 N 个 bit 是1,否则就是0。听着有点绕,看个例子,我们以0x0100为例,按从第0位到第15位逐个判断第0位和第15位是不是1,代码如下:

for (i = 0 ; i < 16; ++i) {
  if ((0x0100 & (1 << i)) > 0) {
    // 第 i 位为1
  }
}

这里有两个位操作,1 << i是将1左移 i 位,为什么是这样呢,因为这样可以构成0x0001,0x0002,0x0004,...,0x8000等数字,这些数字依次从第0位,第1位,第2位,...,第15位为1,其他位都是0。然后我们用这样的数与另外一个数做位与运算时,就可以依次判断这个数的第0位,第1位,第2位,...,第15位是否为1了,下面是一个计算示例,第11位为1,其他位都是0,从而可以 判断另一个数的第11位是不是0。

通过这样的逻辑我们就可以判断一行的 LED 中第几列应该点亮,然后实现文字的“显示”了,实现代码如下。wordHex是对应字的16个16进制数的数组。dotCount的值是16,用于控制绘制16x16大小的点阵。每隔一行我们向下移动一段直径距离,每隔一列,我们向右移动一段直径距离。然后如果当前绘制位置的数值对应的 bit位为1,就用蓝色绘制,否则就用灰色绘制。这里说一下为什么左移的时候要用dotCount - j - 1,这是因为绘制是从左到右的,而16进制数的左边是高位,而数字j是从小到大递增的,因此要通过这种方式保证判断的顺序是从高位(第15位)到低位(第0位),和绘制的顺序保持一致。

 for (int i = 0; i < dotCount; ++i) {
  var position = startPos + Offset(0.0, radius * i * 2);
  for (int j = 0; j < dotCount; ++j) {
    var dotPosition = startPos + Offset(radius * 2 * j, position.dy);

    if ((wordHex[i] & ((1 << dotCount - j - 1))) != 0) {
      paint.color = Colors.blue[600]!;
      canvas.drawCircle(dotPosition, radius, paint);
    } else {
      paint.color = Colors.grey;
      canvas.drawCircle(dotPosition, radius, paint);
    }
  }
}

绘制的结果如下所示。

由点聚集成字的动画实现

接下来我们来考虑如何实现开篇说的类似的动画效果。实际上方法也很简单,就是先按照文字应该“点亮”的 LED 的数量,先在随机的位置绘制这么多数量的 LED,然后通过动画控制这些 LED 移动到目标位置——也就是文字本该绘制的位置。这个移动的计算公式如下,其中 t 是动画值,取值范围为0-1.

需要注意的是,随机点不能在绘图过程生成,那样会导致每次绘制产生新的随机位置,也就是初始位置会变化,导致上面的公式实际不成立,就达不到预期的效果。另外,也不能在 build 方法中生成,因为每次刷新 build 方法就会被调用,同样会导致初始位置发生变化。所以,生成随机位置应该在 initState方法完成。但是又遇到一个新问题,那就是 initState方法里没有 context,拿不到屏幕宽高,所以不能直接生成位置,我们只需要生成一个0-1的随机系数就可以了,然后在绘制的时候在乘以屏幕宽高就得到实际的初始位置了。初始位置系数生成代码如下:

@override
  void initState() {
  super.initState();
  var wordBitCount = 0;
  for (var hex in dao) {
    wordBitCount += _countBitOne(hex);
  }
  startPositions = List.generate(wordBitCount, (index) {
    return Offset(
      Random().nextDouble(),
      Random().nextDouble(),
    );
  });
  ...
}

wordBitCount是计算一个字中有多少 bit 是1的,以便知道要绘制的 “LED” 数量。接下来是绘制代码了,我们这次对于不亮的直接不绘制,然后要点亮的位置通过上面的位置计算公式计算,这样保证了一开始绘制的是随机位置,随着动画的过程,逐步移动到目标位置,最终汇聚成一个字,就实现了预期的动画效果,代码如下。

void paint(Canvas canvas, Size size) {
  final dotCount = 16;
  final fontSize = 100.0;
  var radius = fontSize / dotCount;
  var startPos =
      Offset(size.width / 2 - fontSize, size.height / 2 - fontSize);
  var paint = Paint()..color = Colors.blue[600]!;

  var paintIndex = 0;
  for (int i = 0; i < dotCount; ++i) {
    var position = startPos + Offset(0.0, radius * i * 2);
    for (int j = 0; j < dotCount; ++j) {
      // 判断第 i 行第几位不为0,不为0则绘制,否则不绘制
      if ((wordHex[i] & ((1 << dotCount - j))) != 0) {
        var startX = startPositions[paintIndex].dx * size.width;
        var startY = startPositions[paintIndex].dy * size.height;
        var endX = startPos.dx + radius * j * 2;
        var endY = position.dy;
        var animationPos = Offset(startX + (endX - startX) * animationValue,
            startY + (endY - startY) * animationValue);
        canvas.drawCircle(animationPos, radius, paint);
        paintIndex++;
      }
    }
  }
}

来看看实现效果吧,是不是很酷炫?完整源码已提交至:绘图相关源码,文件名为:dot_font.dart

总结

本篇介绍了点阵的概念,以及基于点阵如何绘制文字、图形,最后通过先绘制随机点,再汇聚成文字的动画效果。可以看到,化整为零,再聚零为整的动画效果还是蛮酷炫的。实际上,基于这种方式,可以构建更多有趣的动画效果。

以上就是Android实现点汇聚成字的动态效果详解的详细内容,更多关于Android点汇聚成字的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android实现颜色渐变动画效果

    目录 前言 一.Android中插值器TypeEvaluator 二.案例效果实现 1.利用Android自带的颜色插值器ArgbEvaluator 2.看看Android自带颜色插值器ArgbEvaluator核心代码 3.根据ArgbEvaluator的实现来自定义一个颜色插值器 4.使用自己定义的颜色插值器MyColorEvaluator 三.源码 本文实例为大家分享了Android颜色渐变动画效果的实现代码,供大家参考,具体内容如下 前言 案例效果的实现比较简单,利用Android自带的

  • Android Flutter实现GIF动画效果的方法详解

    目录 前言 交错动画机制 代码实现 Interval 介绍 总结 前言 我们之前介绍了不少有关动画的篇章.前面介绍的动画都是只有一个动画效果,那如果我们想对某个组件实现一组动效,比如下面的效果,该怎么办? staggered animation 这个时候我们需要用到组合动效, Flutter 提供了交错动画(Staggered Animation)的方式实现.对于多个 Anmation 对象,可以共用一个 AnimationController,然后在不同的时间段执行动画效果.这就有点像 GIF

  • Android实现列表元素动态效果

    目录 前言 AnimatedList 介绍 元素的插入和删除 使用 GlobalKey 获取 AnimatedListState 总结 前言 列表是移动应用中用得最多的组件了,我们也会经常对列表元素进行增加或删除操作,最简单的方法是列表数据变动后,直接 setState 更新列表界面.这种方式存在一个缺陷就是列表元素会突然消失(删除)或出现(添加),当列表元素内容接近时,我们都没法知道操作是否成功了.而如果能够有动效展示这个消失和出现的过程,那么体验就会好很多,比如下面的这种效果,删除元素的时候

  • Android自定义View实现拖动自动吸边效果

    本文实例为大家分享了Android自定义View实现拖动自动吸边的具体代码,供大家参考,具体内容如下 自定义View,一是为了满足设计需求,二是开发者进阶的标志之一.随心所欲就是我等奋斗的目标!!! 效果 实现逻辑 明确需求 1.实现控件跟随手指拖动2.实现控件自动贴边 整理思路 1.既然要实现控件拖动,那么就离不开onTouchEvent()这个方法,需要监听里面的按下和滑动事件.2. 要实现自动贴边,需要监听onTouchEvent()中手指离开屏幕事件.对于贴边的过程,我们用属性动画来解决

  • Android Flutter实现五种酷炫文字动画效果详解

    目录 前言 波浪涌动效果 波浪线跳动文字组 彩虹动效 滚动广告牌效果 打字效果 其他效果 自定义效果 总结 前言 偶然逛国外博客,看到了一个介绍文字动画的库,进入 pub 一看,立马就爱上这个动画库了,几乎你能想到的文字动画效果它都有!现在正式给大家安利一下这个库:animated_text_kit.本篇我们介绍几个酷炫的效果,其他的效果大家可以自行查看官网文档使用. 波浪涌动效果 波浪涌动 上面的动画效果只需要下面几行代码,其中loadUntil用于控制波浪最终停留的高度,取值是0-1.0,如

  • Android实现流动的渐变色边框效果

    目录 前言 实现思路 总结 前言 记得在介绍 motion_toast 一篇的时候,开篇有一张动图,边框是渐变色而且感觉是流动的.这个动效挺有趣的,当时也有人问怎么实现,经过上一篇<让你的聊天气泡丰富多彩!>后,有了实现思路了. 实现思路 首先要实现但是渐变色边框,这个其实可以参考上一篇的CustomPaint 的渐变填充实现.绘制一个矩形边框,然后让渐变色的区域填充到矩形区域内就可以了. void paint(Canvas canvas, Size size) { final rectWid

  • 利用Android实现光影流动特效的方法详解

    目录 前言 MaskFilter 类简介 MaskFilter 的几种效果对比 光影流动 光影流动效果1 光影流动效果2 光影流动效果3 光影流动效果4:光影沿贝塞尔曲线流动 总结 前言 Flutter 的画笔类 Paint 提供了很多图形绘制的配置属性,来供我们绘制更丰富多彩的图形.前面几篇我们介绍了 shader 属性来绘制全屏渐变的聊天气泡背景.渐变流动的边框和毛玻璃效果的背景图片,具体可以参考下面几篇文章. 让你的聊天气泡丰富多彩! 手把手教你实现一个流动的渐变色边框 利用光影变化构建立

  • Android实现点汇聚成字的动态效果详解

    目录 前言 点阵 点阵图形绘制 由点聚集成字的动画实现 总结 前言 在引入 fl_chart 绘制图表的时候,看到插件有下面这样的动效,随机散乱的圆点最后组合成了 Flutter 的 Logo,挺酷炫的.本篇我们来探讨类似的效果怎么实现. 点阵 在讲解代码实现之前,我们先科普一个知识,即点阵.点阵在日常生活中很常见,比如广告屏,停车系统的显示,行业内称之为 LED 显示屏. LED 显示屏实际上就是由很多 LED 灯组合成的一个显示面板,然后通过显示驱动某些灯亮,某些灯灭就可以实现文字.图形的显

  • Android Studio将程序打包成APK的步骤详解

    第一步:先点击Build选择GenerateSigned APK 第二步:如果之前有编译成APK的话,就直接选择Choose existing已经存在的key:如果没有编译成APK那就选择Create new创建一个新的key的存放路径,然后填上密码,其中First and Last Name填一下,其他的无所谓.如图 尽量保证图中所指的两处密码相同,这样可以避免混淆,然后点击ok.下图的红圈之内填的是存储key的文件名. 做完上述的操作,会返回下图,然后点击next 接下来,一定要点击下图标记

  • Android 判断是否能真正上网的实例详解

    Android 判断是否能真正上网的实例详解 检测网络是否连接 实现代码: /** * 检测网络是否连接 * * @return */ private boolean isNetworkAvailable() { // 得到网络连接信息 ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); // 去进行判断网络是否连接 if (manager.getA

  • Android开发高仿课程表的布局实例详解

    先说下这个demo,这是一个模仿课程表的布局文件,虽然我是个菜鸟,但我还是想留给学习的人一些例子,先看下效果 然后再来看一下我们学校的app 布局分析 先上一张划分好了的布局图 首先整个页面放在一个LinearLayout布局下面,分为上面和下面两个部分,下面一个是显示课程表的详细信息 1:这个没什么好讲的,就是直接一个LinearLayout布局,然后将控件一个TextView用来显示年份,一个View用来当作竖线,一个Spinner用来显示选择周数 2:这个是显示星期几的部件,是我自定义的V

  • Android Studio 生成自定义jar包的步骤详解

    想要将一个项目导出为jar包,供其它项目使用,在eclipse中可以直接导出该项目为jar包,而 在AS中可以通过修改gradle才处理. 接下来就介绍下具体的步骤: 1.新建一个项目,项目名随意,eg:MakeJarApplication,在项目中新建一个module类型为android-library ,命名为testLibrary.如图: 项目结构图 2.让app依赖这个库,在app下的build.gradle文件中添加compile project(':testlibrary') dep

  • Android项目中实体类entity的作用详解

    估计很多入门安卓的朋友对entity很困惑,为什么要写实体类?有什么用?写来干什么? 对于实体类的理解我入门的时候也是困惑了好久,后面用多了才慢慢理解,这篇博客就当复习和笔记. Java中entity(实体类)的写法规范 在日常的Java项目开发中,entity(实体类)是必不可少的,它们一般都有很多的属性,并有相应的setter和getter方法.entity(实体类)的作用一般是和数据表做映射.所以快速写出规范的entity(实体类)是java开发中一项必不可少的技能. 在项目中写实体类一般

  • Android未读消息拖动气泡示例代码详解(附源码)

    前言 拖动清除未读消息可以说在很多应用中都很常见,也被用户广泛接受.本文是一个可以供参考的Demo,希望能有帮助. 提示:以下是本篇文章正文内容,下面案例可供参考 最终效果图及思路 实现关键: 气泡中间的两条边,分别是以ab,cd为数据点,G为控制点的贝塞尔曲线. 步骤: 绘制圆背景以及文本:连接情况绘制贝塞尔曲线:另外端点绘制一个圆 关键代码 1.定义,初始化等 状态:静止.连接.分离.消失 在onSizeChanged中初始化状态,固定气泡以及可动气泡的圆心 代码如下(示例): @Overr

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • Android入门教程之RecyclerView的具体使用详解

    目录 RecyclerView 的基本用法 横向滚动 RecyclerView 点击事件 RecyclerView 的基本用法 和我们之前学习的控件不一样,RecyclerView 属于新增控件,所以我们需要在项目的 build.gradle 中添加 RecyclerView 库的依赖,才能使用该控件 dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementa

  • Android入门教程之Fragment的具体使用详解

    目录 Fragment 的简单用法 动态加载 Fragment Fragment 实现返回栈 Fragment 和 Activity 之间的交互 Fragment 生命周期 Fragment 的简单用法 Fragment 是一种可以嵌入在 Activity 当中的 UI 片段,它能让程序更加合理和充分地利用大屏幕的空间,因此在平板上应用非常广泛 在一个 Activity 中添加两个 Fragment,并让两个 Fragment 平分 Activity 的空间,首先新建一个左侧 Fragment

随机推荐