
回复
项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star
在HarmonyOS NEXT的ArkUI框架中,Grid组件是一种强大的布局容器,它允许开发者创建灵活多变的网格布局。不规则网格布局是Grid组件的一种高级应用方式,通过设置GridItem的行列起止位置,可以让单个网格项占据多行多列,从而实现更加丰富的界面效果。
不规则网格布局具有以下特点:
特点 | 描述 |
---|---|
灵活的空间分配 | 可以让重要内容占据更大的空间,次要内容占据较小空间 |
视觉层次分明 | 通过不同大小的网格项,自然形成视觉焦点和层次 |
信息密度优化 | 在有限空间内高效展示不同重要程度的信息 |
适应不同内容类型 | 可根据内容类型(图片、文本等)分配合适的空间 |
响应式布局支持 | 可以根据屏幕尺寸调整网格项的大小和位置 |
在使用Grid组件创建不规则网格布局时,需要理解Grid与GridItem的关系:
在本案例中,我们将使用Grid和GridItem组件创建一个新闻应用的首页,展示不同类型和重要程度的新闻内容。
我们的新闻应用首页包含以下几个部分:
首先,我们定义新闻数据的接口:
interface NewsData{
id: number,
title: string,
summary?: string,
image: Resource,
category: string,
time: string,
isHot?: boolean,
isTop?: boolean,
readCount?: number
}
这个接口定义了新闻的基本属性,包括:
// 顶部导航栏
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')
顶部导航栏包含:
// 分类标签栏
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')
分类标签栏包含:
// 新闻网格布局
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')
网格容器配置:
// 头条新闻 - 占据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
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}`)
})
右侧小新闻特点:
// 底部横向新闻列表
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}`)
})
})
底部横向新闻列表特点:
属性 | 说明 | 示例 |
---|---|---|
rowsTemplate | 设置行高和行数 | '200px 200px 100px 100px 100px' |
columnsTemplate | 设置列宽和列数 | '1fr 1fr 1fr' |
rowsGap | 行间距 | 12 |
columnsGap | 列间距 | 12 |
layoutDirection | 布局方向 | GridDirection.Row |
属性 | 说明 | 示例 |
---|---|---|
rowStart | 起始行索引 | 0 |
rowEnd | 结束行索引 | 1 |
columnStart | 起始列索引 | 0 |
columnEnd | 结束列索引 | 1 |
在不规则网格布局中,正确设置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)
在新闻应用中,卡片设计对用户体验至关重要:
在本教程中,我们学习了如何使用HarmonyOS NEXT中的Grid和GridItem组件创建不规则网格布局,并实现了一个新闻应用首页。通过合理设置Grid容器的行列结构和GridItem的位置属性,我们成功创建了一个层次分明、重点突出的界面。