cocos2d的事件分成7种:TOUCH,KEYBOARD,ACCELERATION,MOUSE,FOCUS,GAME_CONTROLLER,CUSTOM,分别为 触摸,键盘,加速,鼠标,聚焦,游戏控制器和自定义事件。有了事件,那么就需要有事件监听器,每种事件至少对应一种监听器,由于触摸分单点触摸和多点触摸,触摸事件对应两种监听器,每种监听器中都定义了一个listenerid。
下面分为两个步骤来分析源码:
1----注册监听器
cocos2d提供了两种注册监听器的方式:addEventListenerWithFixedPriority和addEventListenerWithSceneGraPHPriority。
void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener,int fixedPriority) void EventDispatcher::addEventListenerWithSceneGraPHPriority(EventListener* listener,Node* node)
前者参数中有一个优先级,后者将监听器绑定到一个节点,所以listener的回调触发会收到node的globalZorder和localZorder的影响,addEventListenerWithSceneGraPHPriority优先级默认为0。优先级越小,就越早被触发,当事件被吞噬掉了,那么后面的监听器就不会被触发了,吞噬的方式有两种:
<pre name="code" class="html">event->stopPropagation(); listener->setSwallowTouches(true);//限于单点触摸
void EventDispatcher::addEventListener(EventListener* listener) { if (_inDispatch == 0)//当前没有在派发事件,强制加入监听器列表中 { forceAddEventListener(listener); } else { _toAddedListeners.push_back(listener);//加入等待加入列表中 } listener->retain(); }当派发事件结束的时候,会将等待列表加入到监听器列表中。
void EventDispatcher::updateListeners(Event* event) { CCASSERT(_inDispatch > 0,"If program goes here,there should be event in dispatch."); //....省略部分代码......... if (!_toAddedListeners.empty()) { for (auto& listener : _toAddedListeners) { forceAddEventListener(listener); } _toAddedListeners.clear(); } }
_listenerMap维护了一份listenerId到EventListenerVector的映射,EventListenerVector内部_fixedListeners和_sceneGraphListeners,当加入push一个监听器,根据它的优先级来决定到底是放入到哪一个列表中。当有新的监听器加入时,会写入一份脏数据,该脏数据标识着哪个listenerId类型的监听器中的_fixedListeners和_sceneGraphListeners有变换,在进行事件派发之前需要判断当前事件是否会收到这个脏数据的影响。如果有影响,就需要在派发之前对列表进行重新排序。
void EventDispatcher::forceAddEventListener(EventListener* listener) { EventListenerVector* listeners = nullptr; EventListener::ListenerID listenerID = listener->getListenerID(); auto itr = _listenerMap.find(listenerID); if (itr == _listenerMap.end()) { listeners = new (std::nothrow) EventListenerVector(); _listenerMap.insert(std::make_pair(listenerID,listeners)); } else { listeners = itr->second; } //EventListenerVector 包括_fixedListeners和_sceneGraphListeners listeners->push_back(listener); if (listener->getFixedPriority() == 0) { setDirty(listenerID,DirtyFlag::SCENE_GRAPH_PRIORITY); auto node = listener->getAssociatedNode(); CCASSERT(node != nullptr,"Invalid scene graph priority!"); associateNodeAndEventListener(node,listener); if (node->isRunning()) { resumeEventListenersForTarget(node); } } else { setDirty(listenerID,DirtyFlag::FIXED_PRIORITY); } }
//标识哪个listenerID类型的列表需要变化,是_fixedListeners还是_sceneGraphListeners取决于flag void EventDispatcher::setDirty(const EventListener::ListenerID& listenerID,DirtyFlag flag) { auto iter = _priorityDirtyFlagMap.find(listenerID); if (iter == _priorityDirtyFlagMap.end()) { _priorityDirtyFlagMap.insert(std::make_pair(listenerID,flag)); } else { int ret = (int)flag | (int)iter->second; iter->second = (DirtyFlag) ret; } }
2-----事件派发
//派发事件,先处理脏数据,监听器排序,派发
void EventDispatcher::dispatchEvent(Event* event) { if (!_isEnabled) return; //脏数据不仅仅是在加入监听器的时候会存在,在已存在的监听器绑定到的node resume的时候也会产生脏数据,只会影响到_sceneGraphListeners中的监听器 updateDirtyFlagForSceneGraph(); //标识正在派发事件 DispatchGuard guard(_inDispatch); if (event->getType() == Event::Type::TOUCH) { //触摸事件单独处理 dispatchTouchEvent(static_cast<EventTouch*>(event)); return; } auto listenerID = __getListenerID(event); //对listenerID类型的监听器进行排序(如果需要的话) //DirtyFlag::FIXED_PRIORITY-------->fixedPriorityListeners排序 //DirtyFlag::SCENE_GRAPH_PRIORITY-------->sceneGraPHPriorityListeners排序 sortEventListeners(listenerID); auto iter = _listenerMap.find(listenerID); if (iter != _listenerMap.end()) { auto listeners = iter->second; auto onEvent = [&event](EventListener* listener) -> bool{ event->setCurrentTarget(listener->getAssociatedNode()); listener->_onEvent(event); return event->isStopped();//是否吞噬此事件 }; //对事件按照顺序进行派发 dispatchEventToListeners(listeners,onEvent); } updateListeners(event); }
派发触摸事件,单点触摸是将event的每个触摸点都进行事件触发,多点触摸是按组进行处理
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 there aren't any touch listeners,return directly. if (nullptr == oneByOneListeners && nullptr == allAtOnceListeners) return; bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners); const std::vector<Touch*>& originalTouches = event->getTouches(); std::vector<Touch*> mutableTouches(originalTouches.size());//保存一组触摸点 std::copy(originalTouches.begin(),originalTouches.end(),mutableTouches.begin()); // // process the target handlers 1st // if (oneByOneListeners) { auto mutableTouchesIter = mutableTouches.begin(); auto touchesIter = originalTouches.begin(); for (; touchesIter != originalTouches.end(); ++touchesIter) { bool isSwallowed = false; //对每个触摸点进行处理 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) { isClaimed = listener->onTouchBegan(*touchesIter,event); if (isClaimed && listener->_isRegistered) { //将该触摸点加入到该监听器认领列表中,后面要处理MOVED,ENDED,CANCELLED事件时需要对应的触摸点在认领列表中,否则不触发对应方法 listener->_claimedTouches.push_back(*touchesIter); } } } //是否能找到BEGAN加入的触摸点,找到了才能触发 else if (listener->_claimedTouches.size() > 0 && ((removedIter = std::find(listener->_claimedTouches.begin(),listener->_claimedTouches.end(),*touchesIter)) != listener->_claimedTouches.end())) { isClaimed = true; switch (eventCode) { //........省略部分代码......... if (listener->_isRegistered) {
<pre name="code" class="html">//当触摸结束了,必须从认领列表中移出该触摸点,否则列表是很庞大的数据,CANCELLED同理listener->_claimedTouches.erase(removedIter);
} //........省略部分代码......... } } // If the event was stopped,return directly. //吞噬事件:方式一 if (event->isStopped()) { updateListeners(event); return true;//阻止后面的监听器处理 } CCASSERT((*touchesIter)->getID() == (*mutableTouchesIter)->getID(),""); //吞噬事件:方式二 if (isClaimed && listener->_isRegistered && listener->_needSwallow) { if (isNeedsMutableSet) { //由于该点的触摸事件被吞噬了,后面多点触摸的时候就不会触发了这个触摸点了 mutableTouchesIter = mutableTouches.erase(mutableTouchesIter); isSwallowed = true; } return true;//阻止后面的监听器处理 } return false; }; // dispatchEventToListeners(oneByOneListeners,onTouchEvent); if (event->isStopped())//此种吞噬事件更强大,stop掉以后,不仅仅影响单点触摸接下的监听器,还回阻止多点触摸的处理 { return; } if (!isSwallowed) ++mutableTouchesIter; } } //接下来就是 多点触摸 // process standard handlers 2nd // if (allAtOnceListeners && mutableTouches.size() > 0)//如果还有触摸点的事件没有被吞噬 { //.........省略部分代码,就是触发回调而已.............. } updateListeners(event); }
对排好序的监听器列表的每个监听器进行事件触发,先触发优先级小于0的,然后是sceneGraPHPriorityListeners,最后是优先级大于0的
void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners,const std::function<bool(EventListener*)>& onEvent) { bool shouldStopPropagation = false; auto fixedPriorityListeners = listeners->getFixedPriorityListeners(); auto sceneGraPHPriorityListeners = listeners->getSceneGraPHPriorityListeners(); ssize_t i = 0; // priority < 0 if (fixedPriorityListeners) { CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()),"Out of range exception!"); if (!fixedPriorityListeners->empty()) { //..........省略部分代码................ } } if (sceneGraPHPriorityListeners) { if (!shouldStopPropagation) { // priority == 0,scene graph priority for (auto& l : *sceneGraPHPriorityListeners) { //..........省略部分代码................ } } } if (fixedPriorityListeners) { if (!shouldStopPropagation) { // priority > 0 ssize_t size = fixedPriorityListeners->size(); for (; i < size; ++i) { //..........省略部分代码................ } } } }
前面经常说监听器排序,接下来就看看怎么排的序,fixedListeners的排序还是挺简单的,整个列表和一个排序函数就搞定了,但是sceneGraphListeners就比较麻烦了,首先它要去计算所有子节点的深度,深度取决于globalZorder和localZorder,并且访问一个节点的时候,要把按照localZorder从低到高的顺序进行访问,并且在localZorder=0的时候把本节点加进去,然后再去访问localZorder>0的子节点。
void EventDispatcher::sortEventListenersOfSceneGraPHPriority(const EventListener::ListenerID& listenerID,Node* rootNode) { //..........省略部分代码................ // Reset priority index _nodePriorityIndex = 0; _nodePriorityMap.clear();//保存所有节点的深度 //访问所有的节点,计算每个节点的深度,此步骤很关键 visitTarget(rootNode,true); // After sort: priority < 0,> 0 std::sort(sceneGraphListeners->begin(),sceneGraphListeners->end(),[this](const EventListener* l1,const EventListener* l2) { return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()]; }); //..........省略部分代码................ }
具体计算深度:
void EventDispatcher::visitTarget(Node* node,bool isRootNode) { int i = 0; auto& children = node->getChildren(); auto childrenCount = children.size(); if(childrenCount > 0) { //向上面说的那样的顺序访问节点 Node* child = nullptr; // visit children zOrder < 0 for( ; i < childrenCount; i++ ) { child = children.at(i); if ( child && child->getLocalZOrder() < 0 ) visitTarget(child,false); else break; } if (_nodeListenersMap.find(node) != _nodeListenersMap.end()) { //访问完了localZorder<0的子节点,将节点加入到对应的globalZorder列表中去,注意globalZorder列表加入的顺序也是很有序的。 _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node); } for( ; i < childrenCount; i++ ) { child = children.at(i); if (child) visitTarget(child,false); } } else { if (_nodeListenersMap.find(node) != _nodeListenersMap.end()) { //叶节点,将节点加入到对应的globalZorder列表中去 _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node); } } if (isRootNode) { std::vector<float> globalZOrders; globalZOrders.reserve(_globalZOrderNodeMap.size()); for (const auto& e : _globalZOrderNodeMap) { globalZOrders.push_back(e.first); } std::sort(globalZOrders.begin(),globalZOrders.end(),[](const float a,const float b){ return a < b; }); for (const auto& globalZ : globalZOrders) { for (const auto& n : _globalZOrderNodeMap[globalZ]) { _nodePriorityMap[n] = ++_nodePriorityIndex;//计算深度 } } _globalZOrderNodeMap.clear(); } }原文链接:https://www.f2er.com/cocos2dx/341664.html