遊戲人生(二),有一種東西叫CCScrollView

搞過app或者game'的一定不會陌生:CCScrollView還有CCTableView。如果不知道什麼是CCScrollView,那麼去看看testcpp或者testlua中的例子吧。

先說說CCScrollView:.cpp和.h文件在\cocos2d-x-2.2.3\extensions\GUI\目錄下,所以要使用CCScrollView首先要引入頭文件#include "cocos-ext.h",以及空間名USING_NS_CC_EXT; 如果頭文件報錯,那麼請檢查工程的配置,這裏就不多說了無非是引用,link的輸入和c/c++裏面的附加包含目錄。(ps:如果是個簡單的工程個人建議你重新建個新工程)。

#include "cocos-ext.h"
USING_NS_CC_EXT;
void HelloWorld::initScroll(){
  CCSize size = CCDirector::sharedDirector()->getWinSize();
  CCScrollView* scroll = CCScrollView::create(size); //scroll->setViewSize(size);
  scroll->setDirection(kCCScrollViewDirectionVertical);  //設置滾動方向
  CCSprite* p1 = CCSprite::create("HelloWorld.png");
  p1->ignoreAnchorPointForPosition(true);
  CCSprite* p2 = CCSprite::create("HelloWorld.png");
  p2->setPosition(ccp(0,size.height));
  p2->ignoreAnchorPointForPosition(true);
  CCLayer* conLayer = CCLayer::create();
  conLayer->addChild(p1);
  conLayer->addChild(p2);
  scroll->setContainer(conLayer);
  scroll->setContentSize(CCSizeMake(size.width,320*2));
  this->addChild(scroll);
}
運行以上代碼,我們就可以使用Scrollview了,總結起來就是

1、create scrollview 

2、create Container層

3、將內容add到containner層

4、container層add到scrollview,scrollview加入指定layer或者scene


這裏有幾個點需要注意:

1、setContentSize不是scrollview大小,而是可滑動面的大小,viewSize是滑動面大小。

2、setContentSize不能放在setContainer前面,如果放在前面,那麼這個滑動後回自動回到第一頁。

3、container層中的內容position需要手動設定

4、如果不create container層,那麼CCScrollView會給你創建個一個,但是直接在scrollview下add多個node只會保留一個,原因在後面分析。



好了,最容易的部分寫完了,下面分析一下CCScrollView的源碼和一些讓人蛋疼的東西。

CCScrollView::CCScrollView()
: m_fZoomScale(0.0f)
, m_fMinZoomScale(0.0f)
, m_fMaxZoomScale(0.0f)
, m_pDelegate(NULL)			   //view管理器
, m_eDirection(kCCScrollViewDirectionBoth)  //滾動類型
, m_bDragging(false)
, m_pContainer(NULL)                        //container層
, m_bTouchMoved(false)
, m_bBounceable(false)                      //彈性開關
, m_bClippingToBounds(false)                //彈性開關
, m_fTouchLength(0.0f)
, m_pTouches(NULL)
, m_fMinScale(0.0f)
, m_fMaxScale(0.0f)
{

}
CCScrollView* CCScrollView::create(CCSize size, CCNode* container/* = NULL*/)
{
    CCScrollView* pRet = new CCScrollView();
    if (pRet && pRet->initWithViewSize(size, container))
    {
        pRet->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(pRet);
    }
    return pRet;
}
bool CCScrollView::initWithViewSize(CCSize size, CCNode *container/* = NULL*/)
{
    if (CCLayer::init())//scrollview繼承CCLayer
    {
        m_pContainer = container;
        
        if (!this->m_pContainer)
        {
        m_pContainer = CCLayer::create();//create container層
            this->m_pContainer->ignoreAnchorPointForPosition(false);
            this->m_pContainer->setAnchorPoint(ccp(0.0f, 0.0f));
        }

        this->setViewSize(size);//設置視圖尺寸,如果使用CCScrollView::create() 引擎會設置 size 爲 CCSizeMake(200, 200)


        setTouchEnabled(true); //scrollview 自然是要有觸摸的
        m_pTouches = new CCArray();//觸點 array
        m_pDelegate = NULL;
        m_bBounceable = true;    //默認彈性開啓
        m_bClippingToBounds = true;
        //m_pContainer->setContentSize(CCSizeZero);
        m_eDirection  = kCCScrollViewDirectionBoth;  //默認both滾動模式
        m_pContainer->setPosition(ccp(0.0f, 0.0f));
        m_fTouchLength = 0.0f;
        
        this->addChild(m_pContainer);
        m_fMinScale = m_fMaxScale = 1.0f;  //默認放縮1.0
        m_mapScriptHandler.clear();  //清理回調列表
        return true;
    }
    return false;
}

我們補習一下滾動模式,CCScrollView中定義了一個枚舉類型來表示滾動模式

typedef enum {
    kCCScrollViewDirectionNone = -1,   //不滾動
    kCCScrollViewDirectionHorizontal = 0,  //橫向滾動
    kCCScrollViewDirectionVertical,   //豎向滾動
    kCCScrollViewDirectionBoth   //雙向滾動
} CCScrollViewDirection;

