132.[HarmonyOS NEXT 实战案例五:SideBarContainer] 侧边栏容器实战:悬浮模式侧边栏(Overlay)进阶篇 原创

全栈若城
发布于 2025-6-30 15:28
浏览
0收藏

[HarmonyOS NEXT 实战案例五:SideBarContainer] 侧边栏容器实战:悬浮模式侧边栏(Overlay)进阶篇

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

132.[HarmonyOS NEXT 实战案例五:SideBarContainer] 侧边栏容器实战:悬浮模式侧边栏(Overlay)进阶篇-鸿蒙开发者社区
132.[HarmonyOS NEXT 实战案例五:SideBarContainer] 侧边栏容器实战:悬浮模式侧边栏(Overlay)进阶篇-鸿蒙开发者社区

一、状态管理进阶

在基础篇中,我们已经实现了移动端抽屉菜单的基本布局和功能。在本篇教程中,我们将深入探讨如何通过状态管理和交互功能增强,使侧边栏更加智能和易用。

1.1 状态变量设计

首先,让我们扩展状态变量,以支持更丰富的功能:

@Entry
@Component
struct MobileMenu {
  // 侧边栏显示状态
  @State isSideBarShow: boolean = false
  
  // 当前选中的菜单项索引
  @State currentIndex: number = 0
  
  // 动画状态
  @State animationState: AnimationStatus = AnimationStatus.Initial
  
  // 用户信息
  @State userInfo: UserInfo = {
    name: '用户名',
    email: '[email protected]',
    avatar: $r('app.media.avatar'),
    isLoggedIn: true
  }
  
  // 菜单项列表
  @State menuItems: MenuItem[] = [
    { id: 0, title: '首页', icon: $r('app.media.ic_home'), badge: 0 },
    { id: 1, title: '消息', icon: $r('app.media.ic_message'), badge: 5 },
    { id: 2, title: '收藏', icon: $r('app.media.ic_favorite'), badge: 0 },
    { id: 3, title: '设置', icon: $r('app.media.ic_settings'), badge: 0 },
    { id: 4, title: '关于', icon: $r('app.media.ic_about'), badge: 0 }
  ]
  
  // 主题设置
  @State isDarkMode: boolean = false
  
  // 屏幕信息
  @State screenWidth: number = 0
  @State screenHeight: number = 0
  
  // 构建方法...
}

// 用户信息接口
interface UserInfo {
  name: string
  email: string
  avatar: Resource
  isLoggedIn: boolean
}

// 菜单项接口
interface MenuItem {
  id: number
  title: string
  icon: Resource
  badge: number
}

// 动画状态枚举
enum AnimationStatus {
  Initial,
  Playing,
  Stopped
}

这些扩展的状态变量使我们能够:

  1. 跟踪用户信息:包括用户名、邮箱、头像和登录状态
  2. 管理菜单项:将菜单项抽象为对象数组,便于动态管理
  3. 添加徽章功能:为菜单项添加徽章,显示未读消息数量等信息
  4. 支持主题切换:通过isDarkMode状态变量支持深色模式
  5. 响应式设计:通过screenWidthscreenHeight状态变量支持响应式布局

1.2 生命周期管理

接下来,我们添加生命周期方法,以便在组件创建和销毁时执行必要的操作:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 屏幕变化监听器ID
  private screenChangeListener: number = 0
  
  aboutToAppear() {
    // 获取屏幕尺寸
    this.updateScreenSize()
    
    // 添加屏幕变化监听器
    this.screenChangeListener = window.on('windowSizeChange', () => {
      this.updateScreenSize()
    })
    
    // 加载用户信息
    this.loadUserInfo()
    
    // 加载菜单项
    this.loadMenuItems()
  }
  
  aboutToDisappear() {
    // 移除屏幕变化监听器
    if (this.screenChangeListener) {
      window.off('windowSizeChange', this.screenChangeListener)
    }
    
    // 保存状态
    this.saveState()
  }
  
  // 更新屏幕尺寸
  private updateScreenSize() {
    this.screenWidth = px2vp(window.getWindowWidth())
    this.screenHeight = px2vp(window.getWindowHeight())
  }
  
  // 加载用户信息
  private loadUserInfo() {
    // 从持久化存储加载用户信息
    // 这里使用模拟数据
    this.userInfo = {
      name: '张三',
      email: '[email protected]',
      avatar: $r('app.media.avatar'),
      isLoggedIn: true
    }
  }
  
  // 加载菜单项
  private loadMenuItems() {
    // 从持久化存储加载菜单项
    // 这里使用模拟数据
    this.menuItems = [
      { id: 0, title: '首页', icon: $r('app.media.ic_home'), badge: 0 },
      { id: 1, title: '消息', icon: $r('app.media.ic_message'), badge: 5 },
      { id: 2, title: '收藏', icon: $r('app.media.ic_favorite'), badge: 0 },
      { id: 3, title: '设置', icon: $r('app.media.ic_settings'), badge: 0 },
      { id: 4, title: '关于', icon: $r('app.media.ic_about'), badge: 0 }
    ]
  }
  
  // 保存状态
  private saveState() {
    // 将状态保存到持久化存储
    // 这里仅作示例,实际应用中应使用AppStorage或其他存储机制
    console.info('保存状态:', {
      currentIndex: this.currentIndex,
      isDarkMode: this.isDarkMode
    })
  }
  
  // 构建方法...
}

