138.[HarmonyOS NEXT 实战案例七:List系列] 多列列表组件实战:打造精美应用推荐页 进阶篇

2025-06-30 22:15:17
104次阅读
0个评论

[HarmonyOS NEXT 实战案例七:List系列] 多列列表组件实战:打造精美应用推荐页 进阶篇

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

效果演示

image.png

一、多列列表的进阶特性

在基础篇中,我们已经学习了如何创建基本的多列应用列表。在本篇教程中,我们将深入探讨多列列表的进阶特性,包括交互功能、布局优化、状态管理等方面。

1.1 多列列表的进阶属性

属性 说明 用途
alignListItem 设置列表项对齐方式 控制列表项在交叉轴上的对齐方式
scrollBar 设置滚动条样式 控制滚动条的显示和外观
edgeEffect 设置边缘效果 控制列表到达边缘时的视觉反馈
chainAnimation 设置链式动画 控制列表项的连锁动画效果
multiSelectable 设置多选模式 允许用户选择多个列表项
cachedCount 设置缓存数量 控制预加载的列表项数量

1.2 多列列表的交互特性

特性 说明 用途
onItemClick 列表项点击事件 监听列表项的点击状态
onScroll 滚动事件 监听列表的滚动状态
onReachStart/End 到达边缘事件 监听列表到达开始/结束位置
onScrollStop 滚动停止事件 监听列表停止滚动的状态

二、应用推荐页的交互增强

在基础版本的应用推荐页基础上,我们可以添加更多的交互功能,使应用推荐页更加实用和用户友好。

2.1 添加应用点击详情功能

我们可以为应用列表项添加点击事件,点击后显示应用的详细信息:

@Component
export struct AdvancedMultiColumnList {
    // 应用数据
    private apps: AppType[] = [...] // 与基础版相同
    
    // 当前选中的应用索引
    @State selectedIndex: number = -1
    
    // 是否显示详情
    @State showDetail: boolean = false
    
    build() {
        Stack() {
            Column() {
                // 标题栏和底部导航栏(与基础版相同)
                
                // 多列应用列表
                List() {
                    ForEach(this.apps, (app:AppType, index: number) => {
                        ListItem() {
                            Column() {
                                // 应用图标
                                Image(app.icon)
                                    .width(60)
                                    .height(60)
                                    .borderRadius(12)

                                // 应用名称
                                Text(app.name)
                                    .width(100)
                                    .fontSize(14)
                                    .margin({ top: 8 })
                                    .maxLines(1)
                                    .textOverflow({ overflow: TextOverflow.Ellipsis })

                                // 下载量
                                Text(app.downloads)
                                    .width(100)
                                    .fontSize(12)
                                    .fontColor('#666666')
                                    .margin({ top: 4 })
                            }
                            .alignItems(HorizontalAlign.Center)
                            .width('100%')
                            .padding(12)
                            .onClick(() => {
                                this.selectedIndex = index
                                this.showDetail = true
                            })
                        }
                        .width('33%') // 每个项目占用1/3的宽度
                        .padding(8)
                    })
                }
                // List属性设置(与基础版相同)
            }
            
            // 应用详情层
            if (this.showDetail && this.selectedIndex >= 0) {
                this.AppDetail()
            }
        }
        .width('100%')
        .height('100%')
    }
    
