cocos2d-x 模态窗口 CCTableView 数字滚动盘 数字密码解锁



效果:
可以拖动两个密码盘滚动,数字总是可停靠中间,如上图那样。数字滚动可以0~9循环,默认密码是22,当两个密码盘都滚动到数字2时,密码盘消失,显示解锁成功,密码盘不再出现。触摸密码盘以外的地方,密码盘消失,表示取消解锁。取消解锁后,再次触摸屏幕打开密码盘。

涉及知识:
模态窗口在cocos2d-x中的实现,触摸事件传递的优先级
CCTableView 的使用,相关调度机制

顺序:
先介绍:NumPadlockLoopLayer,负责数字密码盘的创建,拖动效果,选择操作等行为
再介绍:HelloWorldScene,程序主界面,负责加载数字密码盘,管理密码盘的销毁,及解密成功后的提示


/
//首先来看看数字滚动盘的头文件结构:NumPadlockLoopLayer.h
/
#ifndef NumPadlockLoopDemo_NumPadlockLoopLayer_h
#define NumPadlockLoopDemo_NumPadlockLoopLayer_h

#define NumKeyWidth 220   //单个数字的宽度,像素
#define NumKeyHeight 220 //单个数字的高度,像素
#define NumKeyHeightHalf 110 //单个数字的一般高度
#define NumKeyCount 10  //数字的个数 0~9 共10个
#define DataItemsCount 1000 //密码盘中数据总长度 1000 个数字

#include "cocos2d.h"
#include "cocos-ext.h" //CCTableView这个列表控件所在头文件

class HelloWorld;  //添加数字滚动盘的主页面