这些生命周期方法使我们能够:

  1. 响应屏幕变化:监听窗口大小变化,更新屏幕尺寸状态变量
  2. 加载和保存状态:在组件创建时加载状态,在组件销毁时保存状态
  3. 资源清理:在组件销毁时移除事件监听器,避免内存泄漏

二、交互功能增强

2.1 徽章显示

为了增强菜单项的信息展示能力,我们可以添加徽章功能,显示未读消息数量等信息:

@Builder MenuItem(item: MenuItem) {
  Row() {
    Image(item.icon)
      .width(24)
      .height(24)
      .margin({ right: 16 })

    Text(item.title)
      .fontSize(16)
      .fontColor(this.currentIndex === item.id ? '#FF4081' : '#333333')

    if (item.badge > 0) {
      Blank()
      
      Text(`${item.badge}`)
        .fontSize(12)
        .fontColor(Color.White)
        .backgroundColor('#FF4081')
        .borderRadius(10)
        .width(item.badge > 9 ? 24 : 20)
        .height(20)
        .textAlign(TextAlign.Center)
    }
  }
  .width('100%')
  .padding({ left: 24, right: 24, top: 16, bottom: 16 })
  .backgroundColor(this.currentIndex === item.id ? '#F5F5F5' : '#FFFFFF')
  .borderRadius(8)
  .onClick(() => {
    this.currentIndex = item.id
    this.isSideBarShow = false
  })
}

这段代码实现了徽章显示功能:

  1. 条件渲染:只有当badge值大于0时才显示徽章
  2. 自适应宽度:根据徽章数值调整宽度,确保显示效果良好
  3. 样式设置:使用圆角和背景色使徽章更加醒目

2.2 手势增强

为了提供更自然的交互体验,我们可以增强手势控制:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 手势相关状态
  @State dragOffset: number = 0
  @State isDragging: boolean = false
  
  build() {
    Stack() {
      SideBarContainer(SideBarContainerType.Overlay) {
        // 侧边栏内容...

        // 主内容区
        Stack() {
          this.MainContent()

          // 左侧手势区域
          Column()
            .width(20)
            .height('100%')
            .position({ x: 0, y: 0 })
            .gesture(
              PanGesture({ direction: PanDirection.Horizontal })
                .onActionStart(() => {
                  this.isDragging = true
                  this.dragOffset = 0
                })
                .onActionUpdate((event: GestureEvent) => {
                  if (event.offsetX > 0) {
                    this.dragOffset = Math.min(event.offsetX, 280)
                  }
                })
                .onActionEnd(() => {
                  this.isDragging = false
                  if (this.dragOffset > 140) {
                    this.isSideBarShow = true
                  }
                  this.dragOffset = 0
                })
            )

          // 拖动时的预览
          if (this.isDragging && this.dragOffset > 0) {
            Column() {
              // 侧边栏预览内容
              this.SideBarPreview()
            }
            .width(this.dragOffset)
            .height('100%')
            .position({ x: 0, y: 0 })
            .backgroundColor('#FFFFFF')
          }
        }
      }
      .showSideBar(this.isSideBarShow)
      .sideBarWidth(280)
      .minSideBarWidth(280)
      .maxSideBarWidth(280)
      .onChange((isShow: boolean) => {
        this.isSideBarShow = isShow
      })

      // 遮罩层
      if (this.isSideBarShow) {
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#000000')
          .opacity(0.5)
          .onClick(() => {
            this.isSideBarShow = false
          })
      }
    }
    .width('100%')
    .height('100%')
  }
  
  // 侧边栏预览
  @Builder SideBarPreview() {
    Column() {
      // 简化版的侧边栏内容
      Image(this.userInfo.avatar)
        .width(40)
        .height(40)
        .borderRadius(20)
        .margin({ top: 60, bottom: 20 })

      ForEach(this.menuItems, (item: MenuItem) => {
        Row() {
          Image(item.icon)
            .width(24)
            .height(24)
        }
        .width('100%')
        .padding(16)
      })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
  }
  
  // 其他构建器...
}

