Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程

时间:2023-02-07 21:41:24

1、首先进入ScrollView.h文件中,在声明ScrollView这个类之前先声明一个委托类,之所以称之为委托,因为它的命名中包含 delegate 这个词。如下:

[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. class ScrollView; //前向声明scrollView  
  2.   
  3. class ScrollViewDelegate  
  4. {  
  5. public:  
  6.     virtual ~ScrollViewDelegate() {}  
  7.   
  8.     virtual void scrollViewDidScroll(ScrollView* view) = 0;  
  9.     virtual void scrollViewDidZoom(ScrollView* view) = 0;  
  10. };  

可以看到在这个委托类的声明里有两个函数,一个是 scrollViewDidScroll() ,这是当scrollView在被拖动时会响应该函数;另一个是 scrollViewDidZoom ,我想应该是当scrollView在进行缩放时会响应该函数。

2、接下来就是声明ScrollView这个类了。 一开始我本能认为既然ScrollView要与ScrollViewDelegate这个类关联起来,那么它就应该继承ScrollViewDelegate吧?可实际上并没有, ScrollView只是单纯的继承一个Layer罢了 ,如下:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. class ScrollView : public Layer{};  
问题来了, ScrollView既然与ScrollViewDelegate没有“父子”关系,那么它是如何调用ScrollViewDelegate中的那些函数的呢? 我继续看下ScrollView的类声明。
发现有这么两个函数:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. ScrollViewDelegate* getDelegate() { return _delegate; }  
  2. void setDelegate(ScrollViewDelegate* pDelegate) { _delegate = pDelegate; }  
  3. ScrollViewDelegate* _delegate;  
这里比较容易理解, 先声明一个ScrollViewDelegate* 类型的成员变量,然后通过setDelegate() 与 getDelegate()分别设置与获取_delegate相应的对象

看到这里好像还不是很理解_delegate到底该怎么用,那起码有一些眉目了,毕竟ScrollViewDelegate已经浮出水面,不是那么神秘。继续往下看。

3、在ScrollView.cpp文件中,我开始找下_delegate这个成员变量都在哪里使用过。很快我在setContentOffset()这个函数中找到它的身影
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. void ScrollView::setContentOffset(Point offset, bool animated/* = false*/)  
  2. {  
  3.     if (animated)  
  4.     { //animate scrolling  
  5.         this->setContentOffsetInDuration(offset, BOUNCE_DURATION);  
  6.     }   
  7.     else  
  8.     {   
  9.     ...     ...  
  10.         if (_delegate != NULL)  
  11.         {  
  12.             _delegate->scrollViewDidScroll(this);   //就是这里。  
  13.         }  
  14.     }  
  15. }  
setContentOffset()这个函数大家应该都很熟悉了,就是通过它来设置scrollView的偏移的。我们从上面的代码可以看到每当我们调用到setContentOffset()时,只要_delegate这个变量不为空,那么都会调用下面这行代码:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. _delegate->scrollViewDidScroll(this);  
没错, ScrollView就是在这里调用到ScrollViewDelegate委托类中声明的函数scrollViewDidScroll,它的参数 this 也就是ScrollView这个类所指向的对象啦 ,我在这里就不多做解释啦。

知道了上面这些,接下来就好办了, 我们只要知道在ScrollView中哪里有调用到setContentOffset() 这个函数就可以了

4、我往下搜索 setContentOffset 这个关键词,发现有在好几个地方调用到,其中最关键的还是在onTouchMoved()这个触摸回调函数中使用到。
onTouchMoved我也不用再多说了,每当我们触摸屏幕拖动时都会响应该函数,下面看下它的缩减版代码:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. void ScrollView::onTouchMoved(Touch* touch, Event* event)  
  2. {  
  3.     if (!this->isVisible())  
  4.     {  
  5.         return;  
  6.     }  
  7.   
  8.     if (std::find(_touches.begin(), _touches.end(), touch) != _touches.end())  
  9.     {  
  10.         if (_touches.size() == 1 && _dragging)  
  11.         { // scrolling  
  12.             ...  
  13.               
  14.             if (frame.containsPoint(this->convertToWorldSpace(newPoint)))  
  15.             {  
  16.                 ...  
  17.                 this->setContentOffset(Point(newX, newY));//在这里调用到  
  18.             }  
  19.         }  
  20.         else if (_touches.size() == 2 && !_dragging)  
  21.         {  
  22.             const float len = _container->convertTouchToNodeSpace(_touches[0]).getDistance(  
  23.                                             _container->convertTouchToNodeSpace(_touches[1]));  
  24.             this->setZoomScale(this->getZoomScale()*len/_touchLength);//这里是调用与缩放相关的函数  
  25.         }  
  26.     }  
  27. }  

5、好了,说到这里一切都已经开始变得清晰了!现在我先整理下发型,然后简短的做个总结。
1) 首先在scrollView拖动过程中都会调用onTouchMoved()函数,然后再该函数中调用到到setContainOffset()这个函数,这个函数就是用来设置它的偏移位置的;
2) 在setContainOffset() 会调用到_delegate中的 scrollViewDidScroll()函数。
3) 为什么_delegate能有这么大的权力调用ScrollViewDelegate中的函数呢?原因就在于它是ScrollViewDelegate声明的,说直接点它就是ScrollViewDelegate的私生子!!!


