首页 > 代码库 > cocos2d-x CCScrollView 源代码分析

cocos2d-x CCScrollView 源代码分析

版本号源代码来自2.x,转载请注明

另我实现了能够循环的版本号http://blog.csdn.net/u011225840/article/details/31354703

1.继承树结构


技术分享
能够看出,CCScrollView本质是CCLayer的一种,具备层的一切属性和方法。关于CCLayer的源代码分析,兴许会有。

2.重要的成员

 1.  CCScrollViewDelegate* m_pDelegate;
cocos2d-x中,运用了非常多delegate这样的模式。

以下简单的说明下delegate这样的模式。

(至于delegate与proxy的差别,请先參考下headfirst中的proxy三种情况,然后能够google差别。这里不再赘述。)

XXXDelegate中封装了接口(c++中的实现就是虚函数与必须实现的纯虚函数),类A中存在某些方法,比方说View中的getDataNum,View会依据数据的多少来确定界面的显示方式。可是A与数据并没有直接的关联。于是乎。在View A中的getDataNum会调用A内部的DataDelegate的方法来获取数据的多少。至于这个数据详细是什么,仅仅须要实现一个DataDelegate的详细类就可以。这样。View与数据的耦合度就很低。View仅仅依赖抽象的DataDelegate。
在兴许的源代码分析中。能够看出delegate的妙处。


3.源代码解析

3.1 ccTouchBegan


