ArkUI-布局
列表
List组件的子组件为ListItemGroup或者ListItem,List组件支持使用条件渲染、循环渲染、懒加载等渲染控制方式生成子组件。
布局与约束
List除了提供水平与垂直的单列列表以外,还可以实现多列列表的功能。
Grid和WaterFlow也可以实现单列、多列布局,如果布局每列等宽,且不需要跨行跨列布局,相比Grid和WaterFlow,则更推荐使用List。
设置主轴方向
通过List的listDirection
方法可以设置主轴方向。默认为Axis.Vertical。
List() {
// ...
}
.listDirection(Axis.Horizontal)
设置交叉轴布局
List组件的交叉轴布局可以通过lanes
和alignListItem
方法进行设置。
lanes
lanes属性用于确定交叉轴排列的列表项数量。
lanes属性通常用于在不同尺寸的设备上自适应构建不同行数或者列数的列表。lanes属性的取值是number|LengthConstrain
,number类型表示该List为几列列表,而LengthConstrain则不太一样,设置的是List子组件的最小宽度和最大宽度。
List() {
// ...
}
.lanes(2)
@Entry
@Component
struct EgLanes {
@State egLanes: LengthConstrain = { minLength: 200, maxLength: 300 }
build() {
List() {
// ...
}
.lanes(this.egLanes)
}
}
上边代码中,List组件的lanes值为{minLength:200,maxLength:300}
。
- 当List组件的宽度为300时,由于minLength为200,此时列表为一列。
- 当List组件的宽度为400时,符合minLength的两倍,则此时列表为两列。
alignListItem
alignListItem用于设置子组件在交叉轴方向的对齐方式。
List() {
// ...
}
.alignListItem(ListItemAlign.Center)
自定义列表样式
设置内容间距
在初始化列表的时候,可以通过space
参数,在列表项之间添加间距。
List({ space: 10 }) {
// ...
}
添加分隔线
List提供了divider
属性用于给列表项之间添加分隔线。
在driver属性中,strokeWidth
用于控制分隔线的粗细,color
用于设置分隔线的颜色。startMargin
和endMargin
用于设置分隔线起始端和末端的距离。
List() {
// ...
}
.divider({ strokeWidth:1,color:'#ffe9f0f0',startMargin:10,endMargin:0 })
添加滚动条
在使用List组件时,可通过scrollBar属性控制列表滚动条的显示。
List() {
// ...
}
.scrollBar(BarState.Auto)
支持分组列表
在List组件中使用ListItemGrounp对项目进行分组,可以创建二维列表。
在List组件中可以使用一个或者多个ListItemGrounp组件,ListItemGrounp的宽度默认充满List组件。
在初始化ListItemGrounp时,可以通过header
参数设置列表分组的头部组件。
@Entry
@Component
struct ContactsList {
@Builder itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
build() {
List() {
ListItemGroup({ header: this.itemHead('A') }) {
// 循环渲染分组A的ListItem
}
ListItemGroup({ header: this.itemHead('B') }) {
// 循环渲染分组B的ListItem
}
}
}
}
添加粘性标题
List组件的sticky
属性配合ListItemGrounp组件使用,用于设置ListItemGrounp中的头部组件是否呈现吸顶效果或者尾部组件是否呈现吸顶效果。
通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果需要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer。
import { util } from '@kit.ArkTS'
class Contact {
key: string = util.generateRandomUUID(true);
name: string;
icon: Resource;
constructor(name: string, icon: Resource) {
this.name = name;
this.icon = icon;
}
}
class ContactsGroup {
title: string = ''
contacts: Array<object> | null = null
key: string = ""
}
export let contactsGroups: object[] = [
{
title: 'A',
contacts: [
new Contact('艾佳', $r('app.media.iconA')),
new Contact('安安', $r('app.media.iconB')),
new Contact('Angela', $r('app.media.iconC')),
],
key: util.generateRandomUUID(true)
} as ContactsGroup,
{
title: 'B',
contacts: [
new Contact('白叶', $r('app.media.iconD')),
new Contact('伯明', $r('app.media.iconE')),
],
key: util.generateRandomUUID(true)
} as ContactsGroup,
// ...
]
@Entry
@Component
struct ContactsList {
// 定义分组联系人数据集合contactsGroups数组
@Builder itemHead(text: string) {
// 列表分组的头部组件,对应联系人分组A、B等位置的组件
Text(text)
.fontSize(20)
.backgroundColor('#fff1f3f5')
.width('100%')
.padding(5)
}
build() {
List() {
// 循环渲染ListItemGroup,contactsGroups为多个分组联系人contacts和标题title的数据集合
ForEach(contactsGroups, (itemGroup: ContactsGroup) => {
ListItemGroup({ header: this.itemHead(itemGroup.title) }) {
// 循环渲染ListItem
if (itemGroup.contacts) {
ForEach(itemGroup.contacts, (item: Contact) => {
ListItem() {
// ...
}
}, (item: Contact) => JSON.stringify(item))
}
}
}, (itemGroup: ContactsGroup) => JSON.stringify(itemGroup))
}.sticky(StickyStyle.Header) // 设置吸顶,实现粘性标题效果
}
}
控制滚动位置
List组件初始化时,可以通过scroller
参数绑定一个Scroller对象,进行列表的滚动控制。
private listScroller: Scroller = new Scroller();
Stack({ alignContent: Alignment.Bottom }) {
// 将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。
List({ space: 20, scroller: this.listScroller }) {
// ...
}
Button() {
// ...
}
.onClick(() => {
// 点击按钮时,指定跳转位置,返回列表顶部
this.listScroller.scrollToIndex(0)
})
}
响应滚动位置
List可以通过onScrollIndex
事件来监听当前滚动位置,根据滚动位置去做一系列的处理。
计算索引值时,ListItemGroup作为一个整体占一个索引值,不计算ListItemGroup内部ListItem的索引值。
const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
@Entry
@Component
struct ContactsList {
@State selectedIndex: number = 0;
private listScroller: Scroller = new Scroller();
build() {
Stack({ alignContent: Alignment.End }) {
List({ scroller: this.listScroller }) {}
.onScrollIndex((firstIndex: number) => {
// 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
})
// 字母表索引组件
AlphabetIndexer({ arrayValue: alphabets, selected: 0 })
.selected(this.selectedIndex)
}
}
}
响应列表项侧滑
ListItem的swipeAction
属性可用于实现列表项的左右滑动功能。
-
实现滑出组件的构建。
@Builder itemEnd(index: number) { // 构建尾端滑出组件 Button({ type: ButtonType.Circle }) { Image($r('app.media.ic_public_delete_filled')) .width(20) .height(20) } .onClick(() => { // this.messages为列表数据源,可根据实际场景构造。点击后从数据源删除指定数据项。 this.messages.splice(index, 1); }) }
-
绑定swipeAction属性到ListItem上。
swipeAction属性方法初始化时的必填参数是SwipeActionOptions,SwipeActionOptions中start表示设置列表右滑时滑出的组件,end参数表示设置列表左滑时滑出的组件。// 构建List时,通过ForEach基于数据源this.messages循环渲染ListItem。 ListItem() { // ... } .swipeAction({ end: { // index为该ListItem在List中的索引值。 builder: () => { this.itemEnd(index) }, } }) // 设置侧滑属性.
给列表项添加标记
在ListItem中使用Badge
组件可以给列表项添加标记功能。**Badge是可以附加在单个组件上用于信息标记的容器组件。**所以当希望某个组件上有标记时,需要将Badge组件,作为该组件的父容器使用。
在Badge组件中,count
和position
参数用于设置需要展示的消息数量和提示点显示位置,style
参数设置标记的样式。
ListItem() {
Badge({
count: 1,
position: BadgePosition.RightTop,
style: { badgeSize: 16, badgeColor: '#FA2A2D' }
}) {
// Image组件实现消息联系人头像
// ...
}
}
下拉刷新与上拉加载
List的下拉刷新和上拉加载可以通过监听List的触摸事件来实现。
由于List中只支持ListItem和ListItemGrounp子组件,所以下拉刷新和上拉加载的布局文件,无法直接添加到List组件中,我们需要在List组件中添加一个单独的ListItem组件,作为List组件的唯一子组件,在ListItem中,再依次添加下拉刷新的头布局,循环渲染列表的真正的ListItem,然后添加上拉加载的组件。
@Builder ListLayout() {
List() {
ListItem() {
RefreshLayout({
refreshLayoutClass: new CustomRefreshLoadLayoutClass(this.newsModel.isVisiblePullDown, this.newsModel.pullDownRefreshImage,
this.newsModel.pullDownRefreshText, this.newsModel.pullDownRefreshHeight)
})
}
ForEach(this.newsModel.newsData, (item: NewsData) => {
ListItem() {
NewsItem({ newsData: item })
}
...
}, (item: NewsData, index?: number) => JSON.stringify(item) + index)
ListItem() {
if (this.newsModel.hasMore) {
LoadMoreLayout({
loadMoreLayoutClass: new CustomRefreshLoadLayoutClass(this.newsModel.isVisiblePullUpLoad, this.newsModel.pullUpLoadImage,
this.newsModel.pullUpLoadText, this.newsModel.pullUpLoadHeight)
})
} else {
NoMoreLayout()
}
}
}
...
}
监听onTouch
事件,根据滑动方向距离等因素,确定需要显示的布局。
长列表的处理
当列表项较长时,使用forEach循环渲染的方式,会一次性加载所有的列表元素,对性能开销较大。
可以使用数据懒加载的方式,即LayForEach
实现按需迭代加载数据,提升用户体验。
在List组件中,提供了cachedCount
方法用来设置列表缓存数量,该数量表示会在List当前显示的ListItem前后各缓存一定数量的ListItem,超出该数量的会被销毁,该设置项只在LazyForEach中生效。
网格
ArkUI提供了Grid容器组件和子组件GridItem,用于构建网格布局。Grid用于设置网格布局相关参数,GridItem定义子组件相关特征。Grid组件支持使用条件渲染、循环渲染、懒加载等方式生成子组件。
网格布局是一种二维布局。Grid组件支持自定义行列数和每行每列尺寸占比、设置子组件横跨几行或者几列,同时提供了垂直和水平布局能力。当网格容器组件尺寸发生变化时,所有子组件以及间距会等比例调整,从而实现网格布局的自适应能力。根据Grid的这些布局能力,可以构建出不同样式的网格布局,如下图所示。
如果Grid组件设置了宽高属性,则其尺寸为设置值。如果没有设置宽高属性,Grid组件的尺寸默认适应其父组件的尺寸。
Grid组件根据行列数量与占比属性的设置,可以分为三种布局情况:
-
行、列数量与占比同时设置:Grid只展示固定行列数的元素,其余元素不展示,且Grid不可滚动。(推荐使用该种布局方式)
-
只设置行、列数量与占比中的一个:元素按照设置的方向进行排布,超出的元素可通过滚动的方式展示。
-
行列数量与占比都不设置:元素在布局方向上排布,其行列数由布局方向、单个网格的宽高等多个属性共同决定。超出行列容纳范围的元素不展示,且Grid不可滚动。
设置排列方式
设置行列数量与占比
Grid组件提供了rowTemplate
和columnsTemplate
属性用于设置网格布局行列数量和尺寸占比
rowsTemplate和columnsTemplate属性值是一个由多个空格和'数字+fr'间隔拼接的字符串,fr的个数即网格布局的行或列数,fr前面的数值大小,用于计算该行或列在网格布局宽度上的占比,最终决定该行或列宽度。
Grid() {
...
}
.rowsTemplate('1fr 1fr 1fr')
.columnsTemplate('1fr 2fr 1fr')
设置子组件所占行列数
在Grid组件中,可以通过创建Grid时传入layoutOptions
参数来实现跨多行或者多列的场景。
其中irregularIndexes
和onGetIrregularSizeByIndex
可对仅设置rowsTemplate或columnsTemplate的Grid使用。
onGetRectByIndex
可对同时设置rowsTemplate和columnsTemplate的Grid使用。
以onGetRectByIndex举例,可以根据index设置对应的跨行列布局,返回数据为[rowStart,columnStart,rowSpan,columnSpan]。其中rowStart和columnStart属性表示指定当前元素的起始index和起始列index,rowSpan和columnSpan表示指定当前元素所占用的行和列的数量。
layoutOptions: GridLayoutOptions = {
regularSize: [1, 1],
onGetRectByIndex: (index: number) => {
if (index == key1) { // key1是"0"按键对应的index
return [5, 0, 1, 2]
} else if (index == key2) { // key2是"="按键对应的index
return [4, 3, 2, 1]
}
// ...
// 这里需要根据具体布局返回其他item的位置
}
}
Grid(undefined, this.layoutOptions) {
// ...
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('2fr 1fr 1fr 1fr 1fr 1fr')
设置行列间距
Grid中,通过rowsGap
和columnsGap
可以设置网格布局的行列间距。
Grid() {
...
}
.columnsGap(10)
.rowsGap(15)