IBest-UI@HarmonyOS源码共读计划:Badge徽标

一、引言:一起来啃源码,解锁HarmonyOS NEXT的"组件密码"!

嘿,小伙伴们!今天想和大家聊一个超实用的开源项目------IBest-UI,一个专为鸿蒙生态打造的轻量级UI组件库。如果你正在开发HarmonyOS NEXT应用,一定遇到过这些痛点:重复造轮子、适配多端界面费时费力、深浅模式切换麻烦......别急,IBest-UI就是来"救场"的!

它有多香?

  • 轻量到飞起:核心代码精简,引入即用,绝不给你添负担。
  • 主题随心换:深色模式?浅色模式?一行代码切换,适配鸿蒙元服务毫无压力。
  • 功能小而美:从按钮到弹窗,从徽章到导航栏,覆盖高频场景,样式参考vant,使用过vant的,都知道vant样式有多好看!

但今天咱们不光是"用组件",而是要打开引擎盖,看看里面的"黑科技" !我们发起一个源码共读计划,目标很简单:

  1. 拆解设计思想:比如Badge徽标组件,它是怎么实现动态更新、如何优雅适配不同设备?
  2. 偷师HarmonyOS NEXT:在源码中捕捉ArkTS的高阶用法,学习如何用声明式UI开发"丝滑"应用。
  3. 边学边玩:欢迎随时抛出问题、提交PR,咱们一起让IBest-UI变得更强大!

无论你是想提升源码阅读能力,还是想摸透鸿蒙开发的门道,这个系列都会是你的"实战指南"。准备好和我一起挖宝了吗?Let's go! 🚀

二、准备工作

看一个开源项目,第一步应该是先看 README.md 再看贡献文档 github/CONTRIBUTING.md

  1. 克隆源码
bash 复制代码
# 克隆gitalb仓库
git clone [email protected]:ibestservices/ibest-ui.git

# 或者克隆gitee仓库
git clone [email protected]:ibestservices/ibest-ui.git

# 进入项目
cd ./ibest-ui

# 安装依赖
ohpm install
  1. 查看目录结构

根据贡献文档,可以了解到目录结构

csharp 复制代码
├── 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 读取样式,好处理全局样式

css 复制代码
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的类型

css 复制代码
/**
 * 徽标位置
 */
export type IBestBadgePosition = "top-left" | "top-right" | "bottom-left" | "bottom-right"

接下来就看下主文件index.est

less 复制代码
// 获取全局样式
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-lefttop-rightbottom-leftbottom-right
    • defaultBuilder:自定义内容的构建器。
  • 定义了一系列 props,包括徽标内容,徽标背景色、是否展示为小红点、徽标位置等。可直接参见文档中的API属性。
  1. 方法逻辑
    • isShow:判断徽标是否需要显示,当 content 为 0 且 showZerofalse 时不显示。
    • getContent:根据 max 值限制返回徽标内容,若超出最大值则显示 {max}+
    • getPosition:根据 badgePosition 返回徽标的边缘位置(如 top-left 对应 {left: 0, top: 0})。
    • getTranslate:根据 badgePosition 返回徽标的平移选项(如 top-left 对应 {x: "-50%", y: "-50%"})。
    • build:构建徽标组件,优先渲染 defaultBuilder 内容;若为小红点模式,则创建小红点;否则根据 isShowgetContent 渲染普通徽标。
  1. 渲染逻辑
    • dottrue,渲染一个小红点。
    • 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方法,设置全局样式。

ini 复制代码
/**
 * AppStorage命名空间
 */
export const IBEST_UI_NAMESPACE = '__IBEST-UI_BASE_STYLE'
/**
 * 设置全局样式
 * @param styleData
 */
export function setIBestUIBaseStyle(styleData?: Partial<IBestUIBaseStyleType>) {
	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 常量
    • 定义了一个全局命名空间常量,用于标识存储的全局样式数据。
  1. setIBestUIBaseStyle 方法
    • 该方法用于设置全局样式,接收一个可选参数 styleData,类型为 Partial<IBestUIBaseStyleType>
    • 如果传入了有效的 styleData 对象,则将其与默认样式数据合并,覆盖默认值。
    • 最终将合并后的样式数据存储到 AppStorage 中,使用命名空间 __IBEST-UI_BASE_STYLE
  1. getDefaultBaseStyle 方法
    • 该方法用于生成框架默认的主题样式数据,返回一个 IBestUIBaseStyleObjType 类型的对象。
    • 数据包括颜色(如主题色、透明度)、间距(如 spaceMinispaceBase)、字体大小、边框半径、行高等多种样式属性。
    • 部分值通过 convertDimensionsWidthUnit 方法进行单位转换。
yaml 复制代码
/**
 * 框架默认主题
 */
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同步全局主题,换皮肤只需改配置文件,不用挨个改组件
  1. 源码里藏着啥宝贝?
    • 属性全家桶content(内容)、dot(小红点开关)、max(最大值限制)... 想改啥直接传参
    • 位置 :用getPosition()getTranslate()控制徽标位置,自动计算偏移量(再也不用硬编码坐标啦!)
    • 条件渲染isShow()判断要不要显示徽标,0值显示开关超贴心
  1. 主题定制怎么玩?
    • 通过setIBestUIBaseStyle统一管理样式变量(颜色/圆角/间距...)
    • 源码里直接AppStorage存全局配置,改一次全组件生效(妈妈再也不用担心我改漏文件!)

吐槽&彩蛋

  • ArkTS骚操作convertDimensionsWidthUnit这个工具函数,自动适配不同设备的尺寸单位,鸿蒙生态的"响应式"精髓就在这!
  • 隐藏关卡 :组件库里还有按钮、导航栏等一堆组件,Badge只是开胃菜,后面可以继续挖坑!

一句话总结 :这组件库把鸿蒙的声明式UI玩明白了,跟着抄作业能少写不少代码,建议收藏!

相关推荐
萌萌哒草头将军38 分钟前
⚡⚡⚡Vite 被发现存在安全漏洞🕷,请及时升级到安全版本
前端·javascript·vue.js
小兵张健2 小时前
运用 AI,看这一篇就够了(上)
前端·后端·cursor
Bruce_Liuxiaowei2 小时前
HarmonyOS Next~鸿蒙系统功耗优化体系解析:前台交互与后台任务的全场景节能设计
华为·harmonyos
不怕麻烦的鹿丸2 小时前
node.js判断在线图片链接是否是webp,并将其转格式后上传
前端·javascript·node.js
vvilkim2 小时前
控制CSS中的继承:灵活管理样式传递
前端·css
南城巷陌2 小时前
Next.js中not-found.js触发方式详解
前端·next.js
拉不动的猪3 小时前
前端打包优化举例
前端·javascript·vue.js
Bigger3 小时前
Tauri(十五)——多窗口之间通信方案
前端·rust·app
倔强青铜三3 小时前
WXT浏览器插件开发中文教程(3)----WXT全部入口项详解
前端·javascript·vue.js
Aphasia3113 小时前
快速上手tailwindcss
前端·css·面试