这段代码实现了增强的手势控制:

  1. 拖动状态跟踪:使用isDraggingdragOffset状态变量跟踪拖动状态和偏移量
  2. 渐进式显示:根据拖动偏移量显示侧边栏预览
  3. 阈值判断:当拖动偏移量超过一定阈值(这里是140像素)时,显示完整侧边栏
  4. 预览内容:在拖动过程中显示简化版的侧边栏内容,提供视觉反馈

2.3 主题切换

为了支持深色模式,我们可以添加主题切换功能:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  build() {
    Stack() {
      SideBarContainer(SideBarContainerType.Overlay) {
        // 侧边栏内容...

        // 主内容区...
      }
      // SideBarContainer配置...
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.isDarkMode ? '#121212' : '#FFFFFF')
  }
  
  // 切换主题
  private toggleTheme() {
    this.isDarkMode = !this.isDarkMode
    // 应用主题变化
    this.applyTheme()
  }
  
  // 应用主题
  private applyTheme() {
    // 这里可以更新全局主题或应用特定样式
    console.info('应用主题:', this.isDarkMode ? '深色模式' : '浅色模式')
  }
  
  // 构建器...
}

在侧边栏内容中,我们可以添加主题切换开关:

@Builder SideBarContent() {
  Column() {
    // 用户信息区域...
    
    // 菜单选项...
    
    // 主题切换
    Row() {
      Text('深色模式')
        .fontSize(16)
        .fontColor(this.isDarkMode ? '#FFFFFF' : '#333333')
      
      Blank()
      
      Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
        .onChange((isOn: boolean) => {
          this.isDarkMode = isOn
          this.applyTheme()
        })
    }
    .width('100%')
    .padding({ left: 24, right: 24, top: 16, bottom: 16 })
    .margin({ top: 40 })
    
    // 底部退出按钮...
  }
  .width('100%')
  .height('100%')
  .backgroundColor(this.isDarkMode ? '#121212' : '#FFFFFF')
}

这段代码实现了主题切换功能:

  1. 主题状态:使用isDarkMode状态变量跟踪当前主题
  2. 切换控件:使用Toggle组件提供直观的切换界面
  3. 应用主题:在主题变化时调用applyTheme方法应用主题变化
  4. 自适应样式:根据当前主题调整文本颜色和背景颜色

三、状态持久化

为了在应用重启后恢复用户的设置和状态,我们需要实现状态持久化:

3.1 使用AppStorage

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 从AppStorage加载的状态
  @StorageProp('isDarkMode') isDarkMode: boolean = false
  @StorageProp('currentIndex') currentIndex: number = 0
  
  aboutToAppear() {
    // 初始化AppStorage
    if (AppStorage.Has('isDarkMode') === false) {
      AppStorage.SetOrCreate('isDarkMode', false)
    }
    if (AppStorage.Has('currentIndex') === false) {
      AppStorage.SetOrCreate('currentIndex', 0)
    }
    
    // 其他初始化...
  }
  
  // 更新AppStorage中的状态
  private updateAppStorage() {
    AppStorage.Set('isDarkMode', this.isDarkMode)
    AppStorage.Set('currentIndex', this.currentIndex)
  }
  
  // 构建方法...
}

3.2 使用首选项

