CCTouchDelegate:它是触摸事件委托,就是系统捕捉到触摸事件后交由它或者它的子类处理,所以我们在处理触屏事件时,必须得继承它。它封装了下面这些处理触屏事件的函数:
virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent); virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent); virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent); virtual void ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent); virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
ccTouchesCancelled和ccTouchCancelled函数很少用,在接到系统中断通知,需要取消触摸事件的时候才会调用此方法。如:应用长时间无响应、当前view从window上移除、触摸的时候来电话了等。
CCTargetedTouchDelegate和CCStandardTouchDelegate是CCTouchDelegate的子类,类结构图如下:
CCStandardTouchDelegate用于处理多点触摸;CCTargetedTouchDelegate用于处理单点触摸。
CCTouchDispatcher:实现触摸事件分发,它封装了下面这两个函数,可以把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分发列表中:
void addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority); void addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches);
CCTouchHandler:封装了CCTouchDelegate和其对应的优先级,优先级越高,分发的时候越容易获得事件处理权,CCStandardTouchHandler和CCTargetedTouchHandler是它的子类。
下面分析一下触屏事件处理和执行流程:
用户自定义类继承CCTouchDelegate,重写触屏事件处理函数和registerWithTouchDispatcher函数,在init或者onEnter函数中调用registerWithTouchDispatcher函数,如:
void GameLayer::registerWithTouchDispatcher() { cocos2d::CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true); }把相应的CCTouchDelegate添加到CCTouchDispatcher的分发列表中。addTargetedDelegate函数会创建CCTouchDelegate对应的CCTouchHandler对象并添加到CCMutableArraym_pTargetedHandlers中,看源码:
void CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches) { CCTouchHandler *pHandler = CCTargetedTouchHandler::handlerWithDelegate(pDelegate, nPriority, bSwallowsTouches); if (! m_bLocked) { forceAddHandler(pHandler, m_pTargetedHandlers); } else { /**....*/ } } void CCTouchDispatcher::forceAddHandler(CCTouchHandler *pHandler, CCMutableArray *pArray) { unsigned int u = 0; CCMutableArray::CCMutableArrayIterator iter; for (iter = pArray->begin(); iter != pArray->end(); ++iter) { CCTouchHandler *h = *iter; if (h) { if (h->getPriority() < pHandler->getPriority()) { ++u; } if (h->getDelegate() == pHandler->getDelegate()) { CCAssert(0, ""); return; } } } pArray->insertObjectAtIndex(pHandler, u); }注意forceAddHandler函数中,pHandler是被添加的对象:pHandler->getPriority()的值越小u的值就越小,因此插入到目标容器中的位置也就越靠前,说明优先级的值越小优先级反而越高,也就能先响应事件(CCMenu的默认值是-128) 。 前面事件分发时就是从m_pTargetedHandlers中取出CCXXXTouchHandler,然后调用handler对应的delegate的:pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);,执行的是CCTouchDispatcher的touches函数,考虑到篇幅问题,就不贴出具体代码了。该函数首先会先处理targeted 再处理standard,所以CCTargetedTouchDelegate比CCStandardTouchDelegate优先级高。那什么时候触发执行touches函数呢?CCTouchDispatcher继承了EGLTouchDelegate类,EGLTouchDelegate类源码:
class CC_DLL EGLTouchDelegate { public: virtual void touchesBegan(CCSet* touches, CCEvent* pEvent) = 0; virtual void touchesMoved(CCSet* touches, CCEvent* pEvent) = 0; virtual void touchesEnded(CCSet* touches, CCEvent* pEvent) = 0; virtual void touchesCancelled(CCSet* touches, CCEvent* pEvent) = 0; virtual ~EGLTouchDelegate() {} };CCTouchDispatcher中实现了这四个函数,正是在这四个函数中调用了touches函数:
void CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent) { if (m_bDispatchEvents) { this->touches(touches, pEvent, CCTOUCHBEGAN); } } /**其他三个方法类似 **/这几个触屏处理函数是由具体平台底层调用的,在AppDelegate.cpp中有这段代码:
CCDirector *pDirector = CCDirector::sharedDirector(); pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());继续跟进setOpenGLView函数,发现了这段代码:
CCTouchDispatcher *pTouchDispatcher = CCTouchDispatcher::sharedDispatcher(); m_pobOpenGLView->setTouchDelegate(pTouchDispatcher); pTouchDispatcher->setDispatchEvents(true);调用了具体平台下的CCEGLView类中的setTouchDelegate函数。由于我是在windows平台下,所以CCEGLView此时对应CCEGLView_win32.h文件的CCEGLView类,对应的setTouchDelegate函数为:
void setTouchDelegate(EGLTouchDelegate * pDelegate);系统最终通过CCEGLView类的WindowProc函数处理鼠标在Windows窗口的DOWN、MOVE、UP事件,通过pDelegate分别调用touchesBegan、touchesMoved、touchesEnded函数。
LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_LBUTTONDOWN: if (m_pDelegate && m_pTouch && MK_LBUTTON == wParam) { POINT pt = {(short)LOWORD(lParam), (short)HIWORD(lParam)}; if (PtInRect(&m_rcViewPort, pt)) { m_bCaptured = true; SetCapture(m_hWnd); m_pTouch->SetTouchInfo(0, (float)(pt.x - m_rcViewPort.left) / m_fScreenScaleFactor, (float)(pt.y - m_rcViewPort.top) / m_fScreenScaleFactor); m_pSet->addObject(m_pTouch); m_pDelegate->touchesBegan(m_pSet, NULL); } } break; case WM_MOUSEMOVE: if (MK_LBUTTON == wParam && m_bCaptured) { m_pTouch->SetTouchInfo(0, (float)((short)LOWORD(lParam)- m_rcViewPort.left) / m_fScreenScaleFactor, (float)((short)HIWORD(lParam) - m_rcViewPort.top) / m_fScreenScaleFactor); m_pDelegate->touchesMoved(m_pSet, NULL); } break; case WM_LBUTTONUP: if (m_bCaptured) { m_pTouch->SetTouchInfo(0, (float)((short)LOWORD(lParam)- m_rcViewPort.left) / m_fScreenScaleFactor, (float)((short)HIWORD(lParam) - m_rcViewPort.top) / m_fScreenScaleFactor); m_pDelegate->touchesEnded(m_pSet, NULL); m_pSet->removeObject(m_pTouch); ReleaseCapture(); m_bCaptured = false; } break; /** .... */ } }
ok,现在应该明白了触屏操作相关函数的执行过程了,在其他平台下应该类似。
2. 实现触屏事件处理
知道了原理之后,实现起来就很简单了:定义一个CCTouchDelegate(或者其子类CCTargetedTouchDelegate/CCStandardTouchDelegate),然后重写那几个处理函数(began、move、end),并把定义好的CCTouchDelegate添加到分发列表中,在onExit函数中实现从分发列表中删除。
在平常的开发中,一般有两种方式:(1)继承CCLayer,在层中处理触屏函数。(2)继承CCSprite和CCTouchDelegate(或者其子类)。
上面两种方式,从原理上来说是一样的。
1. 下面是采用继承CCLayer的方式处理触屏事件。
(1)CCStandardTouchDelegate
添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setIsTouchEnabled(true)。主要代码如下:
//init函数中 this->setIsTouchEnabled(true); void GameLayer::ccTouchesBegan(CCSet* pTouches,CCEvent* pEvent) { CCSetIterator it = pTouches->begin(); CCTouch* touch = (CCTouch*)(*it); CCpoint touchLocation = touch->locationInView( touch->view() ); touchLocation = CCDirector::sharedDirector()->convertToGL(m_tBeginPos); /** .... **/ }这里为什么没有把CCStandardTouchDelegate添加进分发列表和从分发列表删除的操作呢,因为setIsTouchEnabled函数已经帮我们做了,看源码:
void CCLayer::setIsTouchEnabled(bool enabled) { if (m_bIsTouchEnabled != enabled) { m_bIsTouchEnabled = enabled; if (m_bIsRunning) { if (enabled) { this->registerWithTouchDispatcher(); } else { // have problems? CCTouchDispatcher::sharedDispatcher()->removeDelegate(this); } } } } void CCLayer::registerWithTouchDispatcher() { /** .... **/ CCTouchDispatcher::sharedDispatcher()->addStandardDelegate(this,0); } void CCLayer::onExit() { if( m_bIsTouchEnabled ) { CCTouchDispatcher::sharedDispatcher()->removeDelegate(this); unregisterScriptTouchHandler(); } CCNode::onExit(); }(2) CCTargetedTouchDelegate
class CC_DLL CCMenu : public CCLayer, public CCRGBAProtocol { /** .... */ virtual void registerWithTouchDispatcher(); /** @brief For phone event handle functions */ virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event); virtual void ccTouchEnded(CCTouch* touch, CCEvent* event); virtual void ccTouchCancelled(CCTouch *touch, CCEvent* event); virtual void ccTouchMoved(CCTouch* touch, CCEvent* event); /** @since v0.99.5 override onExit */ virtual void onExit(); /** .... */ }; } //Menu - Events,在CCLayer的onEnter中被调用 void CCMenu::registerWithTouchDispatcher() { CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, kCCMenuTouchPriority, true); } bool CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event) { /** .... */ } void CCMenu::onExit() { /** .... */ CCLayer::onExit(); }2.下面实现继承CCSprite的方式
class Ball : public CCSprite, public CCTargetedTouchDelegate { public: Ball(void); virtual ~Ball(void); virtual void onEnter(); virtual void onExit(); virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event); virtual void ccTouchMoved(CCTouch* touch, CCEvent* event); virtual void ccTouchEnded(CCTouch* touch, CCEvent* event); /** .... */ }; void Ball::onEnter() { CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true); CCSprite::onEnter(); } void Ball::onExit() { CCTouchDispatcher::sharedDispatcher()->removeDelegate(this); CCSprite::onExit(); } bool Ball::ccTouchBegan(CCTouch* touch, CCEvent* event) { CCPoint touchPoint = touch->locationInView( touch->view() ); touchPoint = CCDirector::sharedDirector()->convertToGL( touchPoint ); /** .... */ return true; }注意: virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)的返回值对触屏消息是有影响的。
bool bClaimed = false;
if (uIndex == CCTOUCHBEGAN)
{
bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);
//返回true
if (bClaimed)
{
pHandler->getClaimedTouches()->addObject(pTouch);
}
} else
if (pHandler->getClaimedTouches()->containsObject(pTouch))
{
// moved ended canceled
bClaimed = true;
switch (sHelper.m_type)
{
case CCTOUCHMOVED:
pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent);
break;
case CCTOUCHENDED:
pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent);
pHandler->getClaimedTouches()->removeObject(pTouch);
break;
case CCTOUCHCANCELLED:
pHandler->getDelegate()->ccTouchCancelled(pTouch, pEvent);
pHandler->getClaimedTouches()->removeObject(pTouch);
break;
}
}
如果返回true,并且addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches),bSwallowsTouches为true,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了
。
if (bClaimed && pHandler->isSwallowsTouches()) { if (bNeedsMutableSet) { pMutableTouches->removeObject(pTouch); } break; }把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。
来自:Alex Zhou的程序世界,本文链接:http://codingnow.cn/cocos2d-x/783.html