    @Builder AppDetail() {
        Column() {
            // 详情顶栏
            Row() {
                Button({
                    type: ButtonType.Circle,
                    stateEffect: true
                }) {
                    Image($r('app.media.ic_back'))
                        .width(24)
                        .height(24)
                }
                .width(40)
                .height(40)
                .backgroundColor('rgba(0, 0, 0, 0.05)')
                .margin({ right: 16 })
                .onClick(() => {
                    this.showDetail = false
                })
                
                Text(this.apps[this.selectedIndex].name)
                    .fontSize(20)
                    .fontWeight(FontWeight.Bold)
            }
            .width('100%')
            .height(56)
            .padding({ left: 16, right: 16 })
            .backgroundColor('#FFFFFF')
            
            // 应用详情内容
            Scroll() {
                Column() {
                    // 应用头部信息
                    Row() {
                        Image(this.apps[this.selectedIndex].icon)
                            .width(80)
                            .height(80)
                            .borderRadius(16)
                            .margin({ right: 16 })
                        
                        Column() {
                            Text(this.apps[this.selectedIndex].name)
                                .fontSize(18)
                                .fontWeight(FontWeight.Bold)
                            
                            Text(this.apps[this.selectedIndex].downloads)
                                .fontSize(14)
                                .fontColor('#666666')
                                .margin({ top: 4 })
                            
                            Row() {
                                Button('安装')
                                    .width(100)
                                    .height(36)
                                    .backgroundColor('#007DFF')
                                    .margin({ top: 8 })
                            }
                        }
                        .alignItems(HorizontalAlign.Start)
                        .layoutWeight(1)
                    }
                    .width('100%')
                    .padding(16)
                    
                    // 应用截图
                    Text('应用截图')
                        .fontSize(16)
                        .fontWeight(FontWeight.Bold)
                        .width('100%')
                        .padding({ left: 16, top: 16, bottom: 8 })
                    
                    List() {
                        ForEach([1, 2, 3, 4], (item) => {
                            ListItem() {
                                // 模拟截图
                                Column() {
                                    Text(`截图 ${item}`)
                                        .width('100%')
                                        .height('100%')
                                        .textAlign(TextAlign.Center)
                                        .backgroundColor('#F0F0F0')
                                }
                                .width(240)
                                .height(400)
                                .borderRadius(8)
                            }
                            .padding({ right: 12 })
                        })
                    }
                    .width('100%')
                    .height(420)
                    .listDirection(Axis.Horizontal)
                    .padding({ left: 16 })
                    
                    // 应用介绍
                    Text('应用介绍')
                        .fontSize(16)
                        .fontWeight(FontWeight.Bold)
                        .width('100%')
                        .padding({ left: 16, top: 16, bottom: 8 })
                    
                    Text('这是一个示例应用介绍,描述应用的主要功能和特点。实际应用中,这里会显示应用的详细介绍信息,包括功能特点、使用方法、更新日志等内容。')
                        .fontSize(14)
                        .width('100%')
                        .padding({ left: 16, right: 16, bottom: 16 })
                }
            }
            .layoutWeight(1)
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#FFFFFF')
    }
}

在这个示例中:

  1. 添加了两个@State装饰的状态变量:selectedIndex和showDetail
  2. 为列表项的Column容器添加了onClick事件处理函数,点击时更新selectedIndex并显示详情
  3. 使用Stack组件作为根容器,可以在应用列表上方显示详情层
  4. 添加了AppDetail Builder函数,用于构建应用详情界面
  5. 在详情界面中,使用Scroll组件使内容可滚动,并添加了应用头部信息、截图和介绍等内容

2.2 实现应用分类功能

我们可以为应用列表添加分类功能,按照不同类别展示应用:

@Component
export struct AdvancedMultiColumnList {
    // 应用数据
    private apps: AppType[] = [...] // 与基础版相同
    
    // 应用分类
    private categories: string[] = ['全部', '游戏', '工具', '社交', '教育', '娱乐']
    
    // 当前选中的分类索引
    @State currentCategoryIndex: number = 0
    
    // 根据分类筛选应用
    private getFilteredApps(): AppType[] {
        if (this.currentCategoryIndex === 0) {
            return this.apps // 返回全部应用
        }
        
        // 模拟根据分类筛选应用
        // 实际应用中,应该根据应用的分类属性进行筛选
        const categoryApps: AppType[] = []
        for (let i = 0; i < this.apps.length; i++) {
            if (i % this.categories.length === this.currentCategoryIndex) {
                categoryApps.push(this.apps[i])
            }
        }
        return categoryApps
    }
    