对于需要在应用重启后保留的数据,我们可以使用首选项(Preferences):

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 首选项实例
  private preferences: Preferences | null = null
  
  aboutToAppear() {
    // 获取首选项实例
    this.getPreferences().then(() => {
      // 加载状态
      this.loadStateFromPreferences()
    })
    
    // 其他初始化...
  }
  
  aboutToDisappear() {
    // 保存状态到首选项
    this.saveStateToPreferences()
    
    // 其他清理...
  }
  
  // 获取首选项实例
  private async getPreferences() {
    try {
      this.preferences = await Preferences.getPreferences(getContext(this), 'MobileMenuPrefs')
    } catch (error) {
      console.error('获取首选项失败:', error)
    }
  }
  
  // 从首选项加载状态
  private async loadStateFromPreferences() {
    if (!this.preferences) return
    
    try {
      // 加载深色模式设置
      const isDarkMode = await this.preferences.get('isDarkMode', false)
      this.isDarkMode = isDarkMode
      
      // 加载当前索引
      const currentIndex = await this.preferences.get('currentIndex', 0)
      this.currentIndex = currentIndex
      
      // 应用主题
      this.applyTheme()
    } catch (error) {
      console.error('加载状态失败:', error)
    }
  }
  
  // 保存状态到首选项
  private async saveStateToPreferences() {
    if (!this.preferences) return
    
    try {
      // 保存深色模式设置
      await this.preferences.put('isDarkMode', this.isDarkMode)
      
      // 保存当前索引
      await this.preferences.put('currentIndex', this.currentIndex)
      
      // 提交更改
      await this.preferences.flush()
    } catch (error) {
      console.error('保存状态失败:', error)
    }
  }
  
  // 构建方法...
}

这段代码实现了状态持久化:

  1. 使用首选项:通过PreferencesAPI保存和加载状态
  2. 异步操作:使用async/await处理异步操作
  3. 错误处理:添加适当的错误处理,提高应用的健壮性
  4. 自动加载和保存:在组件创建时自动加载状态,在组件销毁时自动保存状态

四、高级交互特性

4.1 动画效果增强

为了提供更流畅的用户体验,我们可以增强动画效果:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 动画控制器
  @State sideBarAnimator: AnimatorResult | null = null
  
  aboutToAppear() {
    // 创建动画控制器
    this.createAnimator()
    
    // 其他初始化...
  }
  
  // 创建动画控制器
  private createAnimator() {
    // 创建侧边栏动画
    this.sideBarAnimator = Animator.create({
      duration: 300,  // 动画持续时间,单位毫秒
      curve: Curve.EaseOut,  // 动画曲线
      iterations: 1,  // 动画重复次数
      playMode: PlayMode.Normal  // 动画播放模式
    })
  }
  
  // 播放侧边栏显示动画
  private playSideBarShowAnimation() {
    if (!this.sideBarAnimator) return
    
    // 重置动画
    this.sideBarAnimator.reset()
    
    // 设置动画属性
    this.sideBarAnimator.animate({
      transform: {
        translate: { x: '0%', y: '0%' }
      },
      opacity: 1
    })
    
    // 播放动画
    this.sideBarAnimator.play()
  }
  
  // 播放侧边栏隐藏动画
  private playSideBarHideAnimation() {
    if (!this.sideBarAnimator) return
    
    // 重置动画
    this.sideBarAnimator.reset()
    
    // 设置动画属性
    this.sideBarAnimator.animate({
      transform: {
        translate: { x: '-100%', y: '0%' }
      },
      opacity: 0
    })
    
    // 播放动画
    this.sideBarAnimator.play()
  }
  
  // 构建方法...
}

4.2 自定义过渡效果

我们可以使用Transition组件为侧边栏添加自定义过渡效果:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  build() {
    Stack() {
      SideBarContainer(SideBarContainerType.Overlay) {
        // 侧边栏内容...

        // 主内容区...
      }
      // SideBarContainer配置...
    }
    .width('100%')
    .height('100%')
    .transition({
      type: TransitionType.Insert,
      opacity: 0,
      translate: { x: '-100%', y: '0%' }
    })
    .transition({
      type: TransitionType.Delete,
      opacity: 0,
      translate: { x: '-100%', y: '0%' }
    })
  }
  
  // 构建器...
}

4.3 交互反馈

