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

王董HO
发布于 2026-7-22 08:22
浏览
0收藏

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%')
  }
}

HarmonyOS7 嵌套滚动:Scroll + List 嵌套滚动的正确姿势-鸿蒙开发者社区

HarmonyOS7 嵌套滚动:Scroll + List 嵌套滚动的正确姿势-鸿蒙开发者社区

跑起来你会发现:手指在 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 控制自己所在的组件滚动,会导致索引错乱或者无限循环。控制别的组件可以,控制自己不行。

写在最后

嵌套滚动这事儿,核心就两步:

  1. 给子滚动组件加 .nestedScroll()
  2. Forward 用 PARENT_FIRST,Backward 用 SELF_FIRST

90% 的场景这招就够用了。剩下 10% 的复杂交互,再配合 Scroller 和 onDidScroll 手动控制。

别怕嵌套滚动,也别硬写手动滚动逻辑——nestedScroll 就是专门干这事的。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
已于2026-7-22 08:22:58修改
收藏
回复
举报
2条回复
按时间正序
/
按时间倒序
街头便衣
街头便衣

原来List嵌套必须得给个固定高度啊,怪不得我的页面之前要么卡死要么不动

回复
2026-7-22 10:15:04
在人间耕耘
在人间耕耘

总算有人写这个了,之前一直搞不懂nestedScroll几个枚举值啥区别,看来列表上滚要self-first下滚要parent-first啊,继续加油

回复
2026-7-22 12:14:40
回复
    相关推荐