170.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇
[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局进阶篇
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
在基础篇中,我们学习了如何使用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 = 2
aboutToAppear() {
// 获取设备宽度
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 = false
build() {
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 = true
build() {
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 图片懒加载
为了提高性能,我们可以实现图片的懒加载,只有当图片进入可视区域时才加载:
@Component
struct 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 = 0
build() {
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 = 0
build() {
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. 自定义网格项组件
为了提高代码的可维护性和复用性,我们可以将网格项封装为独立的组件:
@Component
struct 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组件的高级配置、自定义布局策略、交互优化、动画效果等内容。通过这些技巧,你可以构建更加灵活、高效、美观的瀑布流界面。
- 0回答
- 4粉丝
- 0关注
- 171.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局高级篇
- 169.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局基础篇
- 161. [HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:进阶篇
- 167.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局进阶篇
- 173.[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] 基础网格布局进阶篇:电商商品列表的交互与状态管理
- 160.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:基础篇
- 172.[HarmonyOS NEXT 实战案例六:Grid] 响应式网格布局 - 基础篇