为了能够直接从低代码 dsl 生成鸿蒙代码,所以在对标 antd-mobile 上会很严格,因此实现上可能会很怪,但它可能不是一个真正好的鸿蒙组件库
思路
antd-mobile 的 Icon 组件在新版本中使用 svgr 渲染,因此每一个图标都是一个独立的组件,这在减少尺寸包大小方面作用很大。
import React from 'react'
import { AntOutline, ArrowDownCircleOutline } from 'antd-mobile-icons'
import { DemoBlock } from 'demos'
import { Space } from 'antd-mobile'
export default () => {
return (
<>
<DemoBlock title='基础用法'>
<Space wrap style={{ fontSize: 36 }}>
<AntOutline />
<ArrowDownCircleOutline />
</Space>
</DemoBlock>
</>
)
}
但在鸿蒙上 Icon 其实就是 Image 组件加载了图片资源,只要将图片放在 icons/src/main/resources/base/media 路径下,就可以在页面中使用 $r('app.media.AaOutline') 取到图片资源,返回类型为 Resource。因此在鸿蒙上的 Icon 的实现又回到了 antd-mobile@2 的老路上了,使用类似 Icon.type 的方式来指定图标类型
import React from 'react'
import { Icon } from 'antd-mobile'
export default () => {
return (
<>
<Icon type="search" size={40}>
</>
)
}
获取需要的资源文件
所以需求就变的简单了,看起来都是一些无脑操作,将 svg 文件放到 icons/src/main/resources/base/media 然后在 Index.ets 导出即可。
然而事情并不简单,antd-mobile-icons 的开源仓库中并没有提供 svg 源文件,只有通过脚本生成后的 React 组件。
import * as React from "react";
function AaOutline(props) {
return /*#__PURE__*/React.createElement("svg", Object.assign({
width: "1em",
height: "1em",
viewBox: "0 0 48 48",
xmlns: "http://www.w3.org/2000/svg",
xmlnsXlink: "http://www.w3.org/1999/xlink"
}, props, {
style: Object.assign({
verticalAlign: '-0.125em'
}, props.style),
className: ['antd-mobile-icon', props.className].filter(Boolean).join(' ')
}), /*#__PURE__*/React.createElement("g", {
}, /*#__PURE__*/React.createElement("g", {
id: "AaOutline-\u7F16\u7EC4"
}, /*#__PURE__*/React.createElement("rect", {
id: "AaOutline-\u77E9\u5F62",
fill: "#FFFFFF",
opacity: 0,
x: 0,
y: 0,
width: 48,
height: 48
}), /*#__PURE__*/React.createElement("path", {
d: "",
id: "AaOutline-\u5F62\u72B6\u7ED3\u5408",
fill: "currentColor",
fillRule: "nonzero"
}))));
}
export default AaOutline;
因此我又写了一个脚本把上面的 React 组件反向跑成 svg 文件(为了阅读体验,只有部分代码,不可使用)
<svg width="1em" height="1em" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" class="antd-mobile-icon" style="vertical-align: -0.125em;">
<g id="AaOutline-AAOutline" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="AaOutline-编组">
<rect id="AaOutline-矩形" fill="#FFFFFF" opacity="0" x="0" y="0" width="48" height="48">
</rect>
</g>
</g>
</svg>
然而有 151 个组件,还要识别文件名,脚本写起来不畅快。因此我又直接从官网把渲染后的 svg 拷贝下来。
代码如:
<div class="adm-grid" style="--columns: 7;">
<div class="adm-grid-item adm-icon-doc-item" style="--item-span: 1;">
<div class="adm-icon-doc-icon">
<svg width="1em" height="1em" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="antd-mobile-icon" style="vertical-align: -0.125em;">
<g id="AaOutline-AAOutline" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="AaOutline-编组">
<rect id="AaOutline-矩形" fill="#FFFFFF" opacity="0" x="0" y="0" width="48" height="48"></rect>
</g>
</g>
</svg>
</div>
<div class="adm-icon-doc-label">AaOutline</div>
</div>
写一个简单的脚本就能获得,我要的所有的文件
import fs from 'fs';
import path from 'path';
import cheerio from 'cheerio';
function gAntIcons(str: string, dir: string) {
const $ = cheerio.load(str);
$('.adm-grid-item').each((index, element) => {
const label = $(element).find('.adm-icon-doc-label').text().trim();
const svg = $(element).find('svg').parent().html();
const filePath = path.join(__dirname, dir, `${label}.svg`);
fs.writeFileSync(filePath, svg || '<svg></svg>');
});
}
gAntIcons(html, 'svg');
gAntIcons(html2, 'fill');
生成鸿蒙代码
把文件区分在两个文件夹中是因为图标分成线框风格和实底风格,下一步可以获取文件夹下的文件清单,就可以跑出鸿蒙组件的代码,也可以在上面 gAntIcons 中做记录,直接生成代码,分开成两个步骤的原因是后续如果需要扩展图标库,只要把新的 svg 文件放到相应目录下,跑一下脚本就行。因为 antd-mobile-icons 几乎不会更新,上面的相当于一次性代码了。
使用 DevEco Studio 新建一个鸿蒙项目,然后在 文件 - 新建 - 模块 里面添加一个 Static Library, Module name 改成 icons。
生成 icons/src/main/ets/index.ets 文件
export const AaOutline=$r('app.media.AaOutline');
export const AddCircleOutline=$r('app.media.AddCircleOutline');
...
export const VideoOutline=$r('app.media.VideoOutline');
生成 icons/src/main/ets/fill.ets 文件
export const AddressBookFill=$r('app.media.AddressBookFill');
export const AlipayCircleFill=$r('app.media.AlipayCircleFill');`ets
...
export const TeamFill=$r('app.media.TeamFill');
将生成的 svg 图片放到 icons/src/main/resources/base/media 目录下。
然后在主入口文件中 icons/Index.ets 将所有的资源导出。
主入口文件可以在 oh-package.json5 中配置
测试使用
在 entry 中测试使用,新建一个组件
Icon 组件
这个组件也是 antd-mobile for harmony 的 Icon 组件实现
@Component
export struct ADM_Icon {
/**
*字体大小
*/
@Prop fontSize: number = 30
/**
*字体颜色
*/
@Prop color?: ResourceColor = Color.Black;
@Require @Prop type: Resource
build() {
Stack({ alignContent: Alignment.Center }) {
Image(this.type)
.width(this.fontSize).height(this.fontSize).fillColor(this.color)
.interpolation(ImageInterpolation.High)
}
}
}
export default ADM_Icon
在 entry 中引入模块
{
"name": "entry",
"dependencies": {
"antd-mobile-icons": "file:../icons"
}
}
依旧用脚本生成代码 entry/src/main/ets/pages/Index.ets (不然手写151个图标引用有点累)
import { ADM_Icon, } from '../components/Icon'
import {
AaOutline,
AddCircleOutline,
...
AddressBookFill,
AlipayCircleFill,
...
} from 'antd-mobile-icons'
@Entry
@Component
struct Index {
build() {
Scroll() {
Flex({
direction: FlexDirection.Row,
alignItems: ItemAlign.Start,
justifyContent: FlexAlign.SpaceBetween,
wrap: FlexWrap.Wrap
}) {
Row() {
Text("线框风格")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin(10)
}.width("100%")
Column() {
ADM_Icon({ type: AaOutline })
Text('AaOutline').margin(5)
}.alignItems(HorizontalAlign.Center).width('50%')
Column() {
ADM_Icon({ type: AddCircleOutline })
Text('AddCircleOutline').margin(5)
}.alignItems(HorizontalAlign.Center).width('50%')
Row() {
Text("实底风格")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin(10)
}.width("100%")
Column() {
ADM_Icon({ type: AddressBookFill })
Text('AddressBookFill').margin(5)
}.alignItems(HorizontalAlign.Center).width('50%')
Column() {
ADM_Icon({ type: AlipayCircleFill })
Text('AlipayCircleFill').margin(5)
}.alignItems(HorizontalAlign.Center).width('50%')
}.padding({ left: 35, right: 35, top: 35 })
}.scrollBar(BarState.Auto)
}
}
渲染图
发布第三方库 antd-mobile-icon
然后根据官方文档将 antd-mobile-icon 发布到 ohpm 上
ohpm i antd-mobile-icons
import { AaOutline, } from 'antd-mobile-icons'
@Entry
@Component
struct Index {
build() {
Column() {
Image(AaOutline)
Text('AaOutline').margin(5)
}.alignItems(HorizontalAlign.Center).width('50%')
}
}