效果:
可以拖动两个密码盘滚动,数字总是可停靠中间,如上图那样。数字滚动可以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