1.list 的基本使用
list 用于显示列表,当内容超出list实际大小时,支持上下滑动且配合 v1/v2 不同版本的监听器,当内容划出屏幕之后会保存模板新的item直接复用以减少内存占用
1.1 基本显示
@Entry
@ComponentV2
struct Test {
@Local arr: number[] = []
aboutToAppear(): void {
for (let x = 0; x < 100; x++) {
this.arr.push(x)
}
}
build() {
// space 设置主轴方向 item 的间距为
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
// list 内部实际只能给 listItem 和 listGroup,其他的都不行
// listItem 内部只允许存在一个子组件
ListItem() {
Text(`${item}`)
.width("100%")
.padding({ top: 10, bottom: 10 })
.backgroundColor("#f6f7f9")
// 多个子组件报错
// Text(`${item}`)
// .width("100%")
// .padding({ top: 10, bottom: 10 })
// .backgroundColor("#f6f7f9")
}
// listItem 也支持通用属性
// .width("100%")
// .margin({ left: 12, right: 12 })
})
}
.listDirection(Axis.Vertical) //list元素方向为竖向,默认就是 Axis.Vertical
.scrollBar(BarState.Off) // 不显示 scrollBar
.edgeEffect(EdgeEffect.None) // 关闭边缘滑动效果
.divider({
//设置分割线
strokeWidth: 1,
color: "#000",
startMargin: 20,
endMargin: 20,
})
.backgroundColor("#fff")
.width("100%")
.height("100%")
}
}
1.2 list 的表格
使用 lanes(value: number | LengthConstrain, gutter?: Dimension): ListAttribute; 实现表格模式,其中 value 为 number | LengthConstrain 的联合类型, 当传入的类型为 LengthConstrain 时,设置好 item 的最大宽度和最小宽度后,会根据屏幕实际大小自动计算列数,在适配上比较方便但可能产品不会同意~
@Entry
@ComponentV2
struct Test {
@Local arr: number[] = []
aboutToAppear(): void {
for (let x = 0; x < 100; x++) {
this.arr.push(x)
}
}
build() {
List({ space: 10 }) {
// 这里的index 指的是 item 在 this.arr 中的index,而不是在list中的index
// 不使用时可以不写
ForEach(this.arr, (item: number,index:number) => {
ListItem() {
Text(`${item}`)
.width("100%")
.padding({ top: 10, bottom: 10 })
.backgroundColor("#f6f7f9")
}
})
}
.lanes(3, 10) // 分成3 列,列间距为10
.backgroundColor("#fff")
.width("100%")
.height("100%")
}
}
1.3 头和尾
列表可以直接添加 listItem 以达到头和尾的效果,如列表顶部需要一个广告的 Image,且需要随着列表一起滑动,或者更为复杂的布局且跟随list 一起滑动,此时不必嵌套scroll。这个设计相较于安卓来说要简便的多。如下
@Entry
@ComponentV2
struct Test {
@Local arr: number[] = []
private scroller = new Scroller()
private scrollIndex: number = 20
aboutToAppear(): void {
for (let x = 0; x < 100; x++) {
this.arr.push(x)
}
}
build() {
Column() {
List({ space: 10 }) {
ListItem() {
Text("我是头部")
.width("100%")
.height(100)
.backgroundColor("#f00")
}
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`${item}`)
.width("100%")
.padding({ top: 10, bottom: 10 })
.backgroundColor("#f6f7f9")
}
})
ListItem() {
Text("我是尾部")
.width("100%")
.height(100)
.backgroundColor("#f00")
}
}
.backgroundColor("#fff")
.width("100%")
.layoutWeight(1)
}
.width("100%")
.height("100%")
.padding({ top: 50 })
}
}
2.list 的 scroller
list 的 scroll 提供额外的滑动控制及状态获取
private scroller = new Scroller()
build(){
List({ scroller: this.scroller })
}
根据 偏移量 offset 滑动
this.scroller.scrollTo({ xOffset: 0, yOffset: 0 })
根据 list 的下标滑动,如果有头和尾,则实际 index 和 数据源 this.arr 的下标不同
this.scroller.scrollToIndex(this.scrollIndex++)
获取当前 list 的滑动偏移量,如果滑动时监听了 list 的 onScrollFrameBegin 方法,实际用于判断的值应该通过 scroller.currentOffset() 获取而不是 onScrollFrameBegin 的参数
this.scroller.currentOffset()
api.14及以后- 以当前list 组件坐上为0,0 坐标计算,根据 x,y 坐标获取 item 的下标
getItemIndex(x: number, y: number): number;
api.12及以后- 根据index获取item的 Rect(坐标x,y,item的宽高)
getItemRect(index: number): RectResult;
3.复用
直接使用 foreach 渲染组件会渲染所有的内容,当列表数据异常多的时候,会造成卡顿甚至内存溢出闪退。这个时候就需要用到list的复用。
当 item 滑出可视范围时,复用组件会把 这个 item 保存当作模板,已有模板时会直接销毁,节省内存。继续滑动加载时直接使用这个item 的模板直接显示。
3.1 v1组件 的复用
v1 组件的复用使用 lazyForeach,因为目前都升级到 v2 了,这个可以查看官方文档。
3.2 v2组件 的复用
v2 组件使用 Repeat 组件实现复用,简单示例如下:
/**
* 实体类,使用基本数据类型效果不明显
*/
@ObservedV2
class Bean {
@Trace age: number = 0
@Trace type: number = 0
}
@Entry
@ComponentV2
struct Test {
@Local arr: Bean[] = []
aboutToAppear(): void {
for (let x = 0; x < 100; x++) {
let tempBean = new Bean()
tempBean.age = x
this.arr.push(tempBean)
}
}
build() {
Column() {
List() {
// 复用组件,类似于 foreach,也是循环渲染
Repeat(this.arr)
.each(() => {}) //写就完了~
.virtualScroll() // 开启复用,不写这个视为不使用复用
.template( //模板属性
"temp1", //模板名称
// item 内容
(obj: RepeatItem<Bean>) => {
Item1({ bean: obj.item })
},
// 模板缓存个数
{ cachedCount: 2 }
)
// 根据当前 item 的 bean 返回模板名称,对应之后使用该模板
.templateId((item: Bean) => "temp1")
}
.width("100%")
.layoutWeight(1)
}
.width("100%")
.height("100%")
.padding({ top: 50 })
}
}
@ComponentV2
struct Item1 {
@Param @Require bean: Bean
build() {
Text(`item1:${this.bean.age}`)
.onClick(() => {
this.bean.age++
})
}
}
示例中模板的显示使用自定义组件,实际模板也可以使用 @Build 来显示,但@Build 没有生命周期,在实际应用中使用的概率不大,需要的可自行了解。
备注:如使用 @Build 显示,需要传递 obj 而不是 obj.item,否则会影响刷新显示。
3.2 多个模板
通过 templateId() 返回的模板名称和对应的 template() 的 name 对应上时,则使用该模板。
template() 可以设置多个,当 templateId() 返回不同 name 时,选择显示不同的模板。示例如下:
@ObservedV2
class Bean {
@Trace age: number = 0
@Trace type: number = 0
}
@Entry
@ComponentV2
struct Test {
@Local arr: Bean[] = []
aboutToAppear(): void {
this.setData()
}
setData() {
let tempArr: Bean[] = []
for (let x = 0; x < 100; x++) {
let tempBean = new Bean()
tempBean.age = x
tempBean.type = x % 2 == 0 ? 0 : 1
tempArr.push(tempBean)
}
this.arr = tempArr
}
build() {
Column() {
Button("刷新数据")
.onClick(() => {
this.setData()
})
List() {
Repeat(this.arr)
.each(() => {
})
.virtualScroll()
//第一个模板
.template("temp1", (obj: RepeatItem<Bean>) => {
Item1({ bean: obj.item })
}, { cachedCount: 2 })
//第二个模板
.template("temp2", (obj: RepeatItem<Bean>) => {
Item2({ bean: obj.item })
}, { cachedCount: 2 })
// 根据 type 返回不同类型的模板
.templateId((item: Bean) => item.type == 0 ? "temp1" : "temp2")
}
.width("100%")
.layoutWeight(1)
}
.width("100%")
.height("100%")
.padding({ top: 50 })
}
}
@ComponentV2
struct Item1 {
@Param @Require bean: Bean
build() {
Text(`item1:${this.bean.age}`)
.onClick(() => {
this.bean.age++
})
}
}
@ComponentV2
struct Item2 {
@Param @Require bean: Bean
build() {
Text(`item2:${this.bean.age}`)
.onClick(() => {
this.bean.age++
})
}
}
多个模板的好处是可以像 安卓适配器的type一样,同一个适配器显示多种类型的数据。
备注:如果多个组件有通用的如上边用户显示,下边的操作显示,则不推荐使用模板,而是使用同一个组件,并根据类型区分中间的显示。只有完全不通用的item显示,才需要模板
3.3 复用的问题
因为是直接使用已经存在的组件,而不是重新创建的。在滑动时新显示的组件不会调用生命周期 aboutToAppear(),如有操作需要在 aboutToAppear()中进行,则需要单独封装并 使用 @Monitor监听变动,如下:
@ComponentV2
struct Item1 {
@Param @Require bean: Bean
aboutToAppear(): void {
this.setData()
}
@Monitor("bean")
setData(){
// 搞事...
}
build() {
Text(`item1:${this.bean.age}`)
.onClick(() => {
this.bean.age++
})
}
}
4 List 的相关问题
4.1 多列时无法指定某个元素跨列占满一行
使用List 时,头+内容列表+尾 是常见的组合,有些时候需要内容列表分为2列或多列显示时,此时头和尾没有办法跨行占满一行。如需实现此功能,需使用 瀑布流组件WaterFlow
4.2 在tab 中使用 List(或任意使用 scorller 的组件),内容不满一屏时无法响应滑动
使用代码以确保任何时候都响应滑动
.nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST }) //嵌套滑动兼容
.edgeEffect(EdgeEffect.None, { alwaysEnabled: true }) // 任何时候都响应滑动
4.3 上下拉刷新
方案1:使用社区刷新库 ohos/pulltorefresh
方案2:使用官方 Refresh+List,上拉加载更多时,使用 .scrollToIndex 在滑动到 尾部多少个时进行预加载,并在加载时在 list 中添加加载组件显示