class NumPadlockLoopLayer
: public cocos2d::CCLayerColor //数字密码盘继承基类
, public cocos2d::extension::CCTableViewDataSource //CCTableView初始话需要的实现的接口
, public cocos2d::extension::CCTableViewDelegate //CCTableView事件处理需要实现的接口
{
public:
    NumPadlockLoopLayer();
    virtual ~NumPadlockLoopLayer();
    virtual bool init();
    //初始化数字密码锁的创建
    virtual bool onInitDialog();

    //注册触摸接收
    virtual void onEnter();
    //注销触摸接收
    virtual void onExit();
    
    //打开下面注释,可以观察CCTableView的滚动坐标特征,有助于理解实现类中坐标的计算
    //默认每次滚动都会调用此回调函数
    virtual void scrollViewDidScroll(cocos2d::extension::CCScrollView* view){ 
        //cocos2d::CCPoint offset = view->getContentOffset();
        //CCLOG("CCScrollView offset.x=%f,offset.y=%f",offset.x,offset.y);
    }
    virtual void scrollViewDidZoom(cocos2d::extension::CCScrollView* view){}
    
    //处理密码盘触摸按下事件,可以计算点击的是哪一个子项
    virtual void tableCellTouched(cocos2d::extension::CCTableView* table, cocos2d::extension::CCTableViewCell* cell);
    //每一项的宽度和高度
    virtual cocos2d::CCSize cellSizeForTable(cocos2d::extension::CCTableView *table);
    //生成列表每一项的内容
    virtual cocos2d::extension::CCTableViewCell* tableCellAtIndex(cocos2d::extension::CCTableView *table, unsigned int idx);
    //一共多少项
    virtual unsigned int numberOfCellsInTableView(cocos2d::extension::CCTableView *table);
     
    // 触摸事件监听回调
    virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    virtual void ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    virtual void ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    virtual void ccTouchCancelled(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    
    CC_SYNTHESIZE(HelloWorld *, m_pHelloWorldLayer, HelloWorld); 
    
    CREATE_FUNC(NumPadlockLoopLayer);
private:
    cocos2d::CCTexture2D *m_pNumKeysTexture;//用于生成数字锁的数字纹理
    
    cocos2d::extension::CCTableView* tableView1;//第一列数字锁
    cocos2d::extension::CCTableView* tableView2;//第二列数字锁

    bool isTableView1Touched=false; //第一列数字锁是否别接受触屏消息
    bool isTableView2Touched=false; //第二列数字锁是否别接受触屏消息
    
    bool isNumPadlockLoopTouched;//两个数字锁是否至少有一个接受了触屏消息
    
    //调整接受触摸消息的数字锁的数字位置校准,使得被选中的数字总是垂直居中
    void adjustScrollView();
    //根据数字锁当前滚动到的位置坐标,计算当前选中的数字是哪一个
    int findCurrentSelectedIndex(cocos2d::CCPoint tableViewOffSet);
    //检测两个数字锁 选中的数字 是否与密码一直
    bool checkpwd(cocos2d::CCPoint tab1,cocos2d::CCPoint tab2);
    //解密成功展示结果
    void showUnLockSuccess();
};
#endif

///
/实现类NumPadlockLoopLayer.cpp代码如下:
///
#include "NumPadlockLoopLayer.h"
#include "HelloWorldScene.h"

using namespace cocos2d;
using namespace cocos2d::extension;

NumPadlockLoopLayer::NumPadlockLoopLayer()
:m_pNumKeysTexture(NULL)
{}

NumPadlockLoopLayer::~NumPadlockLoopLayer(){
    CCLOG("~NumPadlockLoopLayer()");
     
}

bool NumPadlockLoopLayer::init(){
    bool bRet = false; 
    do {
        //设置Layer的背景半透明
        CC_BREAK_IF(!CCLayerColor::initWithColor(ccc4(0, 0, 0, 125)));

        onInitDialog();
        bRet = true;
    } while (0);
    CCLOG("NumPadlockLoopLayer init");
    return bRet;
}

bool NumPadlockLoopLayer::onInitDialog(){
    bool bRet = false;
    do{
        CC_BREAK_IF(!CCLayer::init());
        //加载数字密码锁的数字纹理
        m_pNumKeysTexture=CCTextureCache::sharedTextureCache()->addImage("item_NOkey.jpg");
        
        //构建第一列数字锁,第一参数实例必须实现CCTableViewDataSource接口,第二参数,指定数字锁的宽度和高度,此处为一个数字宽,2个数字高
        tableView1 =CCTableView::create(this, CCSizeMake(NumKeyWidth, 2 * NumKeyHeight));
        //设置数字锁为竖直方向
        tableView1->setDirection(kCCScrollViewDirectionVertical);
        //次坐标是实际运行时,通过点击预备放置的屏幕位置,在触屏事件中获取
        tableView1->setPosition(ccp(278,174));
        //this 实例必须实现CCTableViewDelegate接口
        tableView1->setDelegate(this);
        //设置竖直列表中 每一项的排序和放置到列表中顺序策略,此处使用从上往下
        tableView1->setVerticalFillOrder(kCCTableViewFillTopDown);  
        this->addChild(tableView1);
        //数据刷新或改变时,需要通过此函数重新加载数据
        //tableView1->reloadData();
        //设置当前列表选择中得位置 基本在整个数据列表中段
        tableView1->setContentOffset(CCPointMake(0, -NumKeyHeight*(DataItemsCount/2+rand()%NumKeyCount)+NumKeyHeightHalf));
        
        tableView2 =CCTableView::create(this, CCSizeMake(NumKeyWidth, 2 * NumKeyHeight));
        tableView2->setDirection(kCCScrollViewDirectionVertical);
        tableView2->setPosition(ccp(530,174));
        tableView2->setDelegate(this);
        tableView2->setVerticalFillOrder(kCCTableViewFillTopDown); 
        this->addChild(tableView2);
        tableView2->reloadData();
        tableView2->setContentOffset(CCPointMake(0, -NumKeyHeight*(DataItemsCount/2+rand()%NumKeyCount)+NumKeyHeightHalf));
        
        //添加连个数字锁的外边框
        CCSprite *bg_keyframe = CCSprite::create("item_keyframe.png");
        CCSize size=this->getContentSize();
        bg_keyframe->setPosition( ccp(size.width/2, size.height/2) );
        this->addChild(bg_keyframe); 
        bRet=true;
    }while (0);
    return bRet;
}

void NumPadlockLoopLayer::onEnter()
{
    CCLayerColor::onEnter();
    // 当第三个参数为true时,且ccTouchBegan返回true时,将拦截所有priority比自己大的屏幕触摸事件,完成类似模态窗口的功能
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,getTouchPriority(),true);
    CCLOG("NumPadlockLoopLayer onEnter");
}

void NumPadlockLoopLayer::onExit()
{
    CCLayerColor::onExit();
    //注销触摸事件接收
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
    CCLOG("NumPadlockLoopLayer onExit");
}


void NumPadlockLoopLayer::tableCellTouched(CCTableView* table, CCTableViewCell* cell)
{
    //用户点击密码锁上的数字时回调用此方法,用户获取用户点击项的索引
    CCLog("cell touched at index: %i", cell->getIdx()%10);
}

