列表是一个复杂的容器,当列表项达到一定数量,使得列表内容超出其范围的时候,就会自动变为可以滚动。列表适合用来展现同类数据类型。
List组件支持使用,条件渲染,循环渲染,懒加载等渲染控制方式生成子组件。
List的布局与约束
List提供垂直方向或者水平方向的布局能力,当其内容超出List本身最大的范围后,其字内容便会可以滚动,初次之外,List还提供了交叉轴方向上自适应排列个数布局的能力。
例如一个纵向的List,可以有一列,同时也可以有多列。
一个横向的List,一般有一行,但是也可以存在多行
以上布局,事实上Grid和WaterFlow两种布局都可以达到,但是如果一个布局的子组件每一个列,宽,长宽全部一致。这里推荐用List。 如果需要每个子组件长宽不一致甚至是随机自适应等情况则才考虑使用Grid 或者 WaterFlow。
约束
指的是List组件的长宽约束。一般是,如果List没有指定大小的情况下,List的长宽规则是,子组件是否能撑到List布局的极限, 到了极限, 也就是list没有指定大小,但是List的父组件已经确定大小了,或者通过父组件计算出List只能撑到某一个大小了。 这种情况下, list中子组件少到不足以撑开的时候,list的大小就是子组件合起来的大小,当子组件的量大了,大到list最大值的时候,List到最大就不变了,内容变为可滚动。
List的基本使用方法
List组件的构建声明是这个样子的
List(value?: {space?:number | string, initiallIndex?: number, scroller?: Scroller})
参数详解:
- space: 代表子项中挨着的间距。
- initiallIndex: 代表首次进列表的时候初始滑动的位置,单位按照索引来进行测算。自动滑动的位置为 置顶第一个。
- scroller: 可滚动组件控制器(所以不止一个组件欧)。 如果一个List有滑动控制的需求,例如点击某一个按钮就会向上移动多少,此时,没有办法直接拿到List的对象去滑动,而是通过滑动控制器绑定完进行控制。
initiallIndex使用案例:
@State list:string[] = ['1', '2', '3', '4', '5', '6', '7']
build() {...List({space: 8, initiallIndex: 3}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}
}
可滚动组件控制器-Scroller
上方我们讲了下List组件构建的时候所需要的参数,其余两个,space, 和 initiallIndex都比较好理解,我们不做赘述。 下面讲讲第三个参数Scroller。
Scroller就是用来控制可滚动组件的滚动行为的。List也是一种可滚动组件,也支持了这个控制器。
Scroller初始化
非常简单,new一下即可。
// 1 初始化对象
scroller: Scroller = new Scroller()// 2 使用对象
build() {...List({space: 8, initiallIndex: 3, scroller: this.scroller}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}
}
Scroller功能
- scrollTo :滑动到指定位置
- scrollEdge: 滑动到边缘
- scrollBy:滑动指定距离
// 1 初始化对象
scroller: Scroller = new Scroller()// 2 使用对象
build() {...List({space: 8, initiallIndex: 3, scroller: this.scroller}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}Button('scroll 50').onclick(()=>{this.scroller.scrollBy(0, 50)})Button('back top').onclick(()=>{this.scroller.scrollEdge(Edge.Top)})
}
List属性
属性 | 功能 |
listDirection | 设置List组件排列方向 |
lanes | 设置List组件行数或者列数 |
divider | 设置ListItem分割线样式 |
scrollBar | 设置滚动条状态 |
alignListItem | 设置Item对齐方式,有时候List的每一个项远远大于Item组件的大小,此时就需要对齐了 |
sticky | 指定ListItemGroup什么部分作为粘性标题 |
swipeAction | 指定侧滑方式 |
listDirection属性-设置列表方向
用于设置List组件的内容排列方向。 其值为一个枚举:
- Axis.Vertical 纵向
- Axis.Horizontal 横向
// 1 初始化对象
scroller: Scroller = new Scroller()// 2 使用对象
build() {...List({space: 8, initiallIndex: 3, scroller: this.scroller}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}.listDirection(Axis.Vertical)
}
lanes属性-设置列数
lanes是车道,划分区域的意思。这个属性设置的时候参数比较复杂。我们看看原型。
lanes(value: number|LengthConstrain, gutter?: Dimension)
- value: 设置列表内容的列数或者行数。这个行数可以为number用于指定是什么列,也可以为LengthConstrain类型,用于指定,每一个列的最小值和最大值,使得根据父容器大小,来动态度的计算应该需要几列。
- gutter: 设置列间距。 此处的列间距,应该变通理解,如果一个表目前是横向的,那么它只有一个横向的”列“!
// 1 初始化对象
scroller: Scroller = new Scroller()// 2 使用对象
build() {...List({space: 8, initiallIndex: 3, scroller: this.scroller}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}.listDirection(Axis.Vertical).lanes(2)
}
List是纵向的,出现了两列
value为LengthConstrain类型时的使用方式
// 1 初始化对象
scroller: Scroller = new Scroller()
// 目标就是使得列表排列的好看,假如宽为300, 则会有一列, 假如宽为400,则两列正好,也符合最小
// 标准,就会展示两列
@State lenthConstrains: LengthConstrains = {minLength: 200, maxLength: 300}// 2 使用对象
build() {...List({space: 8, initiallIndex: 3, scroller: this.scroller}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}.listDirection(Axis.Vertical).lanes(this.lenthConstrains)
}
divider属性-设置分割线样式
// 1 初始化对象
scroller: Scroller = new Scroller()// 2 使用对象
build() {...List({space: 8, initiallIndex: 3, scroller: this.scroller}){ForEach(this.list, (item: string) => {....}, (item: string) => item)}.listDirection(Axis.Vertical).lanes(2).divider({strockWidth: 2,color: '#4472c4',startMargin: 20,endMargin: 20})
}
scrollBar属性-设置滚动条状态
这个比较好理解就不做代码展示了。我们理解下参数的意义即可。
scrollBar(barState: BarState)
BarState枚举说明
- Auto:按需展示,触摸时展示,2秒后就消失。
- Off:不展示
- On:常驻展示、
List,ListItemGroup, ListItem 组件关系
ListItemGroup用于列表数据的分组展示,其组件也是ListItem。ListItem表示单个列表项,可以包含单个组件。
list(){listItemGroup(){listItem(){...}}listItemGroup(){listItem(){...}listItem(){...}}listItemGroup(){listItem(){...}listItem(){...}}
}
粘性标题分组列表
分组列表不好解释,但是我们看一张图就明白其中的意思了
首先,我们要实现分组的效果,之后再加粘性标题,如果如右侧图片,在右侧栏加上导航,可以再加一个额外的组件做。有现成的组件,当然也可以自己写。
1 首先分组
可以用ListItemGroup来做这件事情,ListItemGroup本身支持加一些header的,我们可以把分组做成header。
@Builder itemHeader(title: string){// 列表分组的头部组件,对应联系人分组A、B等位置的组件Text(text).fontSize(20).backgroundColor('#fff1f3f5').width('100%').padding(5)
}build() {List() {ListItemGroup({ header: this.itemHeader('A') }) {// 循环渲染分组A的ListItem}ListItemGroup({ header: this.itemHeader('B') }) {// 循环渲染分组B的ListItem}}
}
当然这是较为低级的写法,通常我们会列好数据结构,用ForEach层层循环渲染即可。
2 添加粘性标题
粘性标题也是我们生活中经常用到的一种交互方式。
如下图所示,在联系人列表中滚动A部分时,B部分开始的头部元素始终处于A的下方。而在开始滚动B部分时,B的头部会固定在屏幕顶部,直到所有B的项均完成滚动后,才被后面的头部替代。
List为我们提供了这种交互,解决方式是:sticky
属性,这个属性是用来配合ListItemGroup
来使用的。使用起来非常简单。
@Builder itemHeader(title: string){// 列表分组的头部组件,对应联系人分组A、B等位置的组件Text(text).fontSize(20).backgroundColor('#fff1f3f5').width('100%').padding(5)
}build() {List() {ListItemGroup({ header: this.itemHeader('A') }) {// 循环渲染分组A的ListItem}ListItemGroup({ header: this.itemHeader('B') }) {// 循环渲染分组B的ListItem}}
}.sticky(StickyStyle.Header)
如果需要底吸功能需要
- 初始化ListItemGroup的Footer
- 将sticky绑定为StickyStyle.Footer
3 响应滚动位置
控制滚动位置
如果想要完成类似于字母导航的功能,则首先需要回顾一下,滚动到指定位置的方式。前面已经讲解过,我们可以在List声明的时候就设置一个参数,为Scroller。 可以通过调用Scroller的一些方法来让列表滑动到指定的位置。响应滚动位置在一定程度上用到了这里的原理。Scroller里面记录了一个位置数据,这个数据实际上是可以被观察的,同时它也是遵循双向绑定的。
响应滚动位置改变导航,与点击导航跳转到指定位置
HarmonyOs ArkUI为我们提供了一个组件AlphabetIndexer
,用来展示一个右侧的索引栏。这里面实现的时候有个有意思的点就是, AlphabetIndexer
内部存了一个双向绑定的参数,List中记录的参数衔接上,那么,当List参数变得时候,AlphabetIndexer
参数自然会变动,界面也会变动。 当AlphabetIndexer
内部检测到用户选择了某个选项,自然也会通过改动这个值, 从而List列表位置自然也变更了。
const alphabels = ['#', '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 ConstractList{@State selectedIndex: number = 0private listController: 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)}}
}
响应列表侧滑
我们可以用List的 swipeAction
属性去实现列表的侧滑功能,其参数为 SwipeActionOptions
来指定是从哪里侧滑。
我们注意到列表项目被侧滑之后,会出现一个删除的按钮,这个按钮是从哪里设置的呢? 实际上,这个swipeAction
与我们之前的提到的粘性标题使用方式有异曲同工之处
- 1 需要指定List框架特定的,已经成为概念的,某界面的内容。 在此处,就是List中已经协议好的,尾端滑出组件,其使用环节位于,ListItem中。
- 2 仅仅需要设置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);})
}
ForEach(list, (item: string, index: number)=>{ListItem(){//此处省略很多布局内容}.swipeAction( // 参数是一个SwipeActionOptions, 可以分别指定滑动方向,以及滑动后需要build的界面行为{end: {builder: ()=> this.itemEnd(index)}})
}, (item: string)=> item.toString)
列表辅助红点逻辑控件-Badge
在应用的开发中,红点逻辑必不可少,比如聊天消息,通知等等。几乎大型的App都需要。正常情况下我们自己写控件也可以实现,但是,ArkUI给我们提供好了这种控件,就比较省事了。 控件名字叫 Badge
。使用方法比较简单,看看基本就懂了。
ListItem() {Badge({count: 1,position: BadgePosition.RightTop,style: {badgeSize: 16, badgeColor: '#FA2A2D'}}) {// Image组件实现消息联系人头像// ...}
}
数据懒加载
循环列表,适用于短列表,当构建具有大量列表项的长列表时, 如果直接采用循环引用的方式,会使得一次性加载所有的元素,会导致页面启动时间过长,影响用户体验,此时,推荐使用数据懒加载LazyForEach
方式按需加载数据,从而提升列表性能。
当使用LazyForEach进行加载时,可以指定列表项的缓存数量
List(){...
}
// 如果是含有ListItemGroup,那么缓存数量就是以group为单位。
// 如果列表是多列的,那么数量就会乘列数。总不能不展示全是不是?
.cachedCount(3)
- cachesCount的数量如果设置的太高,会增大UI对CPU的占用,内存开销也会变大,使用时要根据真实情况来定。
- 列表数据使用懒加载的时候,除了显示界面内的以及前后缓存的列表项数量,其余的列表项是会被销毁的。