搞一个自个的React Native图标库
想在React Native(以下称RN)使用图标,其实办法很多,想简单省事的话,直接梭哈 react-native-vector-icons 就可以,这个属于目前RN平台上最为热门的图标解决方案了,使用广泛也比较稳定,不足之处在于需要将字体文件打包进app,不能热更新。
这里不深究 react-native-vector-icons 的实现原理,来看看如果我们自己想做一个自己的图标库,应该怎么来做呢?
一、怎么显示一个图标?
要想展示一个图标,可以有几种方式:webfont、svg或者图片。
webfont,网页上比较常见的图标库实现方式,需要将字体文件打包到App内部。
svg,体积小,矢量图,拉伸不失真,且可以通过svg属性控制颜色。
图片,使用简单,但是无法动态设置颜色,且无法适应不同倍率的屏幕。
综合来说,webfont需要将字体文件打包到App内部,不能热更,svg本事是xml代码形式的,易于控制,相对来说svg比较适合做图标库。
二、用svg实现图标库的思路
对于图标库来说,需要满足以下两个基本条件:
可以设置大小,任意大小不变形,svg本身就是矢量的,天生就支持。
可以设置颜色,可以通过设置svg fill属性来实现。
那么,思路很简单,只需要开发者,提前做好svg图标。将svg文件解析出来,动态设置颜色,图标大小可以通过设置元素width/height来实现。
三、RN 加载svg图片
一般来说,我们会用 react-native-svg 来加载 svg。
yarn add react-native-svg
加载svg的方式有多种,可以加载远程svg,或者自己编写svg代码均可。
// 使用远程svg图片
<SvgUri
width="100%"
height="100%"
uri="http://thenewcode.com/assets/images/thumbnails/homer-simpson.svg"
/>
// 自行编写svg代码
<Svg height="50%" width="50%" viewBox="0 0 100 100">
<Circle
cx="50"
cy="50"
r="45"
stroke="blue"
strokeWidth="2.5"
fill="green"
/>
<Rect
x="15"
y="15"
width="70"
height="70"
stroke="red"
strokeWidth="2"
fill="yellow"
/>
</Svg>
当然也可以加载本地的svg图片,需要配置metro config和babel,有兴趣可以前往文档进行阅读。
三、解析svg文件
解析svg文件可以通过 react-native-svg-uri 来实现。
npm install react-native-svg-uri --save
<SvgUri width="200" height="200" source={require('./img/homer.svg')} />
在react-native中默认只能require png和xml文件,需要require svg文件,需要做额外的配置。
此外,频繁的require本地文件也会减慢速度,我们可以读取svg文件,解析xml内容,仅保留path标签,其余部分内容对我们显示图片来说是无用的。
我们可以编写一个脚本,批量读取svg文件,解析xml数据,react-native-svg-uri也预留了使用xmlData的接口。
<SvgUri width="200" height="200" svgXmlData={svgXmlData} fill="red" /
react-native-svg-uri 渲染svg图片依赖于 react-native-svg,但是 react-native-svg-uri 这个库已经很久不更新了,其依赖的 react-native-svg 版本过低,会导致实际使用过程,因为原生和react-native-svg-uri 使用的react-native-svg版本不同而报错。
react-native-svg-uri 常年不更新,且代码简单,建议直接将仓库代码扒下来放到自己的工具代码中使用,ts版本也可以从作者仓库里面复制一份,代码可以在 src/lib/react-native-svg-uri 找到,仓库地址可以点击下方查看原文获取。
四、具体步骤
仔细串一串svg图标渲染的整个流程:svg文件->脚本解析->svg xml data->react-native-svg-uri渲染svg图片。
1、准备svg素材
svg素材可以由设计师提供,也可以自行前往iconfont下载,或者自己制作。
2、脚本处理
require大量静态资源影响性能,且需要做额外的配置,为了方便处理,这里我们将所有的svg文件简单处理一下,转换成一个js文件。这里我使用nodejs编写,可以选自己喜欢的脚本语言处理一下。
const path = require('path');
const fs = require('fs');
const svgFileDir = path.resolve(__dirname, '../../src/assets/svg');
function readSvgFile(svgFileName) {
return new Promise((resolve, reject) => {
fs.readFile(path.join(svgFileDir, svgFileName), 'utf8', (error, svgFile) => {
let svgPath = svgFile.replace(/<\?xml.*?\?>|<\!--.*?-->|<!DOCTYPE.*?>/g, '');
svgPath = svgPath.replace(/[\r\n]*/g, '');
if (error) {
return reject(error);
}
if (svgFileName.indexOf('.svg') === -1) {
return resolve({});
}
resolve({
[svgFileName.slice(0, svgFileName.lastIndexOf('.'))]: svgPath,
});
});
});
}
function readSvgDir() {
return new Promise((resolve, reject) => {
fs.readdir(svgFileDir, (error, svgFiles) => {
if (error) {
return reject(error);
}
Promise.all(svgFiles.map(svgFileName => readSvgFile(svgFileName)))
.then(data => resolve(data))
.catch(err => reject(err));
});
});
}
readSvgDir()
.then(data => {
const svgFile = `export default {
${data.filter(item => Object.keys(item)[0]).map(item => `'${Object.keys(item)[0]}': \`${Object.values(item)[0]}\``)}
}\n`;
fs.writeFile(path.resolve(svgFileDir, 'index.js'), svgFile, err => {
if (err) {
throw new Error(err);
}
});
})
.catch(error => {
throw new Error(error);
});
处理的产物,类似于这样。
export default {
'arrow-down': `svg data`,
'arrow-up': `svg data`,
...
}
3、封装Icon Compoment
上面两步已经准备好了所有素材,只需要将对应的svg xml data配合size以及color做渲染即可。
import React from 'react';
import { Pressable, StyleProp, ViewStyle } from 'react-native';
import SvgUri from '../../lib/react-native-svg-uri';
import svgs from '../../assets/svg';
export type IconProps = {
// icon名字
name: string;
// 颜色
color?: string;
// 大小
size?: number;
// 点击回调
onPress?: () => void;
// style
style?: StyleProp<ViewStyle>;
};
const Navbar: React.FC<IconProps> = props => {
const { name, color = '#666', size = 20, onPress, style = {} } = props;
let svgXmlData = (svgs as any)[name];
if (!svgXmlData) {
throw new Error(`No Icon Named ${name} Was Found!`);
}
return (
<Pressable onPress={onPress} style={style}>
<SvgUri width={size} height={size} svgXmlData={svgXmlData} fill={color} />
</Pressable>
);
};
export default Navbar;
使用方式
<Icon name="arrow-down" color="red" />
处理到这里,基本已经完成了,需要新增图标时,只需要将svg文件放到设定好的文件夹中,然后再执行svg的处理脚本即可,为了方便使用,可以添加一个npm script脚本。
{
"scripts": {
"build:svg": "node scripts/svg/index.js"
}
}
以上所有代码,均可在 https://github.com/AspenLuoQiang/react-native-ui-view 仓库中找到,或点击下方查看原文进入代码仓库。