177.[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局高级篇:复杂业务场景与高级定制

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

[HarmonyOS NEXT 实战案例七:Grid] 嵌套网格布局高级篇:复杂业务场景与高级定制

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

效果演示

image.png

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

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