跟着IBest-UI学HarmonyOS NEXT组件封装:Badge徽标

2025-03-29 15:44:08
178次阅读
0个评论

一、引言:一起来啃源码,解锁HarmonyOS NEXT的“组件密码”!
嘿,小伙伴们!今天想和大家聊一个超实用的开源项目——IBest-UI,一个专为鸿蒙生态打造的轻量级UI组件库。如果你正在开发HarmonyOS NEXT应用,一定遇到过这些痛点:重复造轮子、适配多端界面费时费力、深浅模式切换麻烦……别急,IBest-UI就是来“救场”的!
它有多香?
● 轻量到飞起:核心代码精简,引入即用,绝不给你添负担。
● 主题随心换:深色模式?浅色模式?一行代码切换,适配鸿蒙元服务毫无压力。
● 功能小而美:从按钮到弹窗,从徽章到导航栏,覆盖高频场景,样式参考vant,使用过vant的,都知道vant样式有多好看! 但今天咱们不光是“用组件”,而是要打开引擎盖,看看里面的“黑科技”!我们发起一个源码共读计划,目标很简单:

  1. 拆解设计思想:比如Badge徽标组件,它是怎么实现动态更新、如何优雅适配不同设备?
  2. 偷师IBest-UI:在源码中捕捉ArkTS的高阶用法,学习如何在HarmonyOS NEXT中用声明式UI开发“丝滑”应用。
  3. 边学边玩:欢迎随时抛出问题、提交PR,咱们一起让IBest-UI变得更强大! 无论你是想提升源码阅读能力,还是想摸透鸿蒙开发的门道,这个系列都会是你的“实战指南”。准备好和我一起挖宝了吗?Let’s go! 🚀
    二、准备工作 看一个开源项目,第一步应该是先看 README.md 再看贡献文档 github/CONTRIBUTING.md。
  4. 克隆源码

克隆gitalb仓库

git clone git@github.com:ibestservices/ibest-ui.git

或者克隆gitee仓库

git clone git@gitee.com:ibestservices/ibest-ui.git

进入项目

cd ./ibest-ui

安装依赖

ohpm install 2. 查看目录结构 根据贡献文档,可以了解到目录结构 ├── entry # 例子hap包 │ └── src │ ├── main │ │ ├── ets │ │ │ ├── assets │ │ │ │ └── styles # 例子页面样式 │ │ │ ├── components # 例子组件 │ │ │ ├── entryability │ │ │ └── pages # 例子页面 │ │ └── resources │ │ ├── base

...

├── hvigor ├── library # 组件库 │ └── src │ └── main │ ├── ets │ │ ├── assets │ │ │ └── ets # 工具方法 │ │ ├── components # 组件目录 │ │ │ ├── button │ │ │ ├── cell │ │ │ └── ... │ │ └── theme-chalk # 样式变量 │ │ └── src │ └── resources # 组件库资源 │ ├── base

...

根据目录,可以了解到,开发的组件、修复bug,主要是在library里进行开发,entry/main/ets/pages里做组件例子页面。 全局样式变量在 library/src/theme-chalk/...里定义 三、快速找到源代码位置 可以通过快捷方式 Ctrl+Shift+N 转到文件,输入自己所想找到的文件

根据前面的目录结构,我们已经知道了entry是样例文件,library是组件库文件,那么我们要找的文件,就是在 library\src\main\ets\components\badge里 四、源码解析 进到文件里,我们可以看到有三个文件

color.est文件定义了相关样式,可以看到,在 library\src\main\resources\base\element\color.json 读取样式,好处理全局样式 interface IBestBadgeColorType { badgeBgColor: ResourceColor textColor: ResourceColor }

export const IBestBadgeColor: IBestBadgeColorType = { badgeBgColor: r("app.color.ibest_badge_background"), textColor: r("app.color.ibest_badge_text_color") } index.type.ets文件定义了IBestBadgePosition的类型 /**

  • 徽标位置 */ export type IBestBadgePosition = "top-left" | "top-right" | "bottom-left" | "bottom-right" 接下来就看下主文件index.est // 获取全局样式 import { getDefaultBaseStyle, IBEST_UI_NAMESPACE } from "../../theme-chalk/src" import { IBestUIBaseStyleObjType } from "../../theme-chalk/src/index.type" // 根据单位转换尺寸 用于框架固定尺寸格式化 带单位 import { convertDimensionsWidthUnit } from "../../utils/utils" // 获取样式 import { IBestBadgeColor } from "./color" import { IBestBadgePosition } from "./index.type"

