[HarmonyOS NEXT 实战案例二:SideBarContainer] 侧边栏容器实战:电商应用商品筛选侧边栏 基础篇

2025-06-11 23:33:31
109次阅读
0个评论

[HarmonyOS NEXT 实战案例二:SideBarContainer] 侧边栏容器实战:电商应用商品筛选侧边栏 基础篇

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

效果演示

image.png

一、电商应用侧边栏概述

在现代电商应用中,商品筛选功能是提升用户购物体验的关键元素。HarmonyOS NEXT提供的SideBarContainer组件非常适合实现这类功能,它可以创建一个可显示和隐藏的侧边栏,用于放置各种筛选条件,帮助用户快速找到心仪的商品。

1.1 电商筛选侧边栏的特点

电商应用的筛选侧边栏通常具有以下特点:

  1. 悬浮式布局:筛选侧边栏通常采用悬浮在内容区上方的方式显示,不占用主内容区的空间
  2. 丰富的筛选条件:包括价格范围、商品分类、品牌、评分等多种筛选条件
  3. 实时反馈:用户调整筛选条件时,可以实时看到筛选结果或预览筛选效果
  4. 灵活的显示控制:用户可以通过按钮或手势来显示和隐藏筛选侧边栏

1.2 SideBarContainer组件回顾

在开始实现电商筛选侧边栏之前,让我们简要回顾一下SideBarContainer组件的关键特性:

特性 说明
显示模式 支持Embed(嵌入式)、Overlay(悬浮式)和Auto(自适应)三种模式
侧边栏位置 可以设置为Start(左侧)或End(右侧)
控制按钮 可以自定义控制按钮的位置、大小和图标
宽度控制 可以设置侧边栏的默认宽度、最小宽度和最大宽度
自动隐藏 可以设置当侧边栏拖拽到小于最小宽度后是否自动隐藏

对于电商应用的筛选侧边栏,我们通常选择Overlay模式,并将侧边栏位置设置为End(右侧),这样可以提供更好的用户体验。

二、电商筛选侧边栏实战

接下来,我们将通过一个电商应用的实例,详细讲解如何使用SideBarContainer组件实现商品筛选侧边栏。

2.1 需求分析

我们的电商应用筛选侧边栏需要实现以下功能:

  1. 价格范围筛选:使用滑块控制最低价和最高价
  2. 商品分类筛选:使用复选框选择多个商品分类
  3. 确认按钮:应用筛选条件并关闭侧边栏
  4. 主内容区显示商品列表,并提供筛选按钮打开侧边栏

2.2 代码实现

首先,我们来看完整的代码实现:

// 电商应用的商品筛选侧边栏

@Component
export struct ShoppingApp {
    @State isSideBarShow: boolean = false
    @State priceRange: number[] = [0, 1000]
    @State selectedCategories: string[] = []

