暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

169.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局基础篇

原创 若城 2025-06-29
140

[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局基础篇

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

效果演示

在移动应用开发中,网格布局是展示内容的常用方式,特别是对于图片、卡片等元素的展示。HarmonyOS NEXT提供了强大的Grid组件,可以实现各种灵活的网格布局。本教程将详细讲解如何使用Grid组件实现动态网格布局,特别是瀑布流效果,这在社交媒体、图片分享等应用中非常常见。

1. 动态网格布局概述

动态网格布局是一种灵活的布局方式,可以根据内容自动调整元素的大小和位置。在本案例中,我们将实现一个类似于Pinterest、小红书等应用的瀑布流布局,展示不同高度的图片卡片。

1.1 瀑布流布局的特点

瀑布流布局具有以下特点:

特点 描述
多列展示 内容以多列方式排列,通常为2-3列
高度不一 每个元素可以有不同的高度,根据内容自动调整
紧凑排列 元素之间紧密排列,充分利用屏幕空间
无限加载 支持滚动加载更多内容

1.2 应用场景

瀑布流布局适用于以下场景:

  • 图片分享应用(如Pinterest、小红书)
  • 商品展示页面
  • 新闻卡片列表
  • 社交媒体信息流

2. 数据模型设计

在实现瀑布流布局之前,我们需要先定义数据模型。在本案例中,我们定义了PhotoItems接口来表示图片卡片的数据结构:

interface PhotoItems { id: number, imageUrl: Resource, title: string, author: string, authorAvatar: Resource, likes: number, comments: number, tags: string[], height: number, // 用于模拟不同高度的图片 isLiked: boolean, description: string }

这个数据模型包含了图片卡片所需的所有信息,包括:

  • 基本信息:ID、图片URL、标题、描述
  • 作者信息:作者名称、头像
  • 互动数据:点赞数、评论数、是否已点赞
  • 分类信息:标签数组
  • 布局信息:图片高度(用于瀑布流布局)

3. 页面结构设计

我们的瀑布流页面包含以下几个主要部分:

  1. 顶部导航栏:包含标题、搜索按钮和相机按钮
  2. 分类标签栏:用于筛选不同类别的内容
  3. 瀑布流网格:展示图片卡片
  4. 底部导航栏:包含首页、发现、发布、消息和我的等功能入口

3.1 组件状态管理

WaterfallGrid组件中,我们定义了以下状态变量:

@State photoItems: PhotoItems[] = [...] // 图片数据数组 @State selectedCategory: string = '全部' // 当前选中的分类 @State searchKeyword: string = '' // 搜索关键词 @State showSearch: boolean = false // 是否显示搜索框

这些状态变量用于控制页面的显示和交互:

  • photoItems:存储所有图片卡片数据
  • selectedCategory:记录当前选中的分类标签
  • searchKeyword:存储用户输入的搜索关键词
  • showSearch:控制搜索框的显示和隐藏

4. 瀑布流网格实现

4.1 Grid组件基础配置

在HarmonyOS NEXT中,我们使用Grid组件来实现瀑布流布局。以下是Grid组件的基本配置:

Grid() { ForEach(this.getFilteredPhotos(), (item: PhotoItems) => { GridItem() { // 卡片内容 } }) } .columnsTemplate('1fr 1fr') // 两列瀑布流 .rowsGap(16) // 行间距 .columnsGap(12) // 列间距 .width('100%') .layoutWeight(1) .padding({ left: 16, right: 16, bottom: 16 }) .backgroundColor('#F8F8F8') .onScrollIndex((first: number) => { console.log(`当前显示的第一个图片索引: ${first}`) })

关键属性说明:

属性 说明
columnsTemplate 定义网格的列模板,'1fr 1fr’表示两列等宽
rowsGap 行间距,单位为vp
columnsGap 列间距,单位为vp
onScrollIndex 滚动事件回调,返回当前显示的第一个元素索引

4.2 GridItem内容设计

每个GridItem包含一个完整的图片卡片,结构如下:

GridItem() { Column() { // 图片部分 Stack({ alignContent: Alignment.TopEnd }) { Image(item.imageUrl) .width('100%') .height(item.height) .objectFit(ImageFit.Cover) .borderRadius({ topLeft: 12, topRight: 12 }) // 点赞按钮 Button() { Image(item.isLiked ? $r('app.media.heart_filled') : $r('app.media.heart_outline')) .width(20) .height(20) .fillColor(item.isLiked ? '#FF6B6B' : '#FFFFFF') } .width(36) .height(36) .borderRadius(18) .backgroundColor('rgba(0, 0, 0, 0.3)') .margin({ top: 8, right: 8 }) .onClick(() => { this.toggleLike(item.id) }) } // 内容区域 Column() { // 标题、描述、标签、作者信息等 } .padding(12) .alignItems(HorizontalAlign.Start) } .width('100%') .backgroundColor('#FFFFFF') .borderRadius(12) .shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 }) }

每个卡片包含以下部分:

  1. 图片区域:显示主图片,高度根据数据模型中的height属性动态设置
  2. 点赞按钮:位于图片右上角,可以切换点赞状态
  3. 内容区域:包含标题、描述、标签和作者信息等

4.3 卡片内容区域详解

内容区域包含多个部分,布局如下:

Column() { // 标题 Text(item.title) .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor('#333333') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%') .textAlign(TextAlign.Start) // 描述 Text(item.description) .fontSize(14) .fontColor('#666666') .maxLines(3) .textOverflow({ overflow: TextOverflow.Ellipsis }) .width('100%') .textAlign(TextAlign.Start) .margin({ top: 8 }) // 标签 if (item.tags.length > 0) { Row() { ForEach(item.tags.slice(0, 3), (tag: string, index) => { Text(`#${tag}`) .fontSize(12) .fontColor('#007AFF') .backgroundColor('rgba(0, 122, 255, 0.1)') .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .borderRadius(8) .margin({ right: index < Math.min(item.tags.length, 3) - 1 ? 6 : 0 }) }) } .width('100%') .margin({ top: 12 }) } // 作者信息和互动数据 Row() { Row() { Image(item.authorAvatar) .width(24) .height(24) .borderRadius(12) Text(item.author) .fontSize(12) .fontColor('#666666') .margin({ left: 6 }) } .layoutWeight(1) Row() { // 点赞数 Row() { Image($r('app.media.big21')) .width(14) .height(14) .fillColor('#FF6B6B') Text(this.formatNumber(item.likes)) .fontSize(12) .fontColor('#666666') .margin({ left: 2 }) } .margin({ right: 12 }) // 评论数 Row() { Image($r('app.media.search_icon')) .width(14) .height(14) .fillColor('#999999') Text(this.formatNumber(item.comments)) .fontSize(12) .fontColor('#666666') .margin({ left: 2 }) } } } .width('100%') .margin({ top: 12 }) }

内容区域的布局特点:

  1. 标题和描述:使用Text组件显示,设置最大行数和溢出处理
  2. 标签:使用ForEach循环渲染标签,最多显示3个
  3. 作者信息:左侧显示作者头像和名称
  4. 互动数据:右侧显示点赞数和评论数,使用formatNumber方法格式化数字

5. 数据过滤与交互功能

5.1 分类筛选功能

我们实现了分类标签栏,用于筛选不同类别的内容:

Scroll() { Row() { ForEach(this.categories, (category: string, index) => { Button(category) .fontSize(14) .fontColor(this.selectedCategory === category ? '#FFFFFF' : '#333333') .backgroundColor(this.selectedCategory === category ? '#007AFF' : '#F0F0F0') .borderRadius(16) .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .margin({ right: index < this.categories.length - 1 ? 12 : 0 }) .onClick(() => { this.selectedCategory = category }) }) } } .scrollable(ScrollDirection.Horizontal) .scrollBar(BarState.Off) .width('100%') .padding({ left: 16, right: 16, bottom: 16 }) .backgroundColor('#FFFFFF')

当用户点击分类标签时,会更新selectedCategory状态,触发界面重新渲染,显示筛选后的内容。

5.2 搜索功能

搜索功能通过顶部导航栏中的搜索按钮触发,显示搜索输入框:

Row() { Image($r('app.media.search_icon')) .width(20) .height(20) .fillColor('#999999') .margin({ left: 12 }) TextInput({ placeholder: '搜索图片、作者或标签' }) .fontSize(16) .backgroundColor('transparent') .border({ width: 0 }) .layoutWeight(1) .margin({ left: 8, right: 12 }) .onChange((value: string) => { this.searchKeyword = value }) }

当用户输入搜索关键词时,会更新searchKeyword状态,触发界面重新渲染,显示搜索结果。

5.3 数据过滤方法

我们实现了getFilteredPhotos方法,用于根据分类和搜索关键词过滤数据:

getFilteredPhotos(): PhotoItems[] { let filtered = this.photoItems if (this.selectedCategory !== '全部') { filtered = filtered.filter(item => item.tags.some(tag => tag.includes(this.selectedCategory)) ) } if (this.searchKeyword.trim() !== '') { filtered = filtered.filter(item => item.title.includes(this.searchKeyword) || item.author.includes(this.searchKeyword) || item.tags.some(tag => tag.includes(this.searchKeyword)) ) } return filtered }

这个方法首先根据选中的分类进行过滤,然后再根据搜索关键词进行过滤,最终返回符合条件的数据。

5.4 点赞功能

我们实现了toggleLike方法,用于切换图片的点赞状态:

toggleLike(id: number) { const item = this.photoItems.find(item => item.id === id) if (item) { item.isLiked = !item.isLiked item.likes += item.isLiked ? 1 : -1 } }

当用户点击点赞按钮时,会调用这个方法,更新对应图片的点赞状态和点赞数。

6. 辅助功能实现

6.1 数字格式化

为了美观地显示点赞数和评论数,我们实现了formatNumber方法:

formatNumber(num: number): string { if (num >= 1000) { return (num / 1000).toFixed(1) + 'k' } return num.toString() }

这个方法将大于等于1000的数字转换为带k的形式,例如1234转换为1.2k。

总结

本教程详细讲解了如何使用HarmonyOS NEXT的Grid组件实现动态网格布局,特别是瀑布流效果。我们从数据模型设计、页面结构设计、Grid组件配置、GridItem内容设计等方面进行了详细讲解,并实现了分类筛选、搜索、点赞等交互功能。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论