📝 文章概述

在移动应用中,侧边栏(Sidebar/Drawer)是一个常见的导航模式。HarmonyOS 6 API 20引入的HdsSideBar组件提供了符合鸿蒙设计语言的原生侧边栏实现,开箱即用,无需从零开发。本文将深入探讨HdsSideBar的使用方法和最佳实践。

🎯 为什么选择HdsSideBar?

传统方案 vs HdsSideBar

侧边栏实现方案
自定义实现
HdsSideBar
需要手动处理动画
需要处理手势
需要适配不同屏幕
开发周期长
难以保持一致性
动画自动处理
手势内置支持
自动适配屏幕
开箱即用
符合设计规范

对比表

特性 自定义实现 HdsSideBar 优势
开发时间 2-3天 1小时 ⭐⭐⭐⭐⭐
动画效果 需手动实现 内置 ⭐⭐⭐⭐⭐
手势支持 需手动处理 内置 ⭐⭐⭐⭐⭐
屏幕适配 需测试多种设备 自动适配 ⭐⭐⭐⭐⭐
设计一致性 难以保证 符合规范 ⭐⭐⭐⭐⭐
维护成本 ⭐⭐⭐⭐⭐

🚀 快速开始

第一步:导入组件

import { HdsSideBar } from '@kit.UIDesignKit' // API 20
import { SideBarContainerType } from '@ohos.arkui.advanced.SideBarContainer'

第二步:基础配置

@Entry
@ComponentV2
struct BasicSideBarDemo {
  // 🔥 侧边栏状态管理
  @Local isShowSidebar: boolean = false
  @Local isSideBarContainerMask: boolean = true  // 是否显示遮罩
  @Local isAutoHide: boolean = false              // 是否自动隐藏
  @Local blankHeight: number = 48                 // 顶部空白高度
  
