
#我的鸿蒙开发手记#网格与瀑布流布局浅析 原创 精华
网格布局
网格布局从布局结构上看与栅格布局类似,其核心也是将页面分为指定的行和列,相比栅格布局,网格布局更加灵活,不仅可以进行列的合并,也可以对行进行合并。
网格布局的核心是提供行列定义模版,模板本身是一个字符串,行和列分别进行定义,字符串中的fr个数表示行或列的个数,fr前的数字表示此行或列对应的尺寸占比,例如下面的代码:
Grid() {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9], (index: number)=>{
GridItem() {
Text(`数据${index}`).fontSize(30).textAlign(TextAlign.Center)
}.backgroundColor(Color.Red)
})
}.rowsTemplate('1fr 2fr 1fr').columnsTemplate('1fr 2fr 1fr').columnsGap(10).rowsGap(10)
上面代码中的’1fr 2fr 1fr’即是模版的定义,运行效果如图所示。
Grid在使用时,要注意有如下规则:
1.Grid容器的子组件必须为GridItem组件。
2.行列模板同时进行了设置时,将会展示固定行列个数的元素,整个容器不可滚动。
3.当只设置了行或列的模板时,元素按照设置的方向进行布局,超出的内容可以滚动。
4.当行和列的模版都没有设置时,其行列数由子元素的尺寸决定,整个容器不可滚动。
通过行列模版,我们定义的是规则的网格结构,Grid容器也支持定义不规则的网格结构,在构造Grid实例时,其有两个参数,分别为:
参数 | 类型 | 意义 |
---|---|---|
scroller | Scroller | 可滚动时,控制容器的滚动以及获取相关信息。 |
layoutOptions | GridLayoutOptions | 布局参数。 |
layoutOptions布局参数中可以实现onGetRectByIndex配置项,此配置项需要设置为一个函数,用来返回指定索引数据的布局规则。示例代码如下:
Grid(undefined, {
regularSize:[1, 1],
onGetRectByIndex: (index: number)=>{
// 注意此处的index从0开始,表示数据下标
if (index == 0) {
// 从第1行第1列开始,占1行3列
return [0, 0, 1, 3]
}
if (index == 3) {
// 从第2行第3列开始,占2行1列
return [1, 2, 2, 1]
}
if (index == 7) {
// 从第4行第2列开始,占1行2列
return [3, 1, 1, 2]
}
return undefined
}
}) {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], (index: number)=>{
GridItem() {
Text(`数据${index}`).fontSize(30).textAlign(TextAlign.Center)
}.backgroundColor(Color.Red)
})
}.rowsTemplate('1fr 1fr 1fr 1fr').columnsTemplate('1fr 1fr 1fr').columnsGap(10).rowsGap(10)
其中regularSize用来指定标准的子组件布局尺寸,目前只支持一个子组件占单行单列,此参数为后续扩展预留。onGetRectByIndex用来具体设置某个元素的位置和占用行列数,其设置的函数的参数index为元素索引,从0开始,此函数如果返回undefined则表示默认的布局规则,不做额外改变。我们也可以返回一个包含4个数值元素的数组,4个数值分别表示要布局子组件的:起始行索引,起始列索引,占用行数、占用列数。运行上面的代码,效果如图:
需要注意,在设置某个索引位置元素的布局规则时,需要符合布局逻辑,子组件将按照顺序进行布局,如果设置的位置已经被其他子组件占用,则设置无效。同样如果起始位置没有被占用但是其他位置被占用,则只会展示GridItem子组件的一部分。
关于使用Scroller对Grid容器的滚动进行控制,以及相关滚动事件的监听,其用法与List容器完全一致,这里就不再赘述。
瀑布流布局
瀑布流布局常用在展示宽度或高度不规则的信息流中,常见于电商或资讯类软件中。在ArkUI中使用WaterFlow创建瀑布流容器,WaterFlow支持横向滚动也支持纵向滚动,对于横向滚动的瀑布流,可以通过rowsTemplate方法设置行数与对应占比的模版。对于纵向滚动的瀑布流,可以通过columnsTemplate设置列数以及对应占比的模版。
纵向滚动的瀑布流在布局时,通常子元素的宽度固定,高度动态,当发生换行时,新的元素会被布局在高度最小的列下面,同理对于横向布局的瀑布流,子元素高度固定,宽度动态,发生换列时,新的元素会优先布局在宽度最小的行后面。我们若要手动实现这样的布局逻辑会非常复杂,使用WaterFlow则非常简单。例如:
WaterFlow() {
ForEach(this.data, (title: string)=>{
FlowItem() {
Text(title).textAlign(TextAlign.Center).fontSize(35)
}.width('100%')
.height(Math.max(Math.random() * 300, 50))
.backgroundColor(Color.Red)
})
}.rowsGap(10).columnsGap(10)
.layoutDirection(FlexDirection.Column).columnsTemplate('1fr 1fr')
将子组件的宽度改为随机,并设置WaterFlow的布局方向为横向,即可创建横向的瀑布流,如下:
WaterFlow() {
ForEach(this.data, (title: string)=>{
FlowItem() {
Text(title).textAlign(TextAlign.Center).fontSize(35)
}.height('100%')
.width(Math.max(Math.random() * 300, 50))
.backgroundColor(Color.Red)
})
}.rowsGap(10).columnsGap(10)
.layoutDirection(FlexDirection.Row).rowsTemplate('1fr 1fr 1fr 1fr')
WaterFlow的子组件必须时FlowItem,这种布局结构常用来渲染无限滚动的信息流,尤其是在有大量的图片信息时,由于图片的宽高比例不定,使用瀑布流布局效果会非常美观。在实际项目开发中,WaterFlow通常会和LazyForEach懒加载组合使用。
WaterFlow在构造时参数支持的配置项如下:
配置项 | 类型 | 意义 |
---|---|---|
footer | CustomBuilder | 设置尾视图。 |
scroller | Scroller | 滚动控制器。 |
sections | WaterFlowSections | 分组控制器。 |
layoutMode | WaterFlowLayoutMode | 瀑布流布局模式。 |
WaterFlow也支持配置尾视图以及分组,通过分组允许不同的组采用不同的布局规则,通过sections参数,我们可以为不同的分组设置不同的行/列数,不同的间距等。例如下面的示例代码:
@Component
struct WaterFlowLayout {
data: string[] = (() => {
let d: string[] = []
for (let i = 0; i < 100; i++) {
d.push(`${i}`)
}
return d
})()
sections = new WaterFlowSections()
aboutToAppear(): void {
let sec1: SectionOptions = {
itemsCount: 4,
crossCount: 2,
columnsGap: 5,
rowsGap: 5,
margin: {top: 30}
}
let sec2: SectionOptions = {
itemsCount: 8,
crossCount: 4,
columnsGap: 10,
rowsGap: 10,
margin: {top: 30}
}
let sec3: SectionOptions = {
itemsCount: 88,
crossCount: 1,
rowsGap: 15,
margin: {top: 30}
}
let sections = [
sec1, sec2, sec3
]
this.sections.splice(0, 0, sections)
}
build() {
WaterFlow({sections: this.sections}) {
ForEach(this.data, (title: string)=>{
FlowItem() {
Text(title).textAlign(TextAlign.Center).fontSize(35)
}.width('100%')
.height(Math.max(Math.random() * 100, 50))
.backgroundColor(Color.Red)
})
}.rowsGap(10).columnsGap(10)
.layoutDirection(FlexDirection.Column).columnsTemplate('1fr 1fr')
}
}
其中WaterFlowSections可以理解为分组管理器,其中封装了方法来操作分组数据,如示例代码中的splice方法,此方法用来删除、替换或新增分组数据,第1个参数为要操作的分组索引起始位置,第2个参数为要删除的分组个数,第3个参数为添加一组分组数据。具体的分组数据由SectionOptions 类进行定义,此类中提供的配置属性如下:
属性名 | 类型 | 意义 |
---|---|---|
itemsCount | number | 当前分组中的元素个数。 |
crossCount | number | 轨数,即交叉轴方向上的行/列数。 |
columnsGap | Dimension | 设置列间距。 |
rowsGap | Dimension | 设置行间距。 |
margin | Margin | Dimension | 设置当前分组的外边距。 |
WaterFlowSections实例操作分组的方法列举如下:
方法名 | 参数 | 意义 |
---|---|---|
splice | start: number deleteCount: number sections: Array<SectionOptions> | 从指定的索引开始删除多个分组,同时添加多个分组。 |
push | SectionOptions | 添加一个分组。 |
update | sectionIndex: number section: SectionOptions | 更新指定位置的分组。 |
values | 无 | 获取当前所有分组数据。 |
length | 无 | 获取当前分组个数。 |
上面的分组示例代码运行效果如图所示。
