[开源] 36行代码实现一个ScrollView/PageView嵌套辅助组件

最近在群和论坛里发现有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上即可,比如这样


demo仓库地址:https://gitee.com/cocos2d-zp/scrollview-nesting

45赞

我正在找竖着嵌套横着的二维滚动器 :grinning:

Mark!!!

好巧啊,给我测测bug,上线了告诉我 :sunglasses:

好的,没问题。不过我有个疑问,你是怎么看懂源码每个函数,每句代码是在干啥。阅读这些代码花了多少时间啊,我看了一下,感觉要看好多遍,还要使用翻译,才能大致看懂。

你是怎么快速定位到问题出在哪?
从思路上来说,第一定位肯定是在触摸开始,滑动,取消,结束这几个函数这里。然后去这里具体理解每个函数做了啥,然后就查到原因是“如果事件传来的节点也是滚动器的时候,直接中断”,这样?

嗯,就是这样,配合断点,这种控件类的源码还是比较好分析的

mark!!

一gi我li mark!!

mark!

都精确到一行代码了哈哈,666,赞不侵入

mark!!!

你这个实现方式很赞,已切换到你的 :innocent:

mark,收藏一波

可以可以 :test:

这样两个可以同时动,可以做那种限制方向移动的吗?

需求可以实现,不过你需要自己做修改,合适的地方去控制事件的传递

每天顶一顶 学习新技术~大佬:ox::beer:

大哥需要发行嘛 我家贼专业 微信:wei930810

父级scrollview可以加限制,可是子类的触摸事件已经发出去了,这个还怎么限制?

触摸事件传递是两个阶段,从根到顶,再从顶到根,你只需要在第一阶段去处理就行了