    build() {
        Column() {
            // 标题栏(与基础版相同)
            
            // 分类选择器
            List() {
                ForEach(this.categories, (category: string, index: number) => {
                    ListItem() {
                        Text(category)
                            .fontSize(14)
                            .fontColor(this.currentCategoryIndex === index ? '#007DFF' : '#333333')
                            .fontWeight(this.currentCategoryIndex === index ? FontWeight.Bold : FontWeight.Normal)
                            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
                            .backgroundColor(this.currentCategoryIndex === index ? '#E6F2FF' : 'transparent')
                            .borderRadius(16)
                            .onClick(() => {
                                this.currentCategoryIndex = index
                            })
                    }
                    .padding({ right: 8 })
                })
            }
            .width('100%')
            .height(48)
            .listDirection(Axis.Horizontal)
            .padding({ left: 16 })
            
            // 多列应用列表
            List() {
                ForEach(this.getFilteredApps(), (app:AppType) => {
                    // 列表项内容(与基础版相同)
                })
            }
            // List属性设置(与基础版相同)
            
            // 底部导航栏(与基础版相同)
        }
        // Column属性设置(与基础版相同)
    }
}

在这个示例中:

  1. 添加了categories数组,包含不同的应用分类
  2. 添加了currentCategoryIndex状态变量,用于跟踪当前选中的分类
  3. 实现了getFilteredApps方法,根据当前选中的分类筛选应用
  4. 添加了一个水平方向的List组件作为分类选择器,显示不同的分类选项
  5. 为分类选项添加了点击事件处理函数,点击时更新currentCategoryIndex
  6. 在应用列表中使用getFilteredApps方法获取筛选后的应用数据

2.3 实现应用搜索功能

我们可以为应用列表添加搜索功能,根据关键字搜索应用:

@Component
export struct AdvancedMultiColumnList {
    // 应用数据
    private apps: AppType[] = [...] // 与基础版相同
    
    // 搜索关键字
    @State searchKey: string = ''
    
    // 根据关键字搜索应用
    private getSearchedApps(): AppType[] {
        if (!this.searchKey) {
            return this.apps // 返回全部应用
        }
        
        // 根据关键字搜索应用名称
        return this.apps.filter(app => 
            app.name.toLowerCase().includes(this.searchKey.toLowerCase())
        )
    }
    
    build() {
        Column() {
            // 标题栏
            Row() {
                Text('应用推荐')
                    .fontSize(24)
                    .fontWeight(FontWeight.Bold)
            }
            .width('100%')
            .height(56)
            .padding({ left: 16 })
            .backgroundColor('#F1F3F5')
            
            // 搜索框
            Row() {
                Image($r('app.media.ic_search'))
                    .width(24)
                    .height(24)
                    .margin({ right: 8 })
                
                TextInput({ placeholder: '搜索应用', text: this.searchKey })
                    .layoutWeight(1)
                    .height(40)
                    .backgroundColor('transparent')
                    .onChange((value: string) => {
                        this.searchKey = value
                    })
                
                if (this.searchKey) {
                    Button({
                        type: ButtonType.Circle,
                        stateEffect: true
                    }) {
                        Image($r('app.media.ic_clear'))
                            .width(16)
                            .height(16)
                    }
                    .width(24)
                    .height(24)
                    .backgroundColor('rgba(0, 0, 0, 0.05)')
                    .margin({ left: 8 })
                    .onClick(() => {
                        this.searchKey = ''
                    })
                }
            }
            .width('100%')
            .height(56)
            .padding({ left: 16, right: 16 })
            .backgroundColor('#FFFFFF')
            .borderColor('#E5E5E5')
            .borderWidth({ bottom: 1 })
            
            // 多列应用列表
            if (this.getSearchedApps().length > 0) {
                List() {
                    ForEach(this.getSearchedApps(), (app:AppType) => {
                        // 列表项内容(与基础版相同)
                    })
                }
                // List属性设置(与基础版相同)
            } else {
                // 无搜索结果提示
                Column() {
                    Image($r('app.media.ic_no_result'))
                        .width(100)
                        .height(100)
                        .margin({ bottom: 16 })
                    
                    Text('没有找到相关应用')
                        .fontSize(16)
                        .fontColor('#999999')
                }
                .width('100%')
                .layoutWeight(1)
                .justifyContent(FlexAlign.Center)
            }
            
            // 底部导航栏(与基础版相同)
        }
        // Column属性设置(与基础版相同)
    }
}

在这个示例中:

