首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >166. [HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局基础篇

166. [HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局基础篇

作者头像
全栈若城
发布2025-08-10 08:12:30
发布2025-08-10 08:12:30
18000
代码可运行
举报
文章被收录于专栏:若城技术专栏若城技术专栏
运行总次数:0
代码可运行

[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局基础篇

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

效果演示

1. 引言

在移动应用开发中,网格布局是一种常见且实用的UI布局方式,特别适合展示图片、卡片等内容。当网格内容较多时,需要结合滚动功能,让用户能够流畅地浏览所有内容。本教程将详细讲解HarmonyOS NEXT中可滚动网格布局的实现方法,通过一个应用商店首页的案例,帮助开发者掌握Grid组件与Scroller的结合使用技巧。

2. 可滚动网格布局概述

2.1 什么是可滚动网格布局?

可滚动网格布局是指使用Grid组件作为容器,并通过Scroller控制器实现内容滚动的布局方式。当网格内容超出屏幕显示范围时,用户可以通过滑动操作查看更多内容。这种布局方式特别适合展示大量同类型但又各自独立的内容,如应用列表、商品展示、图片库等。

2.2 Grid与Scroller的关系

在HarmonyOS NEXT中,Grid是网格容器组件,用于创建网格布局;而Scroller是滚动控制器,可以绑定到Grid等容器组件上,控制其滚动行为。两者结合使用,可以实现内容丰富、交互流畅的可滚动网格界面。

组件/控制器

作用

主要特性

Grid

网格容器组件

行列布局、间距控制、模板定义

GridItem

网格子项组件

内容展示、事件处理、样式定义

Scroller

滚动控制器

滚动控制、事件监听、滚动位置管理

3. 案例分析:应用商店首页

本教程以一个应用商店首页为例,展示如何实现可滚动网格布局。该页面包含顶部搜索栏、应用分类标签、推荐应用网格列表和底部导航栏。

3.1 页面结构概览
代码语言:javascript
代码运行次数:0
运行
复制
Column
├── 顶部搜索栏 (Row)
├── 应用分类标签 (Row + ForEach)
├── 推荐应用标题 (Row)
├── 推荐应用网格 (Grid + ForEach + GridItem)
└── 底部导航栏 (Row)
3.2 数据模型定义

在实现可滚动网格布局之前,首先需要定义数据模型,用于存储和管理网格中显示的内容。在本案例中,我们定义了两个接口:Category(应用分类)和FeaturedApp(推荐应用)。

代码语言:javascript
代码运行次数:0
运行
复制
interface Category {
    id: number,
    name: string,
    icon: Resource,
    color: string
}

interface FeaturedApp {
    id: number,
    name: string,
    developer: string,
    icon: Resource,
    rating: number,
    downloads: string,
    size: string,
    category: string,
    isFree: boolean,
    screenshots: Resource[]
}

这两个接口定义了应用分类和推荐应用的数据结构,包含了展示所需的各种属性。在组件中,我们使用@State装饰器定义了categories和featuredApps两个状态变量,用于存储实际数据。

4. 实现可滚动网格布局

4.1 创建滚动控制器

首先,需要创建一个Scroller实例,用于控制Grid组件的滚动行为:

代码语言:javascript
代码运行次数:0
运行
复制
private scroller: Scroller = new Scroller()
4.2 构建网格容器

在build方法中,我们使用Grid组件创建网格容器,并将scroller绑定到Grid上:

代码语言:javascript
代码运行次数:0
运行
复制
Grid(this.scroller) {
    // 网格内容
}
.columnsTemplate('1fr') // 单列布局
.rowsGap(16)
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, bottom: 16 })
.backgroundColor('#F8F8F8')
.onScrollIndex((first: number) => {
    console.log(`当前显示的第一个应用索引: ${first}`)
})

这里我们设置了Grid的列模板为’1fr’(单列布局),行间距为16,宽度为100%,并使用layoutWeight(1)使Grid占据剩余空间。同时,我们还设置了内边距和背景色,并添加了onScrollIndex事件监听,用于记录当前显示的第一个应用索引。

4.3 创建网格项

在Grid容器中,我们使用ForEach循环遍历featuredApps数组,为每个应用创建一个GridItem:

代码语言:javascript
代码运行次数:0
运行
复制
ForEach(this.featuredApps, (app:FeaturedApp) => {
    GridItem() {
        Column() {
            // 应用图标和基本信息
            Row() {
                // 应用图标
                Image(app.icon)
                    .width(60)
                    .height(60)
                    .borderRadius(12)
                    .shadow({
                        radius: 4,
                        color: 'rgba(0, 0, 0, 0.2)',
                        offsetX: 0,
                        offsetY: 2
                    })

                // 应用信息
                Column() {
                    // 应用名称
                    Text(app.name)
                        .fontSize(16)
                        .fontWeight(FontWeight.Bold)
                        .fontColor('#333333')
                        .maxLines(1)
                        .textOverflow({ overflow: TextOverflow.Ellipsis })

                    // 开发者
                    Text(app.developer)
                        .fontSize(12)
                        .fontColor('#666666')
                        .margin({ top: 2 })

                    // 星级评分
                    this.StarRating(app.rating)

                    // 下载量和大小
                    Row() {
                        Text(app.downloads)
                            .fontSize(10)
                            .fontColor('#999999')

                        Text('•')
                            .fontSize(10)
                            .fontColor('#999999')
                            .margin({ left: 4, right: 4 })

                        Text(app.size)
                            .fontSize(10)
                            .fontColor('#999999')
                    }
                    .margin({ top: 4 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)
                .margin({ left: 12 })

                // 获取/购买按钮
                Button(app.isFree ? '获取' : '购买')
                    .fontSize(14)
                    .fontColor('#FFFFFF')
                    .backgroundColor('#007AFF')
                    .borderRadius(16)
                    .width(60)
                    .height(32)
            }
            .width('100%')
            .alignItems(VerticalAlign.Top)

            // 应用截图
            Row() {
                ForEach(app.screenshots.slice(0, 3), (screenshot:Resource, index) => {
                    Image(screenshot)
                        .width(80)
                        .height(140)
                        .objectFit(ImageFit.Cover)
                        .borderRadius(8)
                        .margin({ right: index < 2 ? 8 : 0 })
                })
            }
            .width('100%')
            .margin({ top: 16 })

            // 分类标签
            Row() {
                Text(app.category)
                    .fontSize(10)
                    .fontColor('#007AFF')
                    .backgroundColor('rgba(0, 122, 255, 0.1)')
                    .padding({ left: 8, right: 8, top: 4, bottom: 4 })
                    .borderRadius(8)

                if (!app.isFree) {
                    Text('付费')
                        .fontSize(10)
                        .fontColor('#FF9500')
                        .backgroundColor('rgba(255, 149, 0, 0.1)')
                        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
                        .borderRadius(8)
                        .margin({ left: 8 })
                }

                Blank()
            }
            .width('100%')
            .margin({ top: 12 })
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#FFFFFF')
        .borderRadius(16)
        .shadow({
            radius: 8,
            color: 'rgba(0, 0, 0, 0.1)',
            offsetX: 0,
            offsetY: 2
        })
    }
    .onClick(() => {
        console.log(`点击应用: ${app.name}`)
    })
})

每个GridItem包含一个Column,用于垂直排列应用信息。在Column中,我们依次展示应用的图标和基本信息、应用截图和分类标签。同时,我们还为GridItem添加了点击事件处理。

4.4 自定义构建器:星级评分

为了复用星级评分的UI逻辑,我们创建了一个自定义构建器StarRating:

代码语言:javascript
代码运行次数:0
运行
复制
@Builder
StarRating(rating: number) {
    Row() {
        ForEach([1,2,3,4,5], (star:number) => {
            Image(star <= rating ? $r('app.media.heart_filled') : $r('app.media.heart_outline'))
                .width(12)
                .height(12)
                .fillColor(star <= rating ? '#FFD700' : '#E0E0E0')
        })

        Text(rating.toString())
            .fontSize(12)
            .fontColor('#666666')
            .margin({ left: 4 })
    }
}

这个构建器接收一个rating参数,根据评分值显示对应数量的星星,并在右侧显示具体评分数值。

5. Grid组件关键属性解析

5.1 列模板与行间距
代码语言:javascript
代码运行次数:0
运行
复制
.columnsTemplate('1fr') // 单列布局
.rowsGap(16) // 行间距
  • columnsTemplate:定义网格的列模板,'1fr’表示单列布局,每列占据可用空间的一份。
  • rowsGap:定义行与行之间的间距,单位为vp。
5.2 滚动事件监听
代码语言:javascript
代码运行次数:0
运行
复制
.onScrollIndex((first: number) => {
    console.log(`当前显示的第一个应用索引: ${first}`)
})
  • onScrollIndex:当网格滚动时触发,参数first表示当前显示的第一个网格项的索引。

6. 滚动控制器(Scroller)的使用

6.1 创建与绑定
代码语言:javascript
代码运行次数:0
运行
复制
private scroller: Scroller = new Scroller()

// 在build方法中绑定到Grid
Grid(this.scroller) {
    // 网格内容
}
6.2 常用方法与属性

方法/属性

说明

示例

scrollTo

滚动到指定位置

scroller.scrollTo({xOffset: 0, yOffset: 100})

scrollEdge

滚动到边缘

scroller.scrollEdge(Edge.Top)

scrollPage

按页滚动

scroller.scrollPage({next: true})

currentOffset

获取当前滚动偏移量

let offset = scroller.currentOffset()

7. 布局技巧与最佳实践

7.1 网格布局设计原则
  1. 内容优先:根据内容特性选择合适的网格布局方式。
  2. 一致性:保持网格项的视觉一致性,包括大小、间距、样式等。
  3. 响应式:考虑不同屏幕尺寸下的显示效果,适当调整列数和大小。
  4. 交互反馈:为网格项添加适当的点击反馈,提升用户体验。
7.2 性能优化技巧
  1. 懒加载:对于大量数据,可以结合onScrollIndex实现懒加载。
  2. 合理分页:避免一次加载过多数据,可以实现分页加载。
  3. 图片优化:使用合适大小的图片,避免加载过大的图片资源。
7.3 常见问题解决方案

问题

解决方案

网格项大小不一致

使用固定的宽高比,或者在GridItem中设置minHeight

滚动不流畅

减少网格项的复杂度,优化图片加载

网格项内容溢出

使用maxLines和textOverflow控制文本显示

8. 总结

在下一篇教程中,我们将深入探讨可滚动网格布局的进阶技巧,包括多列布局、动态调整列数、网格项动画效果等内容,敬请期待!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局基础篇
    • 效果演示
    • 1. 引言
    • 2. 可滚动网格布局概述
      • 2.1 什么是可滚动网格布局?
      • 2.2 Grid与Scroller的关系
    • 3. 案例分析:应用商店首页
      • 3.1 页面结构概览
      • 3.2 数据模型定义
    • 4. 实现可滚动网格布局
      • 4.1 创建滚动控制器
      • 4.2 构建网格容器
      • 4.3 创建网格项
      • 4.4 自定义构建器:星级评分
    • 5. Grid组件关键属性解析
      • 5.1 列模板与行间距
      • 5.2 滚动事件监听
    • 6. 滚动控制器(Scroller)的使用
      • 6.1 创建与绑定
      • 6.2 常用方法与属性
    • 7. 布局技巧与最佳实践
      • 7.1 网格布局设计原则
      • 7.2 性能优化技巧
      • 7.3 常见问题解决方案
    • 8. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档