首页 > 代码库 > cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论)
cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论)
cocos2d-x源码总目录
http://blog.csdn.net/u011225840/article/details/31743129
源码来自2.x,转载请注明
1.继承结构
首先来看下CCTableView的继承结构
从继承结构上看,CCTableView是一种CCScrollView,所以为了研究CCTableView的源码,清先去了解CCScrollView的源码http://blog.csdn.net/u011225840/article/details/30033501。
其次,CCTableView也继承了CCScrollViewDelegate,从后面的源码分析中,我们可以看出主要是为了实现scrollViewDidScroll这个函数。从而使用CCScrollView的滚动时,可以实现CCTableView自己本身的操作。(如果你看到这里不懂,请务必先弄懂CCScrollView的源码。)
最后,除了继承结构,我们还需要了解三个重要的类。
CCTableViewCell,CCTableViewDelegate,CCTableViewDataSource。通过这三个类,CCTableView将数据与其他操作解耦。
2.相关类的分析
2.1CCTableViewCell
CCtableViewCell主要是含有一个唯一的标识符,允许TableView通过不同的idx来更新TableviewCell。
一般情况下,会写一个CustomCell来继承该类,该Cell上有每一个cell的样式(含有label?含有sprite?全在该cell中实现)
class CCTableViewCell: public CCNode, public CCSortableObject { public: CCTableViewCell() {} /** * The index used internally by SWTableView and its subclasses */ unsigned int getIdx(); void setIdx(unsigned int uIdx); /** * Cleans up any resources linked to this cell and resets <code>idx</code> property. */ void reset(); void setObjectID(unsigned int uIdx); unsigned int getObjectID(); private: unsigned int m_uIdx; };
2.2CCTableViewDataSource
CCTableViewDataSource是非常重要的一个类,TableView的数据相关的处理都与该类有关,请看他提供的四个函数,注释已经给出哦。
一般情况下,我们会让一个Custom类来继承他并实现方法。该Custom类一般是继承DataSource,TableViewDelegate,与一个CClayer,并含有一个CCTableView。
(在文章的最后,我会给出一个例子)。
//根据不同的idx,来告诉tableview cell的大小 virtual CCSize tableCellSizeForIndex(CCTableView *table, unsigned int idx) { return cellSizeForTable(table); }; //提供一个通用的方法,给出table的cell大小,如果该table的cell大小都一样,一般都一样。。 virtual CCSize cellSizeForTable(CCTableView *table) { return CCSizeZero; }; //根据不同的idx,获得table的相应cell,一会分析table的dequeceCell时,再详细讲解此方法。 virtual CCTableViewCell* tableCellAtIndex(CCTableView *table, unsigned int idx) = 0; //返回table的cell个数。 virtual unsigned int numberOfCellsInTableView(CCTableView *table) = 0;
2.3 CCTableViewDelegate
提供了几个Delegate函数,以供TableView使用。Delegate的用法我在CCScrollView源码分析中已经说过,这里不再赘述。
virtual void tableCellTouched(CCTableView* table, CCTableViewCell* cell) = 0;
这里只说下必须实现的这个函数,当table通过idx获取用户正在触摸该cell后,一定会调用该方法。(选择某个物件后,给人物穿上,就是通过这个方法来响应。)
3.CCTableView源码分析
3.1创建时
CCTableView提供了两个create函数
create(CCTableViewDataSource* dataSource, CCSize size);
create(CCTableViewDataSource* dataSource, CCSize size, CCNode *container);
第一个函数,调用了第二个,将container置为NULL。
下面来看第二个函数。
CCTableView* CCTableView::create(CCTableViewDataSource* dataSource, CCSize size, CCNode *container) { CCTableView *table = new CCTableView(); table->initWithViewSize(size, container); table->autorelease(); table->setDataSource(dataSource); table->_updateCellPositions(); table->_updateContentSize(); return table; }
话说这种风格也不怕堆内存空间不足么。
发现三个重要的函数:
3.1.1 initWithViewSize
bool CCTableView::initWithViewSize(CCSize size, CCNode* container/* = NULL*/) { if (CCScrollView::initWithViewSize(size,container)) { m_pCellsUsed = new CCArrayForObjectSorting(); m_pCellsFreed = new CCArrayForObjectSorting(); m_pIndices = new std::set<unsigned int>(); m_eVordering = kCCTableViewFillBottomUp; this->setDirection(kCCScrollViewDirectionVertical); CCScrollView::setDelegate(this); return true; } return false; }
cellsUsed是用来存放正在使用的,显示在view上面的cell。
cellsFreed是用来存放暂时不使用的,没在view上面显示的cell(从cellsUsed被移除后添加进cellsFreed),cellsFreed提供了一种缓存机制。允许我们从tableCellAtIndex中拿到cells,不需要重新创建他,只需要根据idx更新下显示。
indices是用来存放每个cell应该占据的位置区域值。
3.1.2 _updateCellPositions
根据CCTableView呈现的方向以及order,给indices赋值。
void CCTableView::_updateCellPositions() { //根据dataSource,更新cell的位置。 int cellsCount = m_pDataSource->numberOfCellsInTableView(this); m_vCellsPositions.resize(cellsCount + 1, 0.0); if (cellsCount > 0) { float currentPos = 0; CCSize cellSize; for (int i=0; i < cellsCount; i++) { m_vCellsPositions[i] = currentPos; CCLog("The postion is %f",currentPos); //根据idx获取到相应cell的size cellSize = m_pDataSource->tableCellSizeForIndex(this, i); switch (this->getDirection()) { case kCCScrollViewDirectionHorizontal: currentPos += cellSize.width; break; default: currentPos += cellSize.height; break; } } //n个cell需要n+1个Pos 来指定位置 m_vCellsPositions[cellsCount] = currentPos;//1 extra value allows us to get right/bottom of the last cell CCLog("The postion is %f",currentPos); } }
3.1.3 _updateContentSize
这个方法调整了CCTableView的大小与偏移。(注意,调整偏移的时候,会调用scrollViewDidScroll方法。)
void CCTableView::_updateContentSize() { CCSize size = CCSizeZero; unsigned int cellsCount = m_pDataSource->numberOfCellsInTableView(this); //获取到最大的长与宽 if (cellsCount > 0) { float maxPosition = m_vCellsPositions[cellsCount]; switch (this->getDirection()) { case kCCScrollViewDirectionHorizontal: size = CCSizeMake(maxPosition, m_tViewSize.height); break; default: size = CCSizeMake(m_tViewSize.width, maxPosition); break; } } //获取后调用CCScrollView的setContenSize this->setContentSize(size); //调整方向与初始偏移offset if (m_eOldDirection != m_eDirection) { if (m_eDirection == kCCScrollViewDirectionHorizontal) { this->setContentOffset(ccp(0,0)); } else { //这里其实不是很懂 this->setContentOffset(ccp(0,this->minContainerOffset().y)); } m_eOldDirection = m_eDirection; } }
如注释所示,我有个小问题,如果是垂直方向的,则会把初始位置放到minContainerOffset上,为何这样,没懂。。。
下面一节重点讲解scrollviewDidScroll
3.2 滚动时
根据父类CCScrollView,每次设置偏移后,会调用scrollviewDidScroll方法。
void CCTableView::scrollViewDidScroll(CCScrollView* view) { //继承自CCScrollViewDelegate,并且根据CCScrollView的源码,每次移动时(setContentOffset函数),都会调用这个函数 //没有任何元素 unsigned int uCountOfItems = m_pDataSource->numberOfCellsInTableView(this); if (0 == uCountOfItems) { return; } //tableviewdelegate的DidScroll调用 if(m_pTableViewDelegate != NULL) { m_pTableViewDelegate->scrollViewDidScroll(this); } unsigned int startIdx = 0, endIdx = 0, idx = 0, maxIdx = 0; //需要乘以-1的原因很简单,当offset处于正数时,即为cell在初始位置还要往右拉,此时得到的startIdx肯定是不存在的。 CCPoint offset = ccpMult(this->getContentOffset(), -1); maxIdx = MAX(uCountOfItems-1, 0); if (m_eVordering == kCCTableViewFillTopDown) { offset.y = offset.y + m_tViewSize.height/this->getContainer()->getScaleY(); } //查到起始的startIdx startIdx = this->_indexFromOffset(offset); //CCLog("The offset is %f",offset.x); //CCLog("The start index is %d",startIdx); if (startIdx == CC_INVALID_INDEX) { startIdx = uCountOfItems - 1; } if (m_eVordering == kCCTableViewFillTopDown) { offset.y -= m_tViewSize.height/this->getContainer()->getScaleY(); } else { offset.y += m_tViewSize.height/this->getContainer()->getScaleY(); } //起始offset加上显示View的宽度就是endIdx的offset offset.x += m_tViewSize.width/this->getContainer()->getScaleX(); endIdx = this->_indexFromOffset(offset); //如果endIdx 超过,则将endIdx置为最大值 if (endIdx == CC_INVALID_INDEX) { endIdx = uCountOfItems - 1; } if(startIdx > endIdx) { int tmp = startIdx; startIdx = endIdx; endIdx = tmp; } if (m_pCellsUsed->count() > 0) { CCTableViewCell* cell = (CCTableViewCell*)m_pCellsUsed->objectAtIndex(0); //找出正在使用的cell,只要是idx小于startIdx的,就移出。 idx = cell->getIdx(); while(idx <startIdx) { this->_moveCellOutOfSight(cell); if (m_pCellsUsed->count() > 0) { cell = (CCTableViewCell*)m_pCellsUsed->objectAtIndex(0); idx = cell->getIdx(); } else { break; } } } if (m_pCellsUsed->count() > 0) { CCTableViewCell *cell = (CCTableViewCell*)m_pCellsUsed->lastObject(); idx = cell->getIdx(); //同上,移除所有大于endIdx的cell while(idx <= maxIdx && idx > endIdx) { this->_moveCellOutOfSight(cell); if (m_pCellsUsed->count() > 0) { cell = (CCTableViewCell*)m_pCellsUsed->lastObject(); idx = cell->getIdx(); } else { break; } } } //更新在start和end之间的cell for (unsigned int i=startIdx; i <= endIdx; i++) { //if ([m_pIndices containsIndex:i]),indices存在即表明该位置上的cell已经被update。 if (m_pIndices->find(i) != m_pIndices->end()) { continue; } this->updateCellAtIndex(i); } }
该函数中,调用了三个内部函数:
3.2.1 _indexFromOffset
unsigned int CCTableView::_indexFromOffset(CCPoint offset) { int index = 0; const int maxIdx = m_pDataSource->numberOfCellsInTableView(this)-1; //如果是垂直方向上,并且是TopDown的,则改变offset.y if (m_eVordering == kCCTableViewFillTopDown) { offset.y = this->getContainer()->getContentSize().height - offset.y; } //获取该点处于哪个index中 index = this->__indexFromOffset(offset); if (index != -1) { index = MAX(0, index); if (index > maxIdx) { index = CC_INVALID_INDEX; } } return index; } int CCTableView::__indexFromOffset(CCPoint offset) { int low = 0; int high = m_pDataSource->numberOfCellsInTableView(this) - 1; float search; //根据方向来判断需要寻找的是x还是y坐标 switch (this->getDirection()) { case kCCScrollViewDirectionHorizontal: search = offset.x; break; default: search = offset.y; break; } //二分查找,找出点在哪个cell的区间内,返回index while (high >= low) { int index = low + (high - low) / 2; float cellStart = m_vCellsPositions[index]; float cellEnd = m_vCellsPositions[index + 1]; CCLog("The start cell is %f",cellStart); if (search >= cellStart && search <= cellEnd) { return index; } else if (search < cellStart) { high = index - 1; } else { low = index + 1; } } if (low <= 0) { return 0; } <span style="white-space:pre"> </span>//结果是-1则表示超出最大距离,在外部将被赋值为最大距离 return -1; }
3.2.2 _moveCellOutOfSight
void CCTableView::_moveCellOutOfSight(CCTableViewCell *cell) { //此时调用delegate方法。cellWillCycle if(m_pTableViewDelegate != NULL) { m_pTableViewDelegate->tableCellWillRecycle(this, cell); } //做数据处理 m_pCellsFreed->addObject(cell); m_pCellsUsed->removeSortedObject(cell); m_pIndices->erase(cell->getIdx()); // [m_pIndices removeIndex:cell.idx]; cell->reset(); if (cell->getParent() == this->getContainer()) { this->getContainer()->removeChild(cell, true);; } }
3.2.3 updateCellAtIndex
void CCTableView::updateCellAtIndex(unsigned int idx) { if (idx == CC_INVALID_INDEX) { return; } unsigned int uCountOfItems = m_pDataSource->numberOfCellsInTableView(this); if (0 == uCountOfItems || idx > uCountOfItems-1) { return; } //首先将cell移除used CCTableViewCell* cell = this->cellAtIndex(idx); if (cell) { this->_moveCellOutOfSight(cell); } //调用该方法,根据idx获取新cell cell = m_pDataSource->tableCellAtIndex(this, idx); //设置cell this->_setIndexForCell(idx, cell); //将cell加到used中 this->_addCellIfNecessary(cell); }
可以看出,update时,先将所有cell移出,再通过dataSource的tableCellAtIndex来获取更新cell。如果不设置缓存freed,会造成性能瓶颈。
void CCTableView::_setIndexForCell(unsigned int index, CCTableViewCell *cell) { //设置cell的锚点,位置与idx cell->setAnchorPoint(ccp(0.0f, 0.0f)); cell->setPosition(this->_offsetFromIndex(index)); CCLog("The cell position is %f",this->_offsetFromIndex(index).x); cell->setIdx(index); }
void CCTableView::_addCellIfNecessary(CCTableViewCell * cell) { if (cell->getParent() != this->getContainer()) { this->getContainer()->addChild(cell); } m_pCellsUsed->insertSortedObject(cell); m_pIndices->insert(cell->getIdx()); // [m_pIndices addIndex:cell.idx]; }
看到这里,基本上CCTableView的重点已经看完了。下面继续。
3.3 触摸
3.3.1 ccTouchBegan
bool CCTableView::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) { if (!this->isVisible()) { return false; } //父类的ccTouchBegan调用,可以获取touches的多少,并且判断出行为 bool touchResult = CCScrollView::ccTouchBegan(pTouch, pEvent); //啊哦,tableview不支持缩放了哦,只支持滚动 if(m_pTouches->count() == 1) { unsigned int index; CCPoint point; //获取touch在该TableView坐标系下的CCpoint point = this->getContainer()->convertTouchToNodeSpace(pTouch); //获取该point在数据中的位置 index = this->_indexFromOffset(point); //获取在该index上的tableviewCell if (index == CC_INVALID_INDEX) { m_pTouchedCell = NULL; } else { m_pTouchedCell = this->cellAtIndex(index); } //如果该cell存在并且delegate存在,调用delegate的方法,比如说可以pressed if (m_pTouchedCell && m_pTableViewDelegate != NULL) { m_pTableViewDelegate->tableCellHighlight(this, m_pTouchedCell); } } //当触摸点个数不为1,但是存在正在触摸的cell时,将该cell置空,并且调用取消高亮的方法比如说unpressed else if(m_pTouchedCell) { if(m_pTableViewDelegate != NULL) { m_pTableViewDelegate->tableCellUnhighlight(this, m_pTouchedCell); } m_pTouchedCell = NULL; } return touchResult; }
3.3.2 ccTouchMoved
void CCTableView::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) { //先调用父类的move CCScrollView::ccTouchMoved(pTouch, pEvent); //如果移动过程中还存在触摸的cell,则置空并调用delegate if (m_pTouchedCell && isTouchMoved()) { if(m_pTableViewDelegate != NULL) { m_pTableViewDelegate->tableCellUnhighlight(this, m_pTouchedCell); } m_pTouchedCell = NULL; } }
3.3.3 ccTouchEnded
void CCTableView::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) { if (!this->isVisible()) { return; } //move距离过短,则识别为触摸到cell的动作并获取到该cell,调用Delegate的方法。 if (m_pTouchedCell){ CCRect bb = this->boundingBox(); bb.origin = m_pParent->convertToWorldSpace(bb.origin); if (bb.containsPoint(pTouch->getLocation()) && m_pTableViewDelegate != NULL) { m_pTableViewDelegate->tableCellUnhighlight(this, m_pTouchedCell); m_pTableViewDelegate->tableCellTouched(this, m_pTouchedCell); } m_pTouchedCell = NULL; } CCScrollView::ccTouchEnded(pTouch, pEvent); }
3.4 常见的操作
3.4.1 reloadData
void CCTableView::reloadData() { m_eOldDirection = kCCScrollViewDirectionNone; CCObject* pObj = NULL; CCARRAY_FOREACH(m_pCellsUsed, pObj) { CCTableViewCell* cell = (CCTableViewCell*)pObj; if(m_pTableViewDelegate != NULL) { m_pTableViewDelegate->tableCellWillRecycle(this, cell); } m_pCellsFreed->addObject(cell); cell->reset(); if (cell->getParent() == this->getContainer()) { this->getContainer()->removeChild(cell, true); } } m_pIndices->clear(); m_pCellsUsed->release(); m_pCellsUsed = new CCArrayForObjectSorting(); this->_updateCellPositions(); this->_updateContentSize(); if (m_pDataSource->numberOfCellsInTableView(this) > 0) { this->scrollViewDidScroll(this); } }
当你的数据源发生改变后,请调用reloadData。从中可以看出,他重新计算了与数据相关的操作。
3.4.2 refreshData
void CCTableView::refreshData() { int startIndex = 0; int endIndex = 0; // 取出当前可见的item的收尾索引 getStartEndIndex(startIndex, endIndex); // 只刷新看见的item //unsigned int uCountOfItems = m_pDataSource->numberOfCellsInTableView(this); for(unsigned int i = startIndex; i <= endIndex; ++i) { this->updateCellAtIndex(i); } }
区别是显而易见的,数据源没有发生改变,只是startIndex和endIndex发生了改变,并更新显示。
4.小结
1. CCTableView三基友:
CCTableViewCell,负责单个cell,含有唯一idx用于区别。
CCTableViewDataSource,负责数据源相关,包括数据个数,数据根据不同idx的获取,数据size等。
CCTableViewDelegate,负责delegate操作。
2.CCTableView不但继承了CCScrollView,同时也继承了CCScrollViewDelegate。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。