    build() {
        SideBarContainer(SideBarContainerType.Overlay) {
            // 侧边栏内容 - 筛选条件
            Column() {
                Text('商品筛选').fontSize(20).margin(10)

                Text('价格范围').fontSize(16).margin({ top: 15, left: 10 })
                Slider({
                    min: 0,
                    max: 1000,
                    step: 50,
                    value: this.priceRange[0],
                    style: SliderStyle.OutSet
                })
                    .blockColor('#ff4500')
                    .onChange((value: number) => {
                        this.priceRange[0] = value
                    })

                Slider({
                    min: 0,
                    max: 1000,
                    step: 50,
                    value: this.priceRange[1],
                    style: SliderStyle.OutSet
                })
                    .blockColor('#ff4500')
                    .onChange((value: number) => {
                        this.priceRange[1] = value
                    })

                Text(`¥${this.priceRange[0]} - ¥${this.priceRange[1]}`).fontSize(14).margin({ bottom: 20 })

                Text('商品分类').fontSize(16).margin({ top: 10, left: 10 })
                Column() {
                    ForEach(['手机', '电脑', '家电', '服饰', '食品'], (item: string) => {
                        Row() {
                            Checkbox()
                                .onChange((isChecked: boolean) => {
                                    if (isChecked) {
                                        this.selectedCategories.push(item)
                                    } else {
                                        this.selectedCategories = this.selectedCategories.filter(cat => cat !== item)
                                    }
                                })
                            Text(item).fontSize(14).margin({ left: 5 })
                        }
                        .margin({ top: 5, left: 10 })
                    })
                }

                Button('确定', { type: ButtonType.Capsule })
                    .width('80%')
                    .margin(20)
                    .backgroundColor('#ff4500')
                    .onClick(() => {
                        this.isSideBarShow = false
                        console.log('应用筛选条件:', this.priceRange, this.selectedCategories)
                    })
            }
            .width('80%')
            .padding(10)
            .backgroundColor(Color.White)

            // 主内容区 - 商品列表
            Column() {
                Row() {
                    Button('筛选', { type: ButtonType.Capsule })
                        .width(80)
                        .onClick(() => {
                            this.isSideBarShow = true
                        })
                    Text('全部商品').fontSize(20).margin({ left: 10 })
                }
                .width('100%')
                .padding(10)

                Grid() {
                    ForEach(['手机', '笔记本', '耳机', '手表', '平板', '充电器'], (item: string) => {
                        GridItem() {
                            Column() {
                                Image($r(`app.media.phone`))
                                    .width(80)
                                    .height(80)
                                Text(item).fontSize(14).margin({ top: 5 })
                                Text('¥999').fontSize(16).fontColor('#ff4500')
                            }
                            .padding(10)
                            .borderRadius(8)
                            .backgroundColor(Color.White)
                            .shadow({ radius: 2, color: '#999', offsetX: 1, offsetY: 1 })
                        }
                    })
                }
                .columnsTemplate('1fr 1fr')
                .columnsGap(10)
                .rowsGap(10)
                .width('100%')
                .height('100%')
                .padding(10)
            }
        }
        .sideBarPosition(SideBarPosition.End)
        .autoHide(false)
        .onChange((value: boolean) => {
            this.isSideBarShow = value
        })
    }
}

2.3 代码详解

2.3.1 组件结构

我们使用@Component装饰器定义了一个名为ShoppingApp的自定义组件,并使用@State装饰器定义了三个状态变量:

@Component
export struct ShoppingApp {
    @State isSideBarShow: boolean = false  // 控制侧边栏显示状态
    @State priceRange: number[] = [0, 1000]  // 存储价格范围
    @State selectedCategories: string[] = []  // 存储选中的商品分类
    // ...
}

2.3.2 SideBarContainer配置

build方法中,我们使用SideBarContainer组件作为根容器,并设置其显示模式为Overlay,即侧边栏浮在内容区上面。

SideBarContainer(SideBarContainerType.Overlay) {
    // 侧边栏内容 - 筛选条件
    // 主内容区 - 商品列表
}
.sideBarPosition(SideBarPosition.End)  // 侧边栏位于右侧
.autoHide(false)  // 禁用自动隐藏
.onChange((value: boolean) => {
    this.isSideBarShow = value  // 更新侧边栏显示状态
})

我们通过链式调用设置了以下属性:

  • sideBarPosition(SideBarPosition.End):设置侧边栏显示在右侧
  • autoHide(false):禁用自动隐藏功能,确保用户可以完全控制侧边栏的显示和隐藏
  • onChange((value: boolean) => { ... }):监听侧边栏显示状态的变化

2.3.3 侧边栏内容 - 筛选条件

侧边栏内容是SideBarContainer的第一个子组件,我们使用Column组件作为容器,包含价格范围筛选、商品分类筛选和确定按钮。

Column() {
    Text('商品筛选').fontSize(20).margin(10)

    // 价格范围筛选
    Text('价格范围').fontSize(16).margin({ top: 15, left: 10 })
    Slider({
        min: 0,
        max: 1000,
        step: 50,
        value: this.priceRange[0],
        style: SliderStyle.OutSet
    })
        .blockColor('#ff4500')
        .onChange((value: number) => {
            this.priceRange[0] = value
        })

    // 第二个滑块...

    Text(`¥${this.priceRange[0]} - ¥${this.priceRange[1]}`).fontSize(14).margin({ bottom: 20 })

    // 商品分类筛选
    Text('商品分类').fontSize(16).margin({ top: 10, left: 10 })
    Column() {
        ForEach(['手机', '电脑', '家电', '服饰', '食品'], (item: string) => {
            Row() {
                Checkbox()
                    .onChange((isChecked: boolean) => {
                        if (isChecked) {
                            this.selectedCategories.push(item)
                        } else {
                            this.selectedCategories = this.selectedCategories.filter(cat => cat !== item)
                        }
                    })
                Text(item).fontSize(14).margin({ left: 5 })
            }
            .margin({ top: 5, left: 10 })
        })
    }

    // 确定按钮
    Button('确定', { type: ButtonType.Capsule })
        .width('80%')
        .margin(20)
        .backgroundColor('#ff4500')
        .onClick(() => {
            this.isSideBarShow = false
            console.log('应用筛选条件:', this.priceRange, this.selectedCategories)
        })
}
.width('80%')
.padding(10)
.backgroundColor(Color.White)

