[HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(上)

2025-06-06 22:45:54
104次阅读
0个评论

[HarmonyOS NEXT 实战案例五] 社交应用照片墙网格布局(上)

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

效果演示

img_01838240.png

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组件为实现网格布局提供了简单而强大的方式,特别适合照片墙、商品列表等场景。通过本教程,你应该已经掌握了使用这些组件创建复杂网格布局的基本技巧,可以根据实际需求进行灵活调整和扩展。

收藏00

登录 后评论。没有帐号? 注册 一个。