HarmonyOS7 嵌套滚动:Scroll + List 嵌套滚动的正确姿势 原创

前言
搞了半天嵌套滚动,手指一划——要么外层 Scroll 疯狂滚动内层 List 一动不动,要么两个一起滚然后卡死。说实话,嵌套滚动这玩意儿,没搞懂 nestedScroll 之前就是玄学。
今天把 Scroll 嵌套 List 的正确姿势整理一下,踩过的坑也一并分享。
做商品详情页、个人中心这类页面,几乎都会碰到一个结构:外层整体可滚动,内部又有个列表区域也要滚动。这种滚动套滚动的场景,天然就有冲突——你的手指在 List 上划,系统该把滚动事件给谁?
Android 那边处理嵌套滚动要搞 NestedScrollingParent/Child 一套接口,HarmonyOS7 就简单多了,一个属性搞定。但前提是——你得搞懂那几个枚举值到底是啥意思。
嵌套滚动的坑
先看看不处理嵌套滚动是啥效果:
@Entry
@Component
struct BadNestScroll {
build() {
Scroll() {
Column() {
Text('顶部Banner区域')
.width('100%')
.height(200)
.backgroundColor('#ffdddd')
.textAlign(TextAlign.Center)
List() {
ForEach([1, 2, 3, 4, 5, 6, 7, 8], (item: number) => {
ListItem() {
Text(`列表项 ${item}`)
.width('100%')
.height(80)
.textAlign(TextAlign.Center)
}
})
}
.width('100%')
.height(400)
}
}
.width('100%')
.height('100%')
}
}


跑起来你会发现:手指在 List 区域滑动,只有 List 在滚,顶部的 Banner 纹丝不动。想先滚 Banner 再滚 List?做不到。
滚动嵌套冲突分析
说白了,嵌套滚动的核心问题就一个:滚动事件的分发顺序。
当手指在 List 上滑动时,系统面临选择:
- 把事件给 List → List 先滚,外层 Scroll 不动
- 把事件给 Scroll → Scroll 先滚,List 不动
- 两个都给 → 双层同时滚,体验很诡异
不同场景要的效果不一样。商品详情页要的是先滚外层(把 Banner 滚走),再滚内层(浏览列表)——这叫"外层优先"。
Scroll 嵌套 List 的正确写法
上代码:
@Entry
@Component
struct GoodNestScroll {
build() {
Scroll() {
Column() {
Text('顶部Banner区域')
.width('100%')
.height(200)
.backgroundColor('#ffdddd')
.textAlign(TextAlign.Center)
List() {
ForEach([1, 2, 3, 4, 5, 6, 7, 8], (item: number) => {
ListItem() {
Text(`列表项 ${item}`)
.width('100%')
.height(80)
.textAlign(TextAlign.Center)
}
})
}
.width('100%')
.height(400)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
}
.width('100%')
.height('100%')
}
}
关键代码讲解:
.nestedScroll()—— 这是 List 组件上的属性,告诉系统"我嵌套在 Scroll 里了,滚动事件该怎么分"scrollForward: NestedScrollMode.PARENT_FIRST—— 往下滑时(向末尾方向),先让外层 Scroll 滚。这样 Banner 会先滚走,等 Scroll 滚不动了,List 才开始滚scrollBackward: NestedScrollMode.SELF_FIRST—— 往上滑时(向起始方向),先让 List 自己滚。用户回看列表项时不会突然跳到顶部
划重点:
nestedScroll是设在子滚动组件上的,不是外层 Scroll 上!
NestedScroll 属性详解
nestedScroll 接收 NestedScrollOptions 对象,两个方向各自配置:
| 参数 | 含义 |
|---|---|
scrollForward |
往末尾端滚动时的模式(下滑) |
scrollBackward |
往起始端滚动时的模式(上滑) |
每个方向可选的 NestedScrollMode 枚举:
| 枚举值 | 说明 | 典型场景 |
|---|---|---|
SELF_ONLY |
只自己滚,不跟父组件联动 | 不想联动的时候 |
SELF_FIRST |
自己先滚,滚到边缘后父组件滚 | 回滚时先看列表 |
PARENT_FIRST |
父组件先滚,滚到边缘后自己滚 | 下滑时先看 Banner |
PARALLEL |
自己和父组件同时滚 | 特殊联动效果 |
最常见的组合就是:
scrollForward: PARENT_FIRST // 下滑先滚外层
scrollBackward: SELF_FIRST // 上滑先滚内层
这也是评论区吸顶、商品详情页的标准配置。
自定义嵌套滚动
更复杂的场景怎么办?比如我要实现评论区吸顶——List 到顶部后"粘住",再往下滑只滚 List,不滚 Banner。
@Entry
@Component
struct StickyNestScroll {
private scroller: Scroller = new Scroller()
@State listHeight: number = 500
build() {
Scroll(this.scroller) {
Column() {
Text('文章内容区域')
.width('100%')
.height(300)
.backgroundColor('#ddeeff')
.textAlign(TextAlign.Center)
Text('评论区')
.width('100%')
.height(48)
.backgroundColor('#eeeeee')
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
List() {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], (item: number) => {
ListItem() {
Row() {
Text(`用户${item}:`)
.fontWeight(FontWeight.Bold)
.fontSize(14)
Text('这是一条评论内容...')
.fontSize(14)
.margin({ left: 8 })
}
.width('100%')
.padding(16)
}
})
}
.width('100%')
.height(this.listHeight)
.edgeEffect(EdgeEffect.None)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
}
.width('100%')
.height('100%')
.edgeEffect(EdgeEffect.Spring)
}
}
关键代码讲解:
Scroll(this.scroller)—— 传入 Scroller 控制器,后面可以通过代码控制外层滚动List.edgeEffect(EdgeEffect.None)—— 关掉 List 的边缘效果,滚到头不会有弹性回弹,体验更干净Scroll.edgeEffect(EdgeEffect.Spring)—— 外层用弹性效果,手感更好
如果想让吸顶更"刚",可以监听 Scroll 的
onDidScroll回调,动态调整 List 的高度。
踩坑记录
坑 1:nestedScroll 设反了方向
scrollForward 是往末尾方向,不是"向下"。如果你的布局是反的(比如某些 RTL 场景),Forward 和 Backward 的含义会变。跑起来不对,先检查方向。
坑 2:List 高度不设或设成 100%
List 嵌套在 Scroll 里时,必须给 List 一个固定高度,不然它会把所有内容撑开,失去滚动能力。设成 height('100%') 也不行——那等于占满 Scroll,嵌套就没意义了。
坑 3:两个方向的 nestedScroll 设成一样
有些人图省事,Forward 和 Backward 都设 PARENT_FIRST。结果是往上滑也先滚外层,用户回看列表时体验极差。两个方向要根据交互需求分别设。
坑 4:Scroller 控制自己
在 onDidScroll 里用 Scroller 控制自己所在的组件滚动,会导致索引错乱或者无限循环。控制别的组件可以,控制自己不行。
写在最后
嵌套滚动这事儿,核心就两步:
- 给子滚动组件加
.nestedScroll() - Forward 用
PARENT_FIRST,Backward 用SELF_FIRST
90% 的场景这招就够用了。剩下 10% 的复杂交互,再配合 Scroller 和 onDidScroll 手动控制。
别怕嵌套滚动,也别硬写手动滚动逻辑——nestedScroll 就是专门干这事的。




















原来List嵌套必须得给个固定高度啊,怪不得我的页面之前要么卡死要么不动
总算有人写这个了,之前一直搞不懂nestedScroll几个枚举值啥区别,看来列表上滚要self-first下滚要parent-first啊,继续加油