//获取密码锁总共有多少项
unsigned int NumPadlockLoopLayer::numberOfCellsInTableView(CCTableView *table)
{
    //此处为了制造一种10个数字不断循环展示的效果,所以使用一个较大数字,不断重复那10个数字项
    return DataItemsCount;
}
//告诉tableView每个数据项的大小
CCSize NumPadlockLoopLayer::cellSizeForTable(CCTableView *table)
{
    //此处返回每个数字大小
    return CCSizeMake(NumKeyWidth, NumKeyHeight);
}
//通过每一项的索引idx创建每一项的内容
CCTableViewCell* NumPadlockLoopLayer::tableCellAtIndex(CCTableView *table, unsigned int idx)
{
    //CCLog("tableCellAtIndex: %i", idx);
    //尝试复用已有的数据项容器
    CCTableViewCell *pCell = table->dequeueCell();
    //计算当前项需要的数字帧图
    CCSpriteFrame *pFrame=CCSpriteFrame::createWithTexture(m_pNumKeysTexture,
                          CCRectMake(0, NumKeyHeight*NumKeyCount-((idx % NumKeyCount)+1)*NumKeyHeight, NumKeyWidth, NumKeyHeight));
    //没有的数据项容器
    if(!pCell){
        pCell = new CCTableViewCell();
        pCell->autorelease();
        CCSprite *sprite = CCSprite::createWithSpriteFrame(pFrame);
        //设置锚点为当前数字精灵的坐下角
        sprite->setAnchorPoint(CCPointZero);
        //设置当前数字精灵在Cell中的坐下角定位
        sprite->setPosition(ccp(0, 0));
        sprite->setTag(123);
        //在数据项中显示
        pCell->addChild(sprite);
    }else{
        CCSprite *sprite = (CCSprite *)pCell->getChildByTag(123);
        //跟新对应的数字帧图显示
        sprite->setDisplayFrame(pFrame);
    }
    return pCell;
}

bool NumPadlockLoopLayer::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent){
    //分摊给两个 密码锁 处理各自的触屏事件
    isTableView1Touched=tableView1->ccTouchBegan(pTouch, pEvent);
    isTableView2Touched=tableView2->ccTouchBegan(pTouch, pEvent);
    if(!isTableView1Touched && !isTableView2Touched){
        isNumPadlockLoopTouched=false;
        //两个密码锁都没有处理触屏事件,则表示用户没有解锁意向,我们随之将 密码锁 关闭。
        if(m_pHelloWorldLayer){
            //关闭密码锁
            m_pHelloWorldLayer->clearNumPadlock();
        }
    }else{
        // 标记密码锁有处理触屏事件,方便后续的触屏消息处理
        isNumPadlockLoopTouched=true;
    }
    //配合onEnter中的触屏事件注册,吞噬所有优先级比自己高的其他事件监听者的监听能力
    //防止下层接收到触屏消息,做出错误的响应
    return true;
}

void NumPadlockLoopLayer::ccTouchMoved(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent){
    if(isNumPadlockLoopTouched){//密码锁有处理触屏事件
        //分派给 两个密码锁处理 拖动操作
        tableView1->ccTouchMoved(pTouch, pEvent);
        tableView2->ccTouchMoved(pTouch, pEvent); 
    }
}

void NumPadlockLoopLayer::ccTouchEnded(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent){
    //CCPoint pt = CCDirector::sharedDirector()->convertToGL(pTouch->getLocationInView());
    //CCLOG("ccTouchEnded x=%f,y=%f",pt.x,pt.y);
    if(isNumPadlockLoopTouched){
        //分派给 两个密码锁处理 触摸手指离开 操作
        tableView1->ccTouchEnded(pTouch, pEvent);
        tableView2->ccTouchEnded(pTouch, pEvent);
        isNumPadlockLoopTouched=false;//两个密码锁 处理触屏事件 结束
        //校准用户拖动后的数字的位置
        adjustScrollView();
    }
}

//被来电等打断,或长时间无法响应用户选择取消,才会触发此回调
void NumPadlockLoopLayer::ccTouchCancelled(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent){
    if(isNumPadlockLoopTouched){
        tableView1->ccTouchCancelled(pTouch, pEvent);
        tableView2->ccTouchCancelled(pTouch, pEvent);
        isNumPadlockLoopTouched=false;
        adjustScrollView();
    }
}