6、下面我举个例子吧。

我先声明一个叫CoolStar的类,
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. class CoolStar : public Layer,public ScrollViewDelegate  
  2. {  
  3. public:  
  4.     ...  
  5.     bool init();  
  6.     CREATE_FUNC(CoolStar);  
  7.     ...   
  8.     //scroll 委托  
  9.     void scrollViewDidScroll(MyScrollView* view);  
  10.     void scrollViewDidZoom(MyScrollView* view);  
  11. }  
  12. CoolStar为什么要继承ScrollViewDelegate呢?别急,往下看init()函数的定义。  
  13. bool CoolStar::init()  
  14. {  
  15.     auto scroll_layer = Layer::create();  
  16.     ...  
  17.       
  18.     auto m_scroll = ScrollView::create(Size(...),scroll_layer);  
  19.     m_scroll->setDelegate(this);//看这里!!!  
  20.   
  21.     return true;  
  22. }  

上面我创建了一个scrollView,然后设置scrollView的委托指向当前类的对象,也就是this(看注释的地方)
而我们知道setDelegate()这个函数是有参数的,它的参数是一个指向ScrollViewDelegate委托类的指针, 如下:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. void setDelegate(ScrollViewDelegate* pDelegate);  
这就要求我们创建ScrollView的这个类必须是继承与ScrollViewDelegate,否则你就无法如此洒脱的执行下面这步了:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. m_scroll->setDelegate(this);  
恩,现在应该知道为什么CoolStar这个类为什么要继承与ScrollViewDelegate委托类了吧。
接下来定义两个scrollView的委托函数:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. void CoolStar::scrollViewDidScroll(MyScrollView* view)  
  2. {  
  3.     CCLOG("star is so cool");  
  4. }  
  5. void CoolStar::scrollViewDidZoom(MyScrollView* view)  
  6. {  
  7. }  

运行程序,发现每次拖动scrollView时控制台都会输出  "star is so cool" 这串字符串。


附:本文参加了CSDN博客大赛,亲如果觉得这篇文章不错,就大胆的来投上一票吧!!!http://vote.blog.csdn.net/Article/Details?articleid=34140469


尊重原创,转载请注明来源:http://blog.csdn.net/star530/article/details/34140469

//.h
#include "cocos2d.h"
#include <iostream>
USING_NS_CC;

class MyDelegate//自定义委托
{
public:
    virtual void onGameStart() = 0;
    virtual void onGameEnd() = 0;
};

class GameLayer : public Layer//定义游戏层
{
public:
    static cocos2d::Scene* createScene();//单例,创建游戏层
    virtual bool init();
    
    CREATE_FUNC(GameLayer);
    
