标签:cocos2d-x ccmenu alignitemsincolumnsw
原创文章,转载请注明出处:http://blog.csdn.net/sfh366958228/article/details/38901475
一直在想究竟是先看CCMenu还是CCMenuItem后来想想还是先看CCMenu吧,因为它是CCMenuItem的载体,没有CCMenu也不可能有菜单。
typedef enum { kCCMenuStateWaiting, // 表示没有菜单项被选中 kCCMenuStateTrackingTouch // 表示有菜单项被选中 } tCCMenuState; enum { //* priority used by the menu for the event handler kCCMenuHandlerPriority = -128, }; class CC_DLL CCMenu : public CCLayerRGBA // CCMenu的基类居然是CCLayer! { bool m_bEnabled; // CCMenu是否接收触摸事件 public: CCMenu() : m_pSelectedItem(NULL) {} virtual ~CCMenu(){} // 创建一个空菜单 static CCMenu* create(); // 根据多个菜单项列创建菜单 static CCMenu* create(CCMenuItem* item, ...); // 通过子菜单项的数组创建菜单 static CCMenu* createWithArray(CCArray* pArrayOfItems); // 通过一个菜单项创建菜单 static CCMenu* createWithItem(CCMenuItem* item); // 通过多个菜单项列创建菜单 static CCMenu* createWithItems(CCMenuItem *firstItem, va_list args); // 初始化空菜单 bool init(); // 通过子菜单项数组初始化菜单 bool initWithArray(CCArray* pArrayOfItems); // 垂直方向默认间隙排列 void alignItemsVertically(); //垂直方向指定间隙排列 void alignItemsVerticallyWithPadding(float padding); // 水平方向默认间隙排列 void alignItemsHorizontally(); // 水平方向指定间隙排列 void alignItemsHorizontallyWithPadding(float padding); // 通过每行个数排列 void alignItemsInColumns(unsigned int columns, ...); void alignItemsInColumns(unsigned int columns, va_list args); // ? void alignItemsInColumnsWithArray(CCArray* rows); // 通过每列个数排列 void alignItemsInRows(unsigned int rows, ...); void alignItemsInRows(unsigned int rows, va_list args); // ? void alignItemsInRowsWithArray(CCArray* columns); // 设置事件处理优先级,默认为kCCMenuTouchPriority void setHandlerPriority(int newPriority); // 基类方法 virtual void addChild(CCNode * child); virtual void addChild(CCNode * child, int zOrder); virtual void addChild(CCNode * child, int zOrder, int tag); virtual void registerWithTouchDispatcher(); virtual void removeChild(CCNode* child, bool cleanup); // 触摸事件 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); // 退出调用函数 virtual void onExit(); // ? virtual void setOpacityModifyRGB(bool bValue) {CC_UNUSED_PARAM(bValue);} virtual bool isOpacityModifyRGB(void) { return false;} // 获取/设置菜单是否可用 virtual bool isEnabled() { return m_bEnabled; } virtual void setEnabled(bool value) { m_bEnabled = value; }; protected: // 返回被触摸的菜单项 CCMenuItem* itemForTouch(CCTouch * touch); tCCMenuState m_eState; CCMenuItem *m_pSelectedItem; };
static std::vector<unsigned int> ccarray_to_std_vector(CCArray* pArray) // 将CCArray转换成Vector { std::vector<unsigned int> ret; CCObject* pObj; CCARRAY_FOREACH(pArray, pObj) { CCInteger* pInteger = (CCInteger*)pObj; ret.push_back((unsigned int)pInteger->getValue()); } return ret; } enum { kDefaultPadding = 5, }; // //CCMenu // CCMenu* CCMenu::create() { // 传空参数,创建空菜单 return CCMenu::create(NULL, NULL); } CCMenu * CCMenu::create(CCMenuItem* item, ...) { va_list args; va_start(args,item); //处理...形参 CCMenu *pRet = CCMenu::createWithItems(item, args); va_end(args); return pRet; } CCMenu* CCMenu::createWithArray(CCArray* pArrayOfItems) { CCMenu *pRet = new CCMenu(); if (pRet && pRet->initWithArray(pArrayOfItems)) { pRet->autorelease(); // 设置自动释放 } else { CC_SAFE_DELETE(pRet); } return pRet; } CCMenu* CCMenu::createWithItems(CCMenuItem* item, va_list args) { CCArray* pArray = NULL; // 将...形参转换成CCArray if( item ) { pArray = CCArray::create(item, NULL); // 创建一个CCArray CCMenuItem *i = va_arg(args, CCMenuItem*); while(i) { pArray->addObject(i); // 添加其他子菜单项 i = va_arg(args, CCMenuItem*); } } return CCMenu::createWithArray(pArray); } CCMenu* CCMenu::createWithItem(CCMenuItem* item) { return CCMenu::create(item, NULL); } bool CCMenu::init() { return initWithArray(NULL); } bool CCMenu::initWithArray(CCArray* pArrayOfItems) { if (CCLayer::init()) { // 设置触摸相关信息 setTouchPriority(kCCMenuHandlerPriority); setTouchMode(kCCTouchesOneByOne); setTouchEnabled(true); // 打开可用状态 m_bEnabled = true; // 菜单置于屏幕中间,并填充满整个屏幕 CCSize s = CCDirector::sharedDirector()->getWinSize(); this->ignoreAnchorPointForPosition(true); setAnchorPoint(ccp(0.5f, 0.5f)); this->setContentSize(s); setPosition(ccp(s.width/2, s.height/2)); // 遍历添加菜单项 if (pArrayOfItems != NULL) { int z=0; CCObject* pObj = NULL; CCARRAY_FOREACH(pArrayOfItems, pObj) { CCMenuItem* item = (CCMenuItem*)pObj; this->addChild(item, z); z++; } } // 将选中项置为空 m_pSelectedItem = NULL; // 将状态置为没有菜单项被选中 m_eState = kCCMenuStateWaiting; // enable cascade color and opacity on menus setCascadeColorEnabled(true); setCascadeOpacityEnabled(true); return true; } return false; } /* * override add: */ void CCMenu::addChild(CCNode * child) { CCLayer::addChild(child); } void CCMenu::addChild(CCNode * child, int zOrder) { CCLayer::addChild(child, zOrder); } void CCMenu::addChild(CCNode * child, int zOrder, int tag) { // 确保child为CCMenuItem之内 CCAssert( dynamic_cast<CCMenuItem*>(child) != NULL, "Menu only supports MenuItem objects as children"); CCLayer::addChild(child, zOrder, tag); } void CCMenu::onExit() { if (m_eState == kCCMenuStateTrackingTouch) // 如果有菜单项被选中,则取消选择状态 { if (m_pSelectedItem) { // 将选中项菜单选中状态清除,并将用于记录的变量置为空 m_pSelectedItem->unselected(); m_pSelectedItem = NULL; } // 将状态置为等待触摸 m_eState = kCCMenuStateWaiting; } CCLayer::onExit(); } void CCMenu::removeChild(CCNode* child, bool cleanup) { CCMenuItem *pMenuItem = dynamic_cast<CCMenuItem*>(child); CCAssert(pMenuItem != NULL, "Menu only supports MenuItem objects as children"); // 如果是被选中的菜单项,则先把选中置为空 if (m_pSelectedItem == pMenuItem) { m_pSelectedItem = NULL; } CCNode::removeChild(child, cleanup); } //Menu - Events void CCMenu::setHandlerPriority(int newPriority) { CCTouchDispatcher* pDispatcher = CCDirector::sharedDirector()->getTouchDispatcher(); pDispatcher->setPriority(newPriority, this); // 设置触摸优先级 } void CCMenu::registerWithTouchDispatcher() { // 注册触摸 CCDirector* pDirector = CCDirector::sharedDirector(); pDirector->getTouchDispatcher()->addTargetedDelegate(this, this->getTouchPriority(), true); } bool CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event) { CC_UNUSED_PARAM(event); if (m_eState != kCCMenuStateWaiting || ! m_bVisible || !m_bEnabled) { // 排除不执行触摸事件状态 return false; } for (CCNode *c = this->m_pParent; c != NULL; c = c->getParent()) { // 如果菜单父类不可见则返回 if (c->isVisible() == false) { return false; } } <span style="white-space:pre"> </span>// 选出触摸项 m_pSelectedItem = this->itemForTouch(touch); if (m_pSelectedItem) { m_eState = kCCMenuStateTrackingTouch; // 将状态变为有菜单项被选中 m_pSelectedItem->selected(); // 对触摸项执行触摸事件 return true; } return false; } void CCMenu::ccTouchEnded(CCTouch *touch, CCEvent* event) { CC_UNUSED_PARAM(touch); // 消除不使用警告 CC_UNUSED_PARAM(event); CCAssert(m_eState == kCCMenuStateTrackingTouch, "[Menu ccTouchEnded] -- invalid state"); if (m_pSelectedItem) { // 解除菜单项选中状态 m_pSelectedItem->unselected(); m_pSelectedItem->activate(); } m_eState = kCCMenuStateWaiting; } void CCMenu::ccTouchCancelled(CCTouch *touch, CCEvent* event) { CC_UNUSED_PARAM(touch); CC_UNUSED_PARAM(event); CCAssert(m_eState == kCCMenuStateTrackingTouch, "[Menu ccTouchCancelled] -- invalid state"); if (m_pSelectedItem) { // 解除菜单项选中状态 m_pSelectedItem->unselected(); } m_eState = kCCMenuStateWaiting; } void CCMenu::ccTouchMoved(CCTouch* touch, CCEvent* event) { CC_UNUSED_PARAM(event); CCAssert(m_eState == kCCMenuStateTrackingTouch, "[Menu ccTouchMoved] -- invalid state"); CCMenuItem *currentItem = this->itemForTouch(touch); // 获取当前触摸的子菜单项 if (currentItem != m_pSelectedItem) { if (m_pSelectedItem) // 如果当前有选中项,先取消选中 { m_pSelectedItem->unselected(); } m_pSelectedItem = currentItem; // 重新设置选中项 if (m_pSelectedItem) { m_pSelectedItem->selected(); } } } //Menu - Alignment // 垂直方向默认间隙排列 void CCMenu::alignItemsVertically() { this->alignItemsVerticallyWithPadding(kDefaultPadding); } // 垂直方向指定间隙排列 void CCMenu::alignItemsVerticallyWithPadding(float padding) { float height = -padding; // 第一个子菜单没有间隔,用于去掉这个重复 if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<CCNode*>(pObject); if (pChild) { height += pChild->getContentSize().height * pChild->getScaleY() + padding; // 获取子菜单项和间隔的总高度 } } } float y = height / 2.0f; // 因为定位点在中间,所以先除去2 if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<CCNode*>(pObject); if (pChild) { pChild->setPosition(ccp(0, y - pChild->getContentSize().height * pChild->getScaleY() / 2.0f)); // 锚点在中间,所以除2 y -= pChild->getContentSize().height * pChild->getScaleY() + padding; // 减去一个子菜单项和间隔的高度 } } } } // 水平方向默认间隙排列 void CCMenu::alignItemsHorizontally(void) { this->alignItemsHorizontallyWithPadding(kDefaultPadding); } // 水平方向指定间隙排列 void CCMenu::alignItemsHorizontallyWithPadding(float padding) // 原理和垂直排列相似,只不过将x变成y { float width = -padding; if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<CCNode*>(pObject); if (pChild) { width += pChild->getContentSize().width * pChild->getScaleX() + padding; } } } float x = -width / 2.0f; if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<CCNode*>(pObject); if (pChild) { pChild->setPosition(ccp(x + pChild->getContentSize().width * pChild->getScaleX() / 2.0f, 0)); x += pChild->getContentSize().width * pChild->getScaleX() + padding; } } } } void CCMenu::alignItemsInColumns(unsigned int columns, ...) { // 将...转换成va_list va_list args; va_start(args, columns); this->alignItemsInColumns(columns, args); va_end(args); } void CCMenu::alignItemsInColumns(unsigned int columns, va_list args) { // 将va_list转换成CCArray CCArray* rows = CCArray::create(); while (columns) { rows->addObject(CCInteger::create(columns)); columns = va_arg(args, unsigned int); } alignItemsInColumnsWithArray(rows); } void CCMenu::alignItemsInColumnsWithArray(CCArray* rowsArray) { vector<unsigned int> rows = ccarray_to_std_vector(rowsArray); // 将CCArray转换成vector int height = -5; // 为-5是用于除去第一行的间隙 unsigned int row = 0; // 索引,当前行序号 unsigned int rowHeight = 0; // 放置每一行高度(即每行最大高度) unsigned int columnsOccupied = 0; // 已放置列数 unsigned int rowColumns; // 放置该行有多少列 if (m_pChildren && m_pChildren->count() > 0) // 子菜单项数组存在且个数不为0 { CCObject* pObject = NULL; // 临时变量 CCARRAY_FOREACH(m_pChildren, pObject) // 遍历子菜单项数组 { CCNode* pChild = dynamic_cast<CCNode*>(pObject); // 将CCObject对象强制转换成CCNode对象 if (pChild) // 如果强制转换成功 { CCAssert(row < rows.size(), ""); rowColumns = rows[row]; // 获取该行列数 CCAssert(rowColumns, ""); float tmp = pChild->getContentSize().height; // 获取当前子菜单高度 rowHeight = (unsigned int)((rowHeight >= tmp || isnan(tmp)) ? rowHeight : tmp); // 如果行高大于当前子菜单项高度,或者当前子菜单项高度越界,rowHeight不变,否则rowHeight等于当前子菜单项高度(用来存放最大菜单项高度) ++columnsOccupied; // 已放置列数自加 if (columnsOccupied >= rowColumns) // 如果已放置列数大于等于该行可容纳列数 { height += rowHeight + 5; // 高度加上当前高度加上间隙 columnsOccupied = 0; // 重置已放置列数 rowHeight = 0; // 重置每行行高的临时变量 ++row; // 跳转至下一行 } } } } // check if too many rows/columns for available menu items CCAssert(! columnsOccupied, ""); CCSize winSize = CCDirector::sharedDirector()->getWinSize(); // 获取屏幕大小 row = 0; rowHeight = 0; rowColumns = 0; float w = 0.0; float x = 0.0; float y = (float)(height / 2); if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<ccnode>(pObject); if (pChild) { if (rowColumns == 0) // 如果需要放置数量为0才重新读取数据 { rowColumns = rows[row]; // 获取需要放置的数量 w = winSize.width / (1 + rowColumns); //占屏幕宽度大小平分 x = w; } float tmp = pChild->getContentSize().height; rowHeight = (unsigned int)((rowHeight >= tmp || isnan(tmp)) ? rowHeight : tmp); // 获取当前行高度最大高度 pChild->setPosition(ccp(x - winSize.width / 2, y - pChild->getContentSize().height / 2)); // 重新放置位置 x += w; ++columnsOccupied; if (columnsOccupied >= rowColumns) // 如果已放置列数大于等于需要放置的列数 { y -= rowHeight + 5; // 减去一个子菜单项和间隔的高度 columnsOccupied = 0; rowColumns = 0; rowHeight = 0; ++row; } } } } } //alignItemsInRows系列方法参照alignItemsInColumns void CCMenu::alignItemsInRows(unsigned int rows, ...) { va_list args; va_start(args, rows); this->alignItemsInRows(rows, args); va_end(args); } void CCMenu::alignItemsInRows(unsigned int rows, va_list args) { CCArray* pArray = CCArray::create(); while (rows) { pArray->addObject(CCInteger::create(rows)); rows = va_arg(args, unsigned int); } alignItemsInRowsWithArray(pArray); } void CCMenu::alignItemsInRowsWithArray(CCArray* columnArray) { vector<unsigned int=""> columns = ccarray_to_std_vector(columnArray); vector<unsigned int=""> columnWidths; vector<unsigned int=""> columnHeights; int width = -10; int columnHeight = -5; unsigned int column = 0; unsigned int columnWidth = 0; unsigned int rowsOccupied = 0; unsigned int columnRows; if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<ccnode>(pObject); if (pChild) { // check if too many menu items for the amount of rows/columns CCAssert(column < columns.size(), ""); columnRows = columns[column]; // can't have zero rows on a column CCAssert(columnRows, ""); // columnWidth = fmaxf(columnWidth, [item contentSize].width); float tmp = pChild->getContentSize().width; columnWidth = (unsigned int)((columnWidth >= tmp || isnan(tmp)) ? columnWidth : tmp); columnHeight += (int)(pChild->getContentSize().height + 5); ++rowsOccupied; if (rowsOccupied >= columnRows) { columnWidths.push_back(columnWidth); columnHeights.push_back(columnHeight); width += columnWidth + 10; rowsOccupied = 0; columnWidth = 0; columnHeight = -5; ++column; } } } } // check if too many rows/columns for available menu items. CCAssert(! rowsOccupied, ""); CCSize winSize = CCDirector::sharedDirector()->getWinSize(); column = 0; columnWidth = 0; columnRows = 0; float x = (float)(-width / 2); float y = 0.0; if (m_pChildren && m_pChildren->count() > 0) { CCObject* pObject = NULL; CCARRAY_FOREACH(m_pChildren, pObject) { CCNode* pChild = dynamic_cast<ccnode>(pObject); if (pChild) { if (columnRows == 0) { columnRows = columns[column]; y = (float) columnHeights[column]; } // columnWidth = fmaxf(columnWidth, [item contentSize].width); float tmp = pChild->getContentSize().width; columnWidth = (unsigned int)((columnWidth >= tmp || isnan(tmp)) ? columnWidth : tmp); pChild->setPosition(ccp(x + columnWidths[column] / 2, y - winSize.height / 2)); y -= pChild->getContentSize().height + 10; ++rowsOccupied; if (rowsOccupied >= columnRows) { x += columnWidth + 5; rowsOccupied = 0; columnRows = 0; columnWidth = 0; ++column; } } } } } CCMenuItem* CCMenu::itemForTouch(CCTouch *touch){ CCPoint touchLocation = touch->getLocation(); // 获取当前触摸点 if (m_pChildren && m_pChildren->count() > 0) // 判断子节点(子菜单项)是否存在,且个数不为0 { CCObject* pObject = NULL; // 临时变量 CCARRAY_FOREACH(m_pChildren, pObject) // 遍历子菜单项数组 { CCMenuItem* pChild = dynamic_cast<ccmenuitem>(pObject); // 强制转换,获取当前子菜单项 if (pChild && pChild->isVisible() && pChild->isEnabled()) // 如果当前子菜单项不为空、且可见、且可用 { CCPoint local = pChild->convertToNodeSpace(touchLocation); // 把世界坐标转换到当前节点的本地坐标系中 CCRect r = pChild->rect(); // 获取子菜单项范围 r.origin = CCPointZero; if (r.containsPoint(local)) // 判断子菜单项范围中是否包括触摸点 { return pChild; // 返回该子菜单项 } } } } return NULL; // 返回空 } </ccmenuitem></ccnode></ccnode></unsigned></unsigned></unsigned></ccnode>
还是觉得这样脚踏实地的把类中所有方法的具体实现比较靠谱,虽然比较吃力,但看完之后得到的东西会更有用。
从上面的代码我们可以知道,其实CCMenu是一个逻辑挺简单的类,它派生自CCLayer(根本派生)是我起初没有想到的,但后来看完实现之后基本明白了原由,点击菜单需要用到触摸,必须得有监听,CCLayer便是实现触摸监听的入口。
想要创建一个CCMenu依旧是调用create,你可以创建一个空菜单(create()),也可以创建一个只有一个子菜单项目的菜单(createWithItem()),也可以依据多个子菜单项创建创建(create(,...)、createWithItems()、createWithArray)。
创建的时候每次都要添加MenuItem还得想着各自坐标让之前初学Cocos2dx的我叫苦不迭,看了源码后意外发现了alignItemsXXX系列方法,可以快速排列。
下一篇笔记我们一起来学习具体的子菜单项实现吧。
标签:cocos2d-x ccmenu alignitemsincolumnsw
原文地址:http://blog.csdn.net/sfh366958228/article/details/38901475