内层列表还在滚,外层页面已经滑到底了
在做商品详情页时,页面整体是可以滚动的(Scroll),中间有一个“猜你喜欢”的横向瀑布流或者一个小型垂直列表(List)。用户的预期是:当我的手指按在列表上滑动时,列表先滚;等列表滚到底部,继续滑动时,外层的页面再跟着滚。这个行为叫嵌套滚动(NestedScroll)。
但很多开发者第一次做这个功能,往往会发现:要么内层列表根本不响应滚动,手势全被外层 Scroll 吃掉了;要么外层 Scroll 纹丝不动,内层列表滑到底之后出现了经典的“橡皮筋”回弹,死活滑不动整个页面。

NestedScroll 的优先级策略
默认情况下,ArkUI 的滚动组件并不会自动把滚动事件传递给父级容器。要开启这个联动,必须在内层组件(比如 List 或 Grid)上配置 nestedScroll 属性。
typescript
List() {
// 内容项
}
.nestedScroll({
scrollForward: NestedScrollMode.SELF_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
scrollForward 表示手指往上滑(内容往下走)的方向。NestedScrollMode.SELF_FIRST 的意思是:内层自己先滚,滚到边界(顶部或底部)之后,再把剩下的位移量交给外层父容器去滚。
这个策略完美符合刚刚说的预期。如果不配置这个字段,系统默认的行为是 NestedScrollMode.SELF_ONLY,也就是内层组件吃到滚动手势之后,就不再向外传递了。表现就是内层列表滚到了底,外层大页面毫无反应。
外层 Scroll 属性的配合
内层配置好了只是第一步。外层的 Scroll 或 List 组件,必须允许子级组件向外传递滚动事件。大多数情况下,只要内层配置正确,外层默认是接受传递的。
但这中间有个坑:如果外层的组件(比如就是 Scroll)也开启了手势拦截,或者使用了一些特殊的嵌套组合,事件可能传不上来。在排查时,可以试着把外层 Scroll 的滑动相关属性精简,移除不必要的 .gesture 或 .onTouch 拦截。
另外,如果内层滚到底的时候,你希望触发数据加载(比如上拉加载更多),而不是立刻让外层滚动,那就得用 NestedScrollMode.SELF_FIRST 配合列表的 onReachEnd 回调。在回调里发请求,此时不要阻止事件传递,否则外层就会僵住。
跨列表类型的传递
很多时候,内层是横向滑动的列表(比如分类栏),外层是纵向滚动的页面。这种正交方向的滑动,按理说互不干扰。但如果在外层包裹了 Gesture,横滑事件可能被纵向手势识别器误判拦截。
这种场景下,配置内层横向 List 的嵌套滚动方向就要特别注意:
typescript
List({ space: 10 }) {
// 横向卡片
}
.listDirection(Axis.Horizontal)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
如果是横向列表,判断标准是:横滑事件发生,优先内层处理。只有当横向滑到尽头,用户再尝试横滑时,才可能触发父级的横滑操作(如果父级也有横滑能力的话)。如果父级是纵向滚动,正交方向的事件依然会被各自组件正常消耗,不会发生“偷焦点”的情况。
嵌套滚动的性能隐患
嵌套滚动在视觉上很爽,但带给渲染引擎的压力是翻倍的。当内层滚动传递到外层时,外层整个组件的布局会被重新计算。如果外层页面极其复杂,每一次嵌套滚动的交接都会带来一瞬间的卡顿。
在设计这种页面时,尽量让外层 Scroll 内部仅仅包含静态组件和内层可滚动的列表。避免在外层放一些频繁刷新的状态相关的组件,因为外层的滑动会连带它们一起进行脏区重算。内层列表如果在滚动过程中有图片加载,务必开启 autoResize,因为在嵌套滚动的交界处,任何一次长耗时的解码都会让整个嵌套体验变成幻灯片。
更多推荐

所有评论(0)