18 — 侧边栏 SideBarContainer 与分栏交互

一、引言

breakpoint-system

PC 与平板宽屏下,"左侧导航 + 右侧内容"是信息密度最高、操作效率最好的信息架构。HarmonyOS 的 SideBarContainer 提供 sidebar/content 双区域布局。本项目 PC 端(products/pc/src/main/ets/view/Index.ets)用它承载主导航与内容区,并在内容区内嵌套 Navigation 实现评论/个人作品的二级分栏,形成"侧边栏 → 页签 → 内容区 → 侧面板"的分层导航体系。本文拆解其实现细节。

二、SideBarContainer 结构与属性

SideBarContainer 的第一个子组件是侧边栏,第二个是内容区:

SideBarContainer(SideBarContainerType.Embed) {
  Column() {
    /* 侧边栏:Logo + 菜单列表 */
  }
  Column() {
    this.ContentBuilder({ index: this.selectedIndex })
  }
}
.sideBarWidth(112)
.autoHide(false)
.divider({ strokeWidth: 0.5, color: '#ff646466' })
.showControlButton(false)

属性说明本项目取值

sideBarWidth侧边栏宽度112vp 固定
autoHide是否自动隐藏false(常驻)
showControlButton是否显示系统折叠按钮false(自绘菜单)
divider侧栏与内容区分隔线0.5vp 半透明

SideBarContainerType.Embed 表示侧边栏嵌入内容区而非悬浮覆盖,内容区宽度实时重排。

三、PC 端侧边栏菜单实现

菜单由 MSVDataModel 数组驱动,Repeat 渲染,选中项高亮:

List() {
  Repeat<MSVDataModel>(this.data)
    .each((item: RepeatItem<MSVDataModel>) => {
      ListItem() {
        Row({ space: 8 }) {
          MSVTextIcon({ src: this.selectedIndex === item.index ? item.item.icon : item.item.iconDark, iconSize: 24 })
          Text(item.item.text)
            .fontColor(this.selectedIndex === item.index ? '#ffffff' : '#c7c7c8')
        }
        .height(40).borderRadius(8)
        .backgroundColor(this.selectedIndex === item.index ? $r('app.color.all_icon_bg_10') : Color.Transparent)
      }
      .onClick(() => { this.selectedIndex = item.index; })   // 切换内容区
    })
    .virtualScroll({ totalCount: this.data.length })
}

virtualScroll 启用虚拟滚动,长菜单下保证帧率稳定;selectedIndex 是内容区刷新的唯一驱动源。

四、内容区与页签联动

内容区根据 selectedIndex 切换页面,首页内再嵌套 Navigation(ContentBuilder):

@Builder
ContentBuilder(params: ContentParamsModel) {
  if (params.index === 0) {
    Navigation(this.pathStack) {
      Stack({ alignContent: Alignment.TopEnd }) { recommend() }
    }
    .navBarWidthRange(['66%', '100%'])
    .navBarWidth('66%')
    .hideBackButton(true)
    .hideTitleBar(true)
    .mode(this.showSideComment || this.showSideIndividual ? NavigationMode.Split : NavigationMode.Stack)
  } else if (params.index === 1) {
    follow()
  } else if (params.index === 5) {
    mine()
  }
}

内容区导航宽度取 '66%',为右侧侧面板预留空间,与 default 产品按断点取固定宽度的策略互补。

五、分栏模式下评论与个人作品的侧面板

打开评论时置 showSideComment = true 并 pushPathByName('SplitComment'),Navigation 自动切 Split 模式,NavDestination 作为右侧面板展开,主内容区不退出:

.onAction(() => {
  if (this.showSideComment || this.windowInfo.widthBp === WidthBreakpoint.WIDTH_XS) return;
  if (this.windowInfo.widthBp > WidthBreakpoint.WIDTH_SM) {
    this.showSideComment = true;
    this.pathStack.pushPathByName('SplitComment', null);   // 大屏:侧面板
  } else {
    this.showComment = true;                                // 小屏:半模态
  }
})

SplitComment(products/default/.../view/SplitComment.ets)自定义标题栏,点击关闭时出栈并复位开关:

@Builder
CustomTitleBuilder() {
  Column() {
    Column().width(CommonConstants.FULL_PERCENT).height(36)
      .linearGradient({ direction: GradientDirection.Bottom, colors: [['#4D000000', 0.0], ['#00000000', 1.0]] })
    Row() {
      Text($r('app.string.comment_title', this.commentCount))
      Row() { SymbolGlyph($r('sys.symbol.xmark')).fontSize(18) }
        .width(40).aspectRatio(1).borderRadius(CommonConstants.HALF_PERCENT)
        .onClick(() => { this.pathStack.pop(); this.showSideComment = false; })
    }
  }
}

个人作品侧面板同理:showSideIndividual = true 后 pushPathByName('IndividualByRouter', null)。

六、主内容区联动刷新与点击关闭

侧面板打开时主内容区 hitTest 切换为 Block 拦截点击,配合 TapGesture 与 onGestureRecognizerJudgeBegin 实现"点空白收起侧栏":

.hitTestBehavior(this.showSideComment || this.showSideIndividual ? HitTestMode.Block : HitTestMode.Default)
.gesture(TapGesture())
.onGestureRecognizerJudgeBegin((event, current) => {
  if (current && (current.getType() === GestureControl.GestureType.TAP_GESTURE ||
    current.getType() === GestureControl.GestureType.CLICK)) {
    if (this.showSideComment || this.showSideIndividual) {
      this.pathStack.pop();
      this.showSideComment = false;
      this.showSideIndividual = false;
      return GestureJudgeResult.REJECT;
    }
  }
  return GestureJudgeResult.CONTINUE;
})

被拦截的点击不会继续透传给视频播放层,避免误触播放/暂停。

七、总结与最佳实践

  • 侧边栏常驻用 autoHide(false) + showControlButton(false),菜单自绘保证全设备视觉一致。
  • 侧面板优先用 Navigation Split 模式而非弹窗,大屏下避免模态割裂、内容上下文不丢失。
  • 断点 + 布尔开关(showSideComment/showSideIndividual)驱动形态:大屏分栏、手机半模态,一套业务逻辑。
  • divider 与选中高亮提升信息层级;virtualScroll 优化长菜单滚动性能。
  • selectedIndex 单一驱动源 + 声明式刷新,保证侧边栏与内容区联动可靠、无状态漂移。

Logo

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

更多推荐