对于ccTouchBegan中的重要部分,我加入了凝视。能够通过代码看出。CCscrollView支持单点和双点触摸。
bool CCScrollView::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
    if (!this->isVisible() || !m_bCanTouch)
    {
        return false;
    }
    
    CCRect frame = getViewRect();

    //dispatcher does not know about clipping. reject touches outside visible bounds.
	/*
	1. ccScrollView仅仅同意至多两个触摸点。多于两个后将不会觉得发成了触摸。
	2. 当CCScrollView处于移动状态时,在此状态下新发生触摸将不会被觉得发生。
	3.注意frame不是当前的尺寸。而是当前ViewSize的frame,也就是触摸点必须在显示的Rect内才会认定为触摸(能够通过setViewSize来设置大小)
   */
    if (m_pTouches->count() > 2 ||
        m_bTouchMoved          ||
        !frame.containsPoint(m_pContainer->convertToWorldSpace(m_pContainer->convertTouchToNodeSpace(touch))))
    {
		m_pTouches->removeAllObjects();
        return false;
    }

    if (!m_pTouches->containsObject(touch))
    {
        m_pTouches->addObject(touch);
	}
	//CCLOG("CCScrollView::ccTouchBegan %d", m_pTouches->count());
	/*
	 当触摸点为1的时候,设置单点触摸的属性。

尤其是m_bDragging属性表示触摸行为是拖动 */ if (m_pTouches->count() == 1) { // scrolling m_tTouchPoint = this->convertTouchToNodeSpace(touch); m_bTouchMoved = false; m_bDragging = true; //dragging started m_tScrollDistance = ccp(0.0f, 0.0f); m_fTouchLength = 0.0f; } /* 当触摸点个数为2时,设置双点触摸的属性 */ else if (m_pTouches->count() == 2) { m_tTouchPoint = ccpMidpoint(this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)), this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1))); m_fTouchLength = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)), m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1))); m_bDragging = false; } return true; }


3.2 ccTouchMoved

同上。代码里面加了凝视
void CCScrollView::ccTouchMoved(CCTouch* touch, CCEvent* event)
{
	
    if (!this->isVisible())
    {
        return;
    }

	/*
		假设此时不同意滚动,则退出。

这个能够通过set函数设置。默觉得false */ if(this->m_bScrollLock) { return; } if (m_pTouches->containsObject(touch)) { /* 啊哦,好玩的来咯。

滚动状态时 */ if (m_pTouches->count() == 1 && m_bDragging) { // scrolling CCPoint moveDistance, newPoint, maxInset, minInset; CCRect frame; float newX, newY; frame = getViewRect(); //获得当前点的坐标,而且获得当前点与上一次触碰点的距离(moveDistance也是CCPoint,x与y是当前点与上一点的x距离,y距离) newPoint = this->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)); moveDistance = ccpSub(newPoint, m_tTouchPoint); float dis = 0.0f; //假设有方向的限定,依据方向限定获取相应的距离 if (m_eDirection == kCCScrollViewDirectionVertical) { dis = moveDistance.y; } else if (m_eDirection == kCCScrollViewDirectionHorizontal) { dis = moveDistance.x; } else { dis = sqrtf(moveDistance.x*moveDistance.x + moveDistance.y*moveDistance.y); } //假设移动距离过短,则不推断发生了移动 if (!m_bTouchMoved && fabs(convertDistanceFromPointToInch(dis)) < MOVE_INCH ) { //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); return; } //第一次移动。则将moveDistance置0 if (!m_bTouchMoved) { moveDistance = CCPointZero; } m_tTouchPoint = newPoint; m_bTouchMoved = true; //点必须在viewRect内部 if (frame.containsPoint(this->convertToWorldSpace(newPoint))) { //依据能够移动的direction来设置moveDistance switch (m_eDirection) { case kCCScrollViewDirectionVertical: moveDistance = ccp(0.0f, moveDistance.y); break; case kCCScrollViewDirectionHorizontal: moveDistance = ccp(moveDistance.x, 0.0f); break; default: break; } //这个版本号无用啊。。

。。

maxInset = m_fMaxInset; minInset = m_fMinInset; //获取容器的新坐标,注意是容器哦 newX = m_pContainer->getPosition().x + moveDistance.x; newY = m_pContainer->getPosition().y + moveDistance.y; //滚动的CCPoint矢量设置 m_tScrollDistance = moveDistance; this->setContentOffset(ccp(newX, newY)); } } //双点触摸时。效果是缩放。len是双点触摸每次移动时的距离。 //而m_fTouchLength是双点開始时的距离,会依据move过程中距离与初始距离的比例进行缩放 else if (m_pTouches->count() == 2 && !m_bDragging) { const float len = ccpDistance(m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(0)), m_pContainer->convertTouchToNodeSpace((CCTouch*)m_pTouches->objectAtIndex(1))); this->setZoomScale(this->getZoomScale()*len/m_fTouchLength); } } }

单点触摸时。调用了一个函数叫setContentOffset。以下继续分析contentOffset。

setContentOffset

void CCScrollView::setContentOffset(CCPoint offset, bool animated/* = false*/)
{
	//默认情况,不做处理
    if (animated)
    { //animate scrolling
        this->setContentOffsetInDuration(offset, BOUNCE_DURATION);
    } 
	//好玩的东西哦
    else
    { //set the container position directly
		//是否做越界处理。什么是越界。就是当你拖动整个容器时,假设已经到了容器的边界。还能不能再拖动,能够通过set函数进行设置
        if (!m_bBounceable)
        {
            const CCPoint minOffset = this->minContainerOffset();
            const CCPoint maxOffset = this->maxContainerOffset();
            
            offset.x = MAX(minOffset.x, MIN(maxOffset.x, offset.x));
            offset.y = MAX(minOffset.y, MIN(maxOffset.y, offset.y));

			
        }
		//CCLOG("The offset x is %f , y is %f",offset.x,offset.y);
        m_pContainer->setPosition(offset);

		//伟大的delegate来了。当你在滚动过程中想做除了基本界面滚动的额外操作时,请依据自己的不同情况。实现该delegate~完美的依赖抽象的设计。nice
        if (m_pDelegate != NULL)
        {
            m_pDelegate->scrollViewDidScroll(this);   
        }
    }
}

双点触摸时,调用了一个重要的函数setZoomScale。

setZoomScale

void CCScrollView::setZoomScale(float s)
{
    if (m_pContainer->getScale() != s)
    {
        CCPoint oldCenter, newCenter;
        CCPoint center;
        //设置缩放中心
        if (m_fTouchLength == 0.0f) 
        {
            center = ccp(m_tViewSize.width*0.5f, m_tViewSize.height*0.5f);
            center = this->convertToWorldSpace(center);
        }
        else
        {
            center = m_tTouchPoint;
        }
        //缩放后中心的位置相对于world坐标系会产生offset,这里将offset进行计算
        oldCenter = m_pContainer->convertToNodeSpace(center);
        m_pContainer->setScale(MAX(m_fMinScale, MIN(m_fMaxScale, s)));
        newCenter = m_pContainer->convertToWorldSpace(oldCenter);
        
        const CCPoint offset = ccpSub(center, newCenter);
		//delegate的重新出现
        if (m_pDelegate != NULL)
        {
            m_pDelegate->scrollViewDidZoom(this);
        }
		//将产生的offset进行处理
        this->setContentOffset(ccpAdd(m_pContainer->getPosition(),offset));
    }
}

3.3 ccTouchEnded

能坚持看到这里,已经能够看见胜利的曙光了。曙光。。

void CCScrollView::ccTouchEnded(CCTouch* touch, CCEvent* event)
{
    if (!this->isVisible())
    {
        return;
    }
	//将touch从pTouches中移除
    if (m_pTouches->containsObject(touch))
    {
		//当剩下一个touch时。须要在每一帧调用方法deaccelerateScrolling
        if (m_pTouches->count() == 1 && m_bTouchMoved)
        {
            this->schedule(schedule_selector(CCScrollView::deaccelerateScrolling));
        }
		m_pTouches->removeObject(touch);
		//CCLOG("CCScrollView::ccTouchEnded %d", m_pTouches->count());
		//m_pDelegate->scrollViewDidStop(this);
    } 
	//没有touch时,须要设置状态
    if (m_pTouches->count() == 0)
    {
        m_bDragging = false;    
        m_bTouchMoved = false;
    }
}

deaccelerateScrolling

在这个函数中,有一个地方没有理解。求大神指点

void CCScrollView::deaccelerateScrolling(float dt)
{
	//假设刚好在帧開始前 又有一个触摸点发生了began。造成了滚动状态,则取消并返回
    if (m_bDragging)
    {
        this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));
        return;
    }
    
	//好玩的东西来咯
	
    float newX, newY;
    CCPoint maxInset, minInset;
    CCLOG("The end distance is %f",m_tScrollDistance.x);
	//这里我不清楚为啥要出来,我用输出发如今move中。已经将此offset设置过了,不知为何还要设置,求大神解答。
    m_pContainer->setPosition(ccpAdd(m_pContainer->getPosition(), m_tScrollDistance));
    
	//是否同意越界,获得的inset信息
    if (m_bBounceable)
    {
        maxInset = m_fMaxInset;
        minInset = m_fMinInset;
    }
    else
    {
        maxInset = this->maxContainerOffset();
        minInset = this->minContainerOffset();
    }
    
    //check to see if offset lies within the inset bounds
    newX     = MIN(m_pContainer->getPosition().x, maxInset.x);
    newX     = MAX(newX, minInset.x);
    newY     = MIN(m_pContainer->getPosition().y, maxInset.y);
    newY     = MAX(newY, minInset.y);
    
    newX = m_pContainer->getPosition().x;
    newY = m_pContainer->getPosition().y;
    
    m_tScrollDistance     = ccpSub(m_tScrollDistance, ccp(newX - m_pContainer->getPosition().x, newY - m_pContainer->getPosition().y));
    m_tScrollDistance     = ccpMult(m_tScrollDistance, SCROLL_DEACCEL_RATE);
    this->setContentOffset(ccp(newX,newY));
    
    if ((fabsf(m_tScrollDistance.x) <= SCROLL_DEACCEL_DIST &&
         fabsf(m_tScrollDistance.y) <= SCROLL_DEACCEL_DIST) ||
        newY > maxInset.y || newY < minInset.y ||
        newX > maxInset.x || newX < minInset.x ||
        newX == maxInset.x || newX == minInset.x ||
        newY == maxInset.y || newY == minInset.y)
    {
        this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));
		//越界动画。从越界部分慢慢移动到不越界状态的函数。

this->relocateContainer(true); //伟大的delegate。。。 m_pDelegate->scrollViewDidStop(this); } }


relocateContainer


void CCScrollView::relocateContainer(bool animated)
{
	//这个函数将容器从当前地方通过动画移动到玩家自己设置的同意偏移的地方
    CCPoint oldPoint, min, max;
    float newX, newY;
    //偏移值自己能够设置
    min = this->minContainerOffset();
    max = this->maxContainerOffset();
    
    oldPoint = m_pContainer->getPosition();

    newX     = oldPoint.x;
    newY     = oldPoint.y;
    if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionHorizontal)
    {
        newX     = MAX(newX, min.x);
        newX     = MIN(newX, max.x);
    }

    if (m_eDirection == kCCScrollViewDirectionBoth || m_eDirection == kCCScrollViewDirectionVertical)
    {
        newY     = MIN(newY, max.y);
        newY     = MAX(newY, min.y);
    }
	//还是调用setContentOffset,可是须要动画
    if (newY != oldPoint.y || newX != oldPoint.x)
    {
        this->setContentOffset(ccp(newX, newY), animated);
    }
}

setContentOffsetInDuration

void CCScrollView::setContentOffsetInDuration(CCPoint offset, float dt)
{
    CCFiniteTimeAction *scroll, *expire;
    //滚动的偏移动画
    scroll = CCMoveTo::create(dt, offset);
	//滚动完毕后的动画(负责停止performedAnimatedScroll。而且调用delegate)
    expire = CCCallFuncN::create(this, callfuncN_selector(CCScrollView::stoppedAnimatedScroll));
    m_pContainer->runAction(CCSequence::create(scroll, expire, NULL));
	//负责不停调用delegate
    this->schedule(schedule_selector(CCScrollView::performedAnimatedScroll));
}


4.小结

看到这里,CCScrollView里面属于自己独有部分的东西基本已经看完。

能够看出:

1.CCScrollView支持两种操作,滚动和缩放。
2.CCScrollView通过delegate将数据与界面解耦。
3.CCScrollView本质是一个CClayer,他展示的是自己内部的container。而且CCScrollView的触摸以及展示是依据ViewSize 还不是本身的SIze决定的。


cocos2d-x CCScrollView 源代码分析