您的位置:首页 > 移动开发 > Cocos引擎

cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论)

2017-08-17 15:01 585 查看


原文地址: http://blog.csdn.net/u011225840/article/details/30032379



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中实现)

[cpp] view
plain copy

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。
(在文章的最后,我会给出一个例子)。

[cpp] view
plain copy

//根据不同的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。
下面来看第二个函数。

[cpp] view
plain copy

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

[cpp] view
plain copy

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赋值。

[cpp] view
plain copy

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方法。)

[cpp] view
plain copy

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方法。

[cpp] view
plain copy

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

[cpp] view
plain copy

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

[cpp] view
plain copy

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

[cpp] view
plain copy

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,会造成性能瓶颈。

[cpp] view
plain copy

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);

}

[cpp] view
plain copy

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

[cpp] view
plain copy

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

[cpp] view
plain copy

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

[cpp] view
plain copy

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

[cpp] view
plain copy

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

[cpp] view
plain copy

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cocos2d-x源码分析