HarmonyOS鸿蒙Next中List的父/祖父组件是Scroll组件,List的maintainVisibleContentPosition(true)属性失效?

HarmonyOS鸿蒙Next中List的父/祖父组件是Scroll组件,List的maintainVisibleContentPosition(true)属性失效?

const PAGE_SIZE = 20;
@Component
struct ListPage {
  readonly listData: BaseDataSource<ItemData> = new BaseDataSource()
  page = 10

  aboutToAppear(): void {
    const firstData = getData(this.page * PAGE_SIZE, PAGE_SIZE)
    this.listData.array.push(...firstData)
  }

  build() {
    Column() {
      Button('添加上一页的数据').onClick(() => {
        if (this.page >= 0) {
          const list = getData((--this.page) * PAGE_SIZE, PAGE_SIZE)
          this.listData.array.splice(0, 0, ...list)
          this.listData.notifyDatasetChange([{
            type: DataOperationType.ADD,
            index: 0,
            count: list.length,
            key: list.map(item => item.name)
          }])
        }
      })
      //Scroll () {
      Column() {
        List() {
          LazyForEach(this.listData, (item: ItemData) => {
            ListItem() {
              this.listItem(item)
            }
          }, (item: ItemData) => item.name)
        }.width('100%').padding(15)
          .maintainVisibleContentPosition(true)
      }
      //}
    }.width('100%')
      .height('100%')
  }
}

export function getData(start: number, count: number): Array<ItemData> {
  const arr: Array<ItemData> = []
  for (let i = 0; i < count; i++) {
    arr.push(new ItemData(`id = ${i + start}`))
  }
  return arr
}

把父组件Scroll去掉,然后点击按钮添加上一页的数据,可见内容位置是不变的, 然后把父组件Scroll加上,点击按钮钮添加上一页的数据,List就自动滑动到顶部。这个是bug吗?


更多关于HarmonyOS鸿蒙Next中List的父/祖父组件是Scroll组件,List的maintainVisibleContentPosition(true)属性失效?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

5 回复

【背景知识】

  • maintainVisibleContentPosition:设置显示区域上方插入或删除数据时是否要保持可见内容位置不变。只有使用LazyForEach在显示区域外插入或删除数据时,才能保持可见内容位置不变。设置为true后,在显示区域上方插入或删除数据,会触发onDidScrollonScrollIndex事件。详见接口说明。

  • Scroll嵌套List导致按需加载失效:当Scroll容器嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认加载全部ListItem,导致按需加载失效,甚至会导致应用卡顿、崩溃,详细案例可参考布局优化指导

【问题定位】

  1. 样例代码中Scroll内的List组件仅指定了宽度,并未指定高度,而根据背景知识第二条可知,如果不指定List宽高尺寸,则会默认加载全部ListItem,因此怀疑是未指定高度使得List重新加载,导致可视区域数据变化。

  2. 指定内层List组件高度为500,验证点击按钮加载新数据后,可视区域数据未发生变化。

【分析结论】 Scroll内层的List组件未明确指定宽高,导致新数据加载后触发了ListItem的重新加载,使得现象与maintainVisibleContentPosition(true)失效一致。

【修改建议】 给内层List组件显式指定明确宽高,示例代码如下:

Scroll() {
  Column() {
    List() {
      LazyForEach(this.listData, (item: ItemData) => {
        ListItem() {
          Text(item.name).width('100%').height(20)
        }
      }, (item: ItemData) => item.name)
    }
    .width('100%')
    .height(500)
    .padding(15)
    .maintainVisibleContentPosition(true)
  }
}

【常见FAQ】

Q:maintainVisibleContentPosition这个属性不支持Repeat吗? A:Repeat的功能是基于数组类型数据来进行循环渲染,一般与容器组件配合使用。而maintainVisibleContentPosition是UI组件的属性,其使用方法与说明可参考maintainVisibleContentPosition

更多关于HarmonyOS鸿蒙Next中List的父/祖父组件是Scroll组件,List的maintainVisibleContentPosition(true)属性失效?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


maintainVisibleContentPosition 这个属性不支持Repeat吗?

Button('添加上一页的数据').onClick(() => {
  if (this.page >= 0) {
    const list = getData((--this.page) * PAGE_SIZE, PAGE_SIZE)
    this.array.splice(0, 0, ...list)
  }
})
List() {
  Repeat(this.array)
    .each((riItemData) => {
      this.listItemV2(riItemData)
    })
    .key((item) => item.name)
}.width('100%').padding(15)
.maintainVisibleContentPosition(true)

我换成Repeat,list外面也没Scroll,点击按钮内容还是自动向上滑动。

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-list#maintainvisiblecontentposition12

只有使用LazyForEach在显示区域外插入或删除数据时,才能保持可见内容位置不变

貌似不支持Repeat🤣,

在HarmonyOS Next中,当List的父/祖父组件是Scroll时,maintainVisibleContentPosition(true)会失效。这是因为Scroll和List的滚动机制存在冲突,两者都试图控制滚动位置保持逻辑。该属性仅在List作为唯一滚动容器时生效。解决方案是将List作为顶级滚动容器,避免嵌套Scroll组件。华为官方文档已确认该限制。

在HarmonyOS Next中,当List组件被嵌套在Scroll组件内时,maintainVisibleContentPosition属性确实可能会失效。这是因为Scroll组件本身也是一个可滚动容器,它会接管滚动位置的维护逻辑,导致List组件内部的这个属性无法正常工作。

这不是一个bug,而是预期的行为设计。当存在多层滚动容器嵌套时,系统会优先处理最外层滚动容器的行为。要解决这个问题,可以考虑以下方案:

  1. 移除外层的Scroll组件,因为List本身已经是一个可滚动容器

  2. 如果需要保留Scroll容器,可以尝试通过编程方式记录和恢复滚动位置:

let scrollOffset = 0;

Scroll() {
  Column() {
    List() {
      // ...
    }
    .onScroll((offset: number) => {
      scrollOffset = offset;
    })
  }
}

// 在添加数据后恢复位置
this.listData.notifyDatasetChange([...]);
this.scroller.scrollTo({offset: scrollOffset});
  1. 或者考虑重新设计布局,避免不必要的滚动容器嵌套

这种设计是为了确保滚动行为的一致性,特别是在复杂嵌套布局中。开发者需要根据实际场景选择合适的解决方案。

回到顶部