#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)! 原创

急湍甚箭鼠目寸光
发布于 2025-10-22 17:18
浏览
0收藏

@[toc]
哈喽,各位正在鸿蒙大陆探险的勇士们(特别是代码萌新)!今天我们要攻略的 BOSS 是一个看起来很酷炫、但其实(嘘~)很简单的组件——HdsSideBar

这是个啥玩意儿?简单说,它就是你 App 里那个“点击左上角汉堡包按钮,‘嗖’一下划出来的菜单”。

今天,老师兄(咳咳,就是我)就用一个“国风典藏” App 的 Demo,带你彻底搞懂这个侧边栏。最终效果就是左边是菜单(二十四节气、四书五经…),右边是对应的内容,点击左边,右边乖乖切换。

先看一下最终的动态效果图:
#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区

废话不多说,上车!

第一步:开天辟地,准备“灵魂”

#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区

万物皆有“灵魂”,在 ArkTS 里,我们的灵魂就是“状态”(State)。我们要先定义两个“开关”来控制整个页面:

  1. isShowSidebar:这个侧边栏到底是显示还是藏起来?(boolean 值,truefalse
  2. 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 装修图纸”)来画左侧的菜单。
#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区
我们要一个标题(国风典藏),然后用 ForEach 遍历我们刚才定义的 categories 数组,把它们一个个画成 Button

这里有个小技巧:看 fontColorbackgroundColor,我们用了一个三元表达式 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)

#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区
左边搞定了,轮到右边。右边更简单,它就是个“应声虫”。

它啥也不用干,就“读取状态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 布局)

#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区

  // 节气内容卡片网格
  @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 函数)

#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区
现在是“复仇者集结”的时刻!我们要把所有东西塞进 build 函数——这是页面的“主入口”。

首先,HdsSideBar 组件需要我们把“装修图纸”传给它。但它不认识 SideBarPanelBuilder 这么长的名字,它只认识 sideBarBuildercontentBuilder

咋办?用 @BuilderParam 给我们的图纸“起个别名”!

  @BuilderParam contentBuilder: () => void = this.ContentPanelBuilder
  @BuilderParam sideBarBuilder: () => void = this.SideBarPanelBuilder

然后,在 build 函数里:

  1. 我们用 Stack(堆叠布局)打底,这样我们才能把“汉堡包按钮”浮在最上面。
  2. 我们放一个 Button(汉堡包按钮)。它的 onClick 负责切换 this.isShowSidebar 的状态(truefalsefalsetrue)。
  3. 最后,召唤神龙!——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,不然下次你那个汉堡包按钮的状态就不对了!”

总结

#星光不负 码向未来# 鸿蒙 ArkTS 探险:手把手教你“召唤”一个高大上的侧边栏 (HdsSideBar)!-鸿蒙开发者社区
搞定!我们来捋一捋:

  1. @Local 定义“是否显示”和“当前激活项”两个核心状态。
  2. @Builder 画了“左侧菜单”和“右侧内容”两张图纸。
  3. 左侧菜单负责修改“当前激活项”状态。
  4. 右侧内容负责读取“当前激活项”状态,用 if/else 显示不同内容。
  5. build 里,用一个 Button修改“是否显示”状态。
  6. HdsSideBar 组件放进来,把图纸传给它,并且用 isShowSideBar$isShowSideBar 把它和“是否显示”状态双向绑定

是不是感觉鸿蒙6.0开发超级简单?(bushi)

好了,今天的笔记就到这里,快去CV(不是)…快去亲手敲一遍吧!


©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