  1. 添加了searchKey状态变量,用于存储搜索关键字
  2. 实现了getSearchedApps方法,根据搜索关键字筛选应用
  3. 添加了搜索框,包含搜索图标、TextInput组件和清除按钮
  4. 为TextInput组件添加了onChange事件处理函数,更新searchKey
  5. 为清除按钮添加了onClick事件处理函数,清空searchKey
  6. 根据搜索结果是否为空显示不同的内容,如果为空则显示无搜索结果提示

三、多列列表的布局优化

除了基本的布局外,我们还可以对多列列表进行更多的布局优化,使其更加美观和实用。

3.1 响应式列数设置

我们可以根据屏幕宽度动态调整列表的列数,使其在不同设备上都能有良好的显示效果:

@Component
export struct AdvancedMultiColumnList {
    // 应用数据和其他属性
    
    // 屏幕宽度
    @State screenWidth: number = 0
    
    // 根据屏幕宽度计算列数
    private getColumnCount(): number {
        if (this.screenWidth <= 320) {
            return 2 // 窄屏设备显示2列
        } else if (this.screenWidth <= 600) {
            return 3 // 中等宽度设备显示3列
        } else {
            return 4 // 宽屏设备显示4列
        }
    }
    
    // 根据列数计算列表项宽度
    private getItemWidth(): string {
        const columnCount = this.getColumnCount()
        return `${100 / columnCount}%`
    }
    
    aboutToAppear() {
        // 获取屏幕宽度
        this.screenWidth = px2vp(this.context.width)
    }
    
    build() {
        Column() {
            // 标题栏和其他内容(与基础版相同)
            
            // 多列应用列表
            List() {
                ForEach(this.apps, (app:AppType) => {
                    ListItem() {
                        // 列表项内容(与基础版相同)
                    }
                    .width(this.getItemWidth()) // 根据列数设置宽度
                    .padding(8)
                })
            }
            .width('100%')
            .layoutWeight(1)
            .lanes(this.getColumnCount()) // 根据屏幕宽度设置列数
            .divider({ // 设置网格分割线
                strokeWidth: 1,
                color: '#E5E5E5',
                startMargin: 8,
                endMargin: 8
            })
            
            // 底部导航栏(与基础版相同)
        }
        // Column属性设置(与基础版相同)
    }
}

在这个示例中:

  1. 添加了screenWidth状态变量,用于存储屏幕宽度
  2. 实现了getColumnCount方法,根据屏幕宽度计算列数
  3. 实现了getItemWidth方法,根据列数计算列表项宽度
  4. 在aboutToAppear生命周期函数中获取屏幕宽度
  5. 在List组件的lanes属性和ListItem的width属性中使用计算的值

3.2 列表项样式优化

我们可以优化列表项的样式,使其更加美观:

ListItem() {
    Column() {
        // 应用图标容器
        Stack() {
            // 应用图标
            Image(app.icon)
                .width(60)
                .height(60)
                .borderRadius(12)
            
            // 下载量标签
            Text(app.downloads)
                .fontSize(10)
                .fontColor('#FFFFFF')
                .backgroundColor('rgba(0, 0, 0, 0.6)')
                .borderRadius({ bottomLeft: 12, bottomRight: 12 })
                .width(60)
                .height(20)
                .textAlign(TextAlign.Center)
                .position({ x: 0, y: 40 })
        }
        .width(60)
        .height(60)

        // 应用名称
        Text(app.name)
            .width(100)
            .fontSize(14)
            .margin({ top: 8 })
            .maxLines(1)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        
        // 安装按钮
        Button('安装')
            .width(80)
            .height(28)
            .fontSize(12)
            .backgroundColor('#007DFF')
            .margin({ top: 8 })
    }
    .alignItems(HorizontalAlign.Center)
    .width('100%')
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
    .shadow({
        radius: 4,
        color: 'rgba(0, 0, 0, 0.1)',
        offsetX: 0,
        offsetY: 2
    })
}

在这个示例中:

