> 技术文档 > 170. [HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇_grid布局 3列 动态行

170. [HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇_grid布局 3列 动态行


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

文章目录

  • [HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇
    • 效果演示
    • 1. Grid组件高级配置
      • 1.1 列模板与行模板
        • 列模板(columnsTemplate)
        • 行模板(rowsTemplate)
      • 1.2 网格项位置控制
      • 1.3 网格滚动控制
      • 1.4 滚动事件处理
    • 2. 高级布局策略
      • 2.1 动态调整列数
      • 2.2 混合布局策略
      • 2.3 分组瀑布流
    • 3. 高级交互功能
      • 3.1 下拉刷新
      • 3.2 加载更多
      • 3.3 图片懒加载
    • 4. 动画与过渡效果
      • 4.1 网格项动画
      • 4.2 滚动过渡效果
    • 5. 自定义网格项组件
    • 总结

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

效果演示

170. [HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇_grid布局 3列 动态行

在基础篇中,我们学习了如何使用HarmonyOS NEXT的Grid组件实现基本的瀑布流布局。本篇教程将深入探讨动态网格布局的进阶技巧,包括Grid组件的高级配置、自定义布局策略、交互优化等内容,帮助你构建更加灵活、高效的瀑布流界面。

1. Grid组件高级配置

1.1 列模板与行模板

HarmonyOS NEXT的Grid组件提供了灵活的列模板和行模板配置,可以实现更复杂的布局效果。

列模板(columnsTemplate)

在基础篇中,我们使用了简单的两列等宽布局:

.columnsTemplate(\'1fr 1fr\')

实际上,columnsTemplate支持更复杂的配置:

配置方式 示例 说明 等宽列 ‘1fr 1fr 1fr’ 三列等宽布局 固定宽度列 ‘100px 1fr 100px’ 左右固定宽度,中间自适应 比例列 ‘2fr 1fr’ 左列占2份,右列占1份 混合配置 ‘100px 2fr 1fr’ 左侧固定宽度,中右按比例分配

我们可以根据实际需求调整列模板,例如实现三列瀑布流:

.columnsTemplate(\'1fr 1fr 1fr\')

或者实现左右两列不等宽的布局:

.columnsTemplate(\'1.5fr 1fr\')
行模板(rowsTemplate)

除了列模板,Grid还支持行模板配置:

.rowsTemplate(\'1fr 2fr 1fr\')

这在瀑布流布局中较少使用,因为瀑布流通常是根据内容高度自动调整的。但在某些特殊场景下,可以使用行模板实现特定的布局效果。

1.2 网格项位置控制

Grid组件允许精确控制GridItem的位置,通过以下属性:

属性 说明 rowStart 指定网格项起始行号 rowEnd 指定网格项结束行号 columnStart 指定网格项起始列号 columnEnd 指定网格项结束列号

例如,我们可以让某个特定的GridItem跨越两列:

GridItem() { // 内容}.columnStart(1).columnEnd(3)

这在实现特殊布局时非常有用,例如在瀑布流中插入一个横幅广告。

1.3 网格滚动控制

Grid组件提供了丰富的滚动控制属性:

Grid() { // GridItems}.scrollBar(BarState.Auto) // 自动显示滚动条.scrollBarColor(Color.Gray) // 滚动条颜色.scrollBarWidth(10) // 滚动条宽度.edgeEffect(EdgeEffect.Spring) // 滚动到边缘时的效果

滚动效果(edgeEffect)支持以下选项:

选项 说明 EdgeEffect.Spring 弹性效果,滚动到边缘时会有回弹 EdgeEffect.None 无效果 EdgeEffect.Fade 淡出效果

1.4 滚动事件处理

在基础篇中,我们使用了onScrollIndex事件来监听滚动位置:

.onScrollIndex((first: number) => { console.log(`当前显示的第一个图片索引: ${first}`)})

Grid组件还提供了更多滚动事件:

.onScroll((xOffset: number, yOffset: number) => { // 处理滚动事件,xOffset和yOffset是当前滚动位置}).onScrollStop(() => { // 滚动停止时触发}).onReachStart(() => { // 滚动到顶部时触发}).onReachEnd(() => { // 滚动到底部时触发,可用于实现加载更多})

这些事件可以用于实现各种高级功能,例如:

  • 滚动到底部时加载更多数据
  • 滚动时显示/隐藏顶部导航栏
  • 滚动停止时加载图片,提高性能

2. 高级布局策略

2.1 动态调整列数

在不同尺寸的设备上,我们可能需要显示不同数量的列。可以通过监听设备宽度动态调整列模板:

@State columnsCount: number = 2aboutToAppear() { // 获取设备宽度 const deviceWidth = px2vp(window.getWindowWidth()) // 根据宽度设置列数 if (deviceWidth < 600) { this.columnsCount = 2 // 窄屏设备显示2列 } else if (deviceWidth < 840) { this.columnsCount = 3 // 中等宽度设备显示3列 } else { this.columnsCount = 4 // 宽屏设备显示4列 }}build() { Grid() { // GridItems } .columnsTemplate(this.getColumnsTemplate())}getColumnsTemplate(): string { return \'1fr \'.repeat(this.columnsCount).trim()}

这样,我们的瀑布流布局就能够自适应不同尺寸的设备。

2.2 混合布局策略

在某些场景下,我们可能需要在瀑布流中插入特殊的布局元素,例如广告横幅、分组标题等。可以通过条件渲染和位置控制实现:

Grid() { // 特殊横幅(跨越所有列) GridItem() { Banner() } .columnSpan(this.columnsCount) // 跨越所有列 // 普通图片卡片 ForEach(this.getFilteredPhotos(), (item: PhotoItems) => { GridItem() { PhotoCard(item) } })}

2.3 分组瀑布流

我们可以实现分组的瀑布流布局,每个分组有自己的标题:

Grid() { ForEach(this.getPhotoGroups(), (group) => { // 分组标题(跨越所有列) GridItem() { Text(group.title) .fontSize(18) .fontWeight(FontWeight.Bold) .width(\'100%\') .padding(16) } .columnSpan(this.columnsCount) // 分组内的图片卡片 ForEach(group.items, (item: PhotoItems) => { GridItem() { PhotoCard(item) } }) })}

3. 高级交互功能

3.1 下拉刷新

我们可以结合Refresh组件实现下拉刷新功能:

@State refreshing: boolean = falsebuild() { Refresh({ refreshing: $$this.refreshing }) { Grid() { // GridItems } // Grid配置 } .onRefresh(() => { this.refreshData() })}async refreshData() { this.refreshing = true // 模拟网络请求 await new Promise(resolve => setTimeout(resolve, 2000)) // 更新数据 this.photoItems = this.getRandomPhotos() this.refreshing = false}

3.2 加载更多

结合onReachEnd事件,我们可以实现滚动到底部加载更多数据:

@State loading: boolean = false@State hasMore: boolean = truebuild() { Column() { Grid() { // GridItems // 加载更多指示器 if (this.loading || this.hasMore) { GridItem() {  if (this.loading) { LoadingProgress() .width(24) .height(24)  } else { Text(\'上拉加载更多\') .fontSize(14) .fontColor(\'#999999\')  } } .columnSpan(this.columnsCount) .height(50) .justifyContent(FlexAlign.Center) } } .onReachEnd(() => { if (!this.loading && this.hasMore) { this.loadMore() } }) }}async loadMore() { if (this.loading || !this.hasMore) return this.loading = true // 模拟网络请求 await new Promise(resolve => setTimeout(resolve, 2000)) // 加载更多数据 const newItems = this.getMorePhotos() if (newItems.length > 0) { this.photoItems = [...this.photoItems, ...newItems] } else { this.hasMore = false } this.loading = false}

3.3 图片懒加载

为了提高性能,我们可以实现图片的懒加载,只有当图片进入可视区域时才加载:

@Componentstruct LazyImage { @Prop src: Resource @Prop width: string | number @Prop height: string | number @State loaded: boolean = false @State visible: boolean = false aboutToAppear() { // 使用IntersectionObserver检测可见性 // 这里简化处理,实际应使用更复杂的逻辑 setTimeout(() => { this.visible = true }, 100) } build() { Stack() { if (this.visible) { Image(this.src)  .width(this.width)  .height(this.height)  .objectFit(ImageFit.Cover)  .opacity(this.loaded ? 1 : 0)  .onComplete(() => { this.loaded = true  }) } if (!this.loaded) { Column() {  LoadingProgress() .width(24) .height(24) } .width(\'100%\') .height(\'100%\') .backgroundColor(\'#F0F0F0\') .justifyContent(FlexAlign.Center) } } .width(this.width) .height(this.height) }}

然后在GridItem中使用LazyImage替代普通Image:

GridItem() { Column() { Stack({ alignContent: Alignment.TopEnd }) { LazyImage({ src: item.imageUrl, width: \'100%\', height: item.height }) .borderRadius({ topLeft: 12, topRight: 12 }) // 点赞按钮 } // 内容区域 }}

4. 动画与过渡效果

4.1 网格项动画

我们可以为GridItem添加动画效果,使界面更加生动:

@State animationIndex: number = 0build() { Grid() { ForEach(this.getFilteredPhotos(), (item: PhotoItems, index) => { GridItem() { PhotoCard(item) } .opacity(this.animationIndex > index ? 1 : 0) .translate({ y: this.animationIndex > index ? 0 : 20 }) .animation({ delay: 50 * index, duration: 300, curve: Curve.EaseOut }) }) }}aboutToAppear() { // 触发动画 setTimeout(() => { this.animationIndex = this.photoItems.length }, 100)}

这样,网格项会依次淡入并从下方滑入,创造出瀑布流的动态效果。

4.2 滚动过渡效果

我们可以根据滚动位置添加过渡效果,例如顶部导航栏的透明度变化:

@State scrollY: number = 0build() { Column() { // 顶部导航栏 Row() { // 导航栏内容 } .width(\'100%\') .height(56) .padding({ left: 16, right: 16 }) .backgroundColor(Color.lerp(new Color(\'#FFFFFF00\'), new Color(\'#FFFFFFFF\'), Math.min(this.scrollY / 100, 1))) .shadow({ radius: 8, color: `rgba(0, 0, 0, ${Math.min(this.scrollY / 100, 0.1)})`, offsetY: 2 }) // 网格内容 Grid() { // GridItems } .onScroll((_, yOffset) => { this.scrollY = yOffset }) }}

这样,当用户向下滚动时,顶部导航栏会从透明逐渐变为不透明,并添加阴影效果。

5. 自定义网格项组件

为了提高代码的可维护性和复用性,我们可以将网格项封装为独立的组件:

@Componentstruct PhotoCard { @ObjectLink item: PhotoItems @Consume(\'toggleLike\') toggleLike: (id: number) => void @Consume(\'formatNumber\') formatNumber: (num: number) => string build() { Column() { // 图片部分 Stack({ alignContent: Alignment.TopEnd }) { Image(this.item.imageUrl)  .width(\'100%\')  .height(this.item.height)  .objectFit(ImageFit.Cover)  .borderRadius({ topLeft: 12, topRight: 12 }) // 点赞按钮 Button() {  Image(this.item.isLiked ? $r(\'app.media.heart_filled\') : $r(\'app.media.heart_outline\')) .width(20) .height(20) .fillColor(this.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(this.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 }) }}

然后在主组件中使用:

@Provide(\'toggleLike\') toggleLike = this.toggleLike.bind(this)@Provide(\'formatNumber\') formatNumber = this.formatNumber.bind(this)build() { Grid() { ForEach(this.getFilteredPhotos(), (item: PhotoItems) => { GridItem() { PhotoCard({ item: item }) } }) }}

这样可以使代码结构更加清晰,便于维护和扩展。

总结

本教程深入探讨了HarmonyOS NEXT中动态网格布局的进阶技巧,包括Grid组件的高级配置、自定义布局策略、交互优化、动画效果等内容。通过这些技巧,你可以构建更加灵活、高效、美观的瀑布流界面。