.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}
零、前言
首先,掘金作者榜,请投我一票~
,正文开始:
1、关于 Icon 组件
图标是每个应用都不可或缺的,我们通过 Icon
组件,指定一个图标就可以进行显示。可以随意指定其 颜色
、大小
。比起图片来说, Icon
不会因放大而失真。但你想过 Icon
组件是如何实现的吗?你有想过 Icons.android_rounded
到底代表什么吗?我跟你说它的本质就是文字你相信吗?接下来的文本解读系列,将花 2~3
篇来详细聊聊这个 Icon
组件。
2.本系列其他文章
- 《Flutter 文本解读 1 | 从源码认识 Text 组件》
- 《Flutter 文本解读 2 | Text 是如何画出来的》
- 《Flutter 文本解读 3 | Text 组件使用介绍 》
- 《Flutter 文本解读 4 | TextStyle 文字样式解读 》
- 《Flutter 文本解读 5 | RichText 富文本的使用 (上)》
- 《Flutter 文本解读 6 | RichText 富文本的使用 (中)》
- 《Flutter 文本解读 7 | RichText 写个代码高亮组件》
一、认识 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.生成代码文件
信息在手,就可以自动生成代码了,操作如下:
代码语言:javascript复制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);
}
复制代码
到这里,最基本的和核心的处理就结束了,但这也仅仅是个简单的工具。在下一篇中,将对这个工具进行改进,让它更方便使用。毕竟...