鸿蒙 ArkTS 实战:侧滑菜单 SideBarContainer(示例 91)

引言
在移动应用的导航体系中,抽屉式菜单(Drawer Navigation)是最经典的导航模式之一。从最早的 Android 原生 Navigation Drawer,到 Material Design 中规范的 Navigation Rail,再到各平台对侧滑手势的原生支持,抽屉导航以其「隐藏式面板 + 内容覆盖」的独特交互,成为了承载多级菜单、用户中心、功能入口等场景的不二之选。HarmonyOS NEXT 提供了 SideBarContainer 组件,以声明式的方式实现了抽屉导航,支持 Overlay(覆盖式)和 Inline(内联式)两种展示模式。
示例 91 以「我的菜单」为主题,实现了一个左侧抽屉导航页面。用户点击「打开菜单」按钮,左侧菜单栏会从左侧滑出,覆盖在主内容区之上;菜单项以列表形式排列,支持选中高亮;点击菜单项后,菜单自动收起,主内容区显示选中状态,并通过 promptAction.showToast 弹出操作反馈。整个流程涉及 SideBarContainer 的状态绑定、菜单项的 ForEach 渲染、选中态的视觉反馈、以及 onChange 事件的双向同步,几乎涵盖了抽屉导航实现的所有核心要点。
这篇文章会严格按源码顺序,先介绍应用的整体功能与布局结构,再拆解 SideBarContainer 的核心属性与事件,接着逐段解读 .ets 源码中的菜单渲染、选中逻辑、交互反馈,然后分析 Overlay 模式与 Inline 模式的区别、showSideBar 绑定机制、菜单 UI 的样式设计思路,最后给出运行操作指南、可扩展方向与常见问题调试技巧。读完后,你不仅能看懂这一个侧滑菜单页面,还能举一反三,把它应用到用户中心、功能导航、分类浏览等任何需要抽屉导航的场景。
1. 应用概述与功能
「侧滑菜单」是一个面向导航场景的工具型页面,交互路径清晰:点击打开菜单 → 查看菜单项 → 点击选中 → 自动收起。
页面自上而下分为三块区域:顶部是返回栏,左侧「返回」按钮调用 router.back() 返回上一页,中间是标题「侧滑菜单」;中部是 SideBarContainer 容器,左侧菜单栏占 40% 宽度,深色背景(#1f2733),右侧主内容区占满剩余空间,浅灰背景(#f2f3f5);底部功能说明卡片列出三条使用提示。
1.1 核心功能清单
- 抽屉式菜单展示:使用
SideBarContainer组件实现左侧抽屉菜单,支持 Overlay 覆盖模式。 - 菜单项高亮选中:点击菜单项时,选中项文字变蓝色(
#1a6cff)、加粗、带半透明蓝色背景,未选中项为灰色。 - 自动收起:点击菜单项后,侧边栏自动收起,给用户完整的主内容区操作空间。
- 双向状态同步:通过
showSideBar属性和onChange回调实现菜单展开状态的双向绑定。 - 操作反馈:点击菜单项后通过
promptAction.showToast弹出提示,如「点击了首页」。 - 功能说明卡片:主内容区底部展示白色圆角卡片,列出三条使用提示引导用户操作。
1.2 技术要点一览
整个示例用到的关键技术对「抽屉导航」类页面很有代表性:SideBarContainer 组件的 Overlay 模式、showSideBar 状态绑定与 onChange 事件回调、菜单项列表的 ForEach 渲染与选中态判断、Text 组件的动态样式绑定(颜色、字重、背景色)、以及 Divider 分隔线的使用。把这些要点串起来,就构成了一条完整的「状态驱动 → UI 渲染 → 交互反馈 → 状态更新」的交互闭环。
2. 核心知识点
在逐段读代码之前,先把侧滑菜单页面承载的 ArkTS 核心知识讲清楚。
2.1 SideBarContainer 组件与 Overlay 模式
SideBarContainer 是 ArkUI 提供的抽屉容器组件,用于实现「侧边栏 + 主内容区」的布局结构。它支持两种展示模式:
- SideBarContainerType.Overlay:覆盖模式,侧边栏滑出时覆盖在主内容区之上,主内容区不移动。这是最常见的抽屉效果,适合移动端。
- SideBarContainerType.Inline:内联模式,侧边栏展开时主内容区被挤压,侧边栏与主内容区并排显示,适合平板或横屏场景。
示例使用的是 Overlay 模式:
SideBarContainer(SideBarContainerType.Overlay) {
// 左侧菜单栏(第一个子组件)
Column() { ... }
// 右侧主内容区(第二个子组件)
Column() { ... }
}
SideBarContainer 接受一个类型参数和两个子组件:第一个子组件是侧边栏内容,第二个子组件是主内容区。组件内部会自动处理滑动手势和动画过渡。
2.2 showSideBar 状态绑定与 onChange 回调
SideBarContainer 的展开/收起通过 showSideBar 属性控制,而 onChange 回调则在侧边栏状态变化时触发:
SideBarContainer(SideBarContainerType.Overlay) {
// 子组件...
}
.showSideBar(this.show)
.onChange((v: boolean) => {
this.show = v;
})
这里涉及一个双向绑定的模式:
- 正向控制:当
this.show为true时,侧边栏展开;为false时收起。 - 反向同步:当用户通过手势滑动或点击外部区域使侧边栏收起时,
onChange回调被触发,参数v会更新this.show的值。
这个双向绑定确保了无论通过哪种方式改变侧边栏状态,this.show 都能保持同步。如果只使用 showSideBar 而不处理 onChange,那么用户手动收起菜单后,this.show 仍然是 true,下次点击按钮时就会出现状态不一致的问题。
2.3 菜单项的动态样式绑定
菜单项的选中态通过动态样式绑定实现:
Text(item)
.fontColor(this.selIdx === idx ? '#1a6cff' : '#cccccc')
.fontWeight(this.selIdx === idx ? FontWeight.Bold : FontWeight.Normal)
.backgroundColor(this.selIdx === idx ? 'rgba(26,108,255,0.18)' : 'rgba(0,0,0,0)')
这是 ArkUI 中条件样式绑定的标准写法:通过三元运算符根据 this.selIdx 与当前 idx 的比较结果,动态决定 fontColor、fontWeight、backgroundColor 三个属性的值。选中项使用主题蓝色 #1a6cff、加粗字重、半透明蓝色背景,未选中项使用浅灰色 #cccccc、正常字重、透明背景。这种方式比为选中项单独创建一个组件更简洁,性能也更好。
2.4 ForEach 渲染菜单列表
菜单项通过 ForEach 组件从数组渲染:
ForEach(this.menus, (item: string, idx: number) => {
Text(item)
.width('86%')
.height(52)
// ... 其他属性
.onClick(() => {
this.chooseMenu(idx);
})
}, (item: string, idx: number) => idx.toString())
ForEach 的三个参数分别是:
- 数据源:
this.menus数组,包含四个菜单项名称。 - 渲染函数:接收每个元素和索引,返回对应的 UI 组件。这里返回的是一个带完整样式和点击事件的
Text组件。 - 键值生成器:为每个元素生成唯一的 key,用于 ArkUI 的 diff 算法。这里使用
idx.toString()作为 key,确保每个菜单项有稳定的标识。
2.5 promptAction 轻提示
promptAction 是 ArkUI 提供的轻量级提示 API,来自 @kit.ArkUI:
import { promptAction } from '@kit.ArkUI';
promptAction.showToast({ message: '点击了' + this.menus[idx] });
showToast 会在屏幕底部弹出一个短消息,持续约 2 秒后自动消失。它适用于不需要用户交互的简单提示,比自定义的 AlertDialog 更轻量,也不需要额外的状态管理。在侧滑菜单这种交互场景中,promptAction 是最合适的反馈方式。
3. 源码逐段解析
现在开始按源码顺序逐段解读 index91.ets,从导入声明到 build 方法,完整展示侧滑菜单的实现细节。
3.1 导入声明与组件声明
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
@Entry
@Component
struct Index91 {
源码开头导入了两个 ArkUI 模块:router 用于页面导航(调用 router.back() 返回上一页),promptAction 用于轻提示反馈。@Entry 装饰器标记该组件为页面入口,@Component 装饰器标记该结构体为可复用的 UI 组件。
3.2 状态变量与菜单数据
@State show: boolean = false;
@State selIdx: number = 0;
private menus: string[] = ['首页', '消息', '设置', '关于'];
组件声明了两个 @State 状态变量和一个私有数组:
show:控制侧边栏的展开/收起,初始为false(收起状态)。当用户点击「打开菜单」按钮或菜单项时,这个值会被修改。selIdx:记录当前选中的菜单项索引,初始为0(选中第一项「首页」)。这个值驱动菜单项的高亮样式和主内容区的选中状态文本。menus:菜单项名称数组,包含四个导航入口。使用private修饰,因为它不需要从外部访问。
3.3 chooseMenu 选中处理方法
private chooseMenu(idx: number): void {
this.selIdx = idx;
this.show = false;
promptAction.showToast({ message: '点击了' + this.menus[idx] });
}
chooseMenu 是菜单项点击的处理函数,接收一个参数 idx(选中的菜单项索引),执行三个操作:
- 更新
selIdx为点击的索引,触发菜单项的高亮样式重新渲染。 - 设置
show为false,自动收起侧边栏。这是侧滑菜单的标准交互——选中后自动关闭。 - 通过
promptAction.showToast弹出提示,告知用户点击了哪个菜单项。
这三个操作的顺序很重要:先更新选中态,再收起菜单,最后弹出提示。如果先收起菜单再更新选中态,在菜单收起的动画过程中用户可能看到短暂的旧选中状态。
3.4 build 方法整体结构
build 方法构建了整个页面的 UI 结构,最外层是一个 Column,包含顶部返回栏和 SideBarContainer 两部分:
build() {
Column() {
// 顶部返回栏
Row() { ... }
// 侧滑菜单容器
SideBarContainer(SideBarContainerType.Overlay) { ... }
}
.width('100%')
.height('100%')
.backgroundColor('#f2f3f5')
}
最外层 Column 设置为全屏宽高(100%),背景色为浅灰色 #f2f3f5,给整个页面一个统一的底色。
3.5 顶部返回栏
Row() {
Button('返回')
.backgroundColor('#1a6cff')
.fontColor(Color.White)
.onClick(() => {
router.back();
})
Text('侧滑菜单')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Blank()
}
.width('100%')
.padding({ left: 12, right: 12, top: 10, bottom: 10 })
顶部返回栏是一个 Row,宽度占满整屏,带内边距。从左到右依次是:蓝色「返回」按钮(点击调用 router.back())、居中的标题文字「侧滑菜单」、右侧的 Blank()(弹性空白,保证标题居中)。
Blank() 是 ArkUI 中一个特殊的弹性空白组件,它会占据 Row 中所有剩余空间。由于 Blank() 放在标题右侧,它会把标题推到中间位置,实现标题居中的效果。
3.6 SideBarContainer 左侧菜单栏
SideBarContainer 是整个页面的核心,它的第一个子组件是左侧菜单栏:
SideBarContainer(SideBarContainerType.Overlay) {
// 左侧菜单栏
Column() {
Text('我的菜单')
.fontSize(20)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
.margin({ top: 24, bottom: 20 })
Divider()
.color('#3a4657')
.strokeWidth(1)
.margin({ bottom: 10 })
ForEach(this.menus, (item: string, idx: number) => {
Text(item)
.width('86%')
.height(52)
.fontSize(16)
.fontColor(this.selIdx === idx ? '#1a6cff' : '#cccccc')
.fontWeight(this.selIdx === idx ? FontWeight.Bold : FontWeight.Normal)
.borderRadius(8)
.textAlign(TextAlign.Center)
.backgroundColor(this.selIdx === idx ? 'rgba(26,108,255,0.18)' : 'rgba(0,0,0,0)')
.margin({ top: 6 })
.onClick(() => {
this.chooseMenu(idx);
})
}, (item: string, idx: number) => idx.toString())
}
.width('40%')
.height('100%')
.backgroundColor('#1f2733')
.padding({ left: 12, right: 12, top: 8 })
左侧菜单栏的结构分为三部分:
- 标题区:白色大字「我的菜单」,上下带外边距,作为菜单的视觉起点。
- 分隔线:
Divider组件在标题和菜单项之间画一条细线,颜色为深灰色#3a4657,与深色背景形成层次。 - 菜单项列表:通过
ForEach渲染四个Text菜单项,每项宽度 86%、高度 52vp、圆角 8、居中对齐。选中态用主题蓝#1a6cff文字 + 半透明蓝背景,未选中态用浅灰#cccccc文字 + 透明背景。
整个菜单栏宽度为屏幕的 40%,高度 100%,深色背景 #1f2733,带内边距。
3.7 SideBarContainer 右侧主内容区
SideBarContainer 的第二个子组件是右侧主内容区:
// 右侧主内容区
Column() {
Text('主内容')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ top: 80 })
Text('当前选中:' + this.menus[this.selIdx])
.fontSize(14)
.fontColor('#888888')
.margin({ top: 12 })
Text('点击下方按钮打开侧滑菜单')
.fontSize(13)
.fontColor('#aaaaaa')
.margin({ top: 8 })
Button('打开菜单')
.width(180)
.height(46)
.backgroundColor('#1a6cff')
.fontColor(Color.White)
.margin({ top: 40 })
.onClick(() => {
this.show = true;
})
Column() {
Text('功能说明')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Divider()
.color('#eeeeee')
.margin({ top: 10, bottom: 10 })
Text('1. 点击"打开菜单"展开左侧菜单')
.fontSize(13)
.fontColor('#666666')
.margin({ bottom: 6 })
Text('2. 点击菜单项可选中并自动收起')
.fontSize(13)
.fontColor('#666666')
.margin({ bottom: 6 })
Text('3. 点击空白区域或返回箭头也可收起')
.fontSize(13)
.fontColor('#666666')
}
.width('86%')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ top: 40 })
}
.width('100%')
.height('100%')
.backgroundColor('#f2f3f5')
主内容区从上到下依次包含:
- 标题区:大号「主内容」标题 + 动态选中状态文本 + 操作提示文本,垂直排列。
- 打开菜单按钮:蓝色主题按钮,宽度 180vp、高度 46vp,点击后设置
this.show = true展开侧边栏。 - 功能说明卡片:白色圆角卡片(圆角 12),标题「功能说明」下方带浅色分隔线,列出三条使用提示,每行间距 6vp。
3.8 SideBarContainer 属性绑定
最后是 SideBarContainer 的属性绑定部分:
.width('100%')
.layoutWeight(1)
.showSideBar(this.show)
.onChange((v: boolean) => {
this.show = v;
})
SideBarContainer 设置为全宽(100%)、layoutWeight(1) 占满剩余空间。showSideBar(this.show) 绑定状态变量控制展开/收起,onChange 回调在侧边栏状态变化时同步更新 this.show。
layoutWeight(1) 在这里非常关键——它确保 SideBarContainer 占据 Column 中除顶部返回栏之外的所有剩余空间,实现自适应布局。如果不设置 layoutWeight,SideBarContainer 的高度将由内容决定,可能无法填满屏幕。
4. 交互流程详解
4.1 打开菜单流程
用户点击「打开菜单」按钮,触发以下流程:
- 按钮的
onClick回调执行this.show = true。 @State show的变化触发 ArkUI 的响应式更新机制。SideBarContainer检测到showSideBar属性变为true,播放滑入动画,展示左侧菜单栏。- 菜单栏从左侧滑出,覆盖在主内容区之上。
整个过程由 ArkUI 框架自动处理动画和手势,开发者只需关注状态的变化。
4.2 选中菜单项流程
用户点击菜单项,触发以下流程:
- 菜单项的
onClick回调执行this.chooseMenu(idx)。 chooseMenu方法更新this.selIdx为点击的索引。chooseMenu方法设置this.show = false,触发侧边栏收起动画。chooseMenu方法调用promptAction.showToast弹出提示。@State selIdx的变化触发菜单项的样式重新渲染——选中项变蓝色加粗。- 主内容区的「当前选中」文本同步更新为新选中的菜单项名称。
- 侧边栏收起完成后,
onChange回调被触发,this.show被设为false(实际上已经是false,这一步确保状态一致)。
4.3 手势收起流程
用户通过手势或点击菜单外部区域收起侧边栏时:
- ArkUI 框架检测到收起手势,开始收起动画。
- 动画完成后,
onChange回调被触发,参数v为false。 this.show被更新为false,与实际状态保持一致。
如果没有 onChange 回调,this.show 仍然是 true,下次点击「打开菜单」按钮时,由于 showSideBar 已经是 true,不会触发新的展开动画,导致按钮失效。
5. UI 样式设计思路
5.1 深色侧边栏 + 浅色内容区
示例采用了经典的「深色导航 + 浅色内容」配色方案:
- 侧边栏使用深色背景
#1f2733,白色文字,营造出专业的导航氛围。 - 主内容区使用浅灰色背景
#f2f3f5,黑色文字,内容区域清晰可读。 - 选中态使用主题蓝
#1a6cff,在深色和浅色背景上都有良好的视觉效果。
这种配色方案的优点是导航区和内容区层次分明,用户可以快速区分两种功能区域。
5.2 选中态视觉反馈
选中态通过三重视觉效果区分:
- 文字颜色:从浅灰色
#cccccc变为主题蓝#1a6cff。 - 字重:从
FontWeight.Normal变为FontWeight.Bold。 - 背景色:从透明变为半透明蓝色
rgba(26,108,255,0.18)。
三重效果叠加,确保选中项在任何背景下都有清晰的视觉反馈。
5.3 卡片式布局
主内容区的功能说明卡片采用卡片式设计:
- 白色背景
#ffffff,与页面浅灰色背景形成对比。 - 圆角 12vp,视觉柔和。
- 宽度 86%,左右留足边距。
- 内边距 16vp,内容不拥挤。
卡片式布局在移动端应用中非常常见,它通过边框、圆角和阴影(此示例未添加阴影)将相关信息聚合在一起,提升了页面的层次感。
6. 运行与测试
6.1 运行步骤
- 使用 DevEco Studio 打开项目。
- 运行项目到模拟器或真机。
- 在首页找到「侧滑菜单」示例入口,点击进入。
- 点击「打开菜单」按钮,观察侧边栏滑出效果。
- 点击任意菜单项,观察选中高亮、自动收起、Toast 提示效果。
6.2 测试场景
| 测试场景 | 预期结果 |
|---|---|
| 点击「打开菜单」按钮 | 侧边栏从左侧滑出 |
| 点击菜单项「首页」 | 菜单项高亮、侧边栏收起、Toast 显示「点击了首页」 |
| 点击菜单项「设置」 | 菜单项高亮变为「设置」、主内容区文本更新 |
| 点击菜单外部区域 | 侧边栏收起,this.show 更新为 false |
| 连续快速点击菜单项 | 每次都正确切换选中态和收起菜单 |
7. 可扩展方向
7.1 Inline 模式适配
将 SideBarContainerType.Overlay 改为 SideBarContainerType.Inline,即可实现内联模式,侧边栏与主内容区并排显示。适合平板或横屏场景,可以通过屏幕宽度动态切换模式。
7.2 多级菜单
当前示例只有一级菜单,可以扩展为多级菜单结构。点击一级菜单项展开二级子菜单,使用 ForEach 嵌套渲染,配合动画效果实现平滑的折叠展开。
7.3 菜单图标
为每个菜单项添加图标,使用 Row 布局 + Image/Text 组件组合。图标可以使用系统内置图标或自定义资源,提升菜单的视觉辨识度。
7.4 路由导航
将菜单项与路由绑定,点击菜单项不仅更新选中态,还跳转到对应的页面。可以使用 router.pushUrl 或 router.replaceUrl 实现页面导航,构建完整的应用导航体系。
7.5 持久化选中状态
使用 Preferences 存储用户最后选中的菜单项,下次进入页面时自动高亮上次选中的项,提供个性化的用户体验。
8. 常见问题与调试
8.1 侧边栏无法通过按钮打开
问题:点击「打开菜单」按钮后,侧边栏没有展开。
排查:
- 检查
show状态是否正确更新——在onClick回调中添加console.log(this.show)确认。 - 检查
showSideBar(this.show)属性绑定是否正确。 - 确认没有在其他地方重置
this.show的值。
8.2 手势收起后按钮失效
问题:手动滑动收起侧边栏后,再次点击「打开菜单」按钮无效。
排查:
- 确认
onChange回调是否正确设置。没有回调时,this.show在手势收起后仍为true,导致按钮无法触发新的展开。 - 在
onChange回调中添加日志,确认回调是否被触发。
8.3 菜单项样式不更新
问题:点击菜单项后,选中项的高亮样式没有更新。
排查:
- 确认
selIdx是否正确更新——在chooseMenu中添加日志。 - 检查
ForEach的键值生成器是否稳定。如果 key 不稳定,ArkUI 可能无法正确 diff 和重新渲染。 - 确认条件表达式
this.selIdx === idx是否正确——注意类型比较,selIdx是number,idx也是number,类型一致。
8.4 布局错乱
问题:SideBarContainer 没有正确占满屏幕剩余空间。
排查:
- 确认
SideBarContainer设置了.layoutWeight(1),这是自适应布局的关键。 - 检查父容器
Column的高度是否为100%。 - 确认顶部返回栏的高度是固定的,不会影响剩余空间的计算。
9. 技术总结
示例 91 的侧滑菜单虽然代码量不大,但涉及了 SideBarContainer 的核心用法、状态绑定与双向同步、动态样式绑定、列表渲染等多个 ArkUI 关键知识点。通过这个示例,我们可以总结出抽屉导航的实现范式:
- 状态驱动:用一个
@State boolean变量控制侧边栏的展开/收起。 - 双向同步:同时使用
showSideBar属性和onChange回调,确保状态与 UI 始终一致。 - 动态样式:用三元表达式根据选中索引动态计算组件样式,避免创建额外组件。
- 即时反馈:用
promptAction.showToast提供轻量级的操作反馈。
掌握了这些范式后,就可以轻松地将其扩展到多级菜单、路由导航、用户中心等更复杂的场景中。侧滑导航作为移动端应用的基础导航模式,值得每个鸿蒙开发者深入学习和实践。
更多推荐




所有评论(0)