163.[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局基础篇:打造新闻应用首页

2025-06-30 22:43:09
104次阅读
0个评论

[HarmonyOS NEXT 实战案例三:Grid] 不规则网格布局基础篇:打造新闻应用首页

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

效果演示

image.png

1. Grid组件与不规则网格布局概述

在HarmonyOS NEXT的ArkUI框架中,Grid组件是一种强大的布局容器,它允许开发者创建灵活多变的网格布局。不规则网格布局是Grid组件的一种高级应用方式,通过设置GridItem的行列起止位置,可以让单个网格项占据多行多列,从而实现更加丰富的界面效果。

1.1 不规则网格布局的特点

不规则网格布局具有以下特点:

特点 描述
灵活的空间分配 可以让重要内容占据更大的空间,次要内容占据较小空间
视觉层次分明 通过不同大小的网格项,自然形成视觉焦点和层次
信息密度优化 在有限空间内高效展示不同重要程度的信息
适应不同内容类型 可根据内容类型(图片、文本等)分配合适的空间
响应式布局支持 可以根据屏幕尺寸调整网格项的大小和位置

1.2 Grid与GridItem的关系

在使用Grid组件创建不规则网格布局时,需要理解Grid与GridItem的关系:

  • Grid是容器组件,用于设置整体网格布局的参数
  • GridItem是子组件,必须作为Grid的直接子组件使用
  • Grid通过rowsTemplate和columnsTemplate属性定义网格的行列结构
  • GridItem通过rowStart、rowEnd、columnStart、columnEnd属性定义其在网格中的位置和跨度

2. 新闻应用首页实战

在本案例中,我们将使用Grid和GridItem组件创建一个新闻应用的首页,展示不同类型和重要程度的新闻内容。

2.1 页面结构概览

我们的新闻应用首页包含以下几个部分:

  1. 顶部导航栏:显示当前位置和操作按钮
  2. 分类标签栏:显示新闻分类选项
  3. 新闻网格布局:使用不规则网格展示新闻内容
  4. 底部导航栏:提供应用的主要导航选项

2.2 数据模型定义

首先,我们定义新闻数据的接口:

interface NewsData{
    id: number,
    title: string,
    summary?: string,
    image: Resource,
    category: string,
    time: string,
    isHot?: boolean,
    isTop?: boolean,
    readCount?: number
}

这个接口定义了新闻的基本属性,包括:

  • 唯一标识(id)
  • 标题(title)
  • 摘要(summary,可选)
  • 图片资源(image)
  • 分类(category)
  • 发布时间(time)
  • 是否热门(isHot,可选)
  • 是否置顶(isTop,可选)
  • 阅读数(readCount,可选)

2.3 顶部导航栏实现

// 顶部导航栏
Row() {
    Row() {
        Image($r('app.media.location_icon'))
            .width(16)
            .height(16)
            .fillColor('#FF6B35')

        Text('北京')
            .fontSize(16)
            .fontColor('#333333')
            .margin({ left: 4 })

        Image($r('app.media.arrowright'))
            .width(12)
            .height(12)
            .fillColor('#666666')
            .margin({ left: 4 })
    }

    Blank()

    Row() {
        Button() {
            Image($r('app.media.search_icon'))
                .width(20)
                .height(20)
                .fillColor('#666666')
        }
        .width(36)
        .height(36)
        .borderRadius(18)
        .backgroundColor('#F5F5F5')

        Button() {
            Image($r('app.media.home_icon'))
                .width(20)
                .height(20)
                .fillColor('#666666')
        }
        .width(36)
        .height(36)
        .borderRadius(18)
        .backgroundColor('#F5F5F5')
        .margin({ left: 8 })
    }
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('#FFFFFF')

顶部导航栏包含:

  1. 左侧位置信息:显示当前城市和下拉箭头
  2. 右侧操作按钮:搜索按钮和主页按钮

2.4 分类标签栏实现

// 分类标签栏
Row() {
    Text('推荐')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FF6B35')
        .padding({ left: 12, right: 12, top: 6, bottom: 6 })
        .borderRadius(16)
        .backgroundColor('rgba(255, 107, 53, 0.1)')

    Text('科技')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ left: 16 })

    Text('财经')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ left: 16 })

    Text('体育')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ left: 16 })

    Text('娱乐')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ left: 16 })

    Blank()

    Image($r('app.media.more_icon'))
        .width(16)
        .height(16)
        .fillColor('#666666')
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor('#FFFFFF')

分类标签栏包含:

  1. 当前选中的分类(推荐):使用特殊样式突出显示
  2. 其他分类选项:科技、财经、体育、娱乐
  3. 右侧更多按钮:用于显示更多分类

3. 不规则网格布局实现

3.1 网格容器配置

