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

161.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:进阶篇

原创 若城 2025-06-29
126

[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) }

这种设计的优势在于:

  1. 适应不同内容长度:相册名称可能有长有短,自适应高度可以确保所有内容都能完整显示
  2. 布局灵活性:不同 GridItem 可以有不同的高度,更符合实际内容的需求
  3. 维护简便:后续如果需要在 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) }

固定高度的设计适用于以下场景:

  1. 内容统一:所有照片都使用相同的显示尺寸,视觉上更加整齐
  2. 性能优化:固定高度可以减少布局计算,提高渲染性能
  3. 网格美观:确保所有照片在网格中排列整齐,不会因为内容不同而导致高度不一

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 }) }

这种设计有以下特点:

  1. 条件渲染:只有当照片有位置信息时才显示覆盖层
  2. 半透明背景:使用 rgba(0, 0, 0, 0.6) 创建半透明黑色背景,确保白色文字在各种照片上都清晰可见
  3. 位置图标:添加位置图标,增强视觉识别性
  4. 圆角处理:使用 borderRadius 属性使覆盖层更加美观

4.2 点击事件处理

在照片相册应用中,我们为相册和照片添加了点击事件处理:

// 相册点击事件 GridItem() { AlbumCard(album) } .onClick(() => { console.log(`打开相册: ${album.name}`) }) // 照片点击事件 GridItem() { // 照片内容... } .onClick(() => { console.log(`查看照片: ${photo.id}`) })

在实际应用中,点击事件可以用于以下功能:

  1. 打开相册详情:点击相册卡片,导航到相册详情页面,显示该相册中的所有照片
  2. 查看照片大图:点击照片,打开照片查看器,支持放大、缩小、滑动等操作
  3. 编辑照片信息:长按照片,弹出编辑菜单,支持修改照片信息、删除照片等操作

5. 布局优化与视觉设计

5.1 阴影效果增强

在相册卡片中,我们使用了阴影效果增强视觉层次感:

.shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.08)', offsetX: 0, offsetY: 2 })

阴影效果的设计考虑了以下因素:

  1. 轻微阴影:使用低透明度的阴影色 rgba(0, 0, 0, 0.08),创造轻微的阴影效果,不会过于突兀
  2. 向下偏移:设置 offsetY: 2,使阴影向下偏移,符合自然光照的视觉习惯
  3. 适当模糊:设置 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 }) // 其他图标按钮... }

底部工具栏的设计特点:

  1. 突出主要操作:拍照按钮使用了更大的尺寸、圆形设计和阴影效果,使其成为视觉焦点
  2. 当前页面标识:当前页面对应的图标和文字使用蓝色,其他使用灰色,帮助用户识别当前位置
  3. 均匀分布:使用 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 组件实现照片相册应用的进阶技巧.

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

评论