    CC_SYNTHESIZE(MyDelegate*, delegator,Delegator);//CC_SYNTHESIZE宏的使用
};

class StatusLayer : public cocos2d::Layer, public MyDelegate//定义状态层
{
public:
    virtual bool init();
    CREATE_FUNC(StatusLayer);
    void onGameStart() override;//重载两个方法
    void onGameEnd() override;
};

//.cpp
#include "MyDelegate.h"
//初始化状态层
bool StatusLayer::init()
{
    return true;
}

void StatusLayer::onGameStart()
{
    log("GameStart");
}

void StatusLayer::onGameEnd()
{
    log("GameEnd");
}


//初始化游戏层
bool GameLayer::init()
{
    StatusLayer* status_layer = StatusLayer::create();//创建状态层
    this->setDelegator(status_layer);
    //通过游戏层的CC_SYNTHESIZE(MyDelegate*, delegator,Delegator);将delegator指向status_layer对象,delegator是MyDelegate类型指针,父类的指针或引用可以指向子类StatusLayer类型的子对象。通过多态的虚函数,动态联编执行status_layer的函数on_XXXX函数
    this->delegator->onGameStart();
    this->delegator->onGameEnd();
    return true;
}


在进入正题前我先简短的介绍下scrollView应该怎么用吧(想必大家也都会用~~):
1、记得在头文件里包含 “../extensions/cocos-ext.h",顺便声明下作用域:USING_NS_CC_EXT;
2、在类的继承里 加上ScrollViewDelegate,如下:

[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. class HelloWorld : public cocos2d::Layer,public ScrollViewDelegate  
3、在类的声明中, 加上三个scrollView的委托函数 ,如下:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. void scrollViewDidScroll(ScrollView* view);  
  2. void scrollViewDidZoom(ScrollView* view);  
  3. void scrollViewMoveOver(ScrollView* view);  
4、实在不想继续说废话了,直接看实例吧。

先看头文件:
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. #ifndef __HELLOWORLD_SCENE_H__  
  2. #define __HELLOWORLD_SCENE_H__  
  3.   
  4. #include "cocos2d.h"  
  5. #include "../extensions/cocos-ext.h"  
  6.   
  7. USING_NS_CC;  
  8. USING_NS_CC_EXT;  
  9.   
  10. class HelloWorld : public cocos2d::Layer,public ScrollViewDelegate  
  11. {  
  12. public:  
  13.     static cocos2d::Scene* createScene();//获取欢迎画面的Scene  
  14.     virtual bool init();    
  15.   
  16.     void menuCloseCallback(Ref* pSender);  
  17.       
  18.     CREATE_FUNC(HelloWorld);  
  19.   
  20.     //scroll 委托  
  21.     void scrollViewDidScroll(ScrollView* view);  
  22.     void scrollViewDidZoom(ScrollView* view);  
  23.     void scrollViewMoveOver(ScrollView* view);  
  24.   
  25. private:  
  26.     Vector<Sprite*> sp_vec;//声明一个容器  
  27. };  
  28. #endif // __HELLOWORLD_SCENE_H__  

下面看定义
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. bool HelloWorld::init()  
  2. {     
  3.     //首先创建scrollView  
  4.     auto scroll_layer = Layer::create();//创建scrollView中的容器层  
  5.     scroll_layer->setPosition(Point::ZERO);  
  6.     scroll_layer->setAnchorPoint(Point::ZERO);  
  7.     scroll_layer->setContentSize(Size(600,300));//设置容器层大小为(600,300)  
  8.   
  9.     auto scrollView = ScrollView::create(Size(400,300),scroll_layer);//创建scrollView,显示窗口大小为(400,300)  
  10.     scrollView->setDelegate(this);//设置委托  
  11.     scrollView->setDirection(ScrollView::Direction::HORIZONTAL);//设置滚动方向为水平  
  12.     scrollView->setPosition(Point(300,200));  
  13.     this->addChild(scrollView,2);  
  14.   
  15.     //创建三个对象  
  16.     auto boy = Sprite::create("boy.png");//没错,主角又是我们熟悉的那仨。多么温馨。  
  17.     boy->setPosition(Point(150,100));  
  18.     scroll_layer->addChild(boy,2);  
  19.   
  20.     auto girl = Sprite::create("girl_1.png");  
  21.     girl->setPosition(Point(300,100));  
  22.     scroll_layer->addChild(girl,2);   
  23.   
  24.     auto girl3 = Sprite::create("girl_3.png");  
  25.     girl3->setPosition(Point(450,100));  
  26.     scroll_layer->addChild(girl3,2);   
  27.    
  28.     sp_vec.pushBack(boy);//将三个对象放入容器中  
  29.     sp_vec.pushBack(girl);  
  30.     sp_vec.pushBack(girl3);  
  31.   
  32.        return true;  
  33. }  

接下来看下scrollView的委托函数,这里只要看scrollViewDidScroll 就好了。实现效果是对象在某个坐标范围内移动时会有缩放效果。
[cpp]  view plain copy Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程 Cocos2dx 小技巧(十五)话说ScrollView的delegate实现过程
  1. void HelloWorld::scrollViewDidScroll(ScrollView* view)  
  2. {  
  3.     //在scrollView拖动时响应该函数  
  4.   
  5.     auto offsetPosX = (view->getContentOffset()).x;//获得偏移X坐标(向右移动,偏移量为正数,向左则为负数)  
  6. //  CCLOG("offset pos is %f , %f",offsetPos.x,offsetPos.y);  
  7.   
  8.     //for 循环遍历容器中的每个精灵  
  9.     for( auto e : sp_vec )  
  10.     {  
  11.         auto pointX = e->getPositionX();//获得当前对象的X坐标(不管怎么滚动,这个坐标都是不变的)  
  12.         float endPosX = pointX + offsetPosX;//将精灵的 X坐标 + 偏移X坐标  
  13.   
  14.         //当endPosX在 150~250 范围,则对象的大小从左向右递增  
  15.         if( endPosX > 150 && endPosX < 250 )  
  16.         {  
  17.             float x = endPosX / 150;//放大倍数为 endPosX / 150;  
  18.             e->setScale(x);  
  19.             CCLOG("x = %f",x);  
  20.         }  
  21.         //当endPosX在 250~350 范围,则对象的大小从左向右递减  
  22.         else if( endPosX > 250 && endPosX < 350 )   
  23.         {  
  24.             //下面这个公式不好解释,我就这么说吧:  
  25.             //假设 endPosX = 200,那么放大倍数应该是 200 / 150 = 1.33左右,那么当endPosX = 300时,出于对称的原理,我们以250为对称中心,那么  
  26.             //300 的放大倍数也应该是 1.33。这就是下面的公式由来  
  27.             float a = endPosX - 250;  
  28.             float b = 250 - a;  
  29.   
  30.             float x = b / 150;  
  31.             e->setScale(x);  
  32.         }  
  33.         else   
  34.         {  
  35.             //不是在上面的范围,则设置为正常大小  
  36.             e->setScale(1.0f);  
  37.         }  
  38.     }     
  39. }  
  40. void HelloWorld::scrollViewDidZoom(ScrollView* view)  
  41. {  
  42.     //do something  
  43. }  
  44. void HelloWorld::scrollViewMoveOver(ScrollView* view)  
  45. {  
  46.     //do something  
  47. }  

恩,注释写的很清楚啦,但我还是要稍微补充一些东东:我们应该知道,对象放到滚动层上(如scroll_layer->addChild(boy)),那么不管对象在scrollView上如何移动,它获得的坐标都是不会变的(如boy->getPosition()是不变的数值),这种情况下,如果我们想实现对象在某个坐标范围内会有缩放效果,那么只是去获取对象的坐标肯定是行不通的,所以肯定要找一个时刻在变化的”参照物”来利用下,该找什么呢?没错,就是scrollView的偏移坐标(scrollView->getContentOffset())!只要scrollView移动一下,那么它的 偏移量也随之改变。我这里就是利用对象的坐标与scrollView的偏移坐标之间不可告人的秘密,从而实现当前的目的。

下面看下运行效果。


另一个代码示例

//.h
#include "cocos2d.h"
#include "../extensions/cocos-ext.h"
USING_NS_CC_EXT;
USING_NS_CC;

class CCScrollView : public cocos2d::Layer,public ScrollViewDelegate
{
public:
    CREATE_FUNC(CCScrollView);
    virtual bool init();
    static cocos2d::Scene* createScene();

    void scrollViewDidScroll(ScrollView* view);
    void scrollViewDidZoom(ScrollView* view);
    void scrollViewMoveOver(ScrollView* view);

    void menuCloseCallback(Ref* pSender);
private:
    Vector<Sprite*> sp_vec;
};

//.cpp

#include "CCScrollView.h"

Scene* CCScrollView::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();
    
    // 'layer' is an autorelease object
    auto layer = CCScrollView::create();
    
    // add layer as a child to scene
    scene->addChild(layer);
    
    // return the scene
    return scene;
}