// 新闻网格布局
Grid() {
    // 子项内容...
}
.rowsTemplate('200px 200px 100px 100px 100px') // 定义行高
.columnsTemplate('1fr 1fr 1fr') // 3列布局
.rowsGap(12)
.columnsGap(12)
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 16, bottom: 16 })
.backgroundColor('#F8F8F8')

网格容器配置:

  1. 行高定义:前两行高度为200px,后三行高度为100px
  2. 列宽定义:三列等宽布局(1fr 1fr 1fr)
  3. 行列间距:均为12px
  4. 外边距:四周均为16px

3.2 头条新闻实现(占据2行2列)

// 头条新闻 - 占据2行2列
GridItem() {
    Stack({ alignContent: Alignment.BottomStart }) {
        Image(this.newsData[0].image)
            .width('100%')
            .height('100%')
            .objectFit(ImageFit.Cover)
            .borderRadius(12)

        // 渐变遮罩
        Column()
            .width('100%')
            .height('100%')
            .borderRadius(12)
            .linearGradient({
                direction: GradientDirection.Bottom,
                colors: [['rgba(0, 0, 0, 0)', 0.0], ['rgba(0, 0, 0, 0.7)', 1.0]]
            })

        // 新闻信息
        Column() {
            Row() {
                if (this.newsData[0].isTop) {
                    Text('置顶')
                        .fontSize(10)
                        .fontColor('#FFFFFF')
                        .backgroundColor('#FF6B35')
                        .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                        .borderRadius(4)
                }

                Text(this.newsData[0].category)
                    .fontSize(10)
                    .fontColor('#FFFFFF')
                    .backgroundColor('rgba(255, 255, 255, 0.3)')
                    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                    .borderRadius(4)
                    .margin({ left: this.newsData[0].isTop ? 6 : 0 })

                Blank()
            }
            .width('100%')

            Text(this.newsData[0].title)
                .fontSize(18)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FFFFFF')
                .maxLines(2)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .margin({ top: 8 })

            if (this.newsData[0].summary) {
                Text(this.newsData[0].summary)
                    .fontSize(12)
                    .fontColor('rgba(255, 255, 255, 0.8)')
                    .maxLines(2)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                    .margin({ top: 4 })
            }

            Row() {
                Text(this.newsData[0].time)
                    .fontSize(10)
                    .fontColor('rgba(255, 255, 255, 0.7)')

                Blank()

                Row() {
                    Image($r('app.media.cart_icon'))
                        .width(10)
                        .height(10)
                        .fillColor('rgba(255, 255, 255, 0.7)')

                    Text(`${this.newsData[0].readCount}`)
                        .fontSize(10)
                        .fontColor('rgba(255, 255, 255, 0.7)')
                        .margin({ left: 2 })
                }
            }
            .width('100%')
            .margin({ top: 8 })
        }
        .alignItems(HorizontalAlign.Start)
        .width('100%')
        .padding(16)
    }
    .width('100%')
    .height('100%')
}
.rowStart(0)
.rowEnd(1)
.columnStart(0)
.columnEnd(1)
.onClick(() => {
    console.log(`点击头条新闻: ${this.newsData[0].title}`)
})

头条新闻特点:

  1. 位置定义:从第0行开始到第1行结束,从第0列开始到第1列结束(占据2行2列)
  2. 布局结构:使用Stack布局,底层是图片,中间是渐变遮罩,顶层是新闻信息
  3. 渐变效果:从透明到半透明黑色的渐变,增强文字可读性
  4. 信息展示:包含置顶标签、分类标签、标题、摘要、时间和阅读数

3.3 右侧小新闻实现(各占1格)

