Cocos2dx Touch事件原理剖析

简介: Cocos2dx Touch事件原理剖析

问题表现

当多点触摸和单点触摸的操作区域有重叠时,如果单点触摸事件设置为吞噬,那么多点触摸无法正常响应,例如:

网络异常,图片无法展示
|

上图中红色区域的多点触摸是无法正常响应的

代码分析

  • 鼠标点击事件的源头:组装touch事件,并派发出去
void GLViewImpl::onGLFWMouseCallBack(GLFWwindow* /*window*/, int button, int action, int /*modify*/)
{
     this->handleTouchesBegin(1, &id, &_mouseX, &_mouseY);
}
void GLView::handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[])
{
    EventTouch touchEvent;
    touchEvent._eventCode = EventTouch::EventCode::BEGAN;
    auto dispatcher = Director::getInstance()->getEventDispatcher();
    dispatcher->dispatchEvent(&touchEvent);
}
复制代码
  • 具体的派发过程:
void EventDispatcher::dispatchEvent(Event* event)
{
  if (event->getType() == Event::Type::TOUCH)
    {
        dispatchTouchEvent(static_cast<EventTouch*>(event));
        return;
    }
}
void EventDispatcher::dispatchTouchEvent(EventTouch* event)
{
    // 排序
    sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID);
    sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
    auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID);
    auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
    // 先处理单点
    if (oneByOneListeners)
    {
        for(auto& touches: originalTouches){
            dispatchTouchEventToListeners(listeners, onTouchEvent)
        } 
    }
    // 再处理多点,逻辑同上,注意mutableTouches的size
     if (allAtOnceListeners && mutableTouches.size() > 0)
    {
        // ...
    }
}
复制代码
  • dispatchTouchEventToListeners派发事件时,会将listener划分为3个批次进行循环执行
  1. 固定优先级小于0的
  2. 图形优先级
  3. 固定优先级大于0的

每个批次一旦执行失败onEvent,就会跳过这个批次的后续listener

void EventDispatcher::dispatchTouchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent)
    // 细节逻辑:固定优先级的会按照优先级依次排序,找到小于0的index
    for(){
        if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
        {
            shouldStopPropagation = true;
            break;
        }
    } 
}
复制代码
  • 最关键的就是这个onEvent逻辑了,也就是onTouchEvent:
auto onTouchEvent = [&](EventListener* l) -> bool { // Return true to break
    EventListenerTouchOneByOne* listener = static_cast<EventListenerTouchOneByOne*>(l);
    // Skip if the listener was removed.
    if (!listener->_isRegistered)
        return false;
    event->setCurrentTarget(listener->_node);
    bool isClaimed = false;
    std::vector<Touch*>::iterator removedIter;
    EventTouch::EventCode eventCode = event->getEventCode();
    if (eventCode == EventTouch::EventCode::BEGAN)
    {
        if (listener->onTouchBegan)
        {
            // touchBegan返回true,就会将这个touch保存在claimed里面,当touchMove、touchEnd的时候,就能检索到
            // 这也解释了touchBegan返回true,后续的move、end才能正常
            isClaimed = listener->onTouchBegan(touches, event);
            if (isClaimed && listener->_isRegistered)
            {
                listener->_claimedTouches.push_back(touches);
            }
        }
    }
    else if (listener->_claimedTouches.size() > 0
             && ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), touches)) != listener->_claimedTouches.end()))
    {
        isClaimed = true;
        switch (eventCode)
        {
            case EventTouch::EventCode::MOVED:
                if (listener->onTouchMoved)
                {
                    listener->onTouchMoved(touches, event);
                }
                break;
            case EventTouch::EventCode::ENDED:
                if (listener->onTouchEnded)
                {
                    listener->onTouchEnded(touches, event);
                }
                if (listener->_isRegistered)
                {
                    listener->_claimedTouches.erase(removedIter);
                }
                break;
            case EventTouch::EventCode::CANCELLED:
                if (listener->onTouchCancelled)
                {
                    listener->onTouchCancelled(touches, event);
                }
                if (listener->_isRegistered)
                {
                    listener->_claimedTouches.erase(removedIter);
                }
                break;
            default:
                CCASSERT(false, "The eventcode is invalid.");
                break;
        }
    }
    // If the event was stopped, return directly.
    // 可以在touch回调里面设置停止冒泡: event->stopPropagation()
    if (event->isStopped())
    {
        updateListeners(event);
        return true;
    }
    CCASSERT(touches->getID() == (*mutableTouchesIter)->getID(), "touches ID should be equal to mutableTouchesIter's ID.");
    // 当前的listener吞噬触摸点
    if (isClaimed && listener->_isRegistered && listener->_needSwallow) 
    {
        if (isNeedsMutableSet) // 当单点、多点 触摸同时存在
        {
            // 会将当前的touch移除,会影响后续的多点触摸的生效
            mutableTouchesIter = mutableTouches.erase(mutableTouchesIter);
            isSwallowed = true;
        }
        return true;
    }
    return false;
};
复制代码

影响范围

网络异常,图片无法展示
|



目录
相关文章
|
2月前
|
容器
Flutter下拉刷新上拉加载的简单实现方式一
Flutter下拉刷新上拉加载的简单实现方式一
64 2
|
8月前
|
编解码 前端开发 UED
CocosCreator 面试题(十一)Cocos Creator 屏幕适配
CocosCreator 面试题(十一)Cocos Creator 屏幕适配
355 0
|
Dart 开发者
Flutter 手势原理
Flutter 手势原理
268 0
Flutter 手势原理
|
vr&ar 图形学
【Unity3D 灵巧小知识点】☀️ | Unity UGUI组件Scroll View禁止 左右 或 上下 滑动
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。 也可以简单把 Unity 理解为一个游戏引擎,可以用来专业制作游戏!
【Unity3D 灵巧小知识点】☀️ | Unity UGUI组件Scroll View禁止 左右 或 上下 滑动
|
XML Android开发 数据格式
Android动画一:Activity过渡动画详细实现原理
虽然 Android 5.0 之后推出了新的过渡动画方式,但通常只是用于特定的场合使用,activity.overridePendingTransition() 通用方式的过渡动画还是很常用。 原理分析 startActivity(Intent(this,SecondActivity::class.
7596 0
|
iOS开发
iOS开发实战 - 完美解决UIScrollView嵌套滑动手势冲突
我们应该都有用过这个功能,你的朋友微信给你分享了一个淘宝里面的商品链接,然后当你复制这个链接打开淘宝APP的时候,就会弹出一个弹窗,像这样: example.PNG 这个功能想必大家都挺熟悉,受这个启发我们产品也想在我们APP上添加这样一个功能,与这个不一样的是,当我们复制一段网址的时候打开我们的APP会弹出框填一些信息后上传到我们的“资源库”。
4323 0