React Native大火大热,其中为了解决图标,易于修改,换颜色,高清等需求,iconfont的应用更是必不可少。
关于在React Native中使用iconfont,网上已有很多非常好的解决方案,用的最多的就是react-native-vector-icons , 这个库支持很多常用的iconfont,比如FontAwesome, Ionicons, MaterialIcons等等。
但是这个库依赖了不少iOS和Android的原生代码,这让一个前端开发脸上浮现了一个大大的懵逼。 而且自带的字体文件都偏大,做起精简来简直想哭,更别说加入自定义的iconfont了。
其实IconFont就是一些文字,通过在web上的使用,我们可以大概猜出使用方法:
在React Native中同样如此,我们可以通过react-native-vector-icons的源代码来验证我们的想法。
打开react-native-vector-icons/FontAweson.js
文件(线上地址)可以看到一个大大的json对象
var createIconSet = require('./lib/create-icon-set');
var glyphMap = {
"glass": 61440,
"music": 61441,
"search": 61442,
"envelope-o": 61443,
"heart": 61444,
.
. // 此处省略500+行
.
};
var FontAwesome = createIconSet(glyphMap, 'FontAwesome', 'FontAwesome.ttf');
module.exports = FontAwesome;
module.exports.glyphMap = glyphMap;
看到这些亲切的数字了吗,61440
,这不就是传说中的16进制的FXXX
的十进制吗?
16进制有了,写到哪里呢,继续看createIconSet
方法。
其中的Icon组件的render方法:
render: function() {
var { name, size, color, style, ...props } = this.props;
var glyph = glyphMap[name] || '?';
if(typeof glyph === 'number') {
glyph = String.fromCharCode(glyph);
}
size = size || DEFAULT_ICON_SIZE;
var styleDefaults:Object = {
fontSize: size,
fontWeight: 'normal',
fontStyle: 'normal',
color,
};
props.style = [styleDefaults, style];
props.ref = (component) => this._root = component;
styleDefaults.fontFamily = fontReference;
console.log(this.props.children)
return (<Text {...props}>{glyph}{this.props.children}</Text>);
}
其中,最重要的四句话_(我本来写的是两句话,结果越看越多)_:
var glyph = glyphMap[name] || '?';
把刚才的6xxxx
找到;
glyph = String.fromCharCode(glyph);
转成Unicode编码字符串;
styleDefaults.fontFamily = fontReference;
指定字符集;
return (<Text {...props}>{glyph}{this.props.children}</Text>);
把Unicode字符写到Text
组件中。
基本和我们的猜想一样,哇哈哈哈。
由上可知,我们主要需要这个Icon所对应的Unicode码,那这个Unicode码又是神马呢?
实际上,一个字体通常由数个表(table)构成,字体的信息存储在表中。一个最基本的字体文件一定会包含以下表:
而使用TrueType曲线绘制的字体则会包含如下表:
上面列了很多,最重要的其实是第一个表看这高大上的说明Character to glyph mapping
。
如果把字体文件转成类xml格式,这个表类似:
<cmap>
<cmap_format_4 platformID="3" platEncID="1" language="0">
<map code="0xf600" name="uniF600"/>
<map code="0xf601" name="uniF601"/>
<map code="0xf602" name="uniF602"/>
<map code="0xf603" name="uniF603"/>
</cmap_format_4>
</cmap>
这里的0xf600
不就是我们想要的吗,而后面的name就类似与每个字符的命名。如下表:
这里最好给每个icon定一个易于理解的名字,可以使用http://font.baidu.com/editor
有了上面的摸索,要支持自己的IconFont并不难。只需要把字符对应表给整出来就可以了,代码如下:
var map = {"arrow":"62976","checked":"62977","checked-s":"62978","tag-svip":"62995"};
;module.exports = (name)=>String.fromCharCode(map[name]);
使用的时候:
import icon from "./fontConf";
export default class IconExample extends Component {
render() {
return (
<View style={styles.container}>
<Text style={{fontFamily: 'FontIconQui',fontSize:30}}>
arrow-icon:{icon('arrow')}
</Text>
<Text style={{fontFamily: 'FontIconQui',fontSize:30, color:"#ff4444"}}>
vip-icon:{icon('tag-svip')}
</Text>
<Text style={{fontFamily: 'FontIconQui',fontSize:30, color:"#ff4444"}}>
tag-svip:{icon('tag-svip')}
</Text>
</View>
)
}
}
另外,在工程中,需要引入字体文件:
[project root]/android/app/src/main/assets/fonts/
Add to targets
和Create groups
,修改Info.plist文件,添加属性Fonts provided by application
,在这个属性下添加相应字体文件名的item,如下图:iOS上添加字体文件具体的流程可以参考https://github.com/oblador/react-native-vector-icons#option-manually。
你以为做完上面的工作就完了吗,如果你只是对着一个10k的ttf文件,那么左手右手一个慢动作,靠着五姑娘的勤劳也可以很快完成,但是如果面对的是个有700个图标的FontAwesome怎么办?
大神说过: 所有超过五分钟的事情都应该自动化。 大神还说过: 人生苦短,我用python。
所以,我用python撸了个脚本来自动生成字符对应表的代码。
代码很简单,就是读之前说的那个cmap
表。
tmpl = """
var map = {%s};
;module.exports = (name)=>String.fromCharCode(map[name]);
"""
def main(fontFile, output):
try:
font = TTFont(fontFile)
glyphMap = font["cmap"].getcmap(3,1).cmap
tmp = ""
for k in glyphMap:
tmp += '"%s":"%s",' % (glyphMap[k],k)
f=file(output,"w+")
f.write(tmpl % tmp)
f.close()
except Exception, ex:
print ex
用法: $ python iconfont-mapper.py fontawesome.ttf fontawesomeConf.js
依赖fontTools
这个库,完整代码在https://github.com/bob-chen/react-native-iconfont-mapper