这里我们使用了以下组件和技术:

  1. 价格范围筛选

    • 使用两个Slider组件分别控制最低价和最高价
    • 通过onChange事件更新priceRange状态变量
    • 使用Text组件显示当前选择的价格范围
  2. 商品分类筛选

    • 使用ForEach遍历商品分类数组
    • 为每个分类创建一个包含CheckboxTextRow
    • 通过onChange事件更新selectedCategories状态变量
  3. 确定按钮

    • 使用Button组件创建一个胶囊型按钮
    • 通过onClick事件处理函数关闭侧边栏并应用筛选条件

2.3.4 主内容区 - 商品列表

主内容区是SideBarContainer的第二个子组件,我们使用Column组件作为容器,包含一个顶部栏和一个商品网格。

Column() {
    Row() {
        Button('筛选', { type: ButtonType.Capsule })
            .width(80)
            .onClick(() => {
                this.isSideBarShow = true
            })
        Text('全部商品').fontSize(20).margin({ left: 10 })
    }
    .width('100%')
    .padding(10)

    Grid() {
        ForEach(['手机', '笔记本', '耳机', '手表', '平板', '充电器'], (item: string) => {
            GridItem() {
                Column() {
                    Image($r(`app.media.phone`))
                        .width(80)
                        .height(80)
                    Text(item).fontSize(14).margin({ top: 5 })
                    Text('¥999').fontSize(16).fontColor('#ff4500')
                }
                .padding(10)
                .borderRadius(8)
                .backgroundColor(Color.White)
                .shadow({ radius: 2, color: '#999', offsetX: 1, offsetY: 1 })
            }
        })
    }
    .columnsTemplate('1fr 1fr')
    .columnsGap(10)
    .rowsGap(10)
    .width('100%')
    .height('100%')
    .padding(10)
}

这里我们使用了以下组件和技术:

  1. 顶部栏

    • 使用Row组件创建水平布局
    • 包含一个「筛选」按钮和一个标题文本
    • 通过按钮的onClick事件显示侧边栏
  2. 商品网格

    • 使用Grid组件创建网格布局
    • 通过columnsTemplate属性设置为两列等宽布局
    • 使用ForEach遍历商品数组,为每个商品创建一个GridItem
    • 每个GridItem包含商品图片、名称和价格
    • 添加圆角、背景色和阴影效果,提升视觉体验

三、布局技巧与最佳实践

3.1 侧边栏宽度设置

在示例中,我们将侧边栏的宽度设置为80%,这是相对于SideBarContainer默认分配给侧边栏的宽度(200vp)的百分比。在实际应用中,可以根据需要调整这个值,或者使用sideBarWidth属性直接设置侧边栏的宽度。

// 方式1:设置侧边栏子组件的宽度
Column() {
    // 侧边栏内容
}
.width('80%')

// 方式2:设置SideBarContainer的sideBarWidth属性
SideBarContainer(SideBarContainerType.Overlay) {
    // 侧边栏内容
    // 主内容区
}
.sideBarWidth(300)

3.2 响应式布局

为了适应不同屏幕尺寸,我们可以结合媒体查询来实现响应式布局。

// 使用媒体查询调整布局
@Media({ minWidth: 600 }) {
    // 大屏幕布局
    .sideBarWidth(400)
    .columnsTemplate('1fr 1fr 1fr')  // 三列布局
}

@Media({ maxWidth: 599 }) {
    // 小屏幕布局
    .sideBarWidth(300)
    .columnsTemplate('1fr 1fr')  // 两列布局
}

3.3 筛选条件的初始化和重置