为了提供更好的交互反馈,我们可以添加触觉反馈和声音效果:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 触觉反馈控制器
  private vibrationController: VibrationController = new VibrationController()
  
  // 提供触觉反馈
  private provideTactileFeedback() {
    // 触发短暂振动
    this.vibrationController.vibrate(50)
  }
  
  // 构建器...
  
  @Builder MenuItem(item: MenuItem) {
    Row() {
      // 菜单项内容...
    }
    .width('100%')
    .padding({ left: 24, right: 24, top: 16, bottom: 16 })
    .backgroundColor(this.currentIndex === item.id ? '#F5F5F5' : '#FFFFFF')
    .borderRadius(8)
    .onClick(() => {
      // 提供触觉反馈
      this.provideTactileFeedback()
      
      // 更新状态
      this.currentIndex = item.id
      this.isSideBarShow = false
    })
  }
}

五、实战案例:自定义菜单项

在实际应用中,我们可能需要支持不同类型的菜单项,例如带有开关、徽章或子菜单的菜单项。下面我们将实现一个支持多种类型的菜单项系统:

5.1 菜单项类型定义

// 菜单项类型枚举
enum MenuItemType {
  Normal,  // 普通菜单项
  Switch,  // 带开关的菜单项
  Badge,   // 带徽章的菜单项
  Submenu  // 带子菜单的菜单项
}

// 菜单项接口
interface MenuItem {
  id: number
  title: string
  icon: Resource
  type: MenuItemType
  badge?: number
  isOn?: boolean
  subItems?: SubMenuItem[]
}

// 子菜单项接口
interface SubMenuItem {
  id: number
  title: string
  icon?: Resource
}

5.2 菜单项数据

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 菜单项列表
  @State menuItems: MenuItem[] = [
    { 
      id: 0, 
      title: '首页', 
      icon: $r('app.media.ic_home'), 
      type: MenuItemType.Normal 
    },
    { 
      id: 1, 
      title: '消息', 
      icon: $r('app.media.ic_message'), 
      type: MenuItemType.Badge,
      badge: 5 
    },
    { 
      id: 2, 
      title: '收藏', 
      icon: $r('app.media.ic_favorite'), 
      type: MenuItemType.Normal 
    },
    { 
      id: 3, 
      title: '设置', 
      icon: $r('app.media.ic_settings'), 
      type: MenuItemType.Submenu,
      subItems: [
        { id: 31, title: '账号设置' },
        { id: 32, title: '通知设置' },
        { id: 33, title: '隐私设置' }
      ] 
    },
    { 
      id: 4, 
      title: '深色模式', 
      icon: $r('app.media.ic_dark_mode'), 
      type: MenuItemType.Switch,
      isOn: false 
    },
    { 
      id: 5, 
      title: '关于', 
      icon: $r('app.media.ic_about'), 
      type: MenuItemType.Normal 
    }
  ]
  
  // 构建方法...
}

5.3 菜单项构建器

@Builder SideBarContent() {
  Column() {
    // 用户信息区域...
    
    // 菜单选项
    Column() {
      ForEach(this.menuItems, (item: MenuItem) => {
        this.renderMenuItem(item)
      })
    }
    .margin({ top: 50 })
    
    // 底部退出按钮...
  }
  .width('100%')
  .height('100%')
  .backgroundColor(this.isDarkMode ? '#121212' : '#FFFFFF')
}

// 渲染菜单项
@Builder renderMenuItem(item: MenuItem) {
  if (item.type === MenuItemType.Normal) {
    this.NormalMenuItem(item)
  } else if (item.type === MenuItemType.Badge) {
    this.BadgeMenuItem(item)
  } else if (item.type === MenuItemType.Switch) {
    this.SwitchMenuItem(item)
  } else if (item.type === MenuItemType.Submenu) {
    this.SubmenuMenuItem(item)
  }
}

// 普通菜单项
@Builder NormalMenuItem(item: MenuItem) {
  Row() {
    Image(item.icon)
      .width(24)
      .height(24)
      .margin({ right: 16 })

    Text(item.title)
      .fontSize(16)
      .fontColor(this.currentIndex === item.id ? '#FF4081' : '#333333')
  }
  .width('100%')
  .padding({ left: 24, right: 24, top: 16, bottom: 16 })
  .backgroundColor(this.currentIndex === item.id ? '#F5F5F5' : '#FFFFFF')
  .borderRadius(8)
  .onClick(() => {
    this.currentIndex = item.id
    this.isSideBarShow = false
  })
}

