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玩明白了,跟着抄作业能少写不少代码,建议收藏!

相关推荐
江城开朗的豌豆6 分钟前
Vue中key值的秘密:为什么这个小东西能让列表渲染更聪明?
前端·javascript·vue.js
tager7 分钟前
为什么推荐使用Whistle而不是Fiddler、Charles!🤗
前端·fiddler·charles
江城开朗的豌豆16 分钟前
Vue 3.0真香!用了半年后我来告诉你为什么这么爽
前端·javascript·vue.js
前端工作日常16 分钟前
我理解的 npm 作用域包
前端
小小小小宇16 分钟前
移动端软键盘弹出问题
前端
小小小小宇17 分钟前
前端常见浏览器兼容性问题
前端
小小小小宇7 小时前
前端并发控制管理
前端
小小小小宇8 小时前
前端SSE笔记
前端
小小小小宇8 小时前
前端 WebSocket 笔记
前端
小小小小宇9 小时前
前端visibilitychange事件
前端