/**

  • IBestBadge组件用于展示徽标,可以显示徽标内容、背景色、位置等 / @Component export struct IBestBadge { /*

    • 全局公共样式 / @StorageLink(IBEST_UI_NAMESPACE) baseStyle: IBestUIBaseStyleObjType = getDefaultBaseStyle() /*
    • 徽标内容 / @Prop content: string | number = '' /*
    • 徽标背景色 / @Prop color: ResourceColor = IBestBadgeColor.badgeBgColor /*
    • 是否展示为小红点 / @Prop dot: boolean = false /*
    • 最大值,超过最大值会显示 {max}+,仅当 content 为数字时有效 / @Prop max: number = -1 /*
    • 值为0时是否显示徽标 / @Prop showZero: boolean = true /*
    • 徽标位置 / @Prop badgePosition: IBestBadgePosition = 'top-right' /*
    • 自定义内容 */ @BuilderParam defaultBuilder?: CustomBuilder

    /**

    • 判断徽标是否显示
    • @returns boolean 表示徽标是否应该显示 */ isShow(){ return !(typeof this.content == "number" && this.content == 0 && !this.showZero) }

    /**

    • 获取徽标内容
    • @returns string 表示要显示的徽标内容 */ getContent(){ if(typeof this.content == 'number' && this.max > 0 && this.content > this.max){ return this.max + '+' } return this.content.toString() }

    /**

    • 根据徽标位置获取边缘位置
    • @returns Edges 表示徽标的边缘位置 */ getPosition(): Edges{ switch (this.badgePosition){ case 'top-left': return { left: 0, top: 0 } case 'top-right': return { right: 0, top: 0 } case 'bottom-left': return { left: 0, bottom: 0 } case 'bottom-right': return { right: 0, bottom: 0 } } }

    /**

    • 根据徽标位置获取平移选项
    • @returns TranslateOptions 表示徽标的平移选项 */ getTranslate(): TranslateOptions{ switch (this.badgePosition){ case 'top-left': return { x: "-50%", y: "-50%" } case 'top-right': return { x: "50%", y: "-50%" } case 'bottom-left': return { x: "-50%", y: "50%" } case 'bottom-right': return { x: "50%", y: "50%" } } }

    /**

    • 构建徽标组件 */ build() { Row() { if (this.defaultBuilder) { this.defaultBuilder() } if (this.dot) { Text() .width(convertDimensionsWidthUnit(8)) .aspectRatio(1) .borderRadius(this.baseStyle.borderRadiusMax) .backgroundColor(this.color) .position(this.getPosition()) .translate(this.getTranslate()) } else if(this.isShow()) { Text(this.getContent()) .constraintSize({ minWidth: convertDimensionsWidthUnit(16) }) .fontColor(IBestBadgeColor.textColor) .fontSize(convertDimensionsWidthUnit(12, true)) .padding({ left: convertDimensionsWidthUnit(3), right: convertDimensionsWidthUnit(3) }) .backgroundColor(this.color) .borderRadius(this.baseStyle.borderRadiusMax) .position(this.getPosition()) .translate(this.getTranslate()) .textAlign(TextAlign.Center) } } } } 代码解释 这段代码实现了一个名为 IBestBadge 的徽标组件,主要用于展示带有内容、背景色和位置的徽标。以下是详细功能分解:
  1. 属性定义: ○ baseStyle:全局公共样式,通过 @StorageLink 绑定。 ○ cotnten:徽标内容,可以是字符串或数字。 ○ color:徽标背景色,默认为 IBestBadgeColor.badgeBgColor。 ○ dot:是否显示为小红点。 ○ max:徽标内容的最大值,超过时显示 {max}+。 ○ showZero:当 content 为 0 时是否显示徽标。 ○ badgePosition:徽标位置,支持 top-left、top-right、bottom-left 和 bottom-right。 ○ defaultBuilder:自定义内容的构建器。 ● ● 定义了一系列 props,包括徽标内容,徽标背景色、是否展示为小红点、徽标位置等。可直接参见文档中的API属性。 ● ● 方法逻辑: ○ isShow:判断徽标是否需要显示,当 content 为 0 且 showZero 为 false 时不显示。 ○ getContent:根据 max 值限制返回徽标内容,若超出最大值则显示 {max}+。 ○ getPosition:根据 badgePosition 返回徽标的边缘位置(如 top-left 对应 {left: 0, top: 0})。 ○ getTranslate:根据 badgePosition 返回徽标的平移选项(如 top-left 对应 {x: "-50%", y: "-50%"})。 ○ build:构建徽标组件,优先渲染 defaultBuilder 内容;若为小红点模式,则创建小红点;否则根据 isShow 和 getContent 渲染普通徽标。
  2. 渲染逻辑: ○ 若 dot 为 true,渲染一个小红点。 ○ 若 isShow 返回 true,渲染普通徽标,并应用内容、样式和位置。