// 带徽章的菜单项
@Builder BadgeMenuItem(item: MenuItem) {
  Row() {
    Image(item.icon)
      .width(24)
      .height(24)
      .margin({ right: 16 })

    Text(item.title)
      .fontSize(16)
      .fontColor(this.currentIndex === item.id ? '#FF4081' : '#333333')

    if (item.badge && item.badge > 0) {
      Blank()
      
      Text(`${item.badge}`)
        .fontSize(12)
        .fontColor(Color.White)
        .backgroundColor('#FF4081')
        .borderRadius(10)
        .width(item.badge > 9 ? 24 : 20)
        .height(20)
        .textAlign(TextAlign.Center)
    }
  }
  .width('100%')
  .padding({ left: 24, right: 24, top: 16, bottom: 16 })
  .backgroundColor(this.currentIndex === item.id ? '#F5F5F5' : '#FFFFFF')
  .borderRadius(8)
  .onClick(() => {
    this.currentIndex = item.id
    this.isSideBarShow = false
  })
}

// 带开关的菜单项
@Builder SwitchMenuItem(item: MenuItem) {
  Row() {
    Image(item.icon)
      .width(24)
      .height(24)
      .margin({ right: 16 })

    Text(item.title)
      .fontSize(16)
      .fontColor('#333333')

    Blank()
    
    Toggle({ type: ToggleType.Switch, isOn: item.isOn || false })
      .onChange((isOn: boolean) => {
        // 更新菜单项状态
        const index = this.menuItems.findIndex(i => i.id === item.id)
        if (index !== -1) {
          this.menuItems[index].isOn = isOn
        }
        
        // 处理特定菜单项
        if (item.id === 4) {  // 深色模式
          this.isDarkMode = isOn
          this.applyTheme()
        }
      })
  }
  .width('100%')
  .padding({ left: 24, right: 24, top: 16, bottom: 16 })
  .backgroundColor('#FFFFFF')
  .borderRadius(8)
}

// 带子菜单的菜单项
@Builder SubmenuMenuItem(item: MenuItem) {
  Column() {
    // 主菜单项
    Row() {
      Image(item.icon)
        .width(24)
        .height(24)
        .margin({ right: 16 })

      Text(item.title)
        .fontSize(16)
        .fontColor(this.currentIndex === item.id ? '#FF4081' : '#333333')

      Blank()
      
      Image($r('app.media.ic_arrow_down'))
        .width(16)
        .height(16)
        .rotate({ z: 1, angle: this.isSubmenuOpen(item.id) ? 180 : 0 })
    }
    .width('100%')
    .padding({ left: 24, right: 24, top: 16, bottom: 16 })
    .backgroundColor(this.currentIndex === item.id ? '#F5F5F5' : '#FFFFFF')
    .borderRadius(8)
    .onClick(() => {
      this.toggleSubmenu(item.id)
    })
    
    // 子菜单项
    if (this.isSubmenuOpen(item.id) && item.subItems) {
      Column() {
        ForEach(item.subItems, (subItem: SubMenuItem) => {
          Row() {
            if (subItem.icon) {
              Image(subItem.icon)
                .width(20)
                .height(20)
                .margin({ right: 12 })
            }

            Text(subItem.title)
              .fontSize(14)
              .fontColor('#666666')
          }
          .width('100%')
          .padding({ left: 64, right: 24, top: 12, bottom: 12 })
          .backgroundColor('#F9F9F9')
          .onClick(() => {
            this.handleSubItemClick(item.id, subItem.id)
          })
        })
      }
      .width('100%')
    }
  }
  .width('100%')
}

5.4 子菜单状态管理

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 打开的子菜单ID集合
  @State openSubmenus: Set<number> = new Set()
  
  // 检查子菜单是否打开
  private isSubmenuOpen(id: number): boolean {
    return this.openSubmenus.has(id)
  }
  
  // 切换子菜单状态
  private toggleSubmenu(id: number) {
    if (this.isSubmenuOpen(id)) {
      this.openSubmenus.delete(id)
    } else {
      this.openSubmenus.add(id)
    }
    
    // 触发UI更新
    this.openSubmenus = new Set(this.openSubmenus)
  }
  
  // 处理子菜单项点击
  private handleSubItemClick(parentId: number, subItemId: number) {
    console.info('子菜单项点击:', { parentId, subItemId })
    
    // 这里可以根据需要处理子菜单项点击事件
    // 例如,导航到特定页面或执行特定操作
    
    // 关闭侧边栏
    this.isSideBarShow = false
  }
  
  // 构建方法...
}

