[HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(上)
[HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(上)
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 概述
社交应用中的照片墙是一种常见的UI设计模式,它以网格形式展示用户的照片集合,让用户可以浏览、分享和互动。本教程将详细讲解如何使用HarmonyOS NEXT的GridRow和GridCol组件实现一个美观、灵活的社交应用照片墙网格布局。
在本教程中,我们将学习如何设计照片墙的数据结构、如何使用GridRow和GridCol组件创建网格布局、如何实现照片卡片,以及如何处理不同尺寸的照片。通过本教程,你将掌握使用GridRow和GridCol组件实现复杂网格布局的技巧。
2. 数据结构设计
首先,我们需要设计照片墙的数据结构。照片墙中的每张照片通常包含以下信息:
- 照片资源:照片的资源路径
- 照片描述:照片的描述文字
- 点赞数:照片获得的点赞数量
- 评论数:照片获得的评论数量
- 用户信息:发布照片的用户信息
- 发布时间:照片的发布时间
- 照片尺寸:照片的宽高比例
根据这些需求,我们定义以下接口:
// 用户信息接口
interface User {
id: string; // 用户ID
name: string; // 用户名
avatar: string; // 用户头像
}
// 照片信息接口
interface Photo {
id: string; // 照片ID
resource: ResourceStr; // 照片资源
description: string; // 照片描述
likes: number; // 点赞数
comments: number; // 评论数
user: User; // 发布用户
publishTime: string; // 发布时间
aspectRatio: number; // 宽高比例
span: number; // 在网格中占据的列数
}
3. 数据准备
接下来,我们准备一些模拟数据用于展示照片墙:
// 模拟用户数据
private users: User[] = [
{ id: '1', name: '摄影爱好者', avatar: $r('app.media.avatar1') },
{ id: '2', name: '旅行达人', avatar: $r('app.media.avatar2') },
{ id: '3', name: '美食家', avatar: $r('app.media.avatar3') },
{ id: '4', name: '设计师', avatar: $r('app.media.avatar4') },
{ id: '5', name: '运动健将', avatar: $r('app.media.avatar5') }
];
// 模拟照片数据
private photos: Photo[] = [
{
id: '1',
resource: $r('app.media.photo1'),
description: '美丽的海滩日落,难忘的夏日时光',
likes: 256,
comments: 42,
user: this.users[0],
publishTime: '2小时前',
aspectRatio: 4/3,
span: 2
},
{
id: '2',
resource: $r('app.media.photo2'),
description: '城市天际线,繁华都市的夜景',
likes: 189,
comments: 23,
user: this.users[1],
publishTime: '3小时前',
aspectRatio: 1/1,
span: 1
},
{
id: '3',
resource: $r('app.media.photo3'),
description: '美味的早餐,开启美好的一天',
likes: 145,
comments: 18,
user: this.users[2],
publishTime: '5小时前',
aspectRatio: 3/4,
span: 1
},
{
id: '4',
resource: $r('app.media.photo4'),
description: '极简主义设计作品',
likes: 321,
comments: 56,
user: this.users[3],
publishTime: '昨天',
aspectRatio: 16/9,
span: 2
},
{
id: '5',
resource: $r('app.media.photo5'),
description: '晨跑时拍摄的日出',
likes: 210,
comments: 34,
user: this.users[4],
publishTime: '昨天',
aspectRatio: 1/1,
span: 1
},
{
id: '6',
resource: $r('app.media.photo6'),
description: '雨后的彩虹,大自然的奇迹',
likes: 278,
comments: 45,
user: this.users[0],
publishTime: '2天前',
aspectRatio: 3/2,
span: 2
},
{
id: '7',
resource: $r('app.media.photo7'),
description: '古老的建筑,历史的痕迹',
likes: 198,
comments: 29,
user: this.users[1],
publishTime: '2天前',
aspectRatio: 1/1,
span: 1
},
{
id: '8',
resource: $r('app.media.photo8'),
description: '精致的甜点,视觉与味觉的享受',
likes: 167,
comments: 21,
user: this.users[2],
publishTime: '3天前',
aspectRatio: 4/5,
span: 1
}
];
在这个数据结构中,我们为每张照片设置了不同的宽高比例(aspectRatio)和在网格中占据的列数(span),以便实现不同大小的照片布局。
4. 布局实现
4.1 整体布局结构
首先,我们创建一个社交应用照片墙的整体布局结构:
@Component
export struct SocialPhotoWall {
@State photos: Photo[] = []; // 照片数据
@State columns: number = 3; // 网格列数
aboutToAppear() {
// 初始化照片数据
this.photos = this.getPhotoData();
}
build() {
Column() {
// 顶部标题栏
this.TitleBar()
// 照片墙网格
this.PhotoGrid()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 获取照片数据方法
private getPhotoData(): Photo[] {
// 返回模拟的照片数据
return [...]; // 这里是上面定义的photos数组
}
// 以下是各个组件的构建器
@Builder
private TitleBar() {
// 实现标题栏
}
@Builder
private PhotoGrid() {
// 实现照片墙网格
}
@Builder
private PhotoCard(photo: Photo) {
// 实现照片卡片
}
}
4.2 标题栏实现
接下来,我们实现标题栏:
@Builder
private TitleBar() {
Row() {
Text('照片墙')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Blank()
Row() {
Image($r('app.media.ic_search'))
.width(24)
.height(24)
.margin({ right: 16 })
Image($r('app.media.ic_add'))
.width(24)
.height(24)
}
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
}
4.3 照片墙网格实现
现在,我们使用GridRow和GridCol组件实现照片墙网格:
@Builder
private PhotoGrid() {
Scroll() {
GridRow({
columns: this.columns,
gutter: { x: 8, y: 8 }
}) {
ForEach(this.photos, (photo: Photo) => {
GridCol({ span: photo.span }) {
this.PhotoCard(photo)
}
})
}
.width('100%')
.padding(8)
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
}
在这个实现中,我们使用GridRow组件创建一个网格布局,设置列数为this.columns(默认为3),并设置水平和垂直间距为8vp。然后,我们使用ForEach循环遍历照片数据,为每张照片创建一个GridCol,并设置其span属性为照片的span值,这样不同的照片可以占据不同数量的列。
4.4 照片卡片实现
最后,我们实现照片卡片:
@Builder
private PhotoCard(photo: Photo) {
Column() {
// 照片
Stack() {
Image(photo.resource)
.width('100%')
.aspectRatio(photo.aspectRatio)
.borderRadius({ topLeft: 8, topRight: 8 })
// 发布时间标签
Text(photo.publishTime)
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#80000000')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
.position({ x: 8, y: 8 })
}
.width('100%')
// 照片信息
Column() {
// 用户信息
Row() {
Image(photo.user.avatar)
.width(24)
.height(24)
.borderRadius(12)
Text(photo.user.name)
.fontSize(14)
.fontColor('#333333')
.margin({ left: 8 })
}
.width('100%')
.margin({ top: 8, bottom: 4 })
// 照片描述
if (photo.description) {
Text(photo.description)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 4, bottom: 8 })
.width('100%')
}
// 互动信息
Row() {
Row() {
Image($r('app.media.ic_like'))
.width(16)
.height(16)
Text(photo.likes.toString())
.fontSize(12)
.fontColor('#999999')
.margin({ left: 4 })
}
Row() {
Image($r('app.media.ic_comment'))
.width(16)
.height(16)
Text(photo.comments.toString())
.fontSize(12)
.fontColor('#999999')
.margin({ left: 4 })
}
.margin({ left: 16 })
Blank()
Image($r('app.media.ic_more'))
.width(16)
.height(16)
}
.width('100%')
.margin({ top: 8 })
}
.width('100%')
.padding({ left: 8, right: 8, bottom: 8 })
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
}
在照片卡片的实现中,我们使用Column组件作为容器,包含照片、用户信息、照片描述和互动信息等部分。对于照片部分,我们使用Image组件并设置aspectRatio属性为照片的宽高比例,以保持照片的原始比例。
5. GridRow和GridCol配置详解
在照片墙的实现中,我们使用了GridRow和GridCol组件来创建网格布局。下面详细讲解这两个组件的配置:
5.1 GridRow配置
GridRow({
columns: this.columns, // 设置列数为3
gutter: { x: 8, y: 8 } // 设置水平和垂直间距为8vp
})
GridRow组件的主要配置参数:
参数 | 类型 | 描述 |
---|---|---|
columns | number | GridRowColumnOptions | 设置网格布局的列数 |
gutter | Length | { x: Length, y: Length } | 设置网格项之间的间距 |
breakpoints | { value: Array<Length>, reference: BreakpointsReference } | 设置断点值 |
在照片墙的实现中,我们设置了固定的列数(3列)和固定的间距(水平和垂直间距都为8vp)。
5.2 GridCol配置
GridCol({ span: photo.span })
GridCol组件的主要配置参数:
参数 | 类型 | 描述 |
---|---|---|
span | number | GridColSpanOptions | 设置网格项占据的列数 |
offset | number | GridColOffsetOptions | 设置网格项的偏移列数 |
order | number | GridColOrderOptions | 设置网格项的排序 |
在照片墙的实现中,我们根据照片的span属性设置了不同的span值,使不同的照片可以占据不同数量的列,从而实现不同大小的照片布局。
6. 布局效果分析
通过上述实现,我们创建了一个灵活的社交应用照片墙网格布局。下面分析一下这个布局的效果:
6.1 不同大小的照片
通过设置不同的span值,我们实现了不同大小的照片布局:
- span=1:占据1列,适合小尺寸的照片
- span=2:占据2列,适合中等尺寸的照片
- span=3:占据3列(整行),适合大尺寸的照片
这种布局方式可以根据照片的重要性或美观度,给予不同的展示空间,增强视觉层次感。
6.2 照片原始比例保持
通过设置Image组件的aspectRatio属性为照片的宽高比例,我们保持了照片的原始比例,避免了照片被拉伸或压缩导致的变形。
6.3 信息展示的完整性
每个照片卡片不仅展示了照片本身,还包含了用户信息、照片描述、发布时间和互动信息等,提供了完整的社交体验。
7. 完整代码
以下是社交应用照片墙网格布局的完整代码:
// 用户信息接口
interface User {
id: string; // 用户ID
name: string; // 用户名
avatar: string; // 用户头像
}
// 照片信息接口
interface Photo {
id: string; // 照片ID
resource: ResourceStr; // 照片资源
description: string; // 照片描述
likes: number; // 点赞数
comments: number; // 评论数
user: User; // 发布用户
publishTime: string; // 发布时间
aspectRatio: number; // 宽高比例
span: number; // 在网格中占据的列数
}
@Component
export struct SocialPhotoWall {
@State photos: Photo[] = []; // 照片数据
@State columns: number = 3; // 网格列数
// 模拟用户数据
private users: User[] = [
{ id: '1', name: '摄影爱好者', avatar: $r('app.media.avatar1') },
{ id: '2', name: '旅行达人', avatar: $r('app.media.avatar2') },
{ id: '3', name: '美食家', avatar: $r('app.media.avatar3') },
{ id: '4', name: '设计师', avatar: $r('app.media.avatar4') },
{ id: '5', name: '运动健将', avatar: $r('app.media.avatar5') }
];
aboutToAppear() {
// 初始化照片数据
this.photos = this.getPhotoData();
}
build() {
Column() {
// 顶部标题栏
this.TitleBar()
// 照片墙网格
this.PhotoGrid()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 获取照片数据方法
private getPhotoData(): Photo[] {
return [
{
id: '1',
resource: $r('app.media.photo1'),
description: '美丽的海滩日落,难忘的夏日时光',
likes: 256,
comments: 42,
user: this.users[0],
publishTime: '2小时前',
aspectRatio: 4/3,
span: 2
},
{
id: '2',
resource: $r('app.media.photo2'),
description: '城市天际线,繁华都市的夜景',
likes: 189,
comments: 23,
user: this.users[1],
publishTime: '3小时前',
aspectRatio: 1/1,
span: 1
},
{
id: '3',
resource: $r('app.media.photo3'),
description: '美味的早餐,开启美好的一天',
likes: 145,
comments: 18,
user: this.users[2],
publishTime: '5小时前',
aspectRatio: 3/4,
span: 1
},
{
id: '4',
resource: $r('app.media.photo4'),
description: '极简主义设计作品',
likes: 321,
comments: 56,
user: this.users[3],
publishTime: '昨天',
aspectRatio: 16/9,
span: 2
},
{
id: '5',
resource: $r('app.media.photo5'),
description: '晨跑时拍摄的日出',
likes: 210,
comments: 34,
user: this.users[4],
publishTime: '昨天',
aspectRatio: 1/1,
span: 1
},
{
id: '6',
resource: $r('app.media.photo6'),
description: '雨后的彩虹,大自然的奇迹',
likes: 278,
comments: 45,
user: this.users[0],
publishTime: '2天前',
aspectRatio: 3/2,
span: 2
},
{
id: '7',
resource: $r('app.media.photo7'),
description: '古老的建筑,历史的痕迹',
likes: 198,
comments: 29,
user: this.users[1],
publishTime: '2天前',
aspectRatio: 1/1,
span: 1
},
{
id: '8',
resource: $r('app.media.photo8'),
description: '精致的甜点,视觉与味觉的享受',
likes: 167,
comments: 21,
user: this.users[2],
publishTime: '3天前',
aspectRatio: 4/5,
span: 1
}
];
}
@Builder
private TitleBar() {
Row() {
Text('照片墙')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Blank()
Row() {
Image($r('app.media.ic_search'))
.width(24)
.height(24)
.margin({ right: 16 })
Image($r('app.media.ic_add'))
.width(24)
.height(24)
}
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
}
@Builder
private PhotoGrid() {
Scroll() {
GridRow({
columns: this.columns,
gutter: { x: 8, y: 8 }
}) {
ForEach(this.photos, (photo: Photo) => {
GridCol({ span: photo.span }) {
this.PhotoCard(photo)
}
})
}
.width('100%')
.padding(8)
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%')
}
@Builder
private PhotoCard(photo: Photo) {
Column() {
// 照片
Stack() {
Image(photo.resource)
.width('100%')
.aspectRatio(photo.aspectRatio)
.borderRadius({ topLeft: 8, topRight: 8 })
// 发布时间标签
Text(photo.publishTime)
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#80000000')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
.position({ x: 8, y: 8 })
}
.width('100%')
// 照片信息
Column() {
// 用户信息
Row() {
Image(photo.user.avatar)
.width(24)
.height(24)
.borderRadius(12)
Text(photo.user.name)
.fontSize(14)
.fontColor('#333333')
.margin({ left: 8 })
}
.width('100%')
.margin({ top: 8, bottom: 4 })
// 照片描述
if (photo.description) {
Text(photo.description)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 4, bottom: 8 })
.width('100%')
}
// 互动信息
Row() {
Row() {
Image($r('app.media.ic_like'))
.width(16)
.height(16)
Text(photo.likes.toString())
.fontSize(12)
.fontColor('#999999')
.margin({ left: 4 })
}
Row() {
Image($r('app.media.ic_comment'))
.width(16)
.height(16)
Text(photo.comments.toString())
.fontSize(12)
.fontColor('#999999')
.margin({ left: 4 })
}
.margin({ left: 16 })
Blank()
Image($r('app.media.ic_more'))
.width(16)
.height(16)
}
.width('100%')
.margin({ top: 8 })
}
.width('100%')
.padding({ left: 8, right: 8, bottom: 8 })
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
}
}
8. 与其他布局方式的比较
8.1 与Flex布局的比较
布局方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
GridRow/GridCol | 1. 支持不同大小的网格项 2. 支持响应式布局 3. 配置简单,易于使用 |
1. 对于复杂的不规则布局支持有限 | 1. 照片墙 2. 商品列表 3. 卡片布局 |
Flex | 1. 灵活性高 2. 支持多种对齐方式 3. 支持动态调整 |
1. 实现网格布局较复杂 2. 需要更多的嵌套 |
1. 导航栏 2. 工具栏 3. 简单的行列布局 |
8.2 与List布局的比较
布局方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
GridRow/GridCol | 1. 支持二维网格布局 2. 支持不同大小的网格项 3. 支持响应式布局 |
1. 对于长列表的性能优化不如List | 1. 照片墙 2. 商品网格 3. 卡片布局 |
List | 1. 针对长列表优化 2. 支持懒加载 3. 支持列表项复用 |
1. 主要支持一维布局 2. 实现不同大小的列表项较复杂 |
1. 长列表 2. 聊天记录 3. 新闻列表 |
9. 总结
本教程详细讲解了如何使用HarmonyOS NEXT的GridRow和GridCol组件实现社交应用照片墙网格布局。通过合理设计数据结构、配置GridRow和GridCol组件,以及实现照片卡片,我们创建了一个灵活、美观的照片墙布局,能够展示不同大小的照片,并提供完整的社交信息展示。
GridRow和GridCol组件为实现网格布局提供了简单而强大的方式,特别适合照片墙、商品列表等场景。通过本教程,你应该已经掌握了使用这些组件创建复杂网格布局的基本技巧,可以根据实际需求进行灵活调整和扩展。
- 0回答
- 3粉丝
- 0关注
- [HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(下)
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(上)
- [HarmonyOS NEXT 实战案例六] 餐饮菜单网格布局(上)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(上)
- [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(上)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(上)
- [HarmonyOS NEXT 实战案例八] 电影票务网格布局(上)
- [HarmonyOS NEXT 实战案例四] 天气应用网格布局(下)
- [HarmonyOS NEXT 实战案例六] 餐饮菜单网格布局(下)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(下)
- [HarmonyOS NEXT 实战案例九] 旅游景点网格布局(下)
- [HarmonyOS NEXT 实战案例一] 电商首页商品网格布局(下)
- [HarmonyOS NEXT 实战案例八] 电影票务网格布局(下)
- [HarmonyOS NEXT 实战案例三] 音乐专辑网格展示(上)
- [HarmonyOS NEXT 实战案例二] 新闻资讯网格列表(上)