在实际应用中,我们可能需要提供重置筛选条件的功能,或者根据用户之前的选择初始化筛选条件。

// 重置筛选条件
private resetFilters(): void {
    this.priceRange = [0, 1000]
    this.selectedCategories = []
}

// 初始化筛选条件
private initFilters(): void {
    // 从持久化存储或网络请求获取初始值
    const savedPriceRange = AppStorage.Get<number[]>('priceRange')
    if (savedPriceRange) {
        this.priceRange = savedPriceRange
    }
    
    const savedCategories = AppStorage.Get<string[]>('selectedCategories')
    if (savedCategories) {
        this.selectedCategories = savedCategories
    }
}

3.4 筛选条件的保存

为了提升用户体验,我们可以保存用户的筛选条件,以便在下次打开应用时恢复。

// 保存筛选条件
private saveFilters(): void {
    AppStorage.SetOrCreate<number[]>('priceRange', this.priceRange)
    AppStorage.SetOrCreate<string[]>('selectedCategories', this.selectedCategories)
}

四、交互优化

4.1 价格范围验证

在实际应用中,我们需要确保最低价不高于最高价,可以添加验证逻辑:

// 第一个滑块(最低价)
Slider({
    min: 0,
    max: 1000,
    step: 50,
    value: this.priceRange[0],
    style: SliderStyle.OutSet
})
    .blockColor('#ff4500')
    .onChange((value: number) => {
        // 确保最低价不高于最高价
        if (value <= this.priceRange[1]) {
            this.priceRange[0] = value
        } else {
            this.priceRange[0] = this.priceRange[1]
        }
    })

// 第二个滑块(最高价)
Slider({
    min: 0,
    max: 1000,
    step: 50,
    value: this.priceRange[1],
    style: SliderStyle.OutSet
})
    .blockColor('#ff4500')
    .onChange((value: number) => {
        // 确保最高价不低于最低价
        if (value >= this.priceRange[0]) {
            this.priceRange[1] = value
        } else {
            this.priceRange[1] = this.priceRange[0]
        }
    })

4.2 筛选条件预览

为了提供更好的用户体验,我们可以在侧边栏中显示当前选中的筛选条件数量:

Button('筛选', { type: ButtonType.Capsule })
    .width(80)
    .onClick(() => {
        this.isSideBarShow = true
    })

// 如果有筛选条件,显示数量
if (this.selectedCategories.length > 0 || this.priceRange[0] > 0 || this.priceRange[1] < 1000) {
    Badge({
        count: this.getFilterCount(),
        position: BadgePosition.Right,
        style: { color: '#fff', fontSize: 10, badgeSize: 16, badgeColor: '#ff4500' }
    })
}

// 获取筛选条件数量
private getFilterCount(): number {
    let count = 0
    if (this.priceRange[0] > 0 || this.priceRange[1] < 1000) {
        count += 1  // 价格筛选算一个条件
    }
    count += this.selectedCategories.length  // 每个选中的分类算一个条件
    return count
}

4.3 添加重置按钮

在侧边栏中添加重置按钮,方便用户快速清除所有筛选条件:

Row() {
    Button('重置', { type: ButtonType.Normal })
        .width('40%')
        .backgroundColor('#f5f5f5')
        .fontColor('#333')
        .onClick(() => {
            this.resetFilters()
        })
    
    Button('确定', { type: ButtonType.Capsule })
        .width('40%')
        .backgroundColor('#ff4500')
        .onClick(() => {
            this.isSideBarShow = false
            console.log('应用筛选条件:', this.priceRange, this.selectedCategories)
        })
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({ top: 20, bottom: 20 })

五、总结

本教程详细介绍了如何使用HarmonyOS NEXT的SideBarContainer组件实现电商应用的商品筛选侧边栏。 SideBarContainer组件为电商应用提供了一种简单而强大的方式来实现商品筛选功能,通过合理使用这个组件,可以创建出用户体验良好的电商应用界面。 在下一篇教程中,我们将深入探讨如何为电商应用添加更多交互功能和状态管理,例如筛选结果的实时预览、多级筛选、排序功能等,敬请期待!

收藏00

登录 后评论。没有帐号? 注册 一个。

全栈若城

  • 0回答
  • 4粉丝
  • 0关注
相关话题