# 45号应用:菜单演示 — 可展开手风琴菜单的设计与实现
一、应用概述菜单演示Accordion Menu Demo是一个可展开/收起的手风琴菜单应用。四个主菜单美食、娱乐、运动、学习各自包含 4 个子项点击主菜单展开子项列表再次点击收起。菜单项带有展开指示箭头▶/▼和投影阴影子项以圆角列表形式展示。手风琴菜单Accordion是移动应用中最常用的导航模式之一广泛用于设置页面、分类浏览、FAQ 等场景。在 HarmonyOS 中这个应用展示了List 组件的嵌套渲染、条件展开动画和阴影样式的典型用法。二、技术架构2.1 应用结构┌─────────────────────────────────────┐ │ ‹ 返回 菜单演示 │ ├─────────────────────────────────────┤ │ ┌─ 美食 ▶ ─┐│ ← 主菜单 │ └────────────────────────────────┘│ │ ┌─ 娱乐 ▼ ─┐│ ← 展开状态 │ │ • 电影 ││ │ │ • 音乐 ││ ← 子菜单 │ │ • 游戏 ││ │ │ • 阅读 ││ │ └────────────────────────────────┘│ │ ┌─ 运动 ▶ ─┐│ │ ├─ 学习 ▶ ─┤│ ├─────────────────────────────────────┤ │ 点击主菜单展开/收起子项 │ └─────────────────────────────────────┘2.2 数据模型interface MenuItem { id: number; name: string; // 主菜单名称含 emoji sub: string[]; // 子项名称数组 expanded: boolean; // 是否展开 }2.3 状态变量变量类型用途menusMenuItem[]4 个菜单项目驱动列表渲染三、核心代码深度解析3.1 手风琴展开/收起逻辑toggleMenu(id: number): void { const newMenus: MenuItem[] this.menus.concat(); for (let i 0; i newMenus.length; i) { if (newMenus[i].id id) { newMenus[i] { id: newMenus[i].id, name: newMenus[i].name, sub: newMenus[i].sub, expanded: !newMenus[i].expanded }; } } this.menus newMenus; }手风琴 vs 普通展开当前实现是独立展开模式——每个菜单项独立切换互不影响。如果要实现经典手风琴模式展开一个时自动收起其他只需稍作修改toggleMenuAccordion(id: number): void { this.menus this.menus.map(menu ({ ...menu, expanded: menu.id id ? !menu.expanded : false })); }经典手风琴模式使用map将所有非目标菜单的expanded设为false实现「最多一个展开」的效果。3.2 List ForEach 嵌套渲染菜单的整体结构是 List 中嵌套多层 ColumnList() { ForEach(this.menus, (menu: MenuItem) { ListItem() { Column() { // 主菜单行 Row() { Text(menu.name).fontSize(18).fontWeight(FontWeight.Medium).layoutWeight(1) Text(menu.expanded ? ▼ : ▶).fontSize(16).fontColor(#aaa) } .padding(16).backgroundColor(#fff).borderRadius(12) .shadow({ radius: 2, color: #20000000, offsetY: 1 }) .onClick(() this.toggleMenu(menu.id)) // 子菜单区域条件渲染 if (menu.expanded) { Column() { ForEach(menu.sub, (s: string) { Row() { Text(•).fontSize(16).fontColor(#0077FF).margin({ left: 20, right: 8 }) Text(s).fontSize(15).fontColor(#555) } .padding({ top: 10, bottom: 10, left: 16 }).width(90%) .backgroundColor(#f5f5f5).borderRadius(8).margin({ bottom: 4 }) .alignSelf(ItemAlign.Center) }) } .margin({ top: 4 }).padding(4).backgroundColor(#fafafa).borderRadius(8) } } } }, (menu: MenuItem) menu.id.toString()) } .layoutWeight(1).width(92%).padding({ top: 8 })嵌套渲染的关键结构List └── ForEach (menus) └── ListItem └── Column ├── Row (主菜单: emoji 名称 箭头) └── if (expanded) └── Column └── ForEach (sub) └── Row (子项: • 名称)为什么用 Column 而非直接展开主菜单使用 Row子菜单使用 Column 嵌套这是因为Row 让 emoji、名称、箭头在同一行水平排列Column 让子项垂直排列在主菜单下方Column 将主菜单和子菜单组合成一个逻辑单元便于 ListItem 管理3.3 展开指示箭头Text(menu.expanded ? ▼ : ▶).fontSize(16).fontColor(#aaa)这是最直观的展开指示器设计▶右三角表示「可展开点击展开」▼下三角表示「已展开点击收起」用户可以通过箭头的方向直观判断当前菜单的状态无需阅读文字说明。3.4 阴影与样式.shadow({ radius: 2, color: #20000000, offsetY: 1 })shadow 属性详解参数值说明radius2阴影模糊半径越大阴影越柔和扩散color#20000000阴影颜色前两位 20 是透明度约 12%后六位 000000 是黑色offsetX未设置默认 0水平偏移量offsetY1垂直向下偏移 1vp产生微微下沉的效果这种阴影效果模拟了 Material Design 的 elevation海拔高度理念——卡片海拔越高阴影越明显。这里的radius: 2, offsetY: 1营造了轻微的「浮起」效果让主菜单看起来是可以点击的交互元素。四、HarmonyOS 特色功能深度解析4.1 条件渲染与展开动画if (menu.expanded) { Column() { ForEach(menu.sub, (s: string) { // 子项内容 }) } .margin({ top: 4 }).padding(4).backgroundColor(#fafafa).borderRadius(8) }ArkUI 的if条件渲染是声明式的——当menu.expanded变化时框架自动处理子组件的创建和销毁expanded行为false→true创建 Column 及其子组件插入到 UI 树true→false销毁 Column 及其子组件从 UI 树移除当前局限这个实现是「显示/隐藏」而非「动画展开/收起」。子菜单是突然出现的没有过渡动画。添加展开动画的改进方案// 方法一使用 animation 属性 Column() .height(menu.expanded ? subHeight : 0) .opacity(menu.expanded ? 1 : 0) .animation({ duration: 300, curve: Curve.FastOutSlowIn }) // 方法二使用 animateTo 显式动画 animateTo({ duration: 300 }, () { this.menus[idx].expanded !this.menus[idx].expanded; })4.2 List 组件与滚动List是 ArkUI 中用于垂直滚动的容器组件List() .layoutWeight(1) // 充满父容器剩余空间 .width(92%) .padding({ top: 8 })layoutWeight(1)的作用让 List 占据 Column 中除导航栏和底部提示文字外的所有垂直空间当菜单展开超过一屏时List 自动变为可滚动无需手动设置scrollable属性List 默认支持滚动4.3 alignSelf 与子项居中.alignSelf(ItemAlign.Center)这个属性让子项在交叉轴上居中对齐。在Column容器中alignSelf覆盖了父容器的alignItems设置。这里子项宽度设为90%通过alignSelf(ItemAlign.Center)在父容器中水平居中。五、UI/UX 设计分析5.1 视觉层级第一层主菜单白色背景 阴影 18pt 字体 第二层子菜单容器浅灰背景 #fafafa 圆角 第三层子项更浅的背景 #f5f5f5 8px 圆角三层背景色递减主菜单#fff纯白子菜单容器#fafafa最浅灰子项#f5f5f5浅灰这种嵌套灰度的视觉语言帮助用户理解层级关系。5.2 底部提示Text(点击主菜单展开/收起子项).fontSize(13).fontColor(#aaa).margin({ bottom: 12 })底部的提示文字是 UX 中的引导性文案帮助用户理解交互方式。对于菜单这种直觉性较强的交互模式提示文字可以较小13pt且颜色较淡#aaa不占据视觉焦点但在需要时提供指引。5.3 emoji 图标系统四个主菜单均使用 emoji 作为图标 美食 — 食物 emoji直觉关联 娱乐 — 游戏手柄代表娱乐 运动 — 跑步的人代表运动 学习 — 书本代表学习Emoji 作为图标的好处是无需导入图标库、国际化友好、跨平台兼容。HarmonyOS 对 emoji 的渲染支持良好显示效果一致。六、最佳实践与优化建议6.1 当前代码亮点简洁的数据模型MenuItem接口清晰子项使用字符串数组而非对象数组简化数据结构不可变更新模式toggleMenu创建新对象新数组确保 State 响应视觉层级分明三层背景色递进层次感强箭头方向反馈▶/▼ 直观指示展开状态6.2 可优化方向// 优化建议 1添加展开/收起动画 // 使用 animateTo 实现平滑展开 toggleMenuWithAnim(id: number): void { animateTo({ duration: 250, curve: Curve.FastOutSlowIn }, () { const newMenus this.menus.map(m m.id id ? { ...m, expanded: !m.expanded } : m ); this.menus newMenus; }); } // 优化建议 2经典手风琴模式只展开一个 toggleMenuAccordion(id: number): void { this.menus this.menus.map(menu ({ ...menu, expanded: menu.id id ? !menu.expanded : false })); } // 优化建议 3添加图标/角标 // 子项右侧显示数字角标 Row() { Text(s).fontSize(15).fontColor(#555).layoutWeight(1) Text(3).fontSize(12).fontColor(#fff).padding(4) .backgroundColor(#F44336).borderRadius(10) } // 优化建议 4支持多级菜单 interface MultiLevelMenu { id: number; name: string; children?: MultiLevelMenu[]; // 递归定义 expanded: boolean; } // 优化建议 5自定义展开图标 // 使用旋转动画的箭头 Image(this.menu.expanded ? $r(app.media.arrow_down) : $r(app.media.arrow_right)) .rotate({ angle: this.menu.expanded ? 180 : 0 }) .animation({ duration: 200 })6.3 性能优化List 虚拟化当前 4 个菜单项远未达到性能瓶颈。如果扩展到 100 菜单List 的虚拟化特性会自动优化渲染性能避免深层嵌套目前主菜单→子菜单只有两层渲染开销极低。如果实现多级菜单3 层应考虑使用递归组件或树形控件子项复用同一级别的子项结构相同HarmonyOS 的组件复用机制会自动优化七、总结与扩展菜单演示应用展示了 ArkUI 中实现手风琴菜单的完整模式知识点应用位置重要程度List ForEach 列表渲染主菜单列表★★★★★条件渲染 if/else子菜单展开/收起★★★★★不可变对象更新toggleMenu★★★★☆shadow 阴影样式主菜单卡片★★★☆☆alignSelf 对齐子项居中★★★☆☆展开箭头指示交互反馈★★★☆☆扩展思路嵌套多级菜单支持三级及以上菜单层级用于复杂的设置页面图标系统为每个菜单项添加 Material Design 图标使用 HarmonyOS 的图标资源展开动画使用animateTo或transition属性添加平滑展开/收起动画搜索过滤添加搜索框实时过滤匹配的菜单项状态保持使用StorageProp或AppStorage在页面切换时保持展开状态自定义主题支持主菜单颜色、字体、圆角等样式自定义手风琴菜单是移动应用中最经典的导航模式之一。通过这个案例你可以掌握 List 嵌套渲染、条件展开和交互状态管理的基本模式这些模式可以复用到任何需要「分类展开」的场景中。

相关新闻