177.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局高级篇:复杂业务场景与高级定制
2025-06-30 22:58:54
104次阅读
0个评论
[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局高级篇:复杂业务场景与高级定制
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
效果演示
1. 引言
在前两篇教程中,我们介绍了HarmonyOS NEXT中嵌套网格布局的基础知识和进阶技巧。本篇教程将深入探讨嵌套网格布局在复杂业务场景中的应用,以及如何进行高级定制,帮助开发者掌握更专业的Grid嵌套技术,应对各种复杂的界面需求。
2. 复杂业务场景实现
2.1 多层次数据仪表板
在企业级应用中,我们经常需要展示多层次的数据分析仪表板。以下是一个实现示例:
build() {
Column() {
// 顶部导航栏
Row() {
Text(this.dashboardData.title)
.fontSize(22)
.fontWeight(FontWeight.Bold)
Blank()
Row() {
ForEach(this.timeRanges, (range) => {
Text(range)
.fontSize(14)
.fontColor(this.selectedTimeRange === range ? '#007AFF' : '#666666')
.backgroundColor(this.selectedTimeRange === range ? '#E9F2FF' : 'transparent')
.padding(8)
.borderRadius(4)
.margin({ right: 8 })
.onClick(() => {
this.selectedTimeRange = range
this.refreshData()
})
})
}
}
.width('100%')
.padding(16)
// 主要内容区域
Scroll() {
Column() {
// 层级1:关键指标概览
Text('关键指标概览')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 16, bottom: 12 })
.alignSelf(ItemAlign.Start)
Grid() {
ForEach(Object.entries(this.dashboardData.overview), ([key, value], index) => {
GridItem() {
this.OverviewCard(
key === 'totalSales' ? '总销售额' :
key === 'totalOrders' ? '总订单数' :
key === 'totalUsers' ? '总用户数' : '总收入',
value as number,
this.getIconForKey(key),
this.getColorForKey(key)
)
}
})
}
.columnsTemplate(this.getOverviewColumnsTemplate())
.rowsGap(12)
.columnsGap(12)
.width('100%')
// 层级2:趋势分析
Text('趋势分析')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 24, bottom: 12 })
.alignSelf(ItemAlign.Start)
// 主要趋势图表
Grid() {
GridItem() {
this.ChartCard(this.dashboardData.charts[0], true) // 大尺寸图表
}
.gridSpan({ row: 1, col: 2 })
// 次要趋势图表
ForEach(this.dashboardData.charts.slice(1, 3), (chart: Chart) => {
GridItem() {
this.ChartCard(chart, false) // 小尺寸图表
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
// 层级3:详细数据分析
Row() {
// 左侧:快捷操作
Column() {
Text('快捷操作')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 24, bottom: 12 })
.alignSelf(ItemAlign.Start)
Grid() {
ForEach(this.dashboardData.quickActions, (action: QuickAction) => {
GridItem() {
Column() {
Image(action.icon)
.width(32)
.height(32)
.margin({ bottom: 8 })
Text(action.title)
.fontSize(14)
.fontColor('#333333')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.padding(16)
.onClick(() => {
// 处理点击事件
})
}
})
}
.columnsTemplate(this.getQuickActionsColumnsTemplate())
.rowsGap(12)
.columnsGap(12)
.width('100%')
}
.layoutWeight(1)
.margin({ right: 8 })
// 右侧:最近活动
Column() {
Text('最近活动')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 24, bottom: 12 })
.alignSelf(ItemAlign.Start)
Column() {
List() {
ForEach(this.dashboardData.recentActivities, (activity: RecentActivity) => {
ListItem() {
Row() {
Image(activity.icon)
.width(40)
.height(40)
.margin({ right: 12 })
Column() {
Text(activity.title)
.fontSize(16)
.fontColor('#333333')
.margin({ bottom: 4 })
Text(activity.time)
.fontSize(14)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding(12)
}
})
}
.divider({ strokeWidth: 1, color: '#EEEEEE' })
}
.backgroundColor('#FFFFFF')
.borderRadius(8)
.width('100%')
}
.layoutWeight(1)
.margin({ left: 8 })
}
.width('100%')
// 层级4:数据明细
Text('数据明细')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 24, bottom: 12 })
.alignSelf(ItemAlign.Start)
// 数据表格
Column() {
// 表头
Row() {
Text('订单ID').layoutWeight(1).fontWeight(FontWeight.Bold)
Text('客户').layoutWeight(1).fontWeight(FontWeight.Bold)
Text('金额').layoutWeight(1).fontWeight(FontWeight.Bold)
Text('状态').layoutWeight(1).fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
// 表格内容
List() {
ForEach(this.getOrderData(), (order) => {
ListItem() {
Row() {
Text(order.id).layoutWeight(1)
Text(order.customer).layoutWeight(1)
Text(`¥${this.formatNumber(order.amount)}`).layoutWeight(1)
Text(order.status)
.layoutWeight(1)
.fontColor(this.getStatusColor(order.status))
}
.width('100%')
.padding(12)
}
})
}
.divider({ strokeWidth: 1, color: '#EEEEEE' })
}
.backgroundColor('#FFFFFF')
.borderRadius(8)
.width('100%')
}
.width('100%')
.padding(16)
}
.layoutWeight(1)
.backgroundColor('#F8F8F8')
}
.width('100%')
.height('100%')
}
2.2 电子商务产品展示
电子商务应用需要灵活的产品展示布局,以下是一个使用嵌套网格的实现:
@Component
struct ProductGrid {
@State products: Product[] = []
@State categories: string[] = ['全部', '热门', '新品', '促销', '限时']
@State selectedCategory: string = '全部'
@StorageProp('deviceType') deviceType: string = 'phone'
build() {
Column() {
// 顶部导航栏
Row() {
Text('商品列表')
.fontSize(22)
.fontWeight(FontWeight.Bold)
Blank()
Button() {
Image($r('app.media.search'))
.width(24)
.height(24)
}
.backgroundColor('transparent')
.margin({ right: 8 })
Button() {
Image($r('app.media.filter'))
.width(24)
.height(24)
}
.backgroundColor('transparent')
}
.width('100%')
.padding(16)
// 分类标签
Scroll() {
Row() {
ForEach(this.categories, (category) => {
Text(category)
.fontSize(14)
.fontColor(this.selectedCategory === category ? '#007AFF' : '#666666')
.backgroundColor(this.selectedCategory === category ? '#E9F2FF' : 'transparent')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(16)
.margin({ right: 12 })
.onClick(() => {
this.selectedCategory = category
})
})
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
// 产品网格
Scroll() {
Column() {
// 特色产品(大尺寸)
if (this.selectedCategory === '全部' || this.selectedCategory === '热门') {
Text('特色产品')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 16, bottom: 12, left: 16 })
.alignSelf(ItemAlign.Start)
Grid() {
ForEach(this.getFeatureProducts(), (product: Product) => {
GridItem() {
this.FeatureProductCard(product)
}
})
}
.columnsTemplate(this.deviceType === 'phone' ? '1fr' : '1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.padding({ left: 16, right: 16 })
.width('100%')
}
// 常规产品网格
Text(this.selectedCategory === '全部' ? '所有产品' : this.selectedCategory)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 24, bottom: 12, left: 16 })
.alignSelf(ItemAlign.Start)
Grid() {
ForEach(this.getFilteredProducts(), (product: Product) => {
GridItem() {
this.ProductCard(product)
}
})
}
.columnsTemplate(this.getProductColumnsTemplate())
.rowsGap(12)
.columnsGap(12)
.padding({ left: 16, right: 16, bottom: 16 })
.width('100%')
}
.width('100%')
}
.layoutWeight(1)
.backgroundColor('#F8F8F8')
}
.width('100%')
.height('100%')
}
@Builder
FeatureProductCard(product: Product) {
Stack({ alignContent: Alignment.BottomStart }) {
Image(product.image)
.width('100%')
.height('100%')
.objectFit(ImageFit.Cover)
.borderRadius(12)
Column() {
Text(product.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ bottom: 4 })
Text(`¥${product.price.toFixed(2)}`)
.fontSize(16)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
if (product.discount) {
Text(`${product.discount}折`)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF6B00')
.borderRadius(4)
.padding(4)
.margin({ top: 4 })
}
}
.padding(16)
.width('100%')
.backgroundImage({
image: $r('app.media.gradient'),
repeatMode: RepeatMode.NoRepeat,
size: { width: '100%', height: '50%' }
})
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.height(this.deviceType === 'phone' ? 200 : 240)
.onClick(() => {
// 处理点击事件
})
}
@Builder
ProductCard(product: Product) {
Column() {
Stack({ alignContent: Alignment.TopEnd }) {
Image(product.image)
.width('100%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 })
if (product.discount) {
Text(`${product.discount}折`)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#FF6B00')
.borderRadius(4)
.padding(4)
.margin(8)
}
}
Column() {
Text(product.name)
.fontSize(16)
.fontColor('#333333')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ bottom: 4 })
Row() {
Text(`¥${product.price.toFixed(2)}`)
.fontSize(16)
.fontColor('#FF6B00')
.fontWeight(FontWeight.Bold)
if (product.originalPrice) {
Text(`¥${product.originalPrice.toFixed(2)}`)
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 4 })
}
}
.width('100%')
Row() {
Text(`销量 ${product.sales}`)
.fontSize(12)
.fontColor('#999999')
Blank()
Row() {
Image($r('app.media.star'))
.width(12)
.height(12)
Text(product.rating.toFixed(1))
.fontSize(12)
.fontColor('#999999')
.margin({ left: 2 })
}
}
.width('100%')
.margin({ top: 4 })
}
.width('100%')
.padding(8)
}
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onClick(() => {
// 处理点击事件
})
}
getProductColumnsTemplate(): string {
switch (this.deviceType) {
case 'phone':
return '1fr 1fr'
case 'tablet':
return '1fr 1fr 1fr'
default: // desktop
return '1fr 1fr 1fr 1fr'
}
}
getFeatureProducts(): Product[] {
return this.products.filter(p => p.featured).slice(0, this.deviceType === 'phone' ? 1 : 2)
}
getFilteredProducts(): Product[] {
if (this.selectedCategory === '全部') {
return this.products
}
return this.products.filter(p => {
switch (this.selectedCategory) {
case '热门':
return p.sales > 1000
case '新品':
return p.isNew
case '促销':
return p.discount !== null
case '限时':
return p.limitedTime
default:
return true
}
})
}
}
3. 高级定制技巧
3.1 自定义网格布局算法
在某些复杂场景下,我们可能需要实现自定义的网格布局算法,例如瀑布流布局:
@Component
struct WaterfallGrid {
@State items: any[] = []
@State columnHeights: number[] = []
@State columnCount: number = 2
@StorageProp('deviceType') deviceType: string = 'phone'
aboutToAppear() {
// 根据设备类型设置列数
this.updateColumnCount()
// 初始化列高度数组
this.columnHeights = new Array(this.columnCount).fill(0)
}
updateColumnCount() {
switch (this.deviceType) {
case 'phone':
this.columnCount = 2
break
case 'tablet':
this.columnCount = 3
break
default: // desktop
this.columnCount = 4
break
}
}
// 获取下一个应该放置项目的列索引(高度最小的列)
getNextColumnIndex(): number {
let minHeight = this.columnHeights[0]
let index = 0
for (let i = 1; i < this.columnHeights.length; i++) {
if (this.columnHeights[i] < minHeight) {
minHeight = this.columnHeights[i]
index = i
}
}
return index
}
// 计算项目的位置
calculateItemPosition(index: number, itemHeight: number): { column: number, top: number } {
const columnIndex = this.getNextColumnIndex()
const top = this.columnHeights[columnIndex]
// 更新列高度
this.columnHeights[columnIndex] += itemHeight + 12 // 项目高度 + 间距
return { column: columnIndex, top: top }
}
build() {
Column() {
// 瀑布流容器
Stack({ alignContent: Alignment.TopStart }) {
ForEach(this.items, (item, index) => {
// 计算项目位置
const itemHeight = item.height || 200 // 默认高度或从数据中获取
const position = this.calculateItemPosition(index, itemHeight)
// 渲染项目
this.GridItem(item, itemHeight)
.position({ x: `${(position.column / this.columnCount) * 100}%`, y: position.top })
.width(`${(1 / this.columnCount) * 100 - 3}%`) // 减去间距的百分比
})
}
.width('100%')
.height(Math.max(...this.columnHeights) + 100) // 确保容器高度足够
.padding(16)
}
.width('100%')
}
@Builder
GridItem(item: any, height: number) {
Column() {
Image(item.image)
.width('100%')
.height(height)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 })
Column() {
Text(item.title)
.fontSize(16)
.fontColor('#333333')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ bottom: 4 })
if (item.description) {
Text(item.description)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
}
.width('100%')
.padding(8)
}
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
}
3.2 动态网格模板生成
在某些场景下,我们需要根据数据动态生成网格模板:
@Component
struct DynamicGrid {
@State items: any[] = []
@State templateColumns: string = ''
@State templateRows: string = ''
@State areas: any = {}
aboutToAppear() {
this.generateGridTemplate()
}
// 根据数据动态生成网格模板
generateGridTemplate() {
// 示例:根据项目重要性生成不同大小的网格区域
const importantItems = this.items.filter(item => item.importance === 'high')
const mediumItems = this.items.filter(item => item.importance === 'medium')
const normalItems = this.items.filter(item => item.importance === 'normal')
// 计算需要的列数和行数
const columnCount = 4
let rowCount = 0
let currentRow = 0
let currentCol = 0
const newAreas = {}
// 放置重要项目(2x2)
importantItems.forEach((item, index) => {
if (currentCol + 2 > columnCount) {
currentCol = 0
currentRow += 2
}
newAreas[`area_${item.id}`] = {
rowStart: currentRow,
rowEnd: currentRow + 2,
columnStart: currentCol,
columnEnd: currentCol + 2
}
currentCol += 2
rowCount = Math.max(rowCount, currentRow + 2)
})
// 放置中等重要项目(2x1或1x2)
mediumItems.forEach((item, index) => {
const isHorizontal = index % 2 === 0 // 交替使用水平和垂直布局
if (isHorizontal) {
if (currentCol + 2 > columnCount) {
currentCol = 0
currentRow += 1
}
newAreas[`area_${item.id}`] = {
rowStart: currentRow,
rowEnd: currentRow + 1,
columnStart: currentCol,
columnEnd: currentCol + 2
}
currentCol += 2
} else {
if (currentCol + 1 > columnCount) {
currentCol = 0
currentRow += 2
}
newAreas[`area_${item.id}`] = {
rowStart: currentRow,
rowEnd: currentRow + 2,
columnStart: currentCol,
columnEnd: currentCol + 1
}
currentCol += 1
}
rowCount = Math.max(rowCount, currentRow + (isHorizontal ? 1 : 2))
})
// 放置普通项目(1x1)
normalItems.forEach((item) => {
if (currentCol + 1 > columnCount) {
currentCol = 0
currentRow += 1
}
newAreas[`area_${item.id}`] = {
rowStart: currentRow,
rowEnd: currentRow + 1,
columnStart: currentCol,
columnEnd: currentCol + 1
}
currentCol += 1
rowCount = Math.max(rowCount, currentRow + 1)
})
// 生成模板字符串
this.templateColumns = 'repeat(4, 1fr)'
this.templateRows = `repeat(${rowCount}, auto)`
this.areas = newAreas
}
build() {
Column() {
Grid() {
ForEach(this.items, (item) => {
GridItem() {
// 根据项目类型渲染不同内容
if (item.importance === 'high') {
this.LargeGridItem(item)
} else if (item.importance === 'medium') {
this.MediumGridItem(item)
} else {
this.NormalGridItem(item)
}
}
.gridArea(`area_${item.id}`)
})
}
.columnsTemplate(this.templateColumns)
.rowsTemplate(this.templateRows)
.areas(this.areas)
.width('100%')
.rowsGap(12)
.columnsGap(12)
}
.width('100%')
.padding(16)
}
// 不同大小的网格项构建器
@Builder
LargeGridItem(item: any) { /* 大尺寸项目内容 */ }
@Builder
MediumGridItem(item: any) { /* 中等尺寸项目内容 */ }
@Builder
NormalGridItem(item: any) { /* 普通尺寸项目内容 */ }
}
3.3 网格布局动画与过渡
实现网格布局的动画和过渡效果:
@Component
struct AnimatedGrid {
@State items: any[] = []
@State isGridVisible: boolean = false
@State layoutMode: 'grid' | 'list' = 'grid'
@State animationInProgress: boolean = false
aboutToAppear() {
// 延迟显示网格,以便应用入场动画
setTimeout(() => {
this.isGridVisible = true
}, 100)
}
build() {
Column() {
// 布局切换按钮
Row() {
Button('网格视图')
.backgroundColor(this.layoutMode === 'grid' ? '#007AFF' : '#EEEEEE')
.fontColor(this.layoutMode === 'grid' ? '#FFFFFF' : '#333333')
.onClick(() => this.switchLayout('grid'))
Button('列表视图')
.backgroundColor(this.layoutMode === 'list' ? '#007AFF' : '#EEEEEE')
.fontColor(this.layoutMode === 'list' ? '#FFFFFF' : '#333333')
.onClick(() => this.switchLayout('list'))
}
.margin({ bottom: 16 })
// 网格/列表容器
if (this.layoutMode === 'grid') {
Grid() {
ForEach(this.items, (item, index) => {
GridItem() {
this.GridItemContent(item)
}
.opacity(this.isGridVisible ? 1 : 0)
.translate({ y: this.isGridVisible ? 0 : 50 })
.animation({
delay: 30 * index,
duration: 300,
curve: Curve.EaseOut,
iterations: 1,
playMode: PlayMode.Normal
})
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
} else {
List() {
ForEach(this.items, (item, index) => {
ListItem() {
this.ListItemContent(item)
}
.opacity(this.isGridVisible ? 1 : 0)
.translate({ x: this.isGridVisible ? 0 : -50 })
.animation({
delay: 30 * index,
duration: 300,
curve: Curve.EaseOut,
iterations: 1,
playMode: PlayMode.Normal
})
})
}
.divider({ strokeWidth: 1, color: '#EEEEEE' })
.width('100%')
}
}
.width('100%')
.padding(16)
}
switchLayout(mode: 'grid' | 'list') {
if (this.layoutMode === mode || this.animationInProgress) {
return
}
// 开始布局切换动画
this.animationInProgress = true
this.isGridVisible = false
// 延迟切换布局,等待退出动画完成
setTimeout(() => {
this.layoutMode = mode
// 延迟显示新布局,以便应用入场动画
setTimeout(() => {
this.isGridVisible = true
this.animationInProgress = false
}, 50)
}, 300)
}
@Builder
GridItemContent(item: any) { /* 网格项内容 */ }
@Builder
ListItemContent(item: any) { /* 列表项内容 */ }
}
4. 高级数据处理
4.1 分组数据的网格展示
处理分组数据并在网格中展示:
@Component
struct GroupedGrid {
@State groupedData: { [key: string]: any[] } = {}
@State expandedGroups: Set<string> = new Set()
build() {
Column() {
ForEach(Object.entries(this.groupedData), ([groupName, items]) => {
Column() {
// 分组标题(可点击展开/折叠)
Row() {
Text(groupName)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Blank()
Image(this.expandedGroups.has(groupName) ?
$r('app.media.arrow_up') : $r('app.media.arrow_down'))
.width(20)
.height(20)
}
.width('100%')
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.onClick(() => {
if (this.expandedGroups.has(groupName)) {
this.expandedGroups.delete(groupName)
} else {
this.expandedGroups.add(groupName)
}
})
// 分组内容(可展开/折叠)
if (this.expandedGroups.has(groupName)) {
Grid() {
ForEach(items, (item) => {
GridItem() {
this.GridItemContent(item)
}
})
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius({ bottomLeft: 8, bottomRight: 8 })
}
}
.margin({ bottom: 16 })
})
}
.width('100%')
.padding(16)
}
@Builder
GridItemContent(item: any) { /* 网格项内容 */ }
}
4.2 虚拟化网格
对于大量数据,可以实现虚拟化网格以提高性能:
@Component
struct VirtualizedGrid {
@State items: any[] = []
@State visibleItems: any[] = []
@State startIndex: number = 0
@State endIndex: number = 0
@State itemsPerRow: number = 2
@State rowHeight: number = 200
@State visibleRowCount: number = 0
@State scrollPosition: number = 0
aboutToAppear() {
// 初始化可见项
this.updateVisibleItems(0)
}
updateVisibleItems(scrollPos: number) {
this.scrollPosition = scrollPos
// 计算可见行的起始和结束索引
const startRow = Math.floor(scrollPos / this.rowHeight)
const endRow = Math.min(
Math.ceil((scrollPos + 800) / this.rowHeight), // 假设视口高度为800
Math.ceil(this.items.length / this.itemsPerRow)
)
// 添加缓冲区(上下各多渲染2行)
const bufferStartRow = Math.max(0, startRow - 2)
const bufferEndRow = Math.min(Math.ceil(this.items.length / this.itemsPerRow), endRow + 2)
// 计算项目索引范围
this.startIndex = bufferStartRow * this.itemsPerRow
this.endIndex = Math.min(bufferEndRow * this.itemsPerRow, this.items.length)
// 更新可见项
this.visibleItems = this.items.slice(this.startIndex, this.endIndex)
}
build() {
Column() {
// 虚拟化网格容器
Scroll() {
Column() {
// 顶部占位符(用于正确的滚动位置)
Column()
.width('100%')
.height(this.startIndex / this.itemsPerRow * this.rowHeight)
// 可见项网格
Grid() {
ForEach(this.visibleItems, (item) => {
GridItem() {
this.GridItemContent(item)
}
.height(this.rowHeight)
})
}
.columnsTemplate(`repeat(${this.itemsPerRow}, 1fr)`)
.rowsGap(12)
.columnsGap(12)
.width('100%')
// 底部占位符(用于正确的滚动高度)
Column()
.width('100%')
.height(Math.max(0, (this.items.length - this.endIndex) / this.itemsPerRow * this.rowHeight))
}
.width('100%')
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Auto)
.onScroll((offset: { xOffset: number, yOffset: number }) => {
this.updateVisibleItems(offset.yOffset)
})
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(16)
}
@Builder
GridItemContent(item: any) { /* 网格项内容 */ }
}
5. 总结
嵌套网格布局的强大之处在于其灵活性和可组合性,通过合理组织网格结构、优化嵌套深度、添加动画效果和交互功能,我们可以构建出既美观又实用的复杂界面。
00
- 0回答
- 4粉丝
- 0关注
相关话题
- 176.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局进阶篇:高级布局与交互技巧
- 183.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局高级篇:复杂场景与性能优化
- 165.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局高级篇:复杂布局与高级技巧
- 171.[HarmonyOS NEXT 实战案例五:Grid] 动态网格布局高级篇
- 162.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:高级篇
- 168.[HarmonyOS NEXT 实战案例四:Grid] 可滚动网格布局高级篇
- 174.[HarmonyOS NEXT 实战案例六:Grid] 响应式网格布局 - 高级篇
- 180.[HarmonyOS NEXT 实战案例八:Grid] 瀑布流网格布局高级篇
- 175.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局基础篇:打造企业级仪表板界面
- 159.[HarmonyOS NEXT 实战案例一:Grid] 基础网格布局高级篇:电商应用的复杂交互与动效实现
- 164.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局进阶篇:新闻应用高级布局与交互
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(下)
- [HarmonyOS NEXT 实战案例七] 健身课程网格布局(上)
- [HarmonyOS NEXT 实战案例:分割布局] 进阶篇 - 三栏布局的嵌套与复杂界面构建
- 160.[HarmonyOS NEXT 实战案例二:Grid] 照片相册网格布局:基础篇