阅读 1500

Flutter 文本解读 8 | Icon 与 RichText 的渊源

零、前言

首先,掘金作者榜,请投我一票~,正文开始:

1、关于 Icon 组件

图标是每个应用都不可或缺的,我们通过 Icon 组件,指定一个图标就可以进行显示。可以随意指定其 颜色大小。比起图片来说, Icon 不会因放大而失真。但你想过 Icon 组件是如何实现的吗?你有想过 Icons.android_rounded 到底代表什么吗?我跟你说它的本质就是文字你相信吗?接下来的文本解读系列,将花 2~3 篇来详细聊聊这个 Icon 组件。

2.本系列其他文章

一、认识 Icon 组件

1. Icon 组件的源码实现

Icon 是一个 StatelessWidget 组件,说明它是个打酱油的,内部 build 到底依赖其他 Widget 实现。构造方法中,向外暴露了几个属性以供用户使用,如 颜色大小图标数据等。

简单瞄一下源码中的 Icon#build 方法,可以看到其内部使用了 RichText,也就说明,Icon 组件的本质也是一种文本。至于更细的代码,现在先不看,先说写前置的知识。


2. IconData 到底是什么

我们一直用的 Icons.xxx 就可以获取到对应的图标数据,你有么有想过,这玩意到底是什么东西。其实不难看出,就是 Icons 类中的一个静态常量,类型为 IconData

IconData 类如下,它主要需要 int 型的 codePoint 对象和 String 型的 fontFamily。也就是说,需要从一个字体文件中通过 一个 int 值 获取对应的 '文字'


3. MaterialIcons 图标字体位置

你不禁会问,那 MaterialIcons 字体在哪里?如果你查看一下 应用的数据, 可以发现如下的图标字体文件。这就是图标数据的来源。

这时你也许会想,我可以用自己的 图标字体 ,默认的这些可以不用,毕竟两个加起来有 1 M 多,挺浪费空间的。可以将 pubspec.yaml 中的 uses-material-design 置为 false 即可,这样就不会将默认的图标字体 打包到应用中。这时你需要注意:Flutter 中一些组件会使用这些字体,在用的时候,换成自己的即可。


4. Icon 和 文字的联系

通过前面的描述,你应该了解 Icon 和 文字的联系了。下面通过两个小例子,加深一些了解。
图标字体 本身也是字体,如果不指定,会使用默认的字体。如下,随便写个 int 值,如果在默认字体中找到了,也是会显示出来的。


图标字体 本身也是字体,所以通过对应的 Unicode ,也可以通过 Text 显示出来字体图标。

甚至你可以通过 TextStyle 对图标进行样式处理,想一想第四篇花里胡哨 的那些东西,也可以用在字体图标中。因为它们的本质是一样的,都是基于 RichText 组件,通过 RenderParagraph 绘制的。这便是知识的联系与贯通。


二、如何自定义图标字体

1.寻找图标字体

我最喜欢的图标网站是 www.iconfont.cn/ 。在这里有海量的图标,提供下载。也可以将自己设计的图标上传进去。

通过搜索,将自己喜欢的图标加入小篮子。

你可以直接下载图标,但我建议最好建个项目 ,方便以后管理。

下面新建的 ruby 的项目,就可以将图标加入其中。


2.修改和下载图标字体

悬浮时可以看到修改的按钮,点击进入修改界面。


在这里可以设置 图标的名字Unicode 。没错,就是 IconData 类中的那个 int 值。


修改完点击下载即可。

其中 iconfont.ttf 就是对于的图标字体文件。

另外 iconfont.css 中记录着 图标的信息。所以想要在 Flutter 中显示一个 图标字体 的两大要素都具备了,就差使用了。


3.在 Flutter 中使用图标字体

首先需要 引入资源 并在 pubspec.yaml 中进行配置。注:文件位置和文件名无所谓,只要对应即可。


这样,就可以将下载的图标字体用在 Flutter 中了。当然,我们也可以仿照 Icons 源码那样,提供一个 TolyIcon.XXX 来获取 IconData 数据。

