之前使用webview的时候一直相对触摸事件了解一下,一直没有去深入理解。导致现在一直不理解触摸事件到底是怎么一回事,今天有时间就来研究一下。
环境:vs2013 + crossapp 1.1.12(目前最新版本)
1. 怎么迅速定位我需要的 触摸事件代码?
a.在研究crossapp源码之前,可以看看这篇博文cocos2d-x学习笔记-触屏事件详解 里面详细讲解了 cocos2dx 触摸事件的来龙去脉,当然也有其他解析cocos2dx 触摸事件的博文,只要你看的舒服就行了。
b. 在libCrossApp里面查找相关 Touch 类信息,至于为什么需要找Touch相关的类,可以看看crossapp的来历。
从源码里面我们可以查找到 下面几个类:CATouchDispatcher CATouch CATouchController CATouchView ,通过类的名字可以很明显的猜测了。
CATouchDispatcher :触摸事件分发类
CATouch :触摸事件类
CATouchDispatcher :触摸事件控制类
CATouchView :触摸视图 继承 CAView
接下来我们只需要在 CATouchDispatcher CATouchDispatcher 构造函数里面下断点就可以了。
当按下F5的时候,程序会停在CATouchDispatcher 构造函数这里 。上图
估计这里应该是程序初始化的时候new CATouchDispatcher ,有兴趣可以去看看,具体步骤 点击堆栈帧,弹出一个list视图,从上到下依次是被调用的关系。上图:
在这里再次感叹vs的强大,很多高大上的功能给开发者减少了多少日夜的调试。
继续F5,程序直接弹出来了。上图
官方自带demo还是不错了 ,基本上控件都涵盖了。
这时候 手动触发一个事件, 随便点击一下 界面上的控件 ,这时候进入了CATouchController构造函数。我们看看哪里调用了这个函数,
void CATouchDispatcher::touchesBegan(CCSet *touches, CAEvent *pEvent) { CC_RETURN_IF(!isDispatchEvents()); m_bLocked = true; CATouch *pTouch; CCSetIterator setIter; for (setIter = touches->begin(); setIter != touches->end(); setIter++) { pTouch = (CATouch *)(*setIter); CATouchController* touchController = new CATouchController(); touchController->setTouch(pTouch); touchController->setEvent(pEvent); m_vTouchControllers[pTouch->getID()] = touchController; touchController->touchBegan(); } m_bLocked = false; }
void CCEGLViewProtocol::handleTouchesBegin(int num, intptr_t ids[], float xs[], float ys[]) { CCSet set; for (int i = 0; i < num; ++i) { intptr_t id = ids[i]; float x = xs[i]; float y = ys[i]; int nUnusedIndex = 0; // it is a new touch if (s_TouchesIntergerDict.find(id) == s_TouchesIntergerDict.end()) { nUnusedIndex = getUnUsedIndex(); // The touches is more than MAX_TOUCHES ? if (nUnusedIndex == -1) { CCLOG("The touches is more than MAX_TOUCHES, nUnusedIndex = %d", nUnusedIndex); continue; } CATouch* pTouch = s_pTouches[nUnusedIndex] = new CATouch(); pTouch->setTouchInfo(nUnusedIndex, (x - m_obViewPortRect.origin.x) / m_fScaleX, (y - m_obViewPortRect.origin.y) / m_fScaleY); s_TouchesIntergerDict.insert(std::make_pair(id, nUnusedIndex)); set.addObject(pTouch); } } if (set.count() == 0) { CCLOG("touchesBegan: count = 0"); return; } m_pDelegate->touchesBegan(&set, NULL); }
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;
这下就清楚了 。
LRESULT CCEGLView::WindowProc() 这个函数将鼠标左键按下事件(当然还有其他事件,不分析) 传输到 void CCEGLViewProtocol::handleTouchesBegin() 里面,然后将
鼠标左键按下事件 封装到 CATouch 类里面。然后CATouchDispatcher函数来处理 CATouch 事件,这个时候代码就到了这里了void CATouchDispatcher::touchesBegan()
这个时候才创建 CATouchController类 ,并且将 CATouch 传进去了 ,需要注意的是 CATouch 里面有一些信息的,比如 location event 等等。
ok到了这里,两个断点都出现了 。
还是回到CATouchController的断点处, 这时候F11继续跟进,跟着进入touchController->touchBegan()函数里面,看看里面都发生了什么。
void CATouchController::touchBegan() { m_tFirstPoint = m_pTouch->getLocation(); std::vector<CAResponder*> vector; CAView* view = dynamic_cast<CAView*>(CAApplication::getApplication()->getTouchDispatcher()->getFirstResponder()); bool isContainsFirstPoint = view && view->convertRectToWorldSpace(view->getBounds()).containsPoint(m_tFirstPoint); if (isContainsFirstPoint) { vector = this->getEventListener(m_pTouch, view); } else { vector = this->getEventListener(m_pTouch, CAApplication::getApplication()->getRootWindow()); } std::vector<CAResponder*>::iterator itr; for (itr=vector.begin(); itr!=vector.end(); itr++) { CC_CONTINUE_IF(!(*itr)->isPriorityScroll()); CC_CONTINUE_IF(!(*itr)->isScrollEnabled()); CC_CONTINUE_IF(!(*itr)->isHorizontalScrollEnabled() && !(*itr)->isVerticalScrollEnabled()); m_vTouchMovedsViewCache.pushBack((*itr)); } m_vTouchesViews.pushBack(vector.back()); //这里需要注意 将vector最后一个元素 添加到了 m_vTouchesViews 里面了 ! if (!m_vTouchMovedsViewCache.empty()) { CAScheduler::schedule(schedule_selector(CATouchController::passingTouchesViews), this, 0, 0, 0.05f); } else { this->passingTouchesViews(); //这里进去就快开始进入事件处理了 } }
vector = this->getEventListener(m_pTouch, CAApplication::getApplication()->getRootWindow());这看起来是最重要的代码了 ,可能进去之后就得到了我们想要的东西。赶紧进入看看
std::vector<CAResponder*> CATouchController::getEventListener(CATouch* touch, CAView* view) { CAResponder* responder = view;<span style="white-space:pre"> </span>//主视图 std::vector<CAResponder*> vector; do { vector.push_back(responder); //视图进入vector里面 CAResponder* lastResponder = NULL; if (CAView* view = dynamic_cast<CAView*>(responder)) { if (view->getViewDelegate()) //查看是否有触摸回调函数 { lastResponder = view->nextResponder(); } else { //程序一般会进入这里 CAVector<CAView*>::const_reverse_iterator itr; //for循环很重要 for (itr=view->CAView::getSubviews().rbegin(); itr!=view->CAView::getSubviews().rend(); itr++) { CAView* subview = *itr; if (subview->isVisible() && subview->isTouchEnabled()) { CCPoint point = subview->convertTouchToNodeSpace(touch); if (subview->getBounds().containsPoint(point)) { lastResponder = subview; //如果最后一个视图的视图局域包含了 鼠标点击(触摸事件)的点位置 ,将 break; //将视图push_back到vector里面 ,继续循环。 } } } } } else if (CAViewController* viewController = dynamic_cast<CAViewController*>(responder)) { CAVector<CAView*>::const_reverse_iterator itr; for (itr=viewController->getView()->CAView::getSubviews().rbegin(); itr!=viewController->getView()->CAView::getSubviews().rend(); itr++) { CAView* subview = *itr; if (subview->isVisible() && subview->isTouchEnabled()) { //CC_BREAK_IF(!subview->isTouchEnabled()); CCPoint point = subview->convertTouchToNodeSpace(touch); if (subview->getBounds().containsPoint(point)) { lastResponder = subview; break; } } } } responder = lastResponder; } while (responder); return vector; }
这里提到了容器的概念 , 这里的容器一般是指 一个CAView 里面含有多个 CAView视图的类,比如常用的控制器CADrawerController CATabBarController CADrawerController ,也可能有 其余的控件类,因为有些控件类我还没有研究过,所以不下定论。
回到 touchBegan函数 ,得到 视图的 vector 之后 ,将 最后一个视图(也就是可能处理事件的控件类或者控制器类) 添加到 m_vTouchesViews
然后进入passingTouchesViews里面查看一下
void CATouchController::passingTouchesViews(float dt) { CAView* view = dynamic_cast<CAView*>(CAApplication::getApplication()->getTouchDispatcher()->getFirstResponder()); bool isContainsFirstPoint = view && view->convertRectToWorldSpace(view->getBounds()).containsPoint(m_tFirstPoint); if (!isContainsFirstPoint && view) { view->ccTouchBegan(m_pTouch, m_pEvent); } CC_RETURN_IF(m_vTouchesViews.empty()); CAResponder* responder = m_vTouchesViews.front(); while (responder->nextResponder()) { responder = responder->nextResponder(); m_vTouchesViews.pushBack(responder); } for (int i=0; i<m_vTouchesViews.size();) { if (!m_vTouchesViews.at(i)->ccTouchBegan(m_pTouch, m_pEvent)) { m_vTouchesViews.erase(i); } else { i++; } } }
CAResponder* CAView::nextResponder() { if (!m_bHaveNextResponder) { return NULL; } if (m_pViewDelegate) { return dynamic_cast<CAResponder*>(m_pViewDelegate); } return this->getSuperview(); }
分析结束了 。