1.首先来了解一下相关的几个类、处理触屏事件时操作和执行的流程
CCTouch:它封装了触摸点,可以通过locationInView函数返回一个CCPoint。
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的子类,类结构图如下:
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对象并添加到CCArray m_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函数.
//
// dispatch events
//
void CCTouchDispatcher::touches(CCSet *pTouches, CCEvent *pEvent, unsigned int uIndex)
{
CCAssert(uIndex >= 0 && uIndex < 4, "");
CCSet *pMutableTouches;
m_bLocked = true;
// optimization to prevent a mutable copy when it is not necessary
unsigned int uTargetedHandlersCount = m_pTargetedHandlers->count();
unsigned int uStandardHandlersCount = m_pStandardHandlers->count();
bool bNeedsMutableSet = (uTargetedHandlersCount && uStandardHandlersCount);
pMutableTouches = (bNeedsMutableSet ? pTouches->mutableCopy() : pTouches);
struct ccTouchHandlerHelperData sHelper = m_sHandlerHelperData[uIndex];
//
// process the target handlers 1st
//
if (uTargetedHandlersCount > 0)
{
CCTouch *pTouch;
CCSetIterator setIter;
for (setIter = pTouches->begin(); setIter != pTouches->end(); ++setIter)
{
pTouch = (CCTouch *)(*setIter);
CCTargetedTouchHandler *pHandler = NULL;
CCObject* pObj = NULL;
CCARRAY_FOREACH(m_pTargetedHandlers, pObj)
{
pHandler = (CCTargetedTouchHandler *)(pObj);
if (! pHandler)
{
break;
}
bool bClaimed = false;
if (uIndex == CCTOUCHBEGAN)
{
bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);
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;
}
}
if (bClaimed && pHandler->isSwallowsTouches())
{
if (bNeedsMutableSet)
{
pMutableTouches->removeObject(pTouch);
}
break;
}
}
}
}
//
// process standard handlers 2nd
//
if (uStandardHandlersCount > 0 && pMutableTouches->count() > 0)
{
CCStandardTouchHandler *pHandler = NULL;
CCObject* pObj = NULL;
CCARRAY_FOREACH(m_pStandardHandlers, pObj)
{
pHandler = (CCStandardTouchHandler*)(pObj);
if (! pHandler)
{
break;
}
switch (sHelper.m_type)
{
case CCTOUCHBEGAN:
pHandler->getDelegate()->ccTouchesBegan(pMutableTouches, pEvent);
break;
case CCTOUCHMOVED:
pHandler->getDelegate()->ccTouchesMoved(pMutableTouches, pEvent);
break;
case CCTOUCHENDED:
pHandler->getDelegate()->ccTouchesEnded(pMutableTouches, pEvent);
break;
case CCTOUCHCANCELLED:
pHandler->getDelegate()->ccTouchesCancelled(pMutableTouches, pEvent);
break;
}
}
}
if (bNeedsMutableSet)
{
pMutableTouches->release();
}
//
// Optimization. To prevent a [handlers copy] which is expensive
// the add/removes/quit is done after the iterations
//
m_bLocked = false;
if (m_bToRemove)
{
m_bToRemove = false;
for (unsigned int i = 0; i < m_pHandlersToRemove->num; ++i)
{
forceRemoveDelegate((CCTouchDelegate*)m_pHandlersToRemove->arr[i]);
}
ccCArrayRemoveAllValues(m_pHandlersToRemove);
}
if (m_bToAdd)
{
m_bToAdd = false;
CCTouchHandler* pHandler = NULL;
CCObject* pObj = NULL;
CCARRAY_FOREACH(m_pHandlersToAdd, pObj)
{
pHandler = (CCTouchHandler*)pObj;
if (! pHandler)
{
break;
}
if (dynamic_cast(pHandler) != NULL)
{
forceAddHandler(pHandler, m_pTargetedHandlers);
}
else
{
forceAddHandler(pHandler, m_pStandardHandlers);
}
}
m_pHandlersToAdd->removeAllObjects();
}
if (m_bToQuit)
{
m_bToQuit = false;
forceRemoveAllDelegates();
}
}
该函数首先会先处理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() {}
};
void CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent)
{
if (m_bDispatchEvents)
{
this->touches(touches, pEvent, CCTOUCHBEGAN);
}
}
/**其他三个方法类似 **/
CCDirector *pDirector = CCDirector::sharedDirector();
pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());
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)
{
BOOL bProcessed = FALSE;
switch (message)
{
case WM_LBUTTONDOWN:
#if(_MSC_VER >= 1600)
// Don't process message generated by Windows Touch
if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;
#endif /* #if(_MSC_VER >= 1600) */
if (m_pDelegate && MK_LBUTTON == wParam)
{
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x, point.y);
pt.x /= m_fFrameZoomFactor;
pt.y /= m_fFrameZoomFactor;
CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);
if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))
{
m_bCaptured = true;
SetCapture(m_hWnd);
int id = 0;
handleTouchesBegin(1, &id, &pt.x, &pt.y);
}
}
break;
case WM_MOUSEMOVE:
#if(_MSC_VER >= 1600)
// Don't process message generated by Windows Touch
if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;
#endif /* #if(_MSC_VER >= 1600) */
if (MK_LBUTTON == wParam && m_bCaptured)
{
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x, point.y);
int id = 0;
pt.x /= m_fFrameZoomFactor;
pt.y /= m_fFrameZoomFactor;
handleTouchesMove(1, &id, &pt.x, &pt.y);
}
break;
case WM_LBUTTONUP:
#if(_MSC_VER >= 1600)
// Don't process message generated by Windows Touch
if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;
#endif /* #if(_MSC_VER >= 1600) */
if (m_bCaptured)
{
POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
CCPoint pt(point.x, point.y);
int id = 0;
pt.x /= m_fFrameZoomFactor;
pt.y /= m_fFrameZoomFactor;
handleTouchesEnd(1, &id, &pt.x, &pt.y);
ReleaseCapture();
m_bCaptured = false;
}
break;
#if(_MSC_VER >= 1600)
case WM_TOUCH:
{
BOOL bHandled = FALSE;
UINT cInputs = LOWORD(wParam);
PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
if (pInputs)
{
if (s_pfGetTouchInputInfoFunction((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT)))
{
for (UINT i=0; i < cInputs; i++)
{
TOUCHINPUT ti = pInputs[i];
POINT input;
input.x = TOUCH_COORD_TO_PIXEL(ti.x);
input.y = TOUCH_COORD_TO_PIXEL(ti.y);
ScreenToClient(m_hWnd, &input);
CCPoint pt(input.x, input.y);
CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y);
if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp))
{
pt.x /= m_fFrameZoomFactor;
pt.y /= m_fFrameZoomFactor;
if (ti.dwFlags & TOUCHEVENTF_DOWN)
handleTouchesBegin(1, reinterpret_cast(&ti.dwID), &pt.x, &pt.y);
else if (ti.dwFlags & TOUCHEVENTF_MOVE)
handleTouchesMove(1, reinterpret_cast(&ti.dwID), &pt.x, &pt.y);
else if (ti.dwFlags & TOUCHEVENTF_UP)
handleTouchesEnd(1, reinterpret_cast(&ti.dwID), &pt.x, &pt.y);
}
}
bHandled = TRUE;
}
delete [] pInputs;
}
if (bHandled)
{
s_pfCloseTouchInputHandleFunction((HTOUCHINPUT)lParam);
}
}
break;
#endif /* #if(_MSC_VER >= 1600) */
case WM_SIZE:
switch (wParam)
{
case SIZE_RESTORED:
CCApplication::sharedApplication()->applicationWillEnterForeground();
break;
case SIZE_MINIMIZED:
CCApplication::sharedApplication()->applicationDidEnterBackground();
break;
}
break;
case WM_KEYDOWN:
if (wParam == VK_F1 || wParam == VK_F2)
{
CCDirector* pDirector = CCDirector::sharedDirector();
if (GetKeyState(VK_LSHIFT) < 0 || GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0)
pDirector->getKeypadDispatcher()->dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked);
}
else if (wParam == VK_ESCAPE)
{
CCDirector::sharedDirector()->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked);
}
if ( m_lpfnAccelerometerKeyHook!=NULL )
{
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
}
break;
case WM_KEYUP:
if ( m_lpfnAccelerometerKeyHook!=NULL )
{
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
}
break;
case WM_CHAR:
{
if (wParam < 0x20)
{
if (VK_BACK == wParam)
{
CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();
}
else if (VK_RETURN == wParam)
{
CCIMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1);
}
else if (VK_TAB == wParam)
{
// tab input
}
else if (VK_ESCAPE == wParam)
{
// ESC input
//CCDirector::sharedDirector()->end();
}
}
else if (wParam < 128)
{
// ascii char
CCIMEDispatcher::sharedDispatcher()->dispatchInsertText((const char *)&wParam, 1);
}
else
{
char szUtf8[8] = {0};
int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL);
CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(szUtf8, nLen);
}
if ( m_lpfnAccelerometerKeyHook!=NULL )
{
(*m_lpfnAccelerometerKeyHook)( message,wParam,lParam );
}
}
break;
case WM_PAINT:
PAINTSTRUCT ps;
BeginPaint(m_hWnd, &ps);
EndPaint(m_hWnd, &ps);
break;
case WM_CLOSE:
CCDirector::sharedDirector()->end();
break;
case WM_DESTROY:
destroyGL();
PostQuitMessage(0);
break;
default:
if (m_wndproc)
{
m_wndproc(message, wParam, lParam, &bProcessed);
if (bProcessed) break;
}
return DefWindowProc(m_hWnd, message, wParam, lParam);
}
if (m_wndproc && !bProcessed)
{
m_wndproc(message, wParam, lParam, &bProcessed);
}
return 0;
}
2.实现触屏事件处理
知道了原理之后,实现起来就很简单了:定义一个CCTouchDelegate(或者其子类CCTargetedTouchDelegate/CCStandardTouchDelegate),
然后重写那几个处理函数(began、move、end),并把定义好的CCTouchDelegate添加到分发列表中,在onExit函数中实现从分发列表中删除。
在平常的开发中,一般有两种方式:(1)继承CCLayer,在层中处理触屏函数。
(2)继承CCSprite和CCTouchDelegate(或者其子类)。
上面两种方式,从原理上来说是一样的。
1. 下面是采用继承CCLayer的方式处理触屏事件。
(1)CCStandardTouchDelegate
添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setTouchEnabled(true)。主要代码如下:
//init函数中
this->setIsTouchEnabled(true);
void GameLayer::ccTouchesBegan(CCSet* pTouches,CCEvent* pEvent)
{
CCSetIterator it = pTouches->begin();
CCTouch* touch = (CCTouch*)(*it);
/** .... **/
}
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();
}
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();
}
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;
}
如果返回true,表示会处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法。
请看CCTouchDispatcher.cpp的touches函数部分源码,它是用来分发事件的:
bool bClaimed = false;
if (uIndex == CCTOUCHBEGAN)
{
bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);
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;
}
}
if (bClaimed && pHandler->isSwallowsTouches())
{
if (bNeedsMutableSet)
{
pMutableTouches->removeObject(pTouch);
}
break;
}
如果返回true,并且addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches),
bSwallowsTouches为true,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了。
把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从
pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。