初始化就先說到這,在看看屬性設置上一些東西

void CCScrollView::setContainer(CCNode * pContainer)
{
    // Make sure that 'm_pContainer' has a non-NULL value since there are
    // lots of logic that use 'm_pContainer'.
    if (NULL == pContainer)
        return;
    this->removeAllChildrenWithCleanup(true);  //這個就是多次使用setContainer卻只能顯示一個sprite的原因
    this->m_pContainer = pContainer //如果setContentSize在setContainer之前,實際上設置的size給引擎自帶的container,而不是目標container
    this->m_pContainer->ignoreAnchorPointForPosition(false);
    this->m_pContainer->setAnchorPoint(ccp(0.0f, 0.0f));
    this->addChild(this->m_pContainer);
    this->setViewSize(this->m_tViewSize);
}
void CCScrollView::setViewSize(CCSize size)
{
    m_tViewSize = size;
    CCLayer::setContentSize(size); //這裏不是container的size,而是scrollview的size
}
void CCScrollView::setContentSize(const CCSize & size)
{
    if (this->getContainer() != NULL)
    {
        this->getContainer()->setContentSize(size); //這裏纔是設置container的size
        this->updateInset();                        //設置彈性狀態下虛擬邊界值。
    }
}
/**雖然setcontainer只能用一次,但是addchild卻可以滿足我們的願望。
 * make sure all children go to the container
 */
void CCScrollView::addChild(CCNode * child, int zOrder, int tag)
{
    child->ignoreAnchorPointForPosition(false);
    child->setAnchorPoint(ccp(0.0f, 0.0f));
    if (m_pContainer != child) {
        m_pContainer->addChild(child, zOrder, tag);
    } else {
        CCLayer::addChild(child, zOrder, tag);
    }
}

屬性設置完了,顯示是沒問題了,不過scrollview到底是如何滾動的?

首先:
    virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
    virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
    virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);

代碼太多,這裏就不全貼出來了,有興趣可以自己去看源碼。讓我們把主要代碼摘出來

void CCScrollView::setContentOffset(CCPoint offset, bool animated/* = false*/) //此方法在ccTouchMoved中調用,功能是將container移動到指定位置
{
    if (animated)
    { //animate scrolling
        this->setContentOffsetInDuration(offset, BOUNCE_DURATION);//將container移動到指定位置,BOUNCE_DURATION=0.15f,移動時間
    } 
    else
    { //set the container position directly
        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));
        }


        m_pContainer->setPosition(offset);


        if (m_pDelegate != NULL)
        {
            m_pDelegate->scrollViewDidScroll(this);   //回調虛函數,由子類override。lua中可以註冊該函數的回調
        }
    }
}
void CCScrollView::setContentOffsetInDuration(CCPoint offset, float dt) //好了,動畫來了。
{
    CCFiniteTimeAction *scroll, *expire;
    scroll = CCMoveTo::create(dt, offset);
    expire = CCCallFuncN::create(this, callfuncN_selector(CCScrollView::stoppedAnimatedScroll));
    m_pContainer->runAction(CCSequence::create(scroll, expire, NULL));
    this->schedule(schedule_selector(CCScrollView::performedAnimatedScroll));  
}
void CCScrollView::deaccelerateScrolling(float dt) //該方法是在ccTouchEnded中的主要內容 ,當滑動停止的時候,container還會移動一段距離。
    if (m_bDragging) //拖拽的話該方法不會多次循環調用
    {
        this->unschedule(schedule_selector(CCScrollView::deaccelerateScrolling));
        return;
    }
    float newX, newY;
    CCPoint maxInset, minInset;
    
    m_pContainer->setPosition(ccpAdd(m_pContainer->getPosition(), m_tScrollDistance));
    
    if (m_bBounceable)            //彈性設置邊界,該值>設置containerSize值。
    {
        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); //重新設置locate,邊界彈性
    }
}
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);
    }


    if (newY != oldPoint.y || newX != oldPoint.x)
    {
       this->setContentOffset(ccp(newX, newY), animated);
    }
}
好了,到這裏基本上大體就算一站了,還有些細節以及放縮就不在細說。那麼我們搞了這麼久究竟是要幹什麼?很簡單,我們希望滑動可以自動匹配圖片的大小。怎麼做那?

方法一:修改源碼:

1、添加一個成員變量,記錄初始滑動位置。

2、添加成員變量:圖片匹配size

3、修改relocateContainer,max,min取最近符合要求的座標,setContentOffset or setContentOffsetInDuration即可

原理就是:

  1. touch開始的時候記錄初始位置,在touch結束的時候, 獲取結束時刻的時間和位置. 通過滑動距離等判斷是否已經翻頁,然後設置最終目標座標。
    • 方法二:自定義控件,繼承CCScrollView,在ccTouchEnded末尾調用自定義函數,實現以上功能。

    • 如果是lua開發,我推薦第一中方法,快速,修改相對少些,不用新增pkg文件。如果是c++的話,還是繼承的好。
    • 試着修改下源碼,不僅有利於學習,而且有快感!!!












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章