  // 🔥 侧边栏内容构建器
  @Builder
  SideBarPanelBuilder() {
    Column() {
      Text('侧边栏内容')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(20)
      
      Button('菜单项1')
        .width('90%')
        .margin(10)
      
      Button('菜单项2')
        .width('90%')
        .margin(10)
      
      Button('菜单项3')
        .width('90%')
        .margin(10)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
  
  // 🔥 主内容构建器
  @Builder
  ContentPanelBuilder() {
    Column() {
      // 顶部导航栏
      Row() {
        Button('☰')
          .onClick(() => {
            this.isShowSidebar = !this.isShowSidebar
          })
        
        Text('主页面')
          .fontSize(20)
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .backgroundColor('#3498DB')
      
      // 主内容区
      Text('这里是主要内容区域')
        .fontSize(16)
        .margin(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F6FA')
  }
  
  build() {
    // 🔥 使用HdsSideBar组件
    HdsSideBar({
      sideBarPanelBuilder: () => this.SideBarPanelBuilder(),
      contentPanelBuilder: () => this.ContentPanelBuilder(),
      autoHide: this.isAutoHide,
      contentAreaMask: this.isSideBarContainerMask,
      sideBarContainerType: SideBarContainerType.Overlay,
      isShowSideBar: this.isShowSidebar,
      $isShowSideBar: (isShowSidebar: boolean) => {
        this.isShowSidebar = !isShowSidebar
      }
    })
  }
}

💡 日记应用实战

完整实现

@Entry
@ComponentV2
struct DiaryApp {
  // 状态管理
  @Local isShowSidebar: boolean = false
  @Local isSideBarContainerMask: boolean = true
  @Local blankHeight: number = 48
  @Local isAutoHide: boolean = false
  @Local diaryList: DiaryRecord[] = []
  
  // 🔥 侧边栏构建器(导航和数据管理)
  @Builder
  SideBarPanelBuilder() {
    Column() {
      // 顶部空白(状态栏高度)
      Blank().height(this.blankHeight)
      
      // 应用标题和图标
      Column() {
        Text('📔')
          .fontSize(48)
          .margin({ bottom: 16 })
        
        Text('极速日记')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2C3E50')
          .margin({ bottom: 8 })
        
        Text('记录生活的美好时光')
          .fontSize(14)
          .fontColor('#7F8C8D')
      }
      .width('100%')
      .alignItems(HorizontalAlign.Center)
      .margin({ bottom: 30 })
      
      // 数据管理区域
      Column() {
        Text('数据管理')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor('#2C3E50')
          .margin({ bottom: 16 })
          .alignSelf(ItemAlign.Start)
        
        // 导入按钮
        Button() {
          Row() {
            Text('📥')
              .fontSize(18)
              .margin({ right: 8 })
            
            Text('导入数据')
              .fontSize(14)
              .fontColor('#27AE60')
              .layoutWeight(1)
          }
          .width('100%')
          .justifyContent(FlexAlign.Start)
        }
        .backgroundColor('#E8F5E8')
        .borderRadius(12)
        .height(44)
        .width('100%')
        .margin({ bottom: 12 })
        .onClick(() => {
          this.importData()
          this.isShowSidebar = false  // 操作后关闭侧边栏
        })
        
        // 导出按钮
        Button() {
          Row() {
            Text('📤')
              .fontSize(18)
              .margin({ right: 8 })
            
            Text('导出数据')
              .fontSize(14)
              .fontColor('#3498DB')
              .layoutWeight(1)
          }
          .width('100%')
          .justifyContent(FlexAlign.Start)
        }
        .backgroundColor('#EBF3FD')
        .borderRadius(12)
        .height(44)
        .width('100%')
        .onClick(() => {
          this.exportData()
          this.isShowSidebar = false
        })
      }
      .width('100%')
      .padding({ left: 20, right: 20 })
      
      Blank()  // 撑开空间
      
      // 底部信息
      Column() {
        Text(`${this.diaryList.length} 篇日记`)
          .fontSize(14)
          .fontColor('#95A5A6')
          .margin({ bottom: 4 })
        
        Text('极速日记 v1.0')
          .fontSize(12)
          .fontColor('#BDC3C7')
      }
      .alignItems(HorizontalAlign.Center)
      .padding({ bottom: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
  
  // 🔥 主内容区构建器
  @Builder
  ContentPanelBuilder() {
    Column() {
      // 顶部空白
      Blank().height(this.blankHeight)
      
      // 顶部标题栏
      Row() {
        // 侧边栏控制按钮
        Button() {
          SymbolGlyph(
            this.isShowSidebar ? 
            $r('sys.symbol.open_sidebar') : 
            $r('sys.symbol.close_sidebar')
          )
            .fontWeight(FontWeight.Normal)
            .fontSize(20)
        }
        .backgroundColor(Color.Transparent)
        .height(40)
        .width(40)
        .margin({ right: 12 })
        .onClick(() => {
          this.isShowSidebar = !this.isShowSidebar
        })
        
        Text('极速日记')
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2C3E50')
        
        Blank()
        
        // 笔记本图标(右上角)
        Button('📒')
          .fontSize(24)
          .backgroundColor('#F8F9FA')
          .borderRadius(8)
          .height(40)
          .width(40)
          .onClick(() => {
            // 跳转到写日记页面
            router.pushUrl({ url: 'pages/WriteDiaryPage' })
          })
      }
      .width('100%')
      .padding({ left: 20, right: 20, top: 10, bottom: 10 })
      
      // 主内容区(日记列表等)
      this.DiaryListBuilder()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F6FA')
  }
  
  // 日记列表构建器
  @Builder
  DiaryListBuilder() {
    // ... 日记列表实现 ...
  }
  
  build() {
    HdsSideBar({
      sideBarPanelBuilder: (): void => this.SideBarPanelBuilder(),
      contentPanelBuilder: (): void => this.ContentPanelBuilder(),
      autoHide: this.isAutoHide,
      contentAreaMask: this.isSideBarContainerMask,
      sideBarContainerType: SideBarContainerType.Overlay,
      isShowSideBar: this.isShowSidebar,
      $isShowSideBar: (isShowSidebar: boolean) => {
        this.isShowSidebar = !isShowSidebar
      }
    })
  }
}

🎨 HdsSideBar参数详解

核心参数

interface HdsSideBarOptions {
  // 🔥 侧边栏内容构建器
  sideBarPanelBuilder: () => void
  
  // 🔥 主内容区构建器
  contentPanelBuilder: () => void
  
  // 🔥 是否显示侧边栏
  isShowSideBar: boolean
  
  // 🔥 侧边栏状态变化回调
  $isShowSideBar?: (isShow: boolean) => void
  
  // 🔥 侧边栏类型
  sideBarContainerType?: SideBarContainerType
  
  // 🔥 是否显示内容区遮罩
  contentAreaMask?: boolean
  
  // 🔥 是否自动隐藏
  autoHide?: boolean
  
  // 🔥 侧边栏宽度
  sideBarWidth?: number | string
  
  // 🔥 最小侧边栏宽度
  minSideBarWidth?: number | string
  
  // 🔥 最大侧边栏宽度
  maxSideBarWidth?: number | string
}

SideBarContainerType枚举

SideBarContainerType
侧边栏类型
Embed
嵌入式
Overlay
悬浮式
AUTO
自动
侧边栏占用主内容空间
主内容被压缩
适合平板横屏
侧边栏悬浮在主内容上
主内容不被压缩
适合手机
根据屏幕自动选择
大屏用Embed
小屏用Overlay

参数配置示例

// 🔥 Overlay模式(推荐用于手机)
HdsSideBar({
  sideBarPanelBuilder: () => this.SideBarBuilder(),
  contentPanelBuilder: () => this.ContentBuilder(),
  isShowSideBar: this.isShowSidebar,
  sideBarContainerType: SideBarContainerType.Overlay, // 悬浮式
  contentAreaMask: true,     // 显示遮罩
  autoHide: false,           // 不自动隐藏
  sideBarWidth: '80%',       // 侧边栏宽度
  minSideBarWidth: 200,      // 最小宽度
  maxSideBarWidth: 300       // 最大宽度
})

// 🔥 Embed模式(推荐用于平板)
HdsSideBar({
  sideBarPanelBuilder: () => this.SideBarBuilder(),
  contentPanelBuilder: () => this.ContentBuilder(),
  isShowSideBar: true,       // 默认显示
  sideBarContainerType: SideBarContainerType.Embed,  // 嵌入式
  contentAreaMask: false,    // 不显示遮罩
  autoHide: false,
  sideBarWidth: 300          // 固定宽度
})

🔧 高级技巧

1. 响应式设计

@ComponentV2
struct ResponsiveSideBar {
  @Local isShowSidebar: boolean = false
  @Local sideBarType: SideBarContainerType = SideBarContainerType.AUTO
  @Local deviceType: string = 'phone'
  
  aboutToAppear() {
    // 检测设备类型
    this.deviceType = this.getDeviceType()
    
    // 根据设备类型设置侧边栏模式
    if (this.deviceType === 'tablet') {
      this.sideBarType = SideBarContainerType.Embed
      this.isShowSidebar = true  // 平板默认显示
    } else {
      this.sideBarType = SideBarContainerType.Overlay
      this.isShowSidebar = false  // 手机默认隐藏
    }
  }
  
  getDeviceType(): string {
    // 根据屏幕宽度判断设备类型
    const screenWidth = display.getDefaultDisplaySync().width
    return screenWidth > 600 ? 'tablet' : 'phone'
  }
  
  build() {
    HdsSideBar({
      sideBarPanelBuilder: () => this.SideBarBuilder(),
      contentPanelBuilder: () => this.ContentBuilder(),
      isShowSideBar: this.isShowSidebar,
      sideBarContainerType: this.sideBarType,
      $isShowSideBar: (isShow) => {
        this.isShowSidebar = !isShow
      }
    })
  }
}

2. 手势增强

@Builder
ContentWithGesture() {
  Column() {
    // 主内容
    // ...
  }
  .gesture(
    // 从左边缘向右滑动打开侧边栏
    PanGesture({ direction: PanDirection.Right })
      .onActionStart((event: GestureEvent) => {
        // 检查是否从左边缘开始
        if (event.fingerList[0].localX < 50) {
          this.isShowSidebar = true
        }
      })
  )
  .gesture(
    // 向左滑动关闭侧边栏
    PanGesture({ direction: PanDirection.Left })
      .onActionStart(() => {
        if (this.isShowSidebar) {
          this.isShowSidebar = false
        }
      })
  )
}

3. 动画定制

@ComponentV2
struct AnimatedSideBar {
  @Local isShowSidebar: boolean = false
  @Local sideBarOpacity: number = 0
  @Local sideBarTranslateX: number = -300
  
  // 监听侧边栏状态变化
  @Watch('onSideBarChange')
  @Local isShowSideBar: boolean = false
  
  onSideBarChange() {
    // 自定义动画
    animateTo(
      {
        duration: 300,
        curve: Curve.EaseInOut
      },
      () => {
        if (this.isShowSideBar) {
          this.sideBarOpacity = 1
          this.sideBarTranslateX = 0
        } else {
          this.sideBarOpacity = 0
          this.sideBarTranslateX = -300
        }
      }
    )
  }
  
  @Builder
  CustomSideBar() {
    Column() {
      // 侧边栏内容
    }
    .opacity(this.sideBarOpacity)
    .translate({ x: this.sideBarTranslateX })
  }
}

4. 嵌套导航

@ComponentV2
struct NestedNavSideBar {
  @Local currentSection: string = 'home'
  @Local isShowSidebar: boolean = false
  
  @Builder
  SideBarWithNav() {
    Column() {
      // 导航菜单
      List() {
        ListItem() {
          this.NavItemBuilder('home', '首页', '🏠')
        }
        .onClick(() => {
          this.currentSection = 'home'
          this.isShowSidebar = false
        })
        
        ListItem() {
          this.NavItemBuilder('favorite', '收藏', '⭐')
        }
        .onClick(() => {
          this.currentSection = 'favorite'
          this.isShowSidebar = false
        })
        
        ListItem() {
          this.NavItemBuilder('settings', '设置', '⚙️')
        }
        .onClick(() => {
          this.currentSection = 'settings'
          this.isShowSidebar = false
        })
      }
    }
  }
  
  @Builder
  NavItemBuilder(id: string, title: string, icon: string) {
    Row() {
      Text(icon)
        .fontSize(20)
        .margin({ right: 12 })
      
      Text(title)
        .fontSize(16)
        .fontColor(this.currentSection === id ? '#3498DB' : '#2C3E50')
        .fontWeight(this.currentSection === id ? FontWeight.Bold : FontWeight.Normal)
      
      Blank()
      
      if (this.currentSection === id) {
        Text('▶')
          .fontSize(12)
          .fontColor('#3498DB')
      }
    }
    .width('100%')
    .height(50)
    .padding({ left: 20, right: 20 })
    .backgroundColor(this.currentSection === id ? '#EBF3FD' : Color.Transparent)
  }
  
  @Builder
  ContentBySection() {
    if (this.currentSection === 'home') {
      this.HomeContent()
    } else if (this.currentSection === 'favorite') {
      this.FavoriteContent()
    } else if (this.currentSection === 'settings') {
      this.SettingsContent()
    }
  }
  
  build() {
    HdsSideBar({
      sideBarPanelBuilder: () => this.SideBarWithNav(),
      contentPanelBuilder: () => this.ContentBySection(),
      isShowSideBar: this.isShowSidebar,
      sideBarContainerType: SideBarContainerType.Overlay,
      $isShowSideBar: (isShow) => {
        this.isShowSidebar = !isShow
      }
    })
  }
}

📊 设计最佳实践

UI设计建议

mindmap
  root((侧边栏设计))
    宽度
      手机: 80%屏宽
      平板: 280-320px固定
      最小宽度: 200px
      最大宽度: 400px
    高度
      全屏高度
      预留状态栏
      底部安全区
    内容
      顶部: Logo+标题
      中间: 导航菜单
      底部: 版本信息
    交互
      点击遮罩关闭
      滑动手势
      动画流畅
    颜色
      背景: 白色/浅灰
      文字: 深灰
      高亮: 主题色

布局建议

区域 高度 内容 说明
顶部区 100-150px Logo、标题、描述 品牌展示
导航区 自适应 菜单项列表 主要功能入口
操作区 100-200px 快捷操作按钮 次要功能
底部区 60-80px 版本、统计信息 辅助信息

🎓 最佳实践

✅ 推荐做法

  1. 合理使用模式

    • 手机:Overlay模式 + 点击遮罩关闭
    • 平板:Embed模式 + 默认显示
  2. 状态管理

    @Local isShowSidebar: boolean = false  // 使用@Local管理状态
    
    
  3. 用户体验

    • 提供明显的打开/关闭按钮
    • 支持边缘滑动手势
    • 操作后自动关闭侧边栏
  4. 性能优化

    • 避免在侧边栏中加载大量数据
    • 使用懒加载加载图片
    • 避免复杂动画

❌ 避免做法

  1. ❌ 侧边栏宽度超过90%屏宽(影响主内容可见性)
  2. ❌ 不提供关闭按钮(用户体验差)
  3. ❌ 侧边栏内容过多(滚动体验差)
  4. ❌ 不适配不同屏幕尺寸
  5. ❌ 动画时间过长(>500ms)

📚 总结

HdsSideBar为HarmonyOS应用提供了:

开箱即用:无需从零实现,节省开发时间

原生体验:符合鸿蒙设计语言,交互流畅

自动适配:智能适配不同屏幕尺寸

功能完善:支持遮罩、手势、动画等

易于定制:灵活的Builder模式,自定义内容

核心要点

  1. 选择合适的模式:Overlay(手机)vs Embed(平板)
  2. 响应式设计:根据设备类型自动调整
  3. 用户体验优先:提供多种打开/关闭方式
  4. 性能优化:避免复杂内容和动画
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