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。

cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论),布布扣,bubuko.com

时间: 2024-08-25 14:00:06

cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论)的相关文章

Tomcat源码分析之—具体启动流程分析

从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息.Catalina.base信息,在initClassLoaders方法中初始化类加载器,然后通过反射初始化org.apache.catalina.startup.Catalina作为catalina守护进程: 一.load Bootstrap中load流程: 反射调用Catalina的load方法

【E2LSH源码分析】E2LSH源码综述及主要数据结构

上一小节,我们对p稳定分布LSH的基本原理进行了介绍(http://blog.csdn.net/jasonding1354/article/details/38237353),在接下来的博文中,我将以E2LSH开源代码为基础,对E2LSH的源码进行注解学习,从而为掌握LSH的基本原理以及未来对相似性搜索的扩展学习打下基础. 1.代码概况 E2LSH的核心代码可以分为3部分: LocalitySensitiveHashing.cpp--主要包含基于LSH的RNN(R-near neighbor)数

cocos2d-x 源码分析 : control 源码分析 ( 控制类组件 controlButton)

源码版本来自3.1rc 转载请注明 cocos2d-x源码分析总目录 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 control的设计整体感觉挺美的,在父类control定义了整个控制事件的基础以及管理,虽然其继承了Layer,但其本身和UI组件的实现并没有关联.在子类(controlButton,controlSwitch,controlStepper等中实现不同的UI组件).下面通过源码来分析control与

8、SpringMVC源码分析(3):分析ModelAndView的形成过程

首先,我们还是从DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception方法开始,看看这个牛逼的ModelAndView是怎么开始的,又是怎么结束的: 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Except

Solr4.8.0源码分析(5)之查询流程分析总述

Solr4.8.0源码分析(5)之查询流程分析总述 前面已经写到,solr查询是通过http发送命令,solr servlet接受并进行处理.所以solr的查询流程从SolrDispatchsFilter的dofilter开始.dofilter包含了对http的各个请求的操作.Solr的查询方式有很多,比如q,fq等,本章只关注select和q.页面下发的查询请求如下:http://localhost:8080/solr/test/select?q=code%3A%E8%BE%BD*+AND+l

7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 这样的方式来执行request的handler方法. 先来分析一下ha.handle方法的调用过程:HandlerAdapter接口有一个抽象实现类AbstractHandlerMethodAdapter,在该抽象类中通过具体方法

Netty源码学习——EventLoopGroup原理:NioEventLoopGroup分析

类结构图: 不了解Executor接口原理的可以查看concurrent包中的api介绍,这里只介绍Netty中EventExecutorGroup的主要功能! 从类的结构图中可以看到EventExecutorGroup是直接继承ScheduledExecutorService这个接口的,为了说明白Group的原理这里顺便提一下ScheduledExecutorService的用途! java.util.concurrent.ScheduledExecutorService An Executo

Apache Spark源码走读之6 -- 存储子系统分析

欢迎转载,转载请注明出处,徽沪一郎. 楔子 Spark计算速度远胜于Hadoop的原因之一就在于中间结果是缓存在内存而不是直接写入到disk,本文尝试分析Spark中存储子系统的构成,并以数据写入和数据读取为例,讲述清楚存储子系统中各部件的交互关系. 存储子系统概览 上图是Spark存储子系统中几个主要模块的关系示意图,现简要说明如下 CacheManager  RDD在进行计算的时候,通过CacheManager来获取数据,并通过CacheManager来存储计算结果 BlockManager

qemu-kvm-1.1.0源码中关于迁移的代码分析

Description Businesses like to have memorable telephone numbers. One way to make a telephone number memorable is to have it spell a memorable word or phrase. For example, you can call the University of Waterloo by dialing the memorable TUT-GLOP. Some