class IconShow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Icon(
      TolyIcon.ruby,
      size: 50,
    );
  }
}

class TolyIcon{
  static const IconData ruby = IconData(0xe64c,fontFamily: 'TolyIcon');
}
复制代码

这时,你也许会说,哇,这 Unicode 一个个敲吗?好麻烦啊。你不知道有种力量叫做工具,程序就是为了解决这些无脑劳动而诞生的。下面来一起看看,如何 让代码生成代码


三、动手写个小工具

1. 实现分析

其实原理很简单,我们只需要通过解析 iconfont.css 中的内容,获取到每个图标的 名称Unicode 码 即可。然后通过代码生成 TolyIcon 类即可,也就是以后自定义字体图标的流程为: 下载 -> 拷贝-> 生成


2.图标名Unicode 码 提取

如下,通过正则 \.icon-(.*?): 就可以匹配出所有的图标名字。


通过 "\\(.*?)" 可以匹配搭配到 Unicode 码


3.Dart 中正则的使用

通过 StringScanner 可以根据正则对字符串进行扫描匹配。如下图我们就获取了 图标名称。上面正则中,通过 () 对匹配的字符串进行分组,下面 _scanner.lastMatch[1] 说明是第一组,也就是括号里的。第 0 组默认是匹配的全字符。

main()async{
  
  File target = File('${Directory.current.path}/assets/iconfont/iconfont.css');
  
  String str = await target.readAsString();

  StringScanner _scanner = StringScanner(str);

  while (!_scanner.isDone) {
    if (_scanner.scan(RegExp(r'\.(.*?):'))) {
      String word = _scanner.lastMatch[1];
      print(word);
    }

    if (!_scanner.isDone) {
      _scanner.position++;
    }
  }
}
复制代码

同理,可以获取 Unicode 码,通过两个 String 列表收集信息。这样就从 iconfont.css 中提取出了最关键的数据。

main() async {
  File target = File('${Directory.current.path}/assets/iconfont/iconfont.css');

  String str = await target.readAsString();

  Map<String, String> iconInfo = {};

  List<String> names = [];
  List<String> unicodes = [];
  StringScanner _scanner = StringScanner(str);
	// 根据正则扫描 
  while (!_scanner.isDone) {
    if (_scanner.scan(RegExp(r'\.(.*?):'))) {
      String word = _scanner.lastMatch[1];
      names.add(word);
    }
    if (_scanner.scan(RegExp(r'"\\(.*?)"'))) {
      String word = _scanner.lastMatch[1];
      unicodes.add(word);
    }
    if (!_scanner.isDone) {
      _scanner.position++;
    }
  }

  assert(names.length == unicodes.length);
	// 合成 map
  Map<String,String> iconMap = Map.fromIterables(names, unicodes);
  iconMap.forEach((key, value) {
    print('----$key-----$value---');
  });
}
复制代码

4.生成代码文件

信息在手,就可以自动生成代码了,操作如下:

String getCode(Map<String, String> iconMap, {String fontName: 'TolyIcon'}) {  
  String result = """import 'package:flutter/widgets.dart';
//Power By 张风捷特烈 --- Generated file. Do not edit.

class $fontName {
    $fontName._();
""";
  iconMap.forEach((key, value) {
    result +=
        """static const IconData $key = IconData( 0x$value, fontFamily: "$fontName");\n""";
  });
  result += "}";
  return result;
}
复制代码

然后将其写入文件即可,运行 icon_builder.dart 就会自动生成对应的 dart 文件。

void save2File(String content,
               {String filePath: 'generate/icon',
                String fontName: 'TolyIcon'}) async{
  
  File target = File(path.join(Directory.current.path,'lib',filePath,'$fontName.dart'));
  if(!target.existsSync()){
   await target.create(recursive: true);
  }
  await target.writeAsString(content);
}
复制代码

到这里,最基本的和核心的处理就结束了,但这也仅仅是个简单的工具。在下一篇中,将对这个工具进行改进,让它更方便使用。毕竟...


文章分类
Android
文章标签