#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)! 原创
@[toc]
哈喽,各位正在鸿蒙大陆探险的勇士们(特别是代码萌新)!今天我们要攻略的 BOSS 是一个看起来很酷炫、但其实(嘘~)很简单的组件——HdsSideBar。
这是个啥玩意儿?简单说,它就是你 App 里那个“点击左上角汉堡包按钮,‘嗖’一下划出来的菜单”。
今天,老师兄(咳咳,就是我)就用一个“国风典藏” App 的 Demo,带你彻底搞懂这个侧边栏。最终效果就是左边是菜单(二十四节气、四书五经…),右边是对应的内容,点击左边,右边乖乖切换。
先看一下最终的动态效果图:

废话不多说,上车!
第一步:开天辟地,准备“灵魂”

万物皆有“灵魂”,在 ArkTS 里,我们的灵魂就是“状态”(State)。我们要先定义两个“开关”来控制整个页面:
isShowSidebar:这个侧边栏到底是显示还是藏起来?(boolean值,true或false)activeCategory:我到底点击了哪个菜单项?(string值,比如默认是“二十四节气”)
同时,我们把菜单的“数据”也准备好。
import { HdsSideBar } from '@kit.UIDesignKit';
@Entry
@ComponentV2
struct HdsSideBarDemo {
@Local isShowSidebar: boolean = true;
@Local activeCategory: string = '二十四节气';
private categories: string[] = ['二十四节气', '四书五经', '传统器物'];
看到 @Local 了吗?这就是在告诉系统:“快!盯住这两个变量!它们一变,UI 就要跟着刷新!”
第二步:装修“左侧菜单” (SideBarPanelBuilder)
HdsSideBar 组件很懒,它说:“我只提供一个框架,你得自己告诉我左侧菜单长啥样,右侧内容长啥样。”
行,我们先用 @Builder (可以理解为“UI 装修图纸”)来画左侧的菜单。

我们要一个标题(国风典藏),然后用 ForEach 遍历我们刚才定义的 categories 数组,把它们一个个画成 Button。
这里有个小技巧:看 fontColor 和 backgroundColor,我们用了一个三元表达式 this.activeCategory === name ? ... : ...。
翻译成人话就是:“如果我当前激活的分类(activeCategory)正好是你这个按钮的名字(name),那你就高亮(变白/变蓝),否则你就给我“灰”着!”
最关键的是 onClick:当按钮被点击时,我们就“修改状态”:this.activeCategory = name;。
// 左侧侧边栏区(中国传统文化目录)
@Builder
SideBarPanelBuilder() {
Column() {
// 标题
Text('国风典藏')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#2c3e50')
.margin({ top: 20, bottom: 10 })
// 分类列表
ForEach(this.categories, (name: string) => {
Button() {
Text(name)
.fontSize(16)
.fontColor(this.activeCategory === name ? Color.White : '#2c3e50')
}
.type(ButtonType.Capsule)
.width('100%')
.height(36)
.backgroundColor(this.activeCategory === name ? '#0D9FFB' : '#f0f3f7')
.margin({ bottom: 8 })
.onClick(() => {
this.activeCategory = name;
})
})
// 装饰说明
Text('以中华传统文化为主题,左侧选择分类,右侧呈现内容卡片')
.fontSize(12)
.fontColor('#7f8c8d')
.margin({ top: 12 })
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#FFFFFF')
}
第三步:装修“右侧内容区” (ContentPanelBuilder)

左边搞定了,轮到右边。右边更简单,它就是个“应声虫”。
它啥也不用干,就“读取状态” this.activeCategory。
我们用 if/else if/else 来判断:
- 如果状态是“二十四节气”,我就调用
SeasonPanel()这个“装修图纸”。 - 如果状态是“四书五经”,我就调用
ClassicsPanel()。 - 否则(那就是“传统器物”了),我就调用
ArtifactsPanel()。
<!-- end list -->
// 右侧内容区(随分类变化)
@Builder
ContentPanelBuilder() {
Column() {
// 顶部标题与描述
Text(this.activeCategory)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#2c3e50')
.margin({ top: 20 })
if (this.activeCategory === '二十四节气') {
this.SeasonPanel()
} else if (this.activeCategory === '四书五经') {
this.ClassicsPanel()
} else {
this.ArtifactsPanel()
}
}
.width('100%')
.height('100%')
.backgroundColor('#FAFAFA')
}
发现了没?左侧负责“写状态”,右侧负责“读状态”。这就是 ArkTS 状态驱动 UI 的核心乐趣!
第四步:准备“内容详情页”
刚才 if/else 里的三个“装修图纸”我们还没画呢。别慌,这都是常规操作,就是 Grid(网格)和 List(列表)的堆砌。
为了让代码更整洁,我们把每种卡片(节气卡、经典卡、器物卡)也单独抽成 @Builder。这叫“模块化”,假装自己很专业。
4.1 节气内容(Grid 布局)

// 节气内容卡片网格
@Builder
SeasonPanel() {
Column() {
Text('春生夏长,秋收冬藏——节气承载着中国人对自然的理解')
.fontSize(14)
.fontColor('#7f8c8d')
.margin({ top: 8, bottom: 16 })
// 简约网格布局
Grid() {
GridItem() { this.SeasonCard('立春', '万物复苏,春风始至', '#60A917') }
GridItem() { this.SeasonCard('清明', '踏青祭祖,天地清明', '#1ABC9C') }
GridItem() { this.SeasonCard('夏至', '昼长夜短,阳气最盛', '#E67E22') }
GridItem() { this.SeasonCard('立秋', '暑去凉来,天高气清', '#D35400') }
GridItem() { this.SeasonCard('冬至', '阴极之至,阳气潜萌', '#2C3E50') }
GridItem() { this.SeasonCard('小寒', '寒气渐盛,注意保暖', '#34495E') }
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.margin({ bottom: 20 })
}
.padding(16)
}
@Builder
SeasonCard(title: string, desc: string, color: string) {
Column() {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#FFFFFF')
.margin({ bottom: 6 })
Text(desc)
.fontSize(12)
.fontColor('#ECF0F1')
}
.padding(16)
.borderRadius(12)
.backgroundColor(color)
.height(110)
}
4.2 四书五经(List 布局)
// 四书五经列表
@Builder
ClassicsPanel() {
Column() {
Text('以仁义礼智信为纲,以修身齐家治国平天下为旨')
.fontSize(14)
.fontColor('#7f8c8d')
.margin({ top: 8, bottom: 16 })
List() {
this.ClassicsItem('大学', '纲领之首,明明德、亲民、止于至善')
this.ClassicsItem('中庸', '不偏不倚,致中和,万物育焉')
this.ClassicsItem('论语', '言简意赅,孔子与弟子言行录')
this.ClassicsItem('孟子', '以仁政为本,民贵君轻的思想')
this.ClassicsItem('诗经', '风雅颂三体,关雎蒹葭千古传唱')
this.ClassicsItem('尚书', '政事文献汇编,历史与制度之源')
}
.divider({ strokeWidth: 0.5, color: '#E5E5EA' })
}
.padding(16)
}
@Builder
ClassicsItem(title: string, desc: string) {
ListItem() {
Column() {
Text(title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#2c3e50')
Text(desc)
.fontSize(12)
.fontColor('#7f8c8d')
.margin({ top: 4 })
}
.padding({ top: 10, bottom: 10 })
}
}
4.3 传统器物(Grid 模拟瀑布流)
// 传统器物瀑布流
@Builder
ArtifactsPanel() {
Column() {
Text('器以载道——茶器、文房、青瓷…皆有风骨气韵')
.fontSize(14)
.fontColor('#7f8c8d')
.margin({ top: 8, bottom: 16 })
// 三列瀑布布局(用Grid模拟)
Grid() {
GridItem() { this.ArtifactCard('青瓷', '温润如玉,色泽淡雅', '#87CBB9') }
GridItem() { this.ArtifactCard('宣纸', '吸墨性佳,书画良材', '#F5E6CC') }
GridItem() { this.ArtifactCard('紫砂壶', '砂质细腻,泡茶留香', '#8E6B5B') }
GridItem() { this.ArtifactCard('团扇', '圆融含蓄,夏日清风', '#C9D6FF') }
GridItem() { this.ArtifactCard('玉佩', '温润坚韧,德行之喻', '#A3D5A5') }
GridItem() { this.ArtifactCard('折扇', '骨韵文雅,摇曳生风', '#FFE3B3') }
}
.columnsTemplate('1fr 1fr 1fr')
.rowsGap(12)
.columnsGap(12)
}
.padding(16)
}
@Builder
ArtifactCard(title: string, desc: string, color: string) {
Column() {
Text(title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#2c3e50')
Text(desc)
.fontSize(12)
.fontColor('#7f8c8d')
.margin({ top: 6 })
}
.padding(14)
.borderRadius(12)
.backgroundColor(color)
.height(120)
}
好了,所有的“零件”都造好了!
第五步:终极合体!(build 函数)

现在是“复仇者集结”的时刻!我们要把所有东西塞进 build 函数——这是页面的“主入口”。
首先,HdsSideBar 组件需要我们把“装修图纸”传给它。但它不认识 SideBarPanelBuilder 这么长的名字,它只认识 sideBarBuilder 和 contentBuilder。
咋办?用 @BuilderParam 给我们的图纸“起个别名”!
@BuilderParam contentBuilder: () => void = this.ContentPanelBuilder
@BuilderParam sideBarBuilder: () => void = this.SideBarPanelBuilder
然后,在 build 函数里:
- 我们用
Stack(堆叠布局)打底,这样我们才能把“汉堡包按钮”浮在最上面。 - 我们放一个
Button(汉堡包按钮)。它的onClick负责切换this.isShowSidebar的状态(true变false,false变true)。 - 最后,召唤神龙!——
HdsSideBar组件登场!
<!-- end list -->
@Builder
build() {
Stack({ alignContent: Alignment.TopStart }) {
// 显隐侧边栏按钮
Button() {
// 使用系统图标,若不可用可替换为文本
SymbolGlyph(this.isShowSidebar ? $r('sys.symbol.open_sidebar') : $r('sys.symbol.close_sidebar'))
.fontWeight(FontWeight.Normal)
.fontSize($r('sys.float.ohos_id_text_size_headline7'))
.fontColor([$r('sys.color.ohos_id_color_titlebar_icon')])
.hitTestBehavior(HitTestMode.None)
}
.id('side_bar_button')
.backgroundColor($r('sys.color.ohos_id_color_button_normal'))
.height(30)
.width(30)
.onClick(() => {
this.isShowSidebar = !this.isShowSidebar;
})
.zIndex(1)
.margin({ top: 10, left: 10 })
// 侧边栏容器
HdsSideBar({
sideBarPanelBuilder: (): void => { this.sideBarBuilder() },
contentPanelBuilder: (): void => { this.contentBuilder() },
sideBarContainerType: SideBarContainerType.Overlay,
maxSideBarWidth: 120,
isShowSideBar: this.isShowSidebar,
$isShowSideBar: (isShowSidebar: boolean) => { this.isShowSidebar = !isShowSidebar },
})
}
}
}
划重点!HdsSideBar 的“双向绑定”
注意看 HdsSideBar 的最后两行:
isShowSideBar: this.isShowSidebar$isShowSideBar: (isShowSidebar: boolean) => { this.isShowSidebar = !isShowSidebar }
这是什么骚操作?
isShowSideBar: this.isShowSidebar 是单向绑定。意思是,我们(父组件)的状态 this.isShowSidebar 变了,会通知 HdsSideBar(子组件):“嘿,该显示/隐藏了!”
$isShowSideBar: ... 是双向绑定(注意那个 $ 符号!)。
这是干嘛的?这是为了让 HdsSideBar(子组件)能反过来修改我们(父组件)的状态!
比如,当侧边栏类型是 Overlay(遮罩模式)时,用户用手指向左一划,或者点击了侧边栏外部的空白区域,HdsSideBar 组件自己就想关闭了。它关闭后,就会通过 $isShowSideBar 这个“回调函数”告诉我们:“嘿,我关了!你也赶紧把你的 this.isShowSidebar 状态改成 false,不然下次你那个汉堡包按钮的状态就不对了!”
总结

搞定!我们来捋一捋:
- 用
@Local定义“是否显示”和“当前激活项”两个核心状态。 - 用
@Builder画了“左侧菜单”和“右侧内容”两张图纸。 - 左侧菜单负责修改“当前激活项”状态。
- 右侧内容负责读取“当前激活项”状态,用
if/else显示不同内容。 - 在
build里,用一个Button来修改“是否显示”状态。 - 把
HdsSideBar组件放进来,把图纸传给它,并且用isShowSideBar和$isShowSideBar把它和“是否显示”状态双向绑定。
是不是感觉鸿蒙6.0开发超级简单?(bushi)
好了,今天的笔记就到这里,快去CV(不是)…快去亲手敲一遍吧!



















