161. [HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:进阶篇
2025-06-30 22:42:13
102次阅读
0个评论
[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:进阶篇
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 状态管理与交互设计
1.1 状态变量设计
在照片相册应用中,我们使用 @State
装饰器定义了几个关键的状态变量:
@State currentTab: number = 0; // 当前选中的标签页(0: 相册, 1: 最近项目)
@State albums: Album[] = []; // 相册数据
@State recentPhotos: Recentphoto[] = []; // 最近照片数据
这些状态变量的变化会自动触发 UI 的更新,实现响应式的用户界面。
1.2 标签页切换交互
标签页切换是照片相册应用中的核心交互之一,我们通过以下方式实现:
Text('相册')
.fontSize(16)
.fontWeight(this.currentTab === 0 ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentTab === 0 ? '#007AFF' : '#8E8E93')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(16)
.backgroundColor(this.currentTab === 0 ? 'rgba(0, 122, 255, 0.1)' : 'transparent')
.onClick(() => {
this.currentTab = 0
})
标签页切换的交互设计包含以下几个方面:
交互元素 | 默认状态 | 选中状态 | 交互效果 |
---|---|---|---|
文本字重 | Normal | Bold | 选中标签文本加粗 |
文本颜色 | #8E8E93(灰色) | #007AFF(蓝色) | 选中标签文本变为蓝色 |
背景颜色 | transparent(透明) | rgba(0, 122, 255, 0.1)(淡蓝色) | 选中标签背景变为淡蓝色 |
通过这种设计,用户可以清晰地识别当前所在的标签页,提升用户体验。
1.3 条件渲染内容区域
根据当前选中的标签页,我们使用条件渲染显示不同的内容:
if (this.currentTab === 0) {
// 相册视图
Column() {
// 相册内容...
}
} else {
// 最近项目视图
Column() {
// 最近项目内容...
}
}
这种方式可以确保只渲染当前需要显示的内容,提高应用性能。
2. Grid 组件高级布局技巧
2.1 不同列数的网格布局
在照片相册应用中,我们为不同的内容区域设置了不同的列数:
// 相册视图 - 2列布局
Grid() {
// GridItem 内容...
}
.columnsTemplate('1fr 1fr') // 2列等宽布局
.columnsGap(16)
.rowsGap(16)
// 最近项目视图 - 3列布局
Grid() {
// GridItem 内容...
}
.columnsTemplate('1fr 1fr 1fr') // 3列等宽布局
.columnsGap(4)
.rowsGap(4)
不同列数的设计考虑了以下因素:
- 相册视图:每个相册包含的信息较多(封面、名称、照片数量、日期),需要更大的显示空间,因此采用 2 列布局
- 最近项目视图:照片本身是主要内容,信息较少,可以采用 3 列布局,在同样的空间内展示更多照片
2.2 自适应高度的 GridItem
在相册视图中,我们没有为 GridItem 设置固定高度,而是让其根据内容自适应:
GridItem() {
Column() {
// 相册封面 - 固定高度
Image(album.cover)
.width('100%')
.height(140)
.objectFit(ImageFit.Cover)
.borderRadius(12)
// 相册信息 - 自适应高度
Column() {
Text(album.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#000000')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
Text(`${album.count}张`)
.fontSize(14)
.fontColor('#8E8E93')
Blank()
Text(album.date)
.fontSize(12)
.fontColor('#8E8E93')
}
.width('100%')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.margin({ top: 12 })
}
.width('100%')
.padding(16)
}
这种设计的优势在于:
- 适应不同内容长度:相册名称可能有长有短,自适应高度可以确保所有内容都能完整显示
- 布局灵活性:不同 GridItem 可以有不同的高度,更符合实际内容的需求
- 维护简便:后续如果需要在 GridItem 中添加新的内容,不需要重新计算和调整高度
2.3 固定高度的 GridItem
在最近项目视图中,我们为 GridItem 设置了固定高度:
GridItem() {
Stack({ alignContent: Alignment.BottomStart }) {
Image(photo.image)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius(8)
// 位置信息覆盖层
if (photo.location) {
// 位置信息内容...
}
}
.width('100%')
.height(120)
}
固定高度的设计适用于以下场景:
- 内容统一:所有照片都使用相同的显示尺寸,视觉上更加整齐
- 性能优化:固定高度可以减少布局计算,提高渲染性能
- 网格美观:确保所有照片在网格中排列整齐,不会因为内容不同而导致高度不一
3. 组件复用与封装
3.1 提取可复用的 UI 组件
在照片相册应用中,我们可以将一些重复使用的 UI 部分提取为独立的函数或组件,以提高代码的可维护性:
// 提取相册卡片组件
@Builder
function AlbumCard(album: Album) {
Column() {
// 相册封面
Image(album.cover)
.width('100%')
.height(140)
.objectFit(ImageFit.Cover)
.borderRadius(12)
// 相册信息
Column() {
Text(album.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#000000')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
Text(`${album.count}张`)
.fontSize(14)
.fontColor('#8E8E93')
Blank()
Text(album.date)
.fontSize(12)
.fontColor('#8E8E93')
}
.width('100%')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.margin({ top: 12 })
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(16)
.shadow({
radius: 8,
color: 'rgba(0, 0, 0, 0.08)',
offsetX: 0,
offsetY: 2
})
}
// 使用提取的组件
Grid() {
ForEach(this.albums, (album:Album) => {
GridItem() {
AlbumCard(album)
}
.onClick(() => {
console.log(`打开相册: ${album.name}`)
})
})
}
3.2 封装交互逻辑
除了 UI 组件,我们还可以封装交互逻辑,使代码更加清晰:
// 封装标签切换逻辑
@Builder
function TabItem(text: string, index: number, currentIndex: number, onTabClick: () => void) {
Text(text)
.fontSize(16)
.fontWeight(currentIndex === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(currentIndex === index ? '#007AFF' : '#8E8E93')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(16)
.backgroundColor(currentIndex === index ? 'rgba(0, 122, 255, 0.1)' : 'transparent')
.onClick(onTabClick)
}
// 使用封装的标签组件
Row() {
TabItem('相册', 0, this.currentTab, () => { this.currentTab = 0 })
TabItem('最近项目', 1, this.currentTab, () => { this.currentTab = 1 })
.margin({ left: 12 })
}
4. 高级交互功能实现
4.1 照片位置信息显示
在最近项目视图中,我们为部分照片添加了位置信息显示:
// 位置信息覆盖层
if (photo.location) {
Row() {
Image($r('app.media.location_icon'))
.width(12)
.height(12)
.fillColor('#FFFFFF')
Text(photo.location)
.fontSize(10)
.fontColor('#FFFFFF')
.margin({ left: 4 })
}
.padding({ left: 6, right: 6, top: 4, bottom: 4 })
.backgroundColor('rgba(0, 0, 0, 0.6)')
.borderRadius(8)
.margin({ left: 8, bottom: 8 })
}
这种设计有以下特点:
- 条件渲染:只有当照片有位置信息时才显示覆盖层
- 半透明背景:使用
rgba(0, 0, 0, 0.6)
创建半透明黑色背景,确保白色文字在各种照片上都清晰可见 - 位置图标:添加位置图标,增强视觉识别性
- 圆角处理:使用
borderRadius
属性使覆盖层更加美观
4.2 点击事件处理
在照片相册应用中,我们为相册和照片添加了点击事件处理:
// 相册点击事件
GridItem() {
AlbumCard(album)
}
.onClick(() => {
console.log(`打开相册: ${album.name}`)
})
// 照片点击事件
GridItem() {
// 照片内容...
}
.onClick(() => {
console.log(`查看照片: ${photo.id}`)
})
在实际应用中,点击事件可以用于以下功能:
- 打开相册详情:点击相册卡片,导航到相册详情页面,显示该相册中的所有照片
- 查看照片大图:点击照片,打开照片查看器,支持放大、缩小、滑动等操作
- 编辑照片信息:长按照片,弹出编辑菜单,支持修改照片信息、删除照片等操作
5. 布局优化与视觉设计
5.1 阴影效果增强
在相册卡片中,我们使用了阴影效果增强视觉层次感:
.shadow({
radius: 8,
color: 'rgba(0, 0, 0, 0.08)',
offsetX: 0,
offsetY: 2
})
阴影效果的设计考虑了以下因素:
- 轻微阴影:使用低透明度的阴影色
rgba(0, 0, 0, 0.08)
,创造轻微的阴影效果,不会过于突兀 - 向下偏移:设置
offsetY: 2
,使阴影向下偏移,符合自然光照的视觉习惯 - 适当模糊:设置
radius: 8
,使阴影边缘适当模糊,更加自然
5.2 底部工具栏设计
底部工具栏的设计采用了以下技巧:
// 底部工具栏
Row() {
// 普通图标按钮
Column() {
Image($r('app.media.photos_icon'))
.width(28)
.height(28)
.fillColor('#007AFF')
Text('照片')
.fontSize(10)
.fontColor('#007AFF')
.margin({ top: 2 })
}
.layoutWeight(1)
// 中间的主要操作按钮
Button() {
Image($r('app.media.camera_icon'))
.width(32)
.height(32)
.fillColor('#FFFFFF')
}
.width(60)
.height(60)
.borderRadius(30)
.backgroundColor('#007AFF')
.shadow({
radius: 12,
color: 'rgba(0, 122, 255, 0.3)',
offsetX: 0,
offsetY: 4
})
// 其他图标按钮...
}
底部工具栏的设计特点:
- 突出主要操作:拍照按钮使用了更大的尺寸、圆形设计和阴影效果,使其成为视觉焦点
- 当前页面标识:当前页面对应的图标和文字使用蓝色,其他使用灰色,帮助用户识别当前位置
- 均匀分布:使用
layoutWeight(1)
使各个按钮均匀分布在工具栏中
5.3 响应式布局增强
为了适应不同屏幕尺寸,我们可以增强照片相册应用的响应式布局能力:
// 根据屏幕宽度动态调整列数和间距
@State screenWidth: number = 0;
aboutToAppear() {
// 获取屏幕宽度
this.screenWidth = px2vp(window.getWindowWidth());
}
build() {
// 根据屏幕宽度计算列数和间距
let albumColumns = '1fr 1fr';
let photoColumns = '1fr 1fr 1fr';
let albumGap = 16;
let photoGap = 4;
if (this.screenWidth >= 600) {
albumColumns = '1fr 1fr 1fr';
photoColumns = '1fr 1fr 1fr 1fr';
albumGap = 20;
photoGap = 8;
}
if (this.screenWidth >= 840) {
albumColumns = '1fr 1fr 1fr 1fr';
photoColumns = '1fr 1fr 1fr 1fr 1fr';
albumGap = 24;
photoGap = 12;
}
// 使用计算得到的值设置 Grid 布局
// ...
Grid() {
// 相册内容...
}
.columnsTemplate(albumColumns)
.columnsGap(albumGap)
.rowsGap(albumGap)
}
6. 总结
在本教程中,我们深入探讨了 HarmonyOS NEXT 中使用 Grid 组件实现照片相册应用的进阶技巧.
00
- 0回答
- 4粉丝
- 0关注
相关话题
- 160.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:基础篇
- 162.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:高级篇
- 167.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局进阶篇
- 173.[HarmonyOS NEXT 实战案例六:Grid] 响应式网格布局 - 进阶篇
- 170.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇
- 179.[HarmonyOS NEXT 实战案例八:Grid] 瀑布流网格布局进阶篇
- 185.[HarmonyOS NEXT 实战案例十:Grid] 仪表板网格布局进阶篇
- 176.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局进阶篇:高级布局与交互技巧
- [HarmonyOS NEXT 实战案例十八] 日历日程视图网格布局(进阶篇)
- 164.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局进阶篇:新闻应用高级布局与交互
- [HarmonyOS NEXT 实战案例十五] 电商分类导航网格布局(进阶篇)
- 182.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局进阶篇:打造高级交互与视觉体验
- 158.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局进阶篇:电商商品列表的交互与状态管理
- [HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(上)
- [HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(下)