  1. 使用Stack组件作为应用图标的容器,可以在图标上方叠加下载量标签
  2. 为下载量标签设置半透明背景和圆角,使其更加美观
  3. 添加了安装按钮,使列表项更加实用
  4. 为整个列表项添加了背景色、圆角和阴影效果,使其看起来像一个卡片

3.3 列表加载优化

在实际应用中,应用列表可能包含大量数据,我们可以实现分页加载功能,提高列表的加载性能:

@Component
export struct AdvancedMultiColumnList {
    // 应用数据
    private allApps: AppType[] = [...] // 所有应用数据
    
    // 当前显示的应用数据
    @State apps: AppType[] = []
    
    // 每页加载的应用数量
    private pageSize: number = 12
    
    // 当前页码
    @State currentPage: number = 1
    
    // 是否正在加载
    @State isLoading: boolean = false
    
    // 是否还有更多数据
    @State hasMore: boolean = true
    
    // 加载下一页数据
    private loadNextPage() {
        if (this.isLoading || !this.hasMore) {
            return
        }
        
        this.isLoading = true
        
        // 模拟网络请求延迟
        setTimeout(() => {
            const start = (this.currentPage - 1) * this.pageSize
            const end = this.currentPage * this.pageSize
            const newApps = this.allApps.slice(start, end)
            
            if (newApps.length > 0) {
                this.apps = [...this.apps, ...newApps]
                this.currentPage++
                this.hasMore = end < this.allApps.length
            } else {
                this.hasMore = false
            }
            
            this.isLoading = false
        }, 1000)
    }
    
    aboutToAppear() {
        // 初始加载第一页数据
        this.loadNextPage()
    }
    
    build() {
        Column() {
            // 标题栏和其他内容(与基础版相同)
            
            // 多列应用列表
            List() {
                ForEach(this.apps, (app:AppType) => {
                    // 列表项内容(与基础版相同)
                })
                
                // 加载更多
                if (this.hasMore || this.isLoading) {
                    ListItem() {
                        Column() {
                            if (this.isLoading) {
                                LoadingProgress()
                                    .width(24)
                                    .height(24)
                                
                                Text('正在加载...')
                                    .fontSize(14)
                                    .fontColor('#999999')
                                    .margin({ top: 8 })
                            } else {
                                Text('加载更多')
                                    .fontSize(14)
                                    .fontColor('#007DFF')
                            }
                        }
                        .width('100%')
                        .height(60)
                        .justifyContent(FlexAlign.Center)
                        .onClick(() => {
                            if (!this.isLoading) {
                                this.loadNextPage()
                            }
                        })
                    }
                    .width('100%')
                }
            }
            .width('100%')
            .layoutWeight(1)
            .lanes(3) // 设置为3列布局
            .divider({ // 设置网格分割线
                strokeWidth: 1,
                color: '#E5E5E5',
                startMargin: 8,
                endMargin: 8
            })
            .onReachEnd(() => {
                // 滚动到底部时自动加载下一页
                this.loadNextPage()
            })
            
            // 底部导航栏(与基础版相同)
        }
        // Column属性设置(与基础版相同)
    }
}

在这个示例中:

  1. 将所有应用数据存储在allApps属性中,当前显示的应用数据存储在apps状态变量中
  2. 添加了pageSize、currentPage、isLoading和hasMore属性,用于控制分页加载
  3. 实现了loadNextPage方法,用于加载下一页数据
  4. 在aboutToAppear生命周期函数中初始加载第一页数据
  5. 在列表底部添加了加载更多的ListItem,显示加载状态或加载更多按钮
  6. 为List组件添加了onReachEnd事件处理函数,滚动到底部时自动加载下一页

总结

在本篇教程中,我们深入探讨了HarmonyOS NEXT的多列列表的进阶特性和用法。 通过这些进阶技巧,我们可以构建更加功能丰富、交互友好的应用推荐页。这些知识点不仅适用于应用推荐页,也可以应用到其他类型的多列列表界面设计中,如商品展示、图片库等。

收藏00

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

全栈若城

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