黑科技:八行代码解决 ScrollView 嵌套事件拦截问题!
前言
各位小伙伴大家好,你在开发排行榜或复杂列表时是否遇到过这样的绝望场景:
- 😫 排行榜主列表是垂直滚动,每个 Item 里有横向奖励列表
- 🤔 横向滑动奖励列表正常,但纵向滑动排行榜却完全失效
- 💢 触摸事件被莫名其妙"劫持",用户体验极差
这是游戏开发中最常见的嵌套滚动问题!
今天,我将深入 Cocos Creator 源码,分享一个革命性的解决方案,用短短几行代码彻底解决这个痛点!
🎯 问题场景重现
排行榜 ScrollView (垂直滚动)
排行榜 ScrollView (垂直)
├── 排行榜 Item 1
│ └── 奖励列表 ScrollView (横向滚动)
├── 排行榜 Item 2
│ └── 奖励列表 ScrollView (横向滚动)
└── 排行榜 Item 3
└── 奖励列表 ScrollView (横向滚动)
现象:
- ✅ 横向滑动奖励列表 → 正常工作
- ❌ 在奖励列表纵向滑动排行榜 → 完全无响应
🔍 问题根源:ViewGroup 的事件拦截机制
// ScrollView 的继承结构
export class ScrollView extends ViewGroup {
// ViewGroup 是所有可交互UI组件的基类
}
关键方法 _hasNestedViewGroup
protected _hasNestedViewGroup(event: Event, captureListeners?: Node[]): boolean {
if (!event || event.eventPhase !== Event.CAPTURING_PHASE) {
return false;
}
if (captureListeners) {
for (const listener of captureListeners) {
const item = listener;
if (this.node === item) {
if (event.target && (event.target as Node).getComponent(ViewGroup)) {
return true;
}
return false;
}
if (item.getComponent(ViewGroup)) {
return true;
}
}
}
return false;
}
事件在捕获阶段被嵌套的 ViewGroup 拦截,导致外层 ScrollView 无法响应。
📍 事件注册的关键:useCapture
protected _registerEvent(): void {
this.node.on(NodeEventType.TOUCH_START, this._onTouchBegan, this, true);
this.node.on(NodeEventType.TOUCH_MOVE, this._onTouchMoved, this, true);
this.node.on(NodeEventType.TOUCH_END, this._onTouchEnded, this, true);
this.node.on(NodeEventType.TOUCH_CANCEL, this._onTouchCancelled, this, true);
// ↑
// useCapture = true
}
🔍 ScrollView 的 _onTouchMoved
protected _onTouchMoved(event: EventTouch, captureListeners?: Node[]): void {
if (!this.enabledInHierarchy || !this._content) return;
if (this._hasNestedViewGroup(event, captureListeners)) {
return; // 👽 事件被拦截
}
const touch = event.touch!;
this._handleMoveLogic(touch);
if (!this.cancelInnerEvents) return;
const deltaMove = touch.getUILocation(_tempVec2);
deltaMove.subtract(touch.getUIStartLocation(_tempVec2_1));
if (deltaMove.length() > 7) {
if (!this._touchMoved && event.target !== this.node) {
const cancelEvent = new EventTouch(event.getTouches(), event.bubbles, SystemEventType.TOUCH_CANCEL);
cancelEvent.touch = event.touch;
cancelEvent.simulate = true;
(event.target as Node).dispatchEvent(cancelEvent);
this._touchMoved = true;
}
}
this._stopPropagationIfTargetIsMe(event);
}
💡 解决方案:事件转发机制
import { _decorator, Component, EventTouch, NodeEventType } from 'cc';
const { ccclass } = _decorator;
interface CustomEventTouch extends EventTouch {
mock?: boolean;
}
@ccclass('NestTouchCmp')
export class NestTouchCmp extends Component {
protected onLoad(): void {
this.node.on(NodeEventType.TOUCH_START, this.OnNestTouchEvent, this, true);
this.node.on(NodeEventType.TOUCH_MOVE, this.OnNestTouchEvent, this, true);
this.node.on(NodeEventType.TOUCH_END, this.OnNestTouchEvent, this, true);
this.node.on(NodeEventType.TOUCH_CANCEL, this.OnNestTouchEvent, this, true);
}
private OnNestTouchEvent(event: CustomEventTouch): void {
if (event.mock || event.simulate || event.target === this.node) return;
const copyEvent: CustomEventTouch = new EventTouch(event.getTouches(), event.bubbles, event.getType());
copyEvent.type = event.type;
copyEvent.touch = event.touch;
copyEvent.mock = true;
this.scheduleOnce(() => {
this.node.dispatchEvent(copyEvent);
});
}
}
🧠 原理总结
- 捕获阶段提前监听事件(useCapture: true)
- 过滤模拟事件和自身事件,防止死循环
- 克隆原事件并打上 mock 标记
- 异步在下一帧重新派发事件
🚀 实战应用示意图
用户纵向滑动奖励列表区域
↓
NestTouchCmp 捕获阶段拦截事件
↓
克隆事件 → 标记为 mock
↓
下一帧异步重新派发事件
↓
外层排行榜 ScrollView 成功响应
🎯 适用场景
- ✅ 排行榜 + 奖励列表
- ✅ 商城 + 商品横向滑动
- ✅ 新闻 + 图片轮播
- ✅ 聊天 + 表情包横滑面板
🎉 最佳实践
✅ 建议
- useCapture: true
- 使用
mock标记防止重复处理 - 异步分发确保安全
- 精准挂载需要处理的 ScrollView
❌ 避免
- 所有 ScrollView 一律使用(只在嵌套场景使用)
- 忽略事件类型检查
🎁 福利推荐:高性能虚拟列表插件
📌 插件地址:
👉 https://store.cocos.com/app/detail/7408
🎮 体验链接:
👉 https://xingkong.asia/virtualList160/
✨ 插件特色
| 功能 | 原生 ScrollView | 虚拟列表插件 |
|---|---|---|
| 支持数据量 | < 100 | > 10000 |
| 内存占用 | 线性增长 | 恒定不变 |
| 滚动流畅度 | 卡顿明显 | 丝般顺滑 |
| 嵌套支持 | 需要 hack | 原生支持 |
❤️ 总结
- 理解 ScrollView 源码,精准定位嵌套滚动冲突根因
- 提出克隆+转发事件的优雅解决方式
- 实战中稳定、高效、易用!
📢 如果你觉得这个方案有帮助:
- 👍 点赞 让更多开发者看到这个方案
- 🔄 转发 给遇到类似问题的朋友
- 💬 评论 分享你的嵌套滚动坑爹经历
关注我,获取更多 Cocos Creator 深度技术干货! 🚀


浙公网安备 33010602011771号