最近在群和论坛里发现有ScrollView/PageView嵌套需求的小伙伴,但是Creator默认不支持,我们尝试解决它。
1 分析原因
- 看一下源码就会发现每一个处理触摸的函数最开始都有这两行
_onTouchBegan(event, captureListeners) {
if (!this.enabledInHierarchy) return;
if (this._hasNestedViewGroup(event, captureListeners)) return;
...
}
- 不能嵌套的原因就在这个_hasNestedViewGroup函数里面
...
if (event.target.getComponent(cc.ViewGroup)) {
return true;
}
...
- 接下来我们顺着这个发现去解决它
2 解决问题
这个时候大部分小伙伴的做法可能都是继承ScrollView或PageView,然后重写一部分方法来解决嵌套的问题。
但是我们这里不想搞这种侵入式的方案,我们尝试换一个角度,比如我们手动发射一个假事件,让它不会被_hasNestedViewGroup过滤掉,并且我们的目标是让它成为一个单独的组件。
代码不多,直接上源码(新鲜出炉,未测BUG)
const { ccclass, property } = cc._decorator;
interface EventTouch extends cc.Event.EventTouch {
simulate?: boolean
sham?: boolean
}
@ccclass
export default class ViewGroupNesting extends cc.Component {
private events: EventTouch[] = [];
onLoad() {
this.node.on(cc.Node.EventType.TOUCH_START, this.onTouchHandle, this, true);
this.node.on(cc.Node.EventType.TOUCH_MOVE, this.onTouchHandle, this, true);
this.node.on(cc.Node.EventType.TOUCH_END, this.onTouchHandle, this, true);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this.onTouchHandle, this, true);
}
private onTouchHandle(event: EventTouch) {
if (event.sham || event.simulate || event.target === this.node) return;
const cancelEvent: EventTouch = new cc.Event.EventTouch(event.getTouches(), event.bubbles);
cancelEvent.type = event.type;
cancelEvent.touch = event.touch;
cancelEvent.sham = true;
// 问:这里为啥不直接dispatchEvent
// 答:必须让ScrollView把真的event先消耗掉,我们再发射假的才可以,
// 可以去CCNode.js下找一个_doDispatchEvent函数,里面用到了_cachedArray这么个全局变量,
// 先发射假的话,真的那个数据就被清空了
this.events.push(cancelEvent);
}
update() {
if (this.events.length === 0) return;
for (let index = 0; index < this.events.length; index++) {
this.node.dispatchEvent(this.events[index]);
}
this.events.length = 0;
}
}
3 使用方法
直接拖到父ScrollView/PageView上即可,比如这样