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

167.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局进阶篇

原创 若城 2025-06-29
148

[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局进阶篇

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

效果演示

1. 引言

在上一篇教程中,我们介绍了HarmonyOS NEXT中可滚动网格布局的基础知识和实现方法。本篇教程将深入探讨可滚动网格布局的进阶技巧,包括多列网格布局、动态列模板、高级滚动控制、自定义网格项样式等内容,帮助开发者构建更加灵活、美观的网格界面。

2. 多列网格布局

2.1 列模板设置

在基础教程中,我们使用了单列布局(columnsTemplate(‘1fr’))。在实际应用中,多列布局更为常见,特别是在平板等大屏设备上。下面介绍如何实现多列网格布局:

Grid(this.scroller) { // 网格内容 } .columnsTemplate('1fr 1fr') // 双列布局 .columnsGap(16) // 列间距 .rowsGap(16) // 行间距

通过设置columnsTemplate为’1fr 1fr’,我们创建了一个双列布局,每列占据可用空间的一份。同时,使用columnsGap设置列间距为16。

2.2 多列布局的响应式调整

为了适应不同屏幕尺寸,我们可以根据屏幕宽度动态调整列数:

@State gridColumns: string = '1fr' aboutToAppear() { // 获取屏幕宽度 const screenWidth = px2vp(window.getWindowWidth()) // 根据屏幕宽度设置列数 if (screenWidth >= 840) { this.gridColumns = '1fr 1fr 1fr' // 大屏设备,三列布局 } else if (screenWidth >= 520) { this.gridColumns = '1fr 1fr' // 中等屏幕,双列布局 } else { this.gridColumns = '1fr' // 小屏设备,单列布局 } } // 在Grid中使用动态列模板 Grid(this.scroller) { // 网格内容 } .columnsTemplate(this.gridColumns)

这样,当应用在不同尺寸的设备上运行时,网格布局会自动调整列数,提供最佳的显示效果。

2.3 列宽比例设置

除了等分列宽,我们还可以设置不同的列宽比例:

// 第一列占1份,第二列占2份 .columnsTemplate('1fr 2fr') // 固定宽度与弹性宽度混合 .columnsTemplate('200px 1fr') // 多列不等宽 .columnsTemplate('1fr 1.5fr 1fr')

通过灵活设置列模板,可以创建出各种复杂的网格布局效果。

3. 高级滚动控制

3.1 滚动事件与回调

除了基础教程中介绍的onScrollIndex事件外,Grid还支持其他滚动相关事件:

Grid(this.scroller) { // 网格内容 } // 滚动开始事件 .onScrollBegin(() => { console.log('开始滚动') }) // 滚动停止事件 .onScrollStop(() => { console.log('停止滚动') }) // 滚动边缘事件 .onReachStart(() => { console.log('到达顶部') }) .onReachEnd(() => { console.log('到达底部') // 可以在这里加载更多数据 this.loadMoreApps() })

这些事件可以帮助我们实现更加精细的滚动控制,如滚动到底部加载更多数据、滚动时显示/隐藏UI元素等。

3.2 编程式滚动控制

使用Scroller控制器,我们可以实现编程式滚动控制:

// 滚动到指定位置 scrollToPosition() { this.scroller.scrollTo({ xOffset: 0, yOffset: 200 }) } // 滚动到指定索引的网格项 scrollToItem(index: number) { this.scroller.scrollToIndex(index) } // 滚动到顶部/底部 scrollToTop() { this.scroller.scrollEdge(Edge.Top) } scrollToBottom() { this.scroller.scrollEdge(Edge.Bottom) } // 按页滚动 scrollNextPage() { this.scroller.scrollPage({ next: true }) } scrollPrevPage() { this.scroller.scrollPage({ next: false }) }

这些方法可以在特定场景下使用,如点击按钮滚动到顶部、切换分类时滚动到特定位置等。

3.3 滚动动画与效果

为了提升用户体验,我们可以为滚动添加动画效果:

// 带动画的滚动 scrollWithAnimation() { this.scroller.scrollTo({ xOffset: 0, yOffset: 500, animation: { duration: 300, // 动画持续时间,单位毫秒 curve: Curve.EaseOut // 动画曲线 } }) }

通过设置animation参数,可以使滚动过程更加平滑自然,提升用户体验。

4. 自定义网格项样式

4.1 网格项布局与样式

在基础教程中,我们为每个GridItem创建了基本的布局和样式。下面介绍一些进阶的网格项样式技巧:

GridItem() { Column() { // 网格项内容 } .width('100%') .height('100%') .backgroundColor('#FFFFFF') .borderRadius(16) .shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.1)', offsetX: 0, offsetY: 2 }) // 添加渐变背景 .backgroundImage({ gradient: { angle: 90, colors: [['#FFFFFF', 0.0], ['#F8F8F8', 1.0]] } }) // 添加边框 .border({ width: 1, color: '#E0E0E0', style: BorderStyle.Solid }) // 添加过渡动画 .transition({ type: TransitionType.All, opacity: 0.2 }) }

这些样式设置可以使网格项更加美观,提升整体视觉效果。

4.2 网格项交互效果

为了提升用户体验,我们可以为网格项添加交互效果:

@State pressedItemId: number = -1 GridItem() { Column() { // 网格项内容 } .scale(this.pressedItemId === app.id ? 0.95 : 1.0) // 按下时缩小 .opacity(this.pressedItemId === app.id ? 0.8 : 1.0) // 按下时降低透明度 } .gesture( LongPressGesture() .onAction(() => { // 长按操作 this.showAppOptions(app.id) }) ) .onTouch((event: TouchEvent) => { if (event.type === TouchType.Down) { this.pressedItemId = app.id } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) { this.pressedItemId = -1 } })

通过监听触摸事件和手势,我们可以实现按下反馈、长按菜单等交互效果,提升用户体验。

4.3 网格项动画效果

为了使网格布局更加生动,我们可以为网格项添加动画效果:

@State animatedItems: number[] = [] onPageShow() { // 页面显示时,逐个显示网格项 this.animatedItems = [] for (let i = 0; i < this.featuredApps.length; i++) { setTimeout(() => { this.animatedItems.push(this.featuredApps[i].id) }, i * 100) // 每隔100毫秒显示一个 } } GridItem() { Column() { // 网格项内容 } .opacity(this.animatedItems.includes(app.id) ? 1.0 : 0.0) .translate({ x: this.animatedItems.includes(app.id) ? 0 : 50, y: 0 }) .transition({ type: TransitionType.All, opacity: 0.3, translate: 0.3 }) }

这段代码实现了网格项的逐个淡入动画效果,使页面加载过程更加生动。

5. 高级网格布局技巧

5.1 网格项跨行跨列

Grid组件支持网格项跨行跨列,可以创建更加复杂的布局效果:

Grid() { // 占据2行2列的大网格项 GridItem() { // 内容 } .rowStart(0) .rowEnd(2) .columnStart(0) .columnEnd(2) // 普通网格项 GridItem() { // 内容 } // 跨2行的网格项 GridItem() { // 内容 } .rowStart(0) .rowEnd(2) } .columnsTemplate('1fr 1fr') .rowsTemplate('1fr 1fr')

通过设置rowStart、rowEnd、columnStart、columnEnd属性,可以控制网格项的跨行跨列,创建出更加丰富的布局效果。

5.2 网格区域命名

Grid组件支持网格区域命名,可以更加直观地定义复杂布局:

Grid() { GridItem() { // 头部内容 } .gridArea('header') GridItem() { // 主要内容 } .gridArea('main') GridItem() { // 侧边栏内容 } .gridArea('sidebar') GridItem() { // 底部内容 } .gridArea('footer') } .areasTemplate([ ['header', 'header'], ['sidebar', 'main'], ['footer', 'footer'] ]) .columnsTemplate('1fr 2fr') .rowsTemplate('auto 1fr auto')

通过areasTemplate定义网格区域,并使用gridArea将网格项放置到指定区域,可以更加灵活地控制布局。

5.3 网格自适应布局

结合媒体查询,我们可以实现更加复杂的自适应网格布局:

@StorageLink('windowWidth') windowWidth: number = 0 @State gridLayout: GridLayoutConfig = { columns: '1fr', areas: [] } aboutToAppear() { this.updateGridLayout() } @Watch('windowWidth') updateGridLayout() { if (this.windowWidth >= 840) { // 大屏布局 this.gridLayout = { columns: '1fr 1fr 1fr', areas: [ ['featured', 'featured', 'sidebar'], ['content', 'content', 'sidebar'] ] } } else if (this.windowWidth >= 520) { // 中屏布局 this.gridLayout = { columns: '1fr 1fr', areas: [ ['featured', 'featured'], ['content', 'sidebar'] ] } } else { // 小屏布局 this.gridLayout = { columns: '1fr', areas: [ ['featured'], ['content'], ['sidebar'] ] } } } // 在Grid中使用动态布局配置 Grid() { // 网格内容 } .columnsTemplate(this.gridLayout.columns) .areasTemplate(this.gridLayout.areas)

这段代码根据窗口宽度动态调整网格布局,包括列数和区域分布,实现了真正的响应式布局。

6. 数据管理与加载

6.1 分页加载

对于大量数据,分页加载是一种常见的优化策略:

@State featuredApps: FeaturedApp[] = [] @State loading: boolean = false @State hasMore: boolean = true @State currentPage: number = 1 @State pageSize: number = 10 aboutToAppear() { this.loadApps() } async loadApps() { if (this.loading || !this.hasMore) return this.loading = true try { // 模拟网络请求 await new Promise(resolve => setTimeout(resolve, 1000)) // 模拟获取数据 const newApps = this.getAppsData(this.currentPage, this.pageSize) // 添加到现有数据 this.featuredApps = [...this.featuredApps, ...newApps] // 更新分页信息 this.currentPage++ this.hasMore = newApps.length === this.pageSize } finally { this.loading = false } } // 在Grid中添加加载更多逻辑 Grid(this.scroller) { // 应用列表 ForEach(this.featuredApps, (app:FeaturedApp) => { GridItem() { // 网格项内容 } }) // 加载更多指示器 if (this.loading) { GridItem() { LoadingProgress() .width(24) .height(24) } .justifyContent(FlexAlign.Center) } } .onReachEnd(() => { this.loadApps() // 滚动到底部时加载更多 })

这段代码实现了滚动到底部加载更多数据的功能,适用于大量数据的展示场景。

6.2 下拉刷新

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

@State refreshing: boolean = false refreshData() { this.refreshing = true // 模拟刷新数据 setTimeout(() => { this.currentPage = 1 this.featuredApps = [] this.hasMore = true this.loadApps() this.refreshing = false }, 1000) } build() { Column() { // 其他UI元素 Refresh({ refreshing: $$this.refreshing, onRefresh: () => this.refreshData() }) { Grid(this.scroller) { // 网格内容 } // Grid属性设置 } } }

通过Refresh组件包装Grid,可以实现下拉刷新功能,提升用户体验。

7. 高级交互与动效

7.1 网格项拖拽排序

通过结合手势和动画,我们可以实现网格项的拖拽排序功能:

@State draggingItemId: number = -1 @State itemPositions: Map<number, Position> = new Map() GridItem() { Column() { // 网格项内容 } .position({ x: this.itemPositions.get(app.id)?.x || 0, y: this.itemPositions.get(app.id)?.y || 0 }) .zIndex(this.draggingItemId === app.id ? 1 : 0) .opacity(this.draggingItemId === app.id ? 0.8 : 1.0) .animation({ duration: this.draggingItemId === app.id ? 0 : 300, curve: Curve.Ease }) } .gesture( PanGesture() .onActionStart(() => { this.draggingItemId = app.id }) .onActionUpdate((event: GestureEvent) => { if (this.draggingItemId === app.id) { // 更新拖拽项位置 const position = this.itemPositions.get(app.id) || { x: 0, y: 0 } this.itemPositions.set(app.id, { x: position.x + event.offsetX, y: position.y + event.offsetY }) // 检测与其他项的交换 this.checkItemSwap(app.id, event) } }) .onActionEnd(() => { if (this.draggingItemId === app.id) { // 重置位置并完成排序 this.itemPositions.set(app.id, { x: 0, y: 0 }) this.draggingItemId = -1 } }) )

这段代码实现了网格项的拖拽排序功能,用户可以通过拖拽调整网格项的顺序。

7.2 网格项展开/折叠

通过状态管理和动画,我们可以实现网格项的展开/折叠效果:

@State expandedItemId: number = -1 GridItem() { Column() { // 基本信息 Row() { // 应用图标和基本信息 } .onClick(() => { this.expandedItemId = this.expandedItemId === app.id ? -1 : app.id }) // 详细信息(展开时显示) if (this.expandedItemId === app.id) { Column() { // 详细信息内容 } .height(this.expandedItemId === app.id ? 200 : 0) .opacity(this.expandedItemId === app.id ? 1.0 : 0.0) .transition({ type: TransitionType.All, opacity: 0.3, height: 0.3 }) } } }

这段代码实现了网格项的展开/折叠效果,点击网格项时可以显示更多详细信息。

8. 总结

在下一篇教程中,我们将探讨更多高级主题,包括自定义网格布局算法、复杂交互模式、性能优化策略等,敬请期待!

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

评论