// 右侧小新闻1
GridItem() {
    Column() {
        Image(this.newsData[1].image)
            .width('100%')
            .height(80)
            .objectFit(ImageFit.Cover)
            .borderRadius(8)

        Column() {
            Row() {
                if (this.newsData[1].isHot) {
                    Text('热')
                        .fontSize(10)
                        .fontColor('#FFFFFF')
                        .backgroundColor('#FF3B30')
                        .padding({ left: 4, right: 4, top: 1, bottom: 1 })
                        .borderRadius(2)
                }

                Text(this.newsData[1].category)
                    .fontSize(10)
                    .fontColor('#FF6B35')
                    .margin({ left: this.newsData[1].isHot ? 4 : 0 })

                Blank()
            }
            .width('100%')

            Text(this.newsData[1].title)
                .fontSize(14)
                .fontWeight(FontWeight.Medium)
                .fontColor('#333333')
                .maxLines(2)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .margin({ top: 4 })

            Row() {
                Text(this.newsData[1].time)
                    .fontSize(10)
                    .fontColor('#999999')

                Blank()

                Text(`${this.newsData[1].readCount}`)
                    .fontSize(10)
                    .fontColor('#999999')
            }
            .width('100%')
            .margin({ top: 6 })
        }
        .alignItems(HorizontalAlign.Start)
        .width('100%')
        .margin({ top: 8 })
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({
        radius: 4,
        color: 'rgba(0, 0, 0, 0.1)',
        offsetX: 0,
        offsetY: 1
    })
}
.rowStart(0)
.columnStart(2)
.onClick(() => {
    console.log(`点击新闻: ${this.newsData[1].title}`)
})

右侧小新闻特点:

  1. 位置定义:位于第0行,第2列(占据1格)
  2. 布局结构:使用Column布局,上方是图片,下方是新闻信息
  3. 卡片样式:白色背景、圆角和阴影效果
  4. 信息展示:包含热门标签、分类、标题、时间和阅读数

3.4 底部横向新闻列表实现(跨列)

// 底部横向新闻列表
ForEach(this.newsData.slice(3), (news:NewsData, index) => {
    GridItem() {
        Row() {
            Image(news.image)
                .width(80)
                .height(60)
                .objectFit(ImageFit.Cover)
                .borderRadius(8)

            Column() {
                Text(news.category)
                    .fontSize(10)
                    .fontColor('#FF6B35')
                    .alignSelf(ItemAlign.Start)

                Text(news.title)
                    .fontSize(14)
                    .fontWeight(FontWeight.Medium)
                    .fontColor('#333333')
                    .maxLines(2)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                    .margin({ top: 4 })

                Row() {
                    Text(news.time)
                        .fontSize(10)
                        .fontColor('#999999')

                    Blank()

                    Text(`${news.readCount}`)
                        .fontSize(10)
                        .fontColor('#999999')
                }
                .width('100%')
                .margin({ top: 6 })
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1)
            .margin({ left: 12 })
        }
        .width('100%')
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
        .shadow({
            radius: 4,
            color: 'rgba(0, 0, 0, 0.1)',
            offsetX: 0,
            offsetY: 1
        })
    }
    .columnStart(0)
    .columnEnd(2)
    .onClick(() => {
        console.log(`点击新闻: ${news.title}`)
    })
})

底部横向新闻列表特点:

  1. 位置定义:从第0列开始到第2列结束(跨越3列)
  2. 布局结构:使用Row布局,左侧是图片,右侧是新闻信息
  3. 循环渲染:使用ForEach循环渲染多条新闻
  4. 信息展示:包含分类、标题、时间和阅读数

4. Grid组件关键属性解析

4.1 Grid容器属性

属性 说明 示例
rowsTemplate 设置行高和行数 '200px 200px 100px 100px 100px'
columnsTemplate 设置列宽和列数 '1fr 1fr 1fr'
rowsGap 行间距 12
columnsGap 列间距 12
layoutDirection 布局方向 GridDirection.Row

4.2 GridItem定位属性

属性 说明 示例
rowStart 起始行索引 0
rowEnd 结束行索引 1
columnStart 起始列索引 0
columnEnd 结束列索引 1

5. 布局技巧与最佳实践

5.1 不规则网格布局设计原则

  1. 重要内容优先:将重要内容放在视觉焦点位置,并分配更大的空间
  2. 层次分明:通过不同大小的网格项创建视觉层次
  3. 内容适配:根据内容类型选择合适的网格大小和布局
  4. 一致性:保持设计语言的一致性,包括间距、圆角、阴影等
  5. 响应式考虑:设计时考虑不同屏幕尺寸下的布局效果

5.2 网格定位技巧

在不规则网格布局中,正确设置GridItem的位置非常重要:

// 示例1:占据第0行到第1行,第0列到第1列(2行2列)
GridItem() {
    // 内容...
}
.rowStart(0)
.rowEnd(1)
.columnStart(0)
.columnEnd(1)

// 示例2:仅占据第0行,第2列(1格)
GridItem() {
    // 内容...
}
.rowStart(0)
.columnStart(2)

// 示例3:跨列显示(从第0列到第2列)
GridItem() {
    // 内容...
}
.columnStart(0)
.columnEnd(2)

5.3 卡片设计技巧

在新闻应用中,卡片设计对用户体验至关重要:

  1. 一致的圆角:所有卡片使用一致的圆角值(如12px)
  2. 适当的阴影:添加轻微阴影增强层次感
  3. 内边距统一:保持卡片内边距一致(如12px)
  4. 图文比例:保持合适的图文比例,增强可读性
  5. 信息层级:通过字体大小、颜色和间距区分信息层级

6. 总结

在本教程中,我们学习了如何使用HarmonyOS NEXT中的Grid和GridItem组件创建不规则网格布局,并实现了一个新闻应用首页。通过合理设置Grid容器的行列结构和GridItem的位置属性,我们成功创建了一个层次分明、重点突出的界面。

收藏00

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