概述
网格布局是由"行"和"列"分割的单元格所组成,通过指定"项目"所在的单元格做出各种各样的布局。网格布局具有较强的页面均分能力,子组件占比控制能力,是一种重要自适应布局,其使用场景有九宫格图片展示、日历、计算器等。
ArkUI提供了Grid容器组件和子组件GridItem,用于构建网格布局。Grid用于设置网格布局相关参数,GridItem定义子组件相关特征。Grid组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。
布局与约束
Grid组件为网格容器,其中容器内每一个条目对应一个GridItem组件,如下图所示。 对应代码
ts
Column() {
Text("Grid")
.textAlign(TextAlign.Center)
.width(100)
Grid() {
ForEach(this.gridItems, (item:string)=>{
GridItem() {
Text(item)
.textAlign(TextAlign.Center)
.backgroundColor('#fffbf0')
.border({color:'#ffc100', width: 1})
.width(100)
.height(100)
}
})
}
.padding({left:10, right:10, top: 10, bottom: 10})
.maxCount(3)
.layoutDirection(GridDirection.Row)
.columnsGap(10)
.rowsGap(15)
}
.padding({top: 10})
.margin({left:5})
.border({color:'#2586d9', width:1})
.backgroundColor('#f0faff')
设置排列方式
设置行列数量与占比
通过设置行列数量与尺寸占比可以确定网格布局的整体排列方式。Grid组件提供了rowsTemplate和columnsTemplate属性用于设置网格布局行列数量与尺寸占比。
rowsTemplate和columnsTemplate属性值是一个由多个空格和'数字+fr'间隔拼接的字符串,fr的个数即网格布局的行或列数,fr前面的数值大小,用于计算该行或列在网格布局宽度上的占比,最终决定该行或列的宽度。
ts
Grid() {
...
}
.rowsTemplate('1fr 1fr 1fr')
.columnsTemplate('1fr 2fr 1fr')
对应代码
ts
@Entry
@Component
struct RowColumnPercentPage {
@State calcItems: Array<string> = []
aboutToAppear() {
for (let index = 0; index < 9; index++) {
this.calcItems.push(`Item${index+1}`)
}
}
build() {
Navigation() {
Grid() {
ForEach(this.calcItems, (item:string)=>{
GridItem() {
Text(item)
}
.backgroundColor('#9dc3e6')
})
}
.rowsTemplate('1fr 1fr 1fr')
.columnsTemplate('1fr 2fr 1fr')
.columnsGap(5)
.rowsGap(5)
.padding({ left: 5, right: 5, top: 5 })
.width('100%')
.height('50%')
.backgroundColor(0xF0F0F0)
}
.title('设置行列数量与占比')
.titleMode(NavigationTitleMode.Mini)
}
}
设置子组件所占行列数
除了大小相同的等比例网格布局,由不同大小的网格组成不均匀分布的网格布局场景在实际应用中十分常见,如下图所示。在Grid组件中,通过设置GridItem的rowStart、rowEnd、columnStart和columnEnd可以实现如图所示的单个网格横跨多行或多列的场景。 对应代码
ts
Grid() {
ForEach(this.calcItems, (item:string)=>{
if (item === "3") {
GridItem() {
Text(item)
}
.columnStart(3)
.columnEnd(4)
.backgroundColor('#9dc3e6')
} else if (item === "4") {
GridItem() {
Text(item)
}
.rowStart(1)
.rowEnd(2)
.backgroundColor('#9dc3e6')
} else if (item === "8") {
GridItem() {
Text(item)
}
.columnStart(1)
.columnEnd(3)
.backgroundColor('#9dc3e6')
} else {
GridItem() {
Text(item)
}.backgroundColor('#9dc3e6')
}
})
}
.rowsTemplate('1fr 1fr 1fr')
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(5)
.padding({ left: 5, right: 5, top: 5 })
.width('100%')
.height(250)
.backgroundColor(0xF0F0F0)
例如计算器 的按键布局就是常见的不均匀网格布局场景。如下图,计算器中的按键"0"和"=",按键"0"横跨第一、二两列,按键"="横跨第五、六两行。使用Grid 构建的网格布局,其行列标号从1开始,依次编号。 在单个网格单元中,rowStart和rowEnd属性表示指定当前元素起始行号和终点行号,columnStart和columnEnd属性表示指定当前元素的起始列号和终点列号。
所以"0"按键横跨第一列和第二列,只要将"0"对应GridItem的columnStart和columnEnd设为1和2,将"="对应GridItem的的rowStart和rowEnd设为5和6即可。
实现的代码
ts
@Entry
@Component
struct CalculatorPage {
calcItems: Array<string> = [
"CE", "C", "/", "X",
"7", "8", "9", "-",
"4", "5", "6", "+",
"1", "2", "3", "=",
"0", "."
]
build() {
Navigation() {
Grid() {
GridItem() {
Text('0')
.fontSize(30)
.backgroundColor('#b2b1b6')
.width('100%')
.height('100%')
.textAlign(TextAlign.End)
.padding({ right: 10 })
.borderRadius(5)
}
.columnStart(1)
.columnEnd(4)
ForEach(this.calcItems, (item: string) => {
if (item === '0') {
GridItem() {
Text(item)
}
.borderRadius(5)
.backgroundColor('#b2b1b6')
.columnStart(1)
.columnEnd(2)
} else if (item === '=') {
GridItem() {
Text(item)
}
.borderRadius(5)
.backgroundColor('#b2b1b6')
.rowStart(4)
.rowEnd(5)
} else {
GridItem() {
Text(item)
}
.borderRadius(5)
.backgroundColor('#b2b1b6')
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('2fr 1fr 1fr 1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(5)
.padding({ left: 5, right: 5, top: 5 })
.width('100%')
.backgroundColor(0xF0F0F0)
.height('70%')
}
.title('计算器')
.titleMode(NavigationTitleMode.Mini)
}
}
设置主轴方向
使用 Grid 构建网格布局时,若没有设置行列数量与占比,可以通过 layoutDirection 可以设置网格布局的主轴方向,决定子组件的排列方式。此时可以结合 minCount 和 maxCount 属性来约束主轴方向上的网格数量。
ts
Grid() {
...
}
.layoutDirection(GridDirection.Row)
ts
Grid() {
...
}
.layoutDirection(GridDirection.Column)
对应完整代码
ts
@Entry
@Component
struct LayoutDirectionPage {
@State calcItems: Array<string> = []
aboutToAppear() {
for (let index = 0; index < 9; index++) {
this.calcItems.push(`${index+1}`)
}
}
build() {
Navigation() {
Grid() {
ForEach(this.calcItems, (item:string)=>{
GridItem() {
Text(item)
}
.width('30%')
.aspectRatio(1)
.backgroundColor('#9dc3e6')
})
}
.layoutDirection(GridDirection.Column)
.columnsGap(5)
.rowsGap(5)
.maxCount(3)
.backgroundColor(0xF0F0F0)
.padding({ left: 15, right: 15, top: 15, bottom:15 })
.width('100%')
}
.title('主轴方向')
.titleMode(NavigationTitleMode.Mini)
}
}
在网格布局中显示数据
网格布局采用二维布局的方式组织其内部元素,如下图所示。 对应代码
ts
Grid() {
GridItem() {
Text('会议')
}.backgroundColor('#9dc3e6')
GridItem() {
Text('签到')
}.backgroundColor('#9dc3e6')
GridItem() {
Text('投票')
}.backgroundColor('#9dc3e6')
GridItem() {
Text('打印')
}.backgroundColor('#9dc3e6')
}
.rowsTemplate('1fr 1fr')
.columnsTemplate('1fr 1fr')
.backgroundColor('#e3e3e5')
.width('100%')
.aspectRatio(1)
设置行列间距
对应代码
ts
Grid() {
GridItem() {
Text('会议')
}.backgroundColor('#9dc3e6')
GridItem() {
Text('签到')
}.backgroundColor('#9dc3e6')
GridItem() {
Text('投票')
}.backgroundColor('#9dc3e6')
GridItem() {
Text('打印')
}.backgroundColor('#9dc3e6')
}
.rowsTemplate('1fr 1fr')
.columnsTemplate('1fr 1fr')
.backgroundColor('#e3e3e5')
.width('100%')
.aspectRatio(1)
.columnsGap(5)
.rowsGap(5)
构建可滚动的网格布局
可滚动的网格布局常用在文件管理、购物或视频列表等页面中,如下图所示。在设置Grid的行列数量与占比时,如果仅设置行、列数量与占比中的一个,即仅设置 rowsTemplate 或仅设置 columnsTemplate 属性,网格单元按照设置的方向排列,超出Grid显示区域后,Grid拥有可滚动能力 如果设置的是 columnsTemplate ,Grid 的滚动方向为垂直方向;如果设置的是 rowsTemplate ,Grid 的滚动方向为水平方向。
对应代码
ts
@Entry
@Component
struct HorizontalScrollPage {
topScrollItem:Array<string> = []
aboutToAppear() {
for (let index = 0; index < 12; index++) {
this.topScrollItem.push(`Item${index + 1}`)
}
}
build() {
Navigation() {
Grid() {
ForEach(this.topScrollItem, (item:string)=> {
GridItem() {
Text(item)
}.backgroundColor('#9dc3e6')
.width('25%')
})
}
.rowsTemplate('1fr 1fr')
.backgroundColor('#e3e3e5')
.width('100%')
.height(150)
.columnsGap(5)
.rowsGap(5)
}
.title('横向可滚动列表')
.titleMode(NavigationTitleMode.Mini)
}
}
控制滚动位置
对应代码
ts
@Entry
@Component
struct ControlScrollPage {
private scroller: Scroller = new Scroller()
topScrollItem: Array<string> = []
aboutToAppear() {
for (let index = 0; index < 16; index++) {
this.topScrollItem.push(`Item${index + 1}`)
}
}
build() {
Navigation() {
Column() {
Grid(this.scroller) {
ForEach(this.topScrollItem, (item: string) => {
GridItem() {
Text(item)
}.backgroundColor('#9dc3e6')
.width('25%')
})
}
.rowsTemplate('1fr 1fr')
.backgroundColor('#e3e3e5')
.width('100%')
.height(150)
Row({ space: 20 }) {
Button('上一页')
.onClick(() => {
this.scroller.scrollPage({
next: false,
})
})
Button('下一页')
.onClick(() => {
this.scroller.scrollPage({
next: true
})
})
}.margin({top:10})
}
}
.title('控制可滚动列表')
.titleMode(NavigationTitleMode.Mini)
}
}