1e41f4b71Sopenharmony_ci# Repeat:子组件复用 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci>**说明:** 4e41f4b71Sopenharmony_ci> 5e41f4b71Sopenharmony_ci>Repeat从API version 12开始支持。 6e41f4b71Sopenharmony_ci> 7e41f4b71Sopenharmony_ci>当前状态管理(V2试用版)仍在逐步开发中,相关功能尚未成熟,建议开发者尝鲜试用。 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ciAPI参数说明见:[Repeat API参数说明](../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md) 10e41f4b71Sopenharmony_ci 11e41f4b71Sopenharmony_ciRepeat组件non-virtualScroll场景(不开启virtualScroll开关)中,Repeat基于数据源进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在Repeat父容器组件中的子组件。Repeat循环渲染和ForEach相比有两个区别,一是优化了部分更新场景下的渲染性能,二是组件生成函数的索引index由框架侧来维护。 12e41f4b71Sopenharmony_ci 13e41f4b71Sopenharmony_ciRepeat组件virtualScroll场景中,Repeat将从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件,必须与滚动类容器组件配合使用。当在滚动类容器组件中使用了Repeat,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会缓存组件,并在下一次迭代中使用。 14e41f4b71Sopenharmony_ci 15e41f4b71Sopenharmony_ci> **注意:** 16e41f4b71Sopenharmony_ci> 17e41f4b71Sopenharmony_ci> Repeat组件的virtualScroll场景不完全兼容V1装饰器,使用V1装饰器存在渲染异常,不建议开发者同时使用。 18e41f4b71Sopenharmony_ci 19e41f4b71Sopenharmony_ci## 使用限制 20e41f4b71Sopenharmony_ci 21e41f4b71Sopenharmony_ci- Repeat使用键值作为标识,因此键值生成函数`key()`必须针对每个数据生成唯一的值。 22e41f4b71Sopenharmony_ci- Repeat virtualScroll场景必须在滚动类容器组件内使用,仅有[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)、[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)、[Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md)以及[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)组件支持virtualScroll场景(此时配置cachedCount会生效)。其它容器组件只适用于non-virtualScroll场景。 23e41f4b71Sopenharmony_ci- Repeat开启virtualScroll后,在每次迭代中,必须创建且只允许创建一个子组件。不开启virtualScroll没有该限制。生成的子组件必须是允许包含在Repeat父容器组件中的子组件。 24e41f4b71Sopenharmony_ci- 当Repeat与@Builder混用时,必须将RepeatItem类型整体进行传参,组件才能监听到数据变化,如果只传递`RepeatItem.item`或`RepeatItem.index`,将会出现UI渲染异常。 25e41f4b71Sopenharmony_ci- template模板目前只支持virtualScroll场景。当多个template type相同时,Repeat会覆盖旧的`template()`函数,仅生效最新的`template()`。 26e41f4b71Sopenharmony_ci- totalCount > array.length时,在父组件容器滚动过程中,应用需要保证列表即将滑动到数据源末尾时请求后续数据,直到数据源全部加载完成,否则列表滑动的过程中会出现滚动效果异常。解决方案见[totalCount值大于数据源长度](#totalcount值大于数据源长度)。 27e41f4b71Sopenharmony_ci 28e41f4b71Sopenharmony_ci## 键值生成规则 29e41f4b71Sopenharmony_ci 30e41f4b71Sopenharmony_ci键值生成函数`key()`的目的是允许Repeat识别数组更改的细节:添加了哪些数据、删除了哪些数据,以及哪些数据改变了位置(索引)。 31e41f4b71Sopenharmony_ci 32e41f4b71Sopenharmony_ci开发者使用建议: 33e41f4b71Sopenharmony_ci 34e41f4b71Sopenharmony_ci- 即使数据项有重复,开发者也必须保证键值key唯一(即使数据源发生变化); 35e41f4b71Sopenharmony_ci- 每次执行`key()`函数时,使用相同的数据项作为输入,输出必须是一致的; 36e41f4b71Sopenharmony_ci- `key()`中使用index是允许的,但不建议这样使用。原因是数据项移动时索引发生变化,即键值发生变化。因此Repeat会认为数据项发生了变化,并触发UI重新渲染,会降低性能表现; 37e41f4b71Sopenharmony_ci- 推荐将简单类型数组转换为类对象数组,并添加一个`readonly id`属性,在构造函数中给它赋一个唯一的值。 38e41f4b71Sopenharmony_ci 39e41f4b71Sopenharmony_ci### non-virtualScroll规则 40e41f4b71Sopenharmony_ci 41e41f4b71Sopenharmony_ci`key()`可以缺省,Repeat会生成默认key值。 42e41f4b71Sopenharmony_ci 43e41f4b71Sopenharmony_ci 44e41f4b71Sopenharmony_ci 45e41f4b71Sopenharmony_ci### virtualScroll规则 46e41f4b71Sopenharmony_ci 47e41f4b71Sopenharmony_ci和non-virtualScroll的键值生成规则基本一致,`key()`可以缺省。 48e41f4b71Sopenharmony_ci 49e41f4b71Sopenharmony_ci 50e41f4b71Sopenharmony_ci 51e41f4b71Sopenharmony_ci## 组件生成及复用规则 52e41f4b71Sopenharmony_ci 53e41f4b71Sopenharmony_ci### non-virtualScroll规则 54e41f4b71Sopenharmony_ci 55e41f4b71Sopenharmony_ci 56e41f4b71Sopenharmony_ci 57e41f4b71Sopenharmony_ci子组件在Repeat首次渲染时全部创建,在数据更新时会对原组件进行复用。 58e41f4b71Sopenharmony_ci 59e41f4b71Sopenharmony_ci在Repeat组件进行数据更新时,它会依次对比上次的所有键值和本次更新之后的区别。若当前键值和上次的某一项键值相同,Repeat会直接复用子组件并对RepeatItem.index索引做对应的更新。 60e41f4b71Sopenharmony_ci 61e41f4b71Sopenharmony_ci当Repeat将所有重复的键值对比完并做了相应的复用后,若上次的键值有不重复的且本次更新之后有新的键值生成需要新建子组件时,Repeat会复用上次多余的子组件并更新RepeatItem.item数据源和RepeatItem.index索引并刷新UI。 62e41f4b71Sopenharmony_ci 63e41f4b71Sopenharmony_ci若上次的剩余>=本次新更新的数量,则组件完全复用并释放多余的未被复用的组件。若上次的剩余小于本次新更新的数量,将剩余的组件复用完后,Repeat会新建多出来的数据项对应的组件。 64e41f4b71Sopenharmony_ci 65e41f4b71Sopenharmony_ci### virtualScroll规则 66e41f4b71Sopenharmony_ci 67e41f4b71Sopenharmony_ci子组件在Repeat首次渲染只生成当前需要的组件,在滑动和数据更新时会缓存下屏的节点,在需要生成新的组件时,对缓存里的组件进行复用。 68e41f4b71Sopenharmony_ci 69e41f4b71Sopenharmony_ci#### 滑动场景 70e41f4b71Sopenharmony_ci 71e41f4b71Sopenharmony_ci滑动前节点现状如下图所示 72e41f4b71Sopenharmony_ci 73e41f4b71Sopenharmony_ci 74e41f4b71Sopenharmony_ci 75e41f4b71Sopenharmony_ci当前Repeat组件templateId有a和b两种,templateId a对应的缓存池,其最大缓存值为3,templateId b对应的缓存池,其最大缓存值为4,其父组件默认预加载节点1个。这时,我们将屏幕右滑,Repeat将开始复用缓存池中的节点。 76e41f4b71Sopenharmony_ci 77e41f4b71Sopenharmony_ci 78e41f4b71Sopenharmony_ci 79e41f4b71Sopenharmony_ciindex=18的数据进入屏幕及父组件预加载的范围内,此时计算出其templateId为b,这时Repeat会从type=b的缓存池中取出一个节点进行复用,更新它的key&index&data,该子节点内部使用了该项数据及索引的其他孙子节点会根据V2状态管理的规则做同步更新。 80e41f4b71Sopenharmony_ci 81e41f4b71Sopenharmony_ciindex=10的节点划出了屏幕及父组件预加载的范围。当UI主线程空闲时,会去检测type=a的缓存池是否还有空间,此时缓存池中有四个节点,超过了额定的3个,Repeat会释放掉最后一个节点。 82e41f4b71Sopenharmony_ci 83e41f4b71Sopenharmony_ci 84e41f4b71Sopenharmony_ci 85e41f4b71Sopenharmony_ci#### 数据更新场景 86e41f4b71Sopenharmony_ci 87e41f4b71Sopenharmony_ci 88e41f4b71Sopenharmony_ci 89e41f4b71Sopenharmony_ci此时我们做如下更新操作,删除index=12节点,更新index=13节点的数据,更新index=14节点的templateId为a,更新index=15节点的key。 90e41f4b71Sopenharmony_ci 91e41f4b71Sopenharmony_ci 92e41f4b71Sopenharmony_ci 93e41f4b71Sopenharmony_ci此时Repeat会通知父组件重新布局,逐一对比templateId值,若和原节点templateId值相同,则复用该节点,更新key、index和data,若templateId值发生变化,则复用相应的templateId缓存池中的节点,并更新key、index和data。 94e41f4b71Sopenharmony_ci 95e41f4b71Sopenharmony_ci 96e41f4b71Sopenharmony_ci 97e41f4b71Sopenharmony_ci上图显示node13节点更新了数据data和index;node14更新了templateId和index,于是从缓存池中取走一个复用;node15由于key值发生变化并且templateId不变,复用自身节点并同步更新key、index、data;node16和node17均只更新index。index=17的节点是新的,从缓存池中复用。 98e41f4b71Sopenharmony_ci 99e41f4b71Sopenharmony_ci 100e41f4b71Sopenharmony_ci 101e41f4b71Sopenharmony_ci## totalCount规则 102e41f4b71Sopenharmony_ci 103e41f4b71Sopenharmony_ci数据源的总长度,可以大于已加载数据项的数量。令arr.length表示数据源长度,以下为totalCount的处理规则: 104e41f4b71Sopenharmony_ci 105e41f4b71Sopenharmony_ci- totalCount缺省/非自然数时,totalCount默认为arr.length,列表正常滚动; 106e41f4b71Sopenharmony_ci- 0 <= totalCount < arr.length时,界面中只渲染“totalCount”个数据; 107e41f4b71Sopenharmony_ci- totalCount > arr.length时,代表Repeat将渲染totalCount个数据,滚动条样式根据totalCount值变化。 108e41f4b71Sopenharmony_ci 109e41f4b71Sopenharmony_ci> **注意:** 当totalCount < arr.length时,在父组件容器滚动过程中,应用需要保证列表即将滑动到数据源末尾时请求后续数据,开发者需要对数据请求的错误场景(如网络延迟)进行保护操作,直到数据源全部加载完成,否则列表滑动的过程中会出现滚动效果异常。 110e41f4b71Sopenharmony_ci 111e41f4b71Sopenharmony_ci## cachedCount规则 112e41f4b71Sopenharmony_ci 113e41f4b71Sopenharmony_cicachedCount是当前模板在Repeat的缓存池中可缓存子节点的最大数量,仅在virtualScroll场景下生效。 114e41f4b71Sopenharmony_ci 115e41f4b71Sopenharmony_ci首先需要明确滚动类容器组件 `.cachedCount()`属性方法和Repeat `cachedCount`的区别。这两者都是为了平衡性能和内存,但是其含义是不同的。 116e41f4b71Sopenharmony_ci 117e41f4b71Sopenharmony_ci- 滚动类容器组件 `.cachedCount()`:是指在可见范围外预加载的节点,这些节点会位于组件树上,但不是可见范围内,List/Grid等容器组件会额外渲染这些可见范围外的节点,从而达到其性能收益。Repeat会将这些节点视为“可见”的。 118e41f4b71Sopenharmony_ci- Repeat `cachedCount`: 是指Repeat视为“不可见”的节点,这些节点是空闲的,框架会暂时保存,在需要使用的时候更新这些节点,从而实现复用。 119e41f4b71Sopenharmony_ci 120e41f4b71Sopenharmony_ci将cachedCount设置为当前模板的节点在屏上可能出现的最大数量时,Repeat可以做到尽可能多的复用。但后果是当屏上没有当前模板的节点时,缓存池也不会释放,应用内存会增大。需要开发者依据具体情况自行把控。 121e41f4b71Sopenharmony_ci 122e41f4b71Sopenharmony_ci- cachedCount缺省时,框架会分别对不同template,根据屏上节点+预加载的节点个数来计算cachedCount。当屏上节点+预加载的节点个数变多时,cachedCount也会对应增长。需要注意cachedCount数量不会减少。 123e41f4b71Sopenharmony_ci- 显式指定cachedCount,推荐设置成和屏幕上节点个数一致。需要注意,不推荐设置cachedCount小于2,因为这会导致在快速滑动场景下创建新的节点,从而导致性能劣化。 124e41f4b71Sopenharmony_ci 125e41f4b71Sopenharmony_ci 126e41f4b71Sopenharmony_ci 127e41f4b71Sopenharmony_ci## 使用场景 128e41f4b71Sopenharmony_ci 129e41f4b71Sopenharmony_ci### non-virtualScroll 130e41f4b71Sopenharmony_ci 131e41f4b71Sopenharmony_ci#### 数据源变化 132e41f4b71Sopenharmony_ci 133e41f4b71Sopenharmony_ci在Repeat组件进行非首次渲染时,它会依次对比上次的所有键值和本次更新之后的区别。若当前键值和上次的某一项键值相同,Repeat会直接复用子组件并对RepeatItem.index索引做对应的更新。 134e41f4b71Sopenharmony_ci 135e41f4b71Sopenharmony_ci当Repeat将所有重复的键值对比完并做了相应的复用后,若上次的键值有不重复的且本次更新之后有新的键值生成需要新建子组件时,Repeat会复用上次多余的子组件并更新RepeatItem.item数据源和RepeatItem.index索引。 136e41f4b71Sopenharmony_ci 137e41f4b71Sopenharmony_ci若上次的剩余>=本次新更新的数量,则组件完全复用,若上次的剩余小于本次新更新的数量,将剩余的组件复用完后,Repeat会新建多出来的数据项对应的组件。 138e41f4b71Sopenharmony_ci 139e41f4b71Sopenharmony_ci```ts 140e41f4b71Sopenharmony_ci@Entry 141e41f4b71Sopenharmony_ci@ComponentV2 142e41f4b71Sopenharmony_cistruct Parent { 143e41f4b71Sopenharmony_ci @Local simpleList: Array<string> = ['one', 'two', 'three']; 144e41f4b71Sopenharmony_ci 145e41f4b71Sopenharmony_ci build() { 146e41f4b71Sopenharmony_ci Row() { 147e41f4b71Sopenharmony_ci Column() { 148e41f4b71Sopenharmony_ci Text('点击修改第3个数组项的值') 149e41f4b71Sopenharmony_ci .fontSize(24) 150e41f4b71Sopenharmony_ci .fontColor(Color.Red) 151e41f4b71Sopenharmony_ci .onClick(() => { 152e41f4b71Sopenharmony_ci this.simpleList[2] = 'new three'; 153e41f4b71Sopenharmony_ci }) 154e41f4b71Sopenharmony_ci 155e41f4b71Sopenharmony_ci Repeat<string>(this.simpleList) 156e41f4b71Sopenharmony_ci .each((obj: RepeatItem<string>)=>{ 157e41f4b71Sopenharmony_ci ChildItem({ item: obj.item }) 158e41f4b71Sopenharmony_ci .margin({top: 20}) 159e41f4b71Sopenharmony_ci }) 160e41f4b71Sopenharmony_ci .key((item: string) => item) 161e41f4b71Sopenharmony_ci } 162e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 163e41f4b71Sopenharmony_ci .width('100%') 164e41f4b71Sopenharmony_ci .height('100%') 165e41f4b71Sopenharmony_ci } 166e41f4b71Sopenharmony_ci .height('100%') 167e41f4b71Sopenharmony_ci .backgroundColor(0xF1F3F5) 168e41f4b71Sopenharmony_ci } 169e41f4b71Sopenharmony_ci} 170e41f4b71Sopenharmony_ci 171e41f4b71Sopenharmony_ci@ComponentV2 172e41f4b71Sopenharmony_cistruct ChildItem { 173e41f4b71Sopenharmony_ci @Param @Require item: string; 174e41f4b71Sopenharmony_ci 175e41f4b71Sopenharmony_ci build() { 176e41f4b71Sopenharmony_ci Text(this.item) 177e41f4b71Sopenharmony_ci .fontSize(30) 178e41f4b71Sopenharmony_ci } 179e41f4b71Sopenharmony_ci} 180e41f4b71Sopenharmony_ci``` 181e41f4b71Sopenharmony_ci 182e41f4b71Sopenharmony_ci 183e41f4b71Sopenharmony_ci 184e41f4b71Sopenharmony_ci第三个数组项重新渲染时会复用之前的第三项的组件,仅对数据做了刷新。 185e41f4b71Sopenharmony_ci 186e41f4b71Sopenharmony_ci#### 索引值变化 187e41f4b71Sopenharmony_ci 188e41f4b71Sopenharmony_ci下方例子当我们交换数组项1和2时,若键值和上次保持一致,Repeat会复用之前的组件,仅对使用了index索引值的组件做数据刷新。 189e41f4b71Sopenharmony_ci 190e41f4b71Sopenharmony_ci```ts 191e41f4b71Sopenharmony_ci@Entry 192e41f4b71Sopenharmony_ci@ComponentV2 193e41f4b71Sopenharmony_cistruct Parent { 194e41f4b71Sopenharmony_ci @Local simpleList: Array<string> = ['one', 'two', 'three']; 195e41f4b71Sopenharmony_ci 196e41f4b71Sopenharmony_ci build() { 197e41f4b71Sopenharmony_ci Row() { 198e41f4b71Sopenharmony_ci Column() { 199e41f4b71Sopenharmony_ci Text('交换数组项1,2') 200e41f4b71Sopenharmony_ci .fontSize(24) 201e41f4b71Sopenharmony_ci .fontColor(Color.Red) 202e41f4b71Sopenharmony_ci .onClick(() => { 203e41f4b71Sopenharmony_ci let temp: string = this.simpleList[2] 204e41f4b71Sopenharmony_ci this.simpleList[2] = this.simpleList[1] 205e41f4b71Sopenharmony_ci this.simpleList[1] = temp 206e41f4b71Sopenharmony_ci }) 207e41f4b71Sopenharmony_ci .margin({bottom: 20}) 208e41f4b71Sopenharmony_ci 209e41f4b71Sopenharmony_ci Repeat<string>(this.simpleList) 210e41f4b71Sopenharmony_ci .each((obj: RepeatItem<string>)=>{ 211e41f4b71Sopenharmony_ci Text("index: " + obj.index) 212e41f4b71Sopenharmony_ci .fontSize(30) 213e41f4b71Sopenharmony_ci ChildItem({ item: obj.item }) 214e41f4b71Sopenharmony_ci .margin({bottom: 20}) 215e41f4b71Sopenharmony_ci }) 216e41f4b71Sopenharmony_ci .key((item: string) => item) 217e41f4b71Sopenharmony_ci } 218e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 219e41f4b71Sopenharmony_ci .width('100%') 220e41f4b71Sopenharmony_ci .height('100%') 221e41f4b71Sopenharmony_ci } 222e41f4b71Sopenharmony_ci .height('100%') 223e41f4b71Sopenharmony_ci .backgroundColor(0xF1F3F5) 224e41f4b71Sopenharmony_ci } 225e41f4b71Sopenharmony_ci} 226e41f4b71Sopenharmony_ci 227e41f4b71Sopenharmony_ci@ComponentV2 228e41f4b71Sopenharmony_cistruct ChildItem { 229e41f4b71Sopenharmony_ci @Param @Require item: string; 230e41f4b71Sopenharmony_ci 231e41f4b71Sopenharmony_ci build() { 232e41f4b71Sopenharmony_ci Text(this.item) 233e41f4b71Sopenharmony_ci .fontSize(30) 234e41f4b71Sopenharmony_ci } 235e41f4b71Sopenharmony_ci} 236e41f4b71Sopenharmony_ci``` 237e41f4b71Sopenharmony_ci 238e41f4b71Sopenharmony_ci 239e41f4b71Sopenharmony_ci 240e41f4b71Sopenharmony_ci### virtualScroll 241e41f4b71Sopenharmony_ci 242e41f4b71Sopenharmony_ci本小节将展示virtualScroll场景下,Repeat的实际使用场景和组件节点的复用情况。根据复用规则可以衍生出大量的测试场景,篇幅原因,只对典型的数据变化进行解释。 243e41f4b71Sopenharmony_ci 244e41f4b71Sopenharmony_ci#### 应用示例 245e41f4b71Sopenharmony_ci 246e41f4b71Sopenharmony_ci下面的代码设计了Repeat组件的virtualScroll场景典型数据源操作,包括**插入数据、修改数据、删除数据、交换数据**。点击相应的文字可以触发数据的变化,依次点击数据项可以交换被点击的两个数据项。 247e41f4b71Sopenharmony_ci 248e41f4b71Sopenharmony_ci```ts 249e41f4b71Sopenharmony_ci@ObservedV2 250e41f4b71Sopenharmony_ciclass Clazz { 251e41f4b71Sopenharmony_ci @Trace message: string = ''; 252e41f4b71Sopenharmony_ci 253e41f4b71Sopenharmony_ci constructor(message: string) { 254e41f4b71Sopenharmony_ci this.message = message; 255e41f4b71Sopenharmony_ci } 256e41f4b71Sopenharmony_ci} 257e41f4b71Sopenharmony_ci 258e41f4b71Sopenharmony_ci@Entry 259e41f4b71Sopenharmony_ci@ComponentV2 260e41f4b71Sopenharmony_cistruct TestPage { 261e41f4b71Sopenharmony_ci @Local simpleList: Array<Clazz> = []; 262e41f4b71Sopenharmony_ci private exchange: number[] = []; 263e41f4b71Sopenharmony_ci private counter: number = 0; 264e41f4b71Sopenharmony_ci 265e41f4b71Sopenharmony_ci aboutToAppear(): void { 266e41f4b71Sopenharmony_ci for (let i = 0; i < 100; i++) { 267e41f4b71Sopenharmony_ci this.simpleList.push(new Clazz('Hello ' + i)); 268e41f4b71Sopenharmony_ci } 269e41f4b71Sopenharmony_ci } 270e41f4b71Sopenharmony_ci 271e41f4b71Sopenharmony_ci build() { 272e41f4b71Sopenharmony_ci Column({ space: 10 }) { 273e41f4b71Sopenharmony_ci Text('点击插入第5项') 274e41f4b71Sopenharmony_ci .fontSize(24) 275e41f4b71Sopenharmony_ci .fontColor(Color.Red) 276e41f4b71Sopenharmony_ci .onClick(() => { 277e41f4b71Sopenharmony_ci this.simpleList.splice(4, 0, new Clazz(`${this.counter++}_new item`)); 278e41f4b71Sopenharmony_ci }) 279e41f4b71Sopenharmony_ci Text('点击修改第5项') 280e41f4b71Sopenharmony_ci .fontSize(24) 281e41f4b71Sopenharmony_ci .fontColor(Color.Red) 282e41f4b71Sopenharmony_ci .onClick(() => { 283e41f4b71Sopenharmony_ci this.simpleList[4].message = `${this.counter++}_new item`; 284e41f4b71Sopenharmony_ci }) 285e41f4b71Sopenharmony_ci Text('点击删除第5项') 286e41f4b71Sopenharmony_ci .fontSize(24) 287e41f4b71Sopenharmony_ci .fontColor(Color.Red) 288e41f4b71Sopenharmony_ci .onClick(() => { 289e41f4b71Sopenharmony_ci this.simpleList.splice(4, 1); 290e41f4b71Sopenharmony_ci }) 291e41f4b71Sopenharmony_ci Text('依次点击两个数据项进行交换') 292e41f4b71Sopenharmony_ci .fontSize(24) 293e41f4b71Sopenharmony_ci .fontColor(Color.Red) 294e41f4b71Sopenharmony_ci 295e41f4b71Sopenharmony_ci List({ initialIndex: 10 }) { 296e41f4b71Sopenharmony_ci Repeat<Clazz>(this.simpleList) 297e41f4b71Sopenharmony_ci .each((obj: RepeatItem<Clazz>) => { 298e41f4b71Sopenharmony_ci ListItem() { 299e41f4b71Sopenharmony_ci Text('[each] ' + obj.item.message) 300e41f4b71Sopenharmony_ci .fontSize(30) 301e41f4b71Sopenharmony_ci .margin({ top: 10 }) 302e41f4b71Sopenharmony_ci } 303e41f4b71Sopenharmony_ci }) 304e41f4b71Sopenharmony_ci .key((item: Clazz, index: number) => { 305e41f4b71Sopenharmony_ci return item.message; 306e41f4b71Sopenharmony_ci }) 307e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: this.simpleList.length }) 308e41f4b71Sopenharmony_ci .templateId((item: Clazz, index: number) => "default") 309e41f4b71Sopenharmony_ci .template('default', (ri) => { 310e41f4b71Sopenharmony_ci Text('[template] ' + ri.item.message) 311e41f4b71Sopenharmony_ci .fontSize(30) 312e41f4b71Sopenharmony_ci .margin({ top: 10 }) 313e41f4b71Sopenharmony_ci .onClick(() => { 314e41f4b71Sopenharmony_ci this.exchange.push(ri.index); 315e41f4b71Sopenharmony_ci if (this.exchange.length === 2) { 316e41f4b71Sopenharmony_ci let _a = this.exchange[0]; 317e41f4b71Sopenharmony_ci let _b = this.exchange[1]; 318e41f4b71Sopenharmony_ci // click to exchange 319e41f4b71Sopenharmony_ci let temp: string = this.simpleList[_a].message; 320e41f4b71Sopenharmony_ci this.simpleList[_a].message = this.simpleList[_b].message; 321e41f4b71Sopenharmony_ci this.simpleList[_b].message = temp; 322e41f4b71Sopenharmony_ci this.exchange = []; 323e41f4b71Sopenharmony_ci } 324e41f4b71Sopenharmony_ci }) 325e41f4b71Sopenharmony_ci }, { cachedCount: 3 }) 326e41f4b71Sopenharmony_ci } 327e41f4b71Sopenharmony_ci .cachedCount(1) 328e41f4b71Sopenharmony_ci .border({ width: 1 }) 329e41f4b71Sopenharmony_ci .width('90%') 330e41f4b71Sopenharmony_ci .height('70%') 331e41f4b71Sopenharmony_ci } 332e41f4b71Sopenharmony_ci .height('100%') 333e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 334e41f4b71Sopenharmony_ci } 335e41f4b71Sopenharmony_ci} 336e41f4b71Sopenharmony_ci``` 337e41f4b71Sopenharmony_ci该应用列表内容为100项自定义类`Clazz`的`message`字符串属性,List组件的cachedCount设为1,template “default”缓存池大小设为3。应用界面如下图所示: 338e41f4b71Sopenharmony_ci 339e41f4b71Sopenharmony_ci 340e41f4b71Sopenharmony_ci 341e41f4b71Sopenharmony_ci#### 节点操作实例 342e41f4b71Sopenharmony_ci 343e41f4b71Sopenharmony_ci当进行数据源变化操作时,key值改变的节点会被重新创建。如果相对应的template的缓存池中有缓存节点,就会进行节点复用。当key值不变时,组件会直接复用并更新index的值。 344e41f4b71Sopenharmony_ci 345e41f4b71Sopenharmony_ci**插入数据** 346e41f4b71Sopenharmony_ci 347e41f4b71Sopenharmony_ci数据操作: 348e41f4b71Sopenharmony_ci 349e41f4b71Sopenharmony_ci 350e41f4b71Sopenharmony_ci 351e41f4b71Sopenharmony_ci本例做了四次插入数据操作,前两次为屏幕上方插入数据,后两次为当前屏幕插入数据。打印onUpdateNode函数执行情况“[旧节点key值] -> [新节点key值]”,代表“旧节点”复用“新节点”。节点复用情况如下: 352e41f4b71Sopenharmony_ci 353e41f4b71Sopenharmony_ci``` 354e41f4b71Sopenharmony_ci// 屏幕上方两次插入 355e41f4b71Sopenharmony_cionUpdateNode [Hello 22] -> [Hello 8] 356e41f4b71Sopenharmony_cionUpdateNode [Hello 21] -> [Hello 7] 357e41f4b71Sopenharmony_ci// 当前屏幕两次插入 358e41f4b71Sopenharmony_cionUpdateNode [Hello 11] -> [2_new item] 359e41f4b71Sopenharmony_cionUpdateNode [Hello 10] -> [3_new item] 360e41f4b71Sopenharmony_ci``` 361e41f4b71Sopenharmony_ci 362e41f4b71Sopenharmony_ci在屏幕上方插入数据时,会发生节点移动,引起当前屏幕的预加载节点改变,预加载节点发生了复用,即下方出缓存的节点22复用给了上方进入缓存的节点8。在当前屏幕插入数据时,会产生新数据项,新的节点会复用屏幕下方出缓存的预加载节点。本应用中屏幕下方添加数据时不会发生复用。 363e41f4b71Sopenharmony_ci 364e41f4b71Sopenharmony_ci**修改数据** 365e41f4b71Sopenharmony_ci 366e41f4b71Sopenharmony_ci数据操作: 367e41f4b71Sopenharmony_ci 368e41f4b71Sopenharmony_ci 369e41f4b71Sopenharmony_ci 370e41f4b71Sopenharmony_ci本例做了四次修改数据操作,前两次为屏幕上方修改数据,后两次为当前屏幕修改数据。打印onUpdateNode函数执行情况“[旧节点key值] -> [新节点key值]”,代表“旧节点”复用“新节点”。节点复用情况如下: 371e41f4b71Sopenharmony_ci 372e41f4b71Sopenharmony_ci``` 373e41f4b71Sopenharmony_ci// 当前屏幕两次修改 374e41f4b71Sopenharmony_cionUpdateNode [1_new item] -> [2_new item] 375e41f4b71Sopenharmony_cionUpdateNode [2_new item] -> [3_new item] 376e41f4b71Sopenharmony_ci``` 377e41f4b71Sopenharmony_ci 378e41f4b71Sopenharmony_ci由于屏幕上方/下方的数据不存在渲染节点,所以不会发生节点复用。在当前屏幕修改节点时,由于节点templateId值没有改变,所以复用自身节点,节点id不变。 379e41f4b71Sopenharmony_ci 380e41f4b71Sopenharmony_ci**交换数据** 381e41f4b71Sopenharmony_ci 382e41f4b71Sopenharmony_ci数据操作: 383e41f4b71Sopenharmony_ci 384e41f4b71Sopenharmony_ci 385e41f4b71Sopenharmony_ci 386e41f4b71Sopenharmony_ci本例在当前屏幕做了两次交换数据操作。由于key值未发生改变,直接交换两个节点,没有节点复用。 387e41f4b71Sopenharmony_ci 388e41f4b71Sopenharmony_ci**删除数据** 389e41f4b71Sopenharmony_ci 390e41f4b71Sopenharmony_ci数据操作: 391e41f4b71Sopenharmony_ci 392e41f4b71Sopenharmony_ci 393e41f4b71Sopenharmony_ci 394e41f4b71Sopenharmony_ci本例做了五次删除数据操作,前两次为屏幕上方删除数据,后三次为当前屏幕删除数据。打印onUpdateNode函数执行情况“[旧节点key值] -> [新节点key值]”,代表“旧节点”复用“新节点”。节点复用情况如下: 395e41f4b71Sopenharmony_ci 396e41f4b71Sopenharmony_ci``` 397e41f4b71Sopenharmony_ci// 屏幕上方两次删除 398e41f4b71Sopenharmony_cionUpdateNode [Hello 9] -> [Hello 23] 399e41f4b71Sopenharmony_cionUpdateNode [Hello 10] -> [Hello 24] 400e41f4b71Sopenharmony_ci// 当前屏幕两次删除没有调用onUpdateNode 401e41f4b71Sopenharmony_ci// 当前屏幕第三次删除 402e41f4b71Sopenharmony_cionUpdateNode [Hello 6] -> [Hello 17] 403e41f4b71Sopenharmony_ci``` 404e41f4b71Sopenharmony_ci 405e41f4b71Sopenharmony_ci在屏幕上方删除数据时,会发生节点移动,引起当前屏幕的预加载节点改变,预加载节点发生了复用,即上方出缓存的节点9复用给了下方进入缓存的节点23。当前屏幕删除数据时,由于List组件的cachedCount预加载属性,前两次删除操作中,进入屏幕的节点已经渲染,不会发生复用,被删除的节点进入对应template的缓存池中。第三次删除时,下方进入预加载缓存的节点17复用了缓存池中的节点6。 406e41f4b71Sopenharmony_ci 407e41f4b71Sopenharmony_ci#### 使用多个template 408e41f4b71Sopenharmony_ci 409e41f4b71Sopenharmony_ci``` 410e41f4b71Sopenharmony_ci@ObservedV2 411e41f4b71Sopenharmony_ciclass Wrap1 { 412e41f4b71Sopenharmony_ci @Trace message: string = ''; 413e41f4b71Sopenharmony_ci 414e41f4b71Sopenharmony_ci constructor(message: string) { 415e41f4b71Sopenharmony_ci this.message = message; 416e41f4b71Sopenharmony_ci } 417e41f4b71Sopenharmony_ci} 418e41f4b71Sopenharmony_ci 419e41f4b71Sopenharmony_ci@Entry 420e41f4b71Sopenharmony_ci@ComponentV2 421e41f4b71Sopenharmony_cistruct Parent { 422e41f4b71Sopenharmony_ci @Local simpleList: Array<Wrap1> = []; 423e41f4b71Sopenharmony_ci 424e41f4b71Sopenharmony_ci aboutToAppear(): void { 425e41f4b71Sopenharmony_ci for (let i=0; i<100; i++) { 426e41f4b71Sopenharmony_ci this.simpleList.push(new Wrap1('Hello' + i)); 427e41f4b71Sopenharmony_ci } 428e41f4b71Sopenharmony_ci } 429e41f4b71Sopenharmony_ci 430e41f4b71Sopenharmony_ci build() { 431e41f4b71Sopenharmony_ci Column() { 432e41f4b71Sopenharmony_ci List() { 433e41f4b71Sopenharmony_ci Repeat<Wrap1>(this.simpleList) 434e41f4b71Sopenharmony_ci .each((obj: RepeatItem<Wrap1>)=>{ 435e41f4b71Sopenharmony_ci ListItem() { 436e41f4b71Sopenharmony_ci Row() { 437e41f4b71Sopenharmony_ci Text('default index ' + obj.index + ': ') 438e41f4b71Sopenharmony_ci .fontSize(30) 439e41f4b71Sopenharmony_ci Text(obj.item.message) 440e41f4b71Sopenharmony_ci .fontSize(30) 441e41f4b71Sopenharmony_ci } 442e41f4b71Sopenharmony_ci } 443e41f4b71Sopenharmony_ci .margin(20) 444e41f4b71Sopenharmony_ci }) 445e41f4b71Sopenharmony_ci .template('odd', (obj: RepeatItem<Wrap1>)=>{ 446e41f4b71Sopenharmony_ci ListItem() { 447e41f4b71Sopenharmony_ci Row() { 448e41f4b71Sopenharmony_ci Text('odd index ' + obj.index + ': ') 449e41f4b71Sopenharmony_ci .fontSize(30) 450e41f4b71Sopenharmony_ci .fontColor(Color.Blue) 451e41f4b71Sopenharmony_ci Text(obj.item.message) 452e41f4b71Sopenharmony_ci .fontSize(30) 453e41f4b71Sopenharmony_ci .fontColor(Color.Blue) 454e41f4b71Sopenharmony_ci } 455e41f4b71Sopenharmony_ci } 456e41f4b71Sopenharmony_ci .margin(20) 457e41f4b71Sopenharmony_ci }) 458e41f4b71Sopenharmony_ci .template('even', (obj: RepeatItem<Wrap1>)=>{ 459e41f4b71Sopenharmony_ci ListItem() { 460e41f4b71Sopenharmony_ci Row() { 461e41f4b71Sopenharmony_ci Text('even index ' + obj.index + ': ') 462e41f4b71Sopenharmony_ci .fontSize(30) 463e41f4b71Sopenharmony_ci .fontColor(Color.Green) 464e41f4b71Sopenharmony_ci Text(obj.item.message) 465e41f4b71Sopenharmony_ci .fontSize(30) 466e41f4b71Sopenharmony_ci .fontColor(Color.Green) 467e41f4b71Sopenharmony_ci } 468e41f4b71Sopenharmony_ci } 469e41f4b71Sopenharmony_ci .margin(20) 470e41f4b71Sopenharmony_ci }) 471e41f4b71Sopenharmony_ci .templateId((item: Wrap1, index: number) => { 472e41f4b71Sopenharmony_ci return index%2 ? 'odd' : 'even'; 473e41f4b71Sopenharmony_ci }) 474e41f4b71Sopenharmony_ci .key((item: Wrap1, index: number) => { 475e41f4b71Sopenharmony_ci return item.message; 476e41f4b71Sopenharmony_ci }) 477e41f4b71Sopenharmony_ci } 478e41f4b71Sopenharmony_ci .cachedCount(5) 479e41f4b71Sopenharmony_ci .width('100%') 480e41f4b71Sopenharmony_ci .height('100%') 481e41f4b71Sopenharmony_ci } 482e41f4b71Sopenharmony_ci .height('100%') 483e41f4b71Sopenharmony_ci } 484e41f4b71Sopenharmony_ci} 485e41f4b71Sopenharmony_ci``` 486e41f4b71Sopenharmony_ci 487e41f4b71Sopenharmony_ci 488e41f4b71Sopenharmony_ci 489e41f4b71Sopenharmony_ci## 常见问题 490e41f4b71Sopenharmony_ci 491e41f4b71Sopenharmony_ci### 屏幕外的列表数据发生变化时,保证滚动条位置不变 492e41f4b71Sopenharmony_ci 493e41f4b71Sopenharmony_ci在List组件中声明Repeat组件,实现key值生成逻辑和each逻辑(如下示例代码),点击按钮“insert”,在屏幕显示的第一个元素前面插入一个元素,屏幕出现向下滚动。 494e41f4b71Sopenharmony_ci 495e41f4b71Sopenharmony_ci```ts 496e41f4b71Sopenharmony_ci// 定义一个类,标记为可观察的 497e41f4b71Sopenharmony_ci// 类中自定义一个数组,标记为可追踪的 498e41f4b71Sopenharmony_ci@ObservedV2 499e41f4b71Sopenharmony_ciclass ArrayHolder { 500e41f4b71Sopenharmony_ci @Trace arr: Array<number> = []; 501e41f4b71Sopenharmony_ci 502e41f4b71Sopenharmony_ci // constructor,用于初始化数组个数 503e41f4b71Sopenharmony_ci constructor(count: number) { 504e41f4b71Sopenharmony_ci for (let i = 0; i < count; i++) { 505e41f4b71Sopenharmony_ci this.arr.push(i); 506e41f4b71Sopenharmony_ci } 507e41f4b71Sopenharmony_ci } 508e41f4b71Sopenharmony_ci} 509e41f4b71Sopenharmony_ci 510e41f4b71Sopenharmony_ci@Entry 511e41f4b71Sopenharmony_ci@ComponentV2 512e41f4b71Sopenharmony_ciexport struct RepeatTemplateSingle { 513e41f4b71Sopenharmony_ci @Local arrayHolder: ArrayHolder = new ArrayHolder(100); 514e41f4b71Sopenharmony_ci @Local totalCount: number = this.arrayHolder.arr.length; 515e41f4b71Sopenharmony_ci scroller: Scroller = new Scroller(); 516e41f4b71Sopenharmony_ci 517e41f4b71Sopenharmony_ci build() { 518e41f4b71Sopenharmony_ci Column({ space: 5 }) { 519e41f4b71Sopenharmony_ci List({ space: 20, initialIndex: 19, scroller: this.scroller }) { 520e41f4b71Sopenharmony_ci Repeat(this.arrayHolder.arr) 521e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: this.totalCount }) 522e41f4b71Sopenharmony_ci .templateId((item, index) => { 523e41f4b71Sopenharmony_ci return 'number'; 524e41f4b71Sopenharmony_ci }) 525e41f4b71Sopenharmony_ci .template('number', (r) => { 526e41f4b71Sopenharmony_ci ListItem() { 527e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "Reuse"); 528e41f4b71Sopenharmony_ci } 529e41f4b71Sopenharmony_ci }) 530e41f4b71Sopenharmony_ci .each((r) => { 531e41f4b71Sopenharmony_ci ListItem() { 532e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "eachMessage"); 533e41f4b71Sopenharmony_ci } 534e41f4b71Sopenharmony_ci }) 535e41f4b71Sopenharmony_ci } 536e41f4b71Sopenharmony_ci .height('30%') 537e41f4b71Sopenharmony_ci 538e41f4b71Sopenharmony_ci Button(`insert totalCount ${this.totalCount}`) 539e41f4b71Sopenharmony_ci .height(60) 540e41f4b71Sopenharmony_ci .onClick(() => { 541e41f4b71Sopenharmony_ci // 插入元素,元素位置为屏幕显示的前一个元素 542e41f4b71Sopenharmony_ci this.arrayHolder.arr.splice(18, 0, this.totalCount); 543e41f4b71Sopenharmony_ci this.totalCount = this.arrayHolder.arr.length; 544e41f4b71Sopenharmony_ci }) 545e41f4b71Sopenharmony_ci } 546e41f4b71Sopenharmony_ci .width('100%') 547e41f4b71Sopenharmony_ci .margin({ top: 5 }) 548e41f4b71Sopenharmony_ci } 549e41f4b71Sopenharmony_ci} 550e41f4b71Sopenharmony_ci``` 551e41f4b71Sopenharmony_ci 552e41f4b71Sopenharmony_ci运行效果: 553e41f4b71Sopenharmony_ci 554e41f4b71Sopenharmony_ci 555e41f4b71Sopenharmony_ci 556e41f4b71Sopenharmony_ci在一些场景中,我们不希望屏幕外的数据源变化影响屏幕中List列表Scroller停留的位置,可以通过List组件的[onScrollIndex](../ui/arkts-layout-development-create-list.md#响应滚动位置)事件对列表滚动动作进行监听,当列表发生滚动时,获取列表滚动位置。使用Scroller组件的[scrollToIndex](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scrolltoindex)特性,滑动到指定index位置,实现屏幕外的数据源增加/删除数据时,Scroller停留的位置不变的效果。 557e41f4b71Sopenharmony_ci 558e41f4b71Sopenharmony_ci示例代码仅对增加数据的情况进行展示。 559e41f4b71Sopenharmony_ci 560e41f4b71Sopenharmony_ci```ts 561e41f4b71Sopenharmony_ci// 定义一个类,标记为可观察的 562e41f4b71Sopenharmony_ci// 类中自定义一个数组,标记为可追踪的 563e41f4b71Sopenharmony_ci@ObservedV2 564e41f4b71Sopenharmony_ciclass ArrayHolder { 565e41f4b71Sopenharmony_ci @Trace arr: Array<number> = []; 566e41f4b71Sopenharmony_ci 567e41f4b71Sopenharmony_ci // constructor,用于初始化数组个数 568e41f4b71Sopenharmony_ci constructor(count: number) { 569e41f4b71Sopenharmony_ci for (let i = 0; i < count; i++) { 570e41f4b71Sopenharmony_ci this.arr.push(i); 571e41f4b71Sopenharmony_ci } 572e41f4b71Sopenharmony_ci } 573e41f4b71Sopenharmony_ci} 574e41f4b71Sopenharmony_ci 575e41f4b71Sopenharmony_ci@Entry 576e41f4b71Sopenharmony_ci@ComponentV2 577e41f4b71Sopenharmony_ciexport struct RepeatTemplateSingle { 578e41f4b71Sopenharmony_ci @Local arrayHolder: ArrayHolder = new ArrayHolder(100); 579e41f4b71Sopenharmony_ci @Local totalCount: number = this.arrayHolder.arr.length; 580e41f4b71Sopenharmony_ci scroller: Scroller = new Scroller(); 581e41f4b71Sopenharmony_ci 582e41f4b71Sopenharmony_ci private start: number = 1; 583e41f4b71Sopenharmony_ci private end: number = 1; 584e41f4b71Sopenharmony_ci 585e41f4b71Sopenharmony_ci build() { 586e41f4b71Sopenharmony_ci Column({ space: 5 }) { 587e41f4b71Sopenharmony_ci List({ space: 20, initialIndex: 19, scroller: this.scroller }) { 588e41f4b71Sopenharmony_ci Repeat(this.arrayHolder.arr) 589e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: this.totalCount }) 590e41f4b71Sopenharmony_ci .templateId((item, index) => { 591e41f4b71Sopenharmony_ci return 'number'; 592e41f4b71Sopenharmony_ci }) 593e41f4b71Sopenharmony_ci .template('number', (r) => { 594e41f4b71Sopenharmony_ci ListItem() { 595e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "Reuse"); 596e41f4b71Sopenharmony_ci } 597e41f4b71Sopenharmony_ci }) 598e41f4b71Sopenharmony_ci .each((r) => { 599e41f4b71Sopenharmony_ci ListItem() { 600e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "eachMessage"); 601e41f4b71Sopenharmony_ci } 602e41f4b71Sopenharmony_ci }) 603e41f4b71Sopenharmony_ci } 604e41f4b71Sopenharmony_ci .onScrollIndex((start, end) => { 605e41f4b71Sopenharmony_ci this.start = start; 606e41f4b71Sopenharmony_ci this.end = end; 607e41f4b71Sopenharmony_ci }) 608e41f4b71Sopenharmony_ci .height('30%') 609e41f4b71Sopenharmony_ci 610e41f4b71Sopenharmony_ci Button(`insert totalCount ${this.totalCount}`) 611e41f4b71Sopenharmony_ci .height(60) 612e41f4b71Sopenharmony_ci .onClick(() => { 613e41f4b71Sopenharmony_ci // 插入元素,元素位置为屏幕显示的前一个元素 614e41f4b71Sopenharmony_ci this.arrayHolder.arr.splice(18, 0, this.totalCount); 615e41f4b71Sopenharmony_ci let rect = this.scroller.getItemRect(this.start); // 获取子组件的大小位置 616e41f4b71Sopenharmony_ci this.scroller.scrollToIndex(this.start + 1); // 滑动到指定index 617e41f4b71Sopenharmony_ci this.scroller.scrollBy(0, -rect.y); // 滑动指定距离 618e41f4b71Sopenharmony_ci this.totalCount = this.arrayHolder.arr.length; 619e41f4b71Sopenharmony_ci }) 620e41f4b71Sopenharmony_ci } 621e41f4b71Sopenharmony_ci .width('100%') 622e41f4b71Sopenharmony_ci .margin({ top: 5 }) 623e41f4b71Sopenharmony_ci } 624e41f4b71Sopenharmony_ci} 625e41f4b71Sopenharmony_ci``` 626e41f4b71Sopenharmony_ci 627e41f4b71Sopenharmony_ci运行效果: 628e41f4b71Sopenharmony_ci 629e41f4b71Sopenharmony_ci 630e41f4b71Sopenharmony_ci 631e41f4b71Sopenharmony_ci### totalCount值大于数据源长度 632e41f4b71Sopenharmony_ci 633e41f4b71Sopenharmony_ci当数据源总长度很大时,会使用懒加载的方式先加载一部分数据,为了使Repeat显示正确的滚动条样式,需要将数据总长度赋值给totalCount,即数据源全部加载完成前,totalCount大于array.length。 634e41f4b71Sopenharmony_ci 635e41f4b71Sopenharmony_citotalCount > array.length时,在父组件容器滚动过程中,应用需要保证列表即将滑动到数据源末尾时请求后续数据,开发者需要对数据请求的错误场景(如网络延迟)进行保护操作,直到数据源全部加载完成,否则列表滑动的过程中会出现滚动效果异常。 636e41f4b71Sopenharmony_ci 637e41f4b71Sopenharmony_ci上述规范可以通过实现父组件List/Grid的[onScrollIndex](../ui/arkts-layout-development-create-list.md#响应滚动位置)属性的回调函数完成。示例代码如下: 638e41f4b71Sopenharmony_ci 639e41f4b71Sopenharmony_ci```ts 640e41f4b71Sopenharmony_ci@ObservedV2 641e41f4b71Sopenharmony_ciclass VehicleData { 642e41f4b71Sopenharmony_ci @Trace name: string; 643e41f4b71Sopenharmony_ci @Trace price: number; 644e41f4b71Sopenharmony_ci 645e41f4b71Sopenharmony_ci constructor(name: string, price: number) { 646e41f4b71Sopenharmony_ci this.name = name; 647e41f4b71Sopenharmony_ci this.price = price; 648e41f4b71Sopenharmony_ci } 649e41f4b71Sopenharmony_ci} 650e41f4b71Sopenharmony_ci 651e41f4b71Sopenharmony_ci@ObservedV2 652e41f4b71Sopenharmony_ciclass VehicleDB { 653e41f4b71Sopenharmony_ci public vehicleItems: VehicleData[] = []; 654e41f4b71Sopenharmony_ci 655e41f4b71Sopenharmony_ci constructor() { 656e41f4b71Sopenharmony_ci // init data size 20 657e41f4b71Sopenharmony_ci for (let i = 1; i <= 20; i++) { 658e41f4b71Sopenharmony_ci this.vehicleItems.push(new VehicleData(`Vehicle${i}`, i)); 659e41f4b71Sopenharmony_ci } 660e41f4b71Sopenharmony_ci } 661e41f4b71Sopenharmony_ci} 662e41f4b71Sopenharmony_ci 663e41f4b71Sopenharmony_ci@Entry 664e41f4b71Sopenharmony_ci@ComponentV2 665e41f4b71Sopenharmony_cistruct entryCompSucc { 666e41f4b71Sopenharmony_ci @Local vehicleItems: VehicleData[] = new VehicleDB().vehicleItems; 667e41f4b71Sopenharmony_ci @Local listChildrenSize: ChildrenMainSize = new ChildrenMainSize(60); 668e41f4b71Sopenharmony_ci @Local totalCount: number = this.vehicleItems.length; 669e41f4b71Sopenharmony_ci scroller: Scroller = new Scroller(); 670e41f4b71Sopenharmony_ci 671e41f4b71Sopenharmony_ci build() { 672e41f4b71Sopenharmony_ci Column({ space: 3 }) { 673e41f4b71Sopenharmony_ci List({ scroller: this.scroller }) { 674e41f4b71Sopenharmony_ci Repeat(this.vehicleItems) 675e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: 50 }) // total data size 50 676e41f4b71Sopenharmony_ci .templateId(() => 'default') 677e41f4b71Sopenharmony_ci .template('default', (ri) => { 678e41f4b71Sopenharmony_ci ListItem() { 679e41f4b71Sopenharmony_ci Column() { 680e41f4b71Sopenharmony_ci Text(`${ri.item.name} + ${ri.index}`) 681e41f4b71Sopenharmony_ci .width('90%') 682e41f4b71Sopenharmony_ci .height(this.listChildrenSize.childDefaultSize) 683e41f4b71Sopenharmony_ci .backgroundColor(0xFFA07A) 684e41f4b71Sopenharmony_ci .textAlign(TextAlign.Center) 685e41f4b71Sopenharmony_ci .fontSize(20) 686e41f4b71Sopenharmony_ci .fontWeight(FontWeight.Bold) 687e41f4b71Sopenharmony_ci } 688e41f4b71Sopenharmony_ci }.border({ width: 1 }) 689e41f4b71Sopenharmony_ci }, { cachedCount: 5 }) 690e41f4b71Sopenharmony_ci .each((ri) => { 691e41f4b71Sopenharmony_ci ListItem() { 692e41f4b71Sopenharmony_ci Text("Wrong: " + `${ri.item.name} + ${ri.index}`) 693e41f4b71Sopenharmony_ci .width('90%') 694e41f4b71Sopenharmony_ci .height(this.listChildrenSize.childDefaultSize) 695e41f4b71Sopenharmony_ci .backgroundColor(0xFFA07A) 696e41f4b71Sopenharmony_ci .textAlign(TextAlign.Center) 697e41f4b71Sopenharmony_ci .fontSize(20) 698e41f4b71Sopenharmony_ci .fontWeight(FontWeight.Bold) 699e41f4b71Sopenharmony_ci }.border({ width: 1 }) 700e41f4b71Sopenharmony_ci }) 701e41f4b71Sopenharmony_ci .key((item, index) => `${index}:${item}`) 702e41f4b71Sopenharmony_ci } 703e41f4b71Sopenharmony_ci .height('50%') 704e41f4b71Sopenharmony_ci .margin({ top: 20 }) 705e41f4b71Sopenharmony_ci .childrenMainSize(this.listChildrenSize) 706e41f4b71Sopenharmony_ci .alignListItem(ListItemAlign.Center) 707e41f4b71Sopenharmony_ci .onScrollIndex((start, end) => { 708e41f4b71Sopenharmony_ci console.log('onScrollIndex', start, end); 709e41f4b71Sopenharmony_ci // lazy data loading 710e41f4b71Sopenharmony_ci if (this.vehicleItems.length < 50) { 711e41f4b71Sopenharmony_ci for (let i = 0; i < 10; i++) { 712e41f4b71Sopenharmony_ci if (this.vehicleItems.length < 50) { 713e41f4b71Sopenharmony_ci this.vehicleItems.push(new VehicleData("Vehicle_loaded", i)); 714e41f4b71Sopenharmony_ci } 715e41f4b71Sopenharmony_ci } 716e41f4b71Sopenharmony_ci } 717e41f4b71Sopenharmony_ci }) 718e41f4b71Sopenharmony_ci } 719e41f4b71Sopenharmony_ci } 720e41f4b71Sopenharmony_ci} 721e41f4b71Sopenharmony_ci``` 722e41f4b71Sopenharmony_ci 723e41f4b71Sopenharmony_ci示例代码运行效果: 724e41f4b71Sopenharmony_ci 725e41f4b71Sopenharmony_ci