开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第22天,点击查看活动详情
问题表现
当多点触摸和单点触摸的操作区域有重叠时,如果单点触摸事件设置为吞噬,那么多点触摸无法正常响应,例如:
上图中红色区域的多点触摸是无法正常响应的
代码分析
- 鼠标点击事件的源头:组装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个批次进行循环执行
- 固定优先级小于0的
- 图形优先级
- 固定优先级大于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;
};
影响范围