控制流图

这个Badge组件,源码很简单,不复杂(相关代码解释已在源码文件里写好了),为什么解析这么简单的组件,主要是想着由简入深,先熟悉下HarmonyOS NEXT的ArkTs的写法。 五、自定义主题样式 其中,我们看组件的第一个属性baseStyle:全局公共样式,通过 @StorageLink 绑定。 @StorageLink,从API version 11开始,该装饰器支持在元服务中使用。在harmonyOS NEXT中,可以适用。 让我们看下官方文档的说明 @StorageLink(key)是和AppStorage中key对应的属性建立双向数据同步:

  1. 本地修改发生,该修改会被写回AppStorage中。
  2. AppStorage中的修改发生后,该修改会被同步到所有绑定AppStorage对应key的属性上,包括单向(@StorageProp和通过Prop创建的单向绑定变量)、双向(@StorageLink和通过Link创建的双向绑定变量)变量和其他实例(比如PersistentStorage)。 @StorageLink要配合着 AppStorage使用,让我们看看AppStorage怎么进行初始化

已知初始化方法为AppStorage.setOrCreate(propName, newValue),那我们找找看,该方法在哪初始化,可以通过快捷方式 Ctrl+Shift+N,通过Text,来快速找到初始化的文件

进到文件library\src\main\ets\theme-chalk\src\index.ets,我们可以找到样式初始化的方法setIBestUIBaseStyle 通过setIBestUIBaseStyle方法,设置全局样式。 /**

  • AppStorage命名空间 / export const IBEST_UI_NAMESPACE = '__IBEST-UI_BASE_STYLE' /*
  • 设置全局样式
  • @param styleData */ export function setIBestUIBaseStyle(styleData?: Partial) { const newStyleData = getDefaultBaseStyle(); if (typeof styleData === 'object' && styleData !== null) { Object.keys(styleData).forEach(item => { if (newStyleData[item]) { newStyleData[item] = (styleData as IBestUIBaseStyleObjType)[item] ?? newStyleData[item] } }) } AppStorage.setOrCreate(IBEST_UI_NAMESPACE, newStyleData) }
  1. IBEST_UI_NAMESPACE 常量
    ○ 定义了一个全局命名空间常量,用于标识存储的全局样式数据。

  2. setIBestUIBaseStyle 方法
    ○ 该方法用于设置全局样式,接收一个可选参数 styleData,类型为 Partial ○ 如果传入了有效的 styleData 对象,则将其与默认样式数据合并,覆盖默认值。 ○ 最终将合并后的样式数据存储到 AppStorage 中,使用命名空间 __IBEST-UI_BASE_STYLE。

  3. getDefaultBaseStyle 方法
    ○ 该方法用于生成框架默认的主题样式数据,返回一个 IBestUIBaseStyleObjType 类型的对象。 ○ 数据包括颜色(如主题色、透明度)、间距(如 spaceMini、spaceBase)、字体大小、边框半径、行高等多种样式属性。 ○ 部分值通过 convertDimensionsWidthUnit 方法进行单位转换。 /**

  • 框架默认主题 */ export function getDefaultBaseStyle(): IBestUIBaseStyleObjType { const data: IBestUIBaseStyleType = { default: THEME_COLOR.DEFAULT, primary: THEME_COLOR.PRIMARY, success: THEME_COLOR.SUCCESS, warning: THEME_COLOR.WARNING, danger: THEME_COLOR.DANGER, primaryOpacity: COLOR_OPACITY.PRIMARY, successOpacity: COLOR_OPACITY.SUCCESS, warningOpacity: COLOR_OPACITY.WARNING, dangerOpacity: COLOR_OPACITY.DANGER, spaceMini: convertDimensionsWidthUnit(SPACE.MINI), spaceBase: convertDimensionsWidthUnit(SPACE.BASE), spaceXs: convertDimensionsWidthUnit(SPACE.XS), spaceSm: convertDimensionsWidthUnit(SPACE.SM), spaceMd: convertDimensionsWidthUnit(SPACE.MD), spaceLg: convertDimensionsWidthUnit(SPACE.LG), spaceXl: convertDimensionsWidthUnit(SPACE.XL), fontSizeXs: convertDimensionsWidthUnit(FONT_SIZE.XS, true), fontSizeSm: convertDimensionsWidthUnit(FONT_SIZE.SM, true), fontSizeMd: convertDimensionsWidthUnit(FONT_SIZE.MD, true), fontSizeLg: convertDimensionsWidthUnit(FONT_SIZE.LG, true), fontSizeXl: convertDimensionsWidthUnit(FONT_SIZE.XL, true), borderRadiusSm: convertDimensionsWidthUnit(BORDER_RADIUS.SM), borderRadiusMd: convertDimensionsWidthUnit(BORDER_RADIUS.MD), borderRadiusLg: convertDimensionsWidthUnit(BORDER_RADIUS.LG), borderRadiusMax: convertDimensionsWidthUnit(BORDER_RADIUS.MAX), lineHeightXs: convertDimensionsWidthUnit(LINE_HEIGHT.XS), lineHeightSm: convertDimensionsWidthUnit(LINE_HEIGHT.SM), lineHeightMd: convertDimensionsWidthUnit(LINE_HEIGHT.MD), lineHeightLg: convertDimensionsWidthUnit(LINE_HEIGHT.LG), // 滚动效果 scrollEdgeEffect: EdgeEffect.Fade, // 滚动条颜色 scrollBarColor: '#dbdfe6', animationDuration: 200 } return data; } 再让我们找找setIBestUIBaseStyle在那边调用,然后可以发现在library\Index.ets中将 setIBestUIBaseStyle 方法重命名为 IBestSetUIBaseStyle,并将此方法暴露出去。

在IBest-UI@HarmonyOS-自定义主题样式文档中,可以看到暴露出来的IBestSetUIBaseStyle方法

由小见大,我们知道了,IBest-UI@HarmonyOS组件库,是怎么实现自定义主题样式 六、结语 咱们一起盘盘这个HarmonyOS组件库! 这篇文章带大家拆解了IBest-UI组件库里的Badge徽标组件,顺便摸清了鸿蒙主题定制的套路。简单说就是:组件虽小,五脏俱全 ,特别适合新手练手! 干了啥?

  1. 组件怎么用? ○ 能显示数字/文字、变红点、自动截断超长内容(比如"99+") ○ 支持四个方位贴牌(左上右上随便钉!) ○ 重点 :通过@StorageLink同步全局主题,换皮肤只需改配置文件,不用挨个改组件
  2. 源码里藏着啥宝贝? ○ 属性全家桶 :content(内容)、dot(小红点开关)、max(最大值限制)... 想改啥直接传参 ○ 位置 :用getPosition()和getTranslate()控制徽标位置,自动计算偏移量(再也不用硬编码坐标啦!) ○ 条件渲染 :isShow()判断要不要显示徽标,0值显示开关超贴心
  3. 主题定制怎么玩? ○ 通过setIBestUIBaseStyle统一管理样式变量(颜色/圆角/间距...) ○ 源码里直接AppStorage存全局配置,改一次全组件生效(妈妈再也不用担心我改漏文件!) 吐槽&彩蛋 ● ArkTS骚操作 :convertDimensionsWidthUnit这个工具函数,自动适配不同设备的尺寸单位,鸿蒙生态的"响应式"精髓就在这! ● 隐藏关卡 :组件库里还有按钮、导航栏等一堆组件,Badge只是开胃菜,后面可以继续挖坑! 一句话总结 :这组件库把鸿蒙的声明式UI玩明白了,跟着抄作业能少写不少代码,建议收藏!
收藏00

登录 后评论。没有帐号? 注册 一个。