六、性能优化与用户体验提升

6.1 延迟加载

为了提高应用的启动性能,我们可以使用延迟加载技术:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 延迟加载状态
  @State isContentLoaded: boolean = false
  
  aboutToAppear() {
    // 其他初始化...
    
    // 延迟加载内容
    setTimeout(() => {
      this.loadMenuItems()
      this.isContentLoaded = true
    }, 100)
  }
  
  build() {
    Stack() {
      if (this.isContentLoaded) {
        SideBarContainer(SideBarContainerType.Overlay) {
          // 侧边栏内容...

          // 主内容区...
        }
        // SideBarContainer配置...
      } else {
        // 加载指示器
        Column() {
          LoadingProgress()
            .width(50)
            .height(50)
            
          Text('加载中...')
            .fontSize(16)
            .margin({ top: 16 })
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
    }
    .width('100%')
    .height('100%')
  }
  
  // 构建器...
}

6.2 列表性能优化

对于包含大量菜单项的侧边栏,我们可以使用LazyForEach优化列表性能:

// 菜单项数据源
class MenuItemDataSource implements IDataSource {
  private menuItems: MenuItem[]
  private listener: DataChangeListener

  constructor(menuItems: MenuItem[]) {
    this.menuItems = menuItems
  }

  totalCount(): number {
    return this.menuItems.length
  }

  getData(index: number): MenuItem {
    return this.menuItems[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    this.listener = listener
  }

  unregisterDataChangeListener() {
  }
}

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 菜单项数据源
  private menuItemDataSource: MenuItemDataSource = new MenuItemDataSource([])
  
  aboutToAppear() {
    // 加载菜单项
    this.loadMenuItems()
    
    // 初始化数据源
    this.menuItemDataSource = new MenuItemDataSource(this.menuItems)
    
    // 其他初始化...
  }
  
  @Builder SideBarContent() {
    Column() {
      // 用户信息区域...
      
      // 菜单选项
      List() {
        LazyForEach(this.menuItemDataSource, (item: MenuItem) => {
          ListItem() {
            this.renderMenuItem(item)
          }
        }, (item: MenuItem) => item.id.toString())
      }
      .width('100%')
      .margin({ top: 50 })
      
      // 底部退出按钮...
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.isDarkMode ? '#121212' : '#FFFFFF')
  }
  
  // 构建器...
}

6.3 缓存优化

为了减少不必要的重新渲染,我们可以使用缓存优化:

@Entry
@Component
struct MobileMenu {
  // 状态变量...
  
  // 缓存的主内容
  @State cachedMainContent: Record<number, Object> = {}
  
  // 构建主内容
  @Builder MainContent() {
    Column() {
      // 顶部应用栏...

      // 内容区域
      Column() {
        // 使用缓存的内容或创建新内容
        if (this.cachedMainContent[this.currentIndex]) {
          // 使用缓存的内容
          this.cachedMainContent[this.currentIndex]
        } else {
          // 创建新内容并缓存
          if (this.currentIndex === 0) {
            this.HomeContent()
          } else if (this.currentIndex === 1) {
            this.MessageContent()
          } else if (this.currentIndex === 2) {
            this.FavoriteContent()
          } else if (this.currentIndex === 3) {
            this.SettingsContent()
          } else if (this.currentIndex === 4) {
            this.AboutContent()
          }
        }
      }
      .width('100%')
      .layoutWeight(1)
      .backgroundColor('#F5F5F5')
    }
    .width('100%')
    .height('100%')
  }
  
  // 缓存内容
  private cacheContent(index: number, content: Object) {
    this.cachedMainContent[index] = content
  }
  
  // 清除缓存
  private clearCache() {
    this.cachedMainContent = {}
  }
  
  // 构建器...
}

七、总结

在本教程中,我们深入探讨了如何通过状态管理和交互功能增强,使HarmonyOS NEXT的SideBarContainer组件的Overlay模式侧边栏更加智能和易用。 通过这些增强功能,我们的移动端抽屉菜单不仅具有基本的导航功能,还提供了更好的用户体验和更丰富的交互方式。这些技术可以应用于各种移动应用场景,例如社交应用、电商应用、内容应用等。
希望本教程能够帮助你更好地理解和使用HarmonyOS NEXT的SideBarContainer组件的Overlay模式,创建出更加优秀的移动应用。

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