void NumPadlockLoopLayer::adjustScrollView(){
    //由于tableView自带的 用户滑动列表效果 不能达到让 数字 停留在 密码锁UI的中间的效果,
    //所以停掉其用户手指离开后的校准回调,而是采用我自己写的方式处理校准
    tableView1->unscheduleAllSelectors();
    tableView2->unscheduleAllSelectors();
    //定义用户手指离开后,被滑动的密码锁 中数字滚动到的所处位置,此处以密码锁UI设置的锚点(0,0)即其左下角为参考点。
    CCPoint offset;
    if(isTableView1Touched){
        offset =tableView1->getContentOffset();//获取密码锁内部数字纹理滚动到位置 
    }
    if(isTableView2Touched){
        offset =tableView2->getContentOffset();
    }
    int y = (int)offset.y;//密码锁是竖直方向,所以取其Y轴位置
    //计算密码锁上 当前位置 距离列表末尾 间隔的数字个数,  
    int num = y/NumKeyHeight+(y % NumKeyHeight>NumKeyHeightHalf ? 1 : 0 );
    //计算 校准 后的位置
    int newoffsety=NumKeyHeight*num-NumKeyHeightHalf;
    offset.y=newoffsety;
    //CCLog("adjustScrollView num=%d",num%10);
    
    //定义两个密码锁中当前所处的位置,用于计算当前选中的数字。
    CCPoint tab1;
    CCPoint tab2;
    if(isTableView1Touched){
        //密码锁1在处理触屏事件,设置密码锁1中的校准位置
        tableView1->setContentOffsetInDuration(offset, 0.3);
        tab1=offset;
        tab2=tableView2->getContentOffset();
    }
    if(isTableView2Touched){
        //密码锁2在处理触屏事件,设置密码锁2中的校准位置
        tableView2->setContentOffsetInDuration(offset, 0.3);
        tab1=tableView1->getContentOffset(); 
        tab2=offset; 
    }
    //计算是否匹配密码
    if(checkpwd(tab1, tab2)){
        //解密成功,将密码锁UI淡出,并触发主界面的解密成功的提示
        CCFiniteTimeAction *action =CCFadeOut::create(0.5);
        CCFiniteTimeAction *callfunc=CCCallFunc::create(this, callfunc_selector(NumPadlockLoopLayer::showUnLockSuccess));
        CCSequence *seque = CCSequence::create(action,callfunc,NULL);
        runAction(seque);
    }
}

bool NumPadlockLoopLayer::checkpwd(cocos2d::CCPoint tab1, cocos2d::CCPoint tab2){
    int tab1Index= findCurrentSelectedIndex(tab1);
    int tab2Index= findCurrentSelectedIndex(tab2);
    //CCLog("tab1Index=%d,tab2Index=%d",tab1Index,tab2Index);
    if(2==tab1Index && 2==tab2Index){
        //匹配主界面提示的 密码
        return true;
    }
    return false;
}

int NumPadlockLoopLayer::findCurrentSelectedIndex(cocos2d::CCPoint tableViewOffSet){
    //获取Y轴的位置
    int y=(int)tableViewOffSet.y;
    //此处y是一个负值,要从密码锁 左下角定位到密码锁Y轴中间,需要减去NumKeyHeightHalf
    int currentSelectedIndex= 9-abs(((y-NumKeyHeightHalf)/NumKeyHeight)%NumKeyCount);
    return currentSelectedIndex;
}

void NumPadlockLoopLayer::showUnLockSuccess(){
    //解密成功的处理
    m_pHelloWorldLayer->unLockSuccess();
    m_pHelloWorldLayer->clearNumPadlock();
}

/
/下面来看看主页面对密码盘的加载调度 
首先看主页面的头文件结构,HelloWorldScene.h
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class NumPadlockLoopLayer;
class HelloWorld : public cocos2d::CCLayer
{
public:
    // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer)
    virtual bool init();
    // there's no 'id' in cpp, so we recommend to return the class instance pointer
    static cocos2d::CCScene* scene();
    // a selector callback
    void menuCloseCallback(CCObject* pSender);
    // preprocessor macro for "static create()" constructor ( node() deprecated )
    CREATE_FUNC(HelloWorld);

    virtual void onEnter();
    virtual void onExit();
    
    virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    
    //显示数字密码循环锁
    void showNumPadlock();
    //清除数字密码循环锁UI
    void clearNumPadlock();
    //解锁成功后展示
    void unLockSuccess();
private:
    NumPadlockLoopLayer * m_pNumPadlockLoopLayer;
    bool isUnLockSuccess=false;//标记解锁是否成功
};
#endif // __HELLOWORLD_SCENE_H__

///
/HelloWorldScene.cpp
///
#include "HelloWorldScene.h"
#include "NumPadlockLoopLayer.h"

using namespace cocos2d; 

CCScene* HelloWorld::scene()
    CCScene *scene = CCScene::create();
    HelloWorld *layer = HelloWorld::create();

    //设置触屏事件处理优先级,默认为0
    layer->setTouchPriority(0);

    scene->addChild(layer);
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }
    srand(time(NULL)); //设置随机数种子,若不设置回导致每次获取的数字雷同
    
    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
                                        "CloseNormal.png",
                                        "CloseSelected.png",
                                        this,
                                        menu_selector(HelloWorld::menuCloseCallback) );
    pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );

    CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
    pMenu->setPosition( CCPointZero );
    this->addChild(pMenu, 1);

    //Demo 提示解密密码
    CCLabelTTF* pLabel = CCLabelTTF::create("解锁密码:22", "Thonburi", 50); 
    CCSize size = CCDirector::sharedDirector()->getWinSize(); 
    pLabel->setPosition( ccp(size.width / 2-100, size.height - 200) ); 
    pLabel->setColor(ccc3(126,126,126));
    pLabel->setTag(123);
    this->addChild(pLabel, 1);

    //BG.jpg是针对平板设计,说以在AppDelegate.cpp中设定了屏幕适配方案
    CCSprite* pSprite = CCSprite::create("BG.jpg");
    pSprite->setPosition( ccp(size.width/2, size.height/2) );
    this->addChild(pSprite, 0);
    //显示数字密码循环锁
    showNumPadlock();
    //设置允许接受触摸事件
    setTouchEnabled(true);
    return true;
}

void HelloWorld::onEnter()
{
    CCLayer::onEnter();
    // 当第三个参数为true时,且ccTouchBegan返回true时,将拦截所有priority比自己大的屏幕触摸事件
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,getTouchPriority(),true);
    CCLOG("HelloWorld onEnter");
}

void HelloWorld::onExit()
{
    CCLayer::onExit();
    //注销触摸事件的监听
    CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
    CCLOG("HelloWorld onExit");
}

void HelloWorld::showNumPadlock(){ 
    m_pNumPadlockLoopLayer=NumPadlockLoopLayer::create();
    //设置触屏事件处理优先级,小于HelloWorld的触屏事件处理优先级,以便于NumPadlockLoopLayer更高优先级处理触屏事件处理
    m_pNumPadlockLoopLayer->setTouchPriority(-1);
    m_pNumPadlockLoopLayer->setPosition(ccp(0, 0));
    m_pNumPadlockLoopLayer->setHelloWorld(this);
    this->addChild(m_pNumPadlockLoopLayer,2);
}

void HelloWorld::unLockSuccess(){
    CCLabelTTF* pLabel=(CCLabelTTF*)getChildByTag(123); 
    pLabel->setString("解密成功!");
    isUnLockSuccess=true;
}

void HelloWorld::clearNumPadlock(){
    if(m_pNumPadlockLoopLayer){
        //销毁密码锁UI
        m_pNumPadlockLoopLayer->removeFromParentAndCleanup(true);
        m_pNumPadlockLoopLayer=NULL;
    }
}

bool HelloWorld::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent){
    CCLog("HelloWorld::ccTouchBegan");
    if ( !isUnLockSuccess) {
        //在还未成功解密时且能接收到触屏事件时,打开密码锁界面
        showNumPadlock(); 
    }
    return true;
}

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    CCDirector::sharedDirector()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}

//在 HelloWorld::scene() 设置主页面的触摸事件的优先级大于 HelloWorld::showNumPadlock 中对
// m_pNumPadlockLoopLayer->setTouchPriority(-1);  实现模态窗口效果,
//在HelloWorld::showNumPadlock方法被调用后, m_pNumPadlockLoopLayer显示的时候
//触摸屏幕不会调用HelloWorld::ccTouchBegan方法,m_pNumPadlockLoopLayer阻止了屏幕触摸事件向优先级大于-1的触屏事件监听者发送。

//到此代码粘贴完毕,如果有不清楚的注释,可以邮箱联系:hezeping888@163.com


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值