bool CCScrollView::init()
{
    bool bRet = false;
    do{
        CC_BREAK_IF(!Layer::init());
        
        //首先创建scrollView的容器
        auto scroll_layer = Layer::create();
        scroll_layer->setPosition(Vec2::ZERO);
        scroll_layer->setAnchorPoint(Vec2::ZERO);
        scroll_layer->setContentSize(Size(600,300));//设置容器的大小
        scroll_layer->setColor(Color3B::WHITE);
        
        auto scrollView = ScrollView::create(Size(400,300),scroll_layer);
        scrollView->setDelegate(this);//设置委托
        scrollView->setDirection(ScrollView::Direction::HORIZONTAL);
        scrollView->setPosition(Vec2(300, 200));
        this->addChild(scrollView,2);
        
        //创建三个对象
        auto boy = Sprite::create("boy.png");
        boy->setPosition(Vec2(150, 100));
        scroll_layer->addChild(boy);
        
        auto girl1 = Sprite::create("girl1.png");
        girl1->setPosition(Vec2(300, 100));
        scroll_layer->addChild(girl1);

        auto girl2 = Sprite::create("girl2.png");
        girl2->setPosition(Vec2(450, 100));
        scroll_layer->addChild(girl2);
        
        sp_vec.pushBack(boy);
        sp_vec.pushBack(girl1);
        sp_vec.pushBack(girl2);
        
        bRet = true;
    }while(0);
    return bRet;
}

void CCScrollView::scrollViewDidScroll(ScrollView* view)
{
    //在ScrollView拖动时响应该函数
    auto offsetPosX = (view->getContentOffset()).x;//获得偏移X坐标,向右为正,向左为负
    auto offsetPos = view->getContentOffset();
    log("offset pos is %f,%f",offsetPos.x,offsetPos.y);
    
    for(auto e : sp_vec)
    {
        auto pointX = e->getPositionX();//获得当前对象的X坐标(不管怎么滚动,这个坐标都是不变的)
        float endPosX = pointX + offsetPosX;//将精灵X坐标+偏移X坐标
        
        //当endPosX在150~250范围,则对象的大小从左向右递增
        if(endPosX > 150 && endPosX < 250)
        {
            float x = endPosX / 150;//放大的倍数x
            e->setScale(x);
            log("x=%f",x);
        }
        //当endPosX在250~350之间,则对象的大小从左向右递减
        else if(endPosX > 250 && endPosX < 350)
        {
            float a = endPosX - 250;
            float b = 250 - a;
            float x = b/150;
            e->setScale(x);
        }
        else
        {
            e->setScale(1.0f);
        }
        
    }
}

void CCScrollView::scrollViewDidZoom(cocos2d::extension::ScrollView *view)
{
    log("scrollViewDidZoom");
}
void CCScrollView::scrollViewMoveOver(cocos2d::extension::ScrollView *view)
{
    log("scrollViewMoveOver");
}