Cocos2d-x CCEditBox & CCTextFieldTTF
下面简单记录一下如何Cocos2d-x中创建输入编辑框。在引擎中为我们提供了这样两个类:CCEditBox 和 CCTextFieldTTF。
一、CCEditBox
①这个类文件的位置
②这个类是继承自 CCControlButton 和 CCIMEDelegate。其中的CCIMEDelegate代理类中定义了四个代理方法,在使用的时候根据需要选择实现相应的委托方法,从方法名就可以大致知道是什么意思了。
class CCEditBoxDelegate { public: virtual ~CCEditBoxDelegate() {}; /** * This method is called when an edit box gains focus after keyboard is shown. * @param editBox The edit box object that generated the event. */ virtual void editBoxEditingDidBegin(CCEditBox* editBox) {}; /** * This method is called when an edit box loses focus after keyboard is hidden. * @param editBox The edit box object that generated the event. */ virtual void editBoxEditingDidEnd(CCEditBox* editBox) {}; /** * This method is called when the edit box text was changed. * @param editBox The edit box object that generated the event. * @param text The new text. */ virtual void editBoxTextChanged(CCEditBox* editBox, const std::string& text) {}; /** * This method is called when the return button was pressed or the outside area of keyboard was touched. * @param editBox The edit box object that generated the event. */ virtual void editBoxReturn(CCEditBox* editBox) = 0; };
下面通过一个demo来使用一下 CCEditBox 这个类中的相关方法(通过查看该类的头文件可知道更加详细的方法介绍)。
注意:使用的时候需要:
#include "cocos-ext.h"
USING_NS_CC_EXT;
头文件:
class HelloWorld : public cocos2d::CCLayer,public CCEditBoxDelegate { 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 editBoxEditingDidBegin(cocos2d::extension::CCEditBox* editBox); virtual void editBoxEditingDidEnd(cocos2d::extension::CCEditBox* editBox); virtual void editBoxTextChanged(cocos2d::extension::CCEditBox* editBox, const std::string& text); virtual void editBoxReturn(cocos2d::extension::CCEditBox* editBox); private: CCEditBox* editBox; };
实现文件:
bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !CCLayer::init() ) { return false; } CCSize winSize = CCDirector::sharedDirector()->getWinSize(); CCSize editSize = CCSizeMake(300, 50); //第一个size参数表示输入编辑框的大小,第二个参数九宫格是用于输入编辑框的背景 editBox = CCEditBox::create(editSize, CCScale9Sprite::create("12.png")); editBox->cocos2d::CCNode::setPosition(winSize.width/2, winSize.height-80); //以setFont开头的有几个方法是 用于设置输入文字的字体,大小,颜色 editBox->setFontSize(25); editBox->setFontColor(ccRED); //设置输入编辑框在还没有输入的时候默认的提示文字 editBox->setPlaceHolder("Name: "); //同样的,也有几个对应的方法的是用于设置这些提示文字的,都是以setPlaceHolder开头的 editBox->setPlaceholderFontColor(ccWHITE); //设置输入编辑文字的长度,一个字符为一个长度 editBox->setMaxLength(20); //设置键盘中return键显示的字符 editBox->setReturnType(kKeyboardReturnTypeGo); //包括这些选项 // kKeyboardReturnTypeDefault: 默认使用键盘return 类型 // kKeyboardReturnTypeDone: 默认使用键盘return类型为“Done”字样 // kKeyboardReturnTypeSend: 默认使用键盘return类型为“Send”字样 // kKeyboardReturnTypeSearch: 默认使用键盘return类型为“Search”字样 // kKeyboardReturnTypeGo: 默认使用键盘return类型为“Go”字样 //设置输入编辑框的编辑类型 editBox->setInputMode(kEditBoxInputModeAny); //包括这些选项 // kEditBoxInputModeAny: 开启任何文本的输入键盘,包括换行 // kEditBoxInputModeEmailAddr: 开启 邮件地址 输入类型键盘 // kEditBoxInputModeNumeric: 开启 数字符号 输入类型键盘 // kEditBoxInputModePhoneNumber: 开启 电话号码 输入类型键盘 // kEditBoxInputModeUrl: 开启 URL 输入类型键盘 // kEditBoxInputModeDecimal: 开启 数字 输入类型键盘,允许小数点 // kEditBoxInputModeSingleLine: 开启任何文本的输入键盘,不包括换行 //设置该属性输入密码时为替代符 // editBox->setInputFlag(kEditBoxInputFlagPassword); //如果只是简单输入字符,则不用这个设置 //包括这些选项 // kEditBoxInputFlagPassword, // kEditBoxInputFlagSensitive, // kEditBoxInputFlagInitialCapsWord, // kEditBoxInputFlagInitialCapsSentence, // kEditBoxInputFlagInitialCapsAllCharacters //设置委托代理对象为当前类 editBox->setDelegate(this); this->addChild(editBox); return true; }
委托方法可以根据需要自定义实现相关的内容。
void HelloWorld::editBoxEditingDidBegin(cocos2d::extension::CCEditBox *editBox) { } void HelloWorld::editBoxEditingDidEnd(cocos2d::extension::CCEditBox *editBox) { } void HelloWorld::editBoxTextChanged(cocos2d::extension::CCEditBox *editBox, const std::string &text) { } void HelloWorld::editBoxReturn(cocos2d::extension::CCEditBox *editBox) { CCLOG("the text = %s",editBox->getText()); }
下面有一点要提示:本人在使用过程中遇到过这样一个问题,创建好对象添加到layer中的时候,点击编辑框,没有响应(没有调出键盘),debug了好一会才发现,是触摸层级的问题,后来修改了layer的触摸等级(降低了layer的优先级),就可以了。
二、CCTextFieldTTF
①这个类文件的位置
libs/cocos2dx/text_input_node/
②这个类是继承了 CCLabelTTF 和 CCIMEDelegate。
通过其继承了CCLabelTTF这个类和 CCIMEDelegate 键盘代理类。
根据其继承自CCLabelTTF,我们就可以大致猜测到其实现了,估计就是一个动态的 CCLabelTTF ,通过不断监听输入的字符,动态设置 label 的字符,进行显示而已吧!
而其继承 CCIMEDelegate 这个键盘代理类,其中的代理方法主要是处理键盘的出现和隐藏过程中的一些自定义过程实现,如果需要,可以继承这个类,并重写其中的相关方法。
另外,在layer中使用CCTextFieldTTF创建输入编辑框对象的时候,需要继承 CCTextFieldDelegate 这个代理类,其中也定义了相关的委托方法,使用的时候根据需要选择实现即可。
而这个 CCTextFieldDelegate 委托方法主要是处理在编辑输入框键入字符过程中的一些自定义实现。下面就是这个代理类中的相关方法,看方法名就知道键入字符分成四个过程了。
class CC_DLL CCTextFieldDelegate { public: /** @brief If the sender doesn't want to attach to the IME, return true; */ virtual bool onTextFieldAttachWithIME(CCTextFieldTTF * sender) { CC_UNUSED_PARAM(sender); return false; } /** @brief If the sender doesn't want to detach from the IME, return true; */ virtual bool onTextFieldDetachWithIME(CCTextFieldTTF * sender) { CC_UNUSED_PARAM(sender); return false; } /** @brief If the sender doesn't want to insert the text, return true; */ virtual bool onTextFieldInsertText(CCTextFieldTTF * sender, const char * text, int nLen) { CC_UNUSED_PARAM(sender); CC_UNUSED_PARAM(text); CC_UNUSED_PARAM(nLen); return false; } /** @brief If the sender doesn't want to delete the delText, return true; */ virtual bool onTextFieldDeleteBackward(CCTextFieldTTF * sender, const char * delText, int nLen) { CC_UNUSED_PARAM(sender); CC_UNUSED_PARAM(delText); CC_UNUSED_PARAM(nLen); return false; } /** @brief If the sender doesn't want to draw, return true. */ virtual bool onDraw(CCTextFieldTTF * sender) { CC_UNUSED_PARAM(sender); return false; } };
其中的方法 return true或者false要注意!(看方法前面的注释)
下面通过testcpp中的一个 TextFieldTTFActionTest 来具体实践一下(本人抽离出相关的代码并做了相应的补充修改)
简单对程序说明一下:实现了键盘出现和隐藏时候的视图内容的自动调整,同时在输入字符和删除字符的时候有动作特效。
还有就是键盘的锚地位置是在其左下角,在键盘出现和隐藏方法中输出的数值中就可以发现。
我的测试的过程中有一个特别坑的bug,就是在onEnter这个方法中,忘了 CCLayer::onEnter(); 对父类的调用,所以导致程序莫名其妙的问题(无法执行动作),找了好久,看到http://blog.csdn.net/somestill/article/details/9875743这篇文章后,才找出这个bug。
下面直接贴出代码,看懂应该没有什么问题。
头文件:
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" USING_NS_CC; class HelloWorld : public cocos2d::CCLayer,public CCTextFieldDelegate,public CCIMEDelegate { 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(); // preprocessor macro for "static create()" constructor ( node() deprecated ) CREATE_FUNC(HelloWorld); void callbackRemoveNodeWhenDidAction(CCNode * pNode); virtual void onClickTrackNode(bool bClicked); // CCLayer virtual void onEnter(); virtual void onExit(); virtual void registerWithTouchDispatcher(); virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent); virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent); // CCTextFieldDelegate virtual bool onTextFieldAttachWithIME(CCTextFieldTTF * pSender); virtual bool onTextFieldDetachWithIME(CCTextFieldTTF * pSender); virtual bool onTextFieldInsertText(CCTextFieldTTF * pSender, const char * text, int nLen); virtual bool onTextFieldDeleteBackward(CCTextFieldTTF * pSender, const char * delText, int nLen); virtual bool onDraw(CCTextFieldTTF * pSender); //CCIMEDelegate //keyboard show/hide notification virtual void keyboardWillShow(CCIMEKeyboardNotificationInfo& info); virtual void keyboardWillHide(CCIMEKeyboardNotificationInfo& info); private: CCTextFieldTTF* m_pTextField; CCAction* m_pTextFieldAction; bool m_bAction; int m_nCharLimit; // the textfield max char limit CCPoint m_beginPos; float adjustVert; }; #endif // __HELLOWORLD_SCENE_H__
实现文件:
#include "HelloWorldScene.h" #include "SimpleAudioEngine.h" using namespace cocos2d; using namespace CocosDenshion; #define FONT_NAME "Thonburi" #define FONT_SIZE 36 CCScene* HelloWorld::scene() { // 'scene' is an autorelease object CCScene *scene = CCScene::create(); // 'layer' is an autorelease object HelloWorld *layer = HelloWorld::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !CCLayer::init() ) { return false; } setTouchEnabled(true); //注意要设置当前layer为可触摸 CCSize size = CCDirector::sharedDirector()->getWinSize(); CCSprite* pSprite = CCSprite::create("HelloWorld.png"); pSprite->setPosition( ccp(size.width/2, size.height/2) ); this->addChild(pSprite, 0); return true; } void HelloWorld::registerWithTouchDispatcher() { CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true); } void HelloWorld::onEnter() { CCLayer::onEnter(); //这个父类的调用很重要! m_nCharLimit = 12; m_pTextFieldAction = CCRepeatForever::create( CCSequence::create( CCFadeOut::create(0.25), CCFadeIn::create(0.25), NULL )); m_pTextFieldAction->retain(); //这里一定要retain一次,否则会出现内存问题。 m_bAction = false; // add CCTextFieldTTF CCSize s = CCDirector::sharedDirector()->getWinSize(); m_pTextField = CCTextFieldTTF::textFieldWithPlaceHolder("<click here for input>", FONT_NAME, FONT_SIZE); m_pTextField->setColor(ccWHITE); //设置输入编辑框中字符的颜色 // m_pTextField->setSecureTextEntry(true); //输入密码时,用点字符替代 m_pTextField->setDelegate(this); m_pTextField->setPosition(ccp(s.width / 2, s.height / 2-30)); //将输入编辑框的y轴位置设低是为了测试,当出现键盘的时候,输入编辑框的自动向上调整。 addChild(m_pTextField); } //返回节点的rect static CCRect getRect(CCNode * pNode) { CCRect rc; rc.origin = pNode->getPosition(); rc.size = pNode->getContentSize(); rc.origin.x -= rc.size.width / 2; rc.origin.y -= rc.size.height / 2; return rc; } bool HelloWorld::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) { CCLOG("++++++++++++++++++++++++++++++++++++++++++++"); m_beginPos = pTouch->getLocation(); return true; } void HelloWorld::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) { if (! m_pTextField) { return; } CCPoint endPos = pTouch->getLocation(); // 以下这部分代码是用于检测 begin touch 到 end touch之间的距离是否超过5.0,如果是,则返回;否则,继续执行下面的判断是否点击到编辑框的代码。 float delta = 5.0f; if (::abs(endPos.x - m_beginPos.x) > delta || ::abs(endPos.y - m_beginPos.y) > delta) { // not click m_beginPos.x = m_beginPos.y = -1; return; } // decide the trackNode is clicked. CCRect rect; rect = getRect(m_pTextField); this->onClickTrackNode(rect.containsPoint(endPos)); CCLOG("----------------------------------"); } void HelloWorld::onClickTrackNode(bool bClicked) { if (bClicked) { // TextFieldTTFTest be clicked CCLOG("attachWithIME"); m_pTextField->attachWithIME(); //调用键盘 } else { // TextFieldTTFTest not be clicked CCLOG("detachWithIME"); m_pTextField->detachWithIME(); //隐藏键盘 } } void HelloWorld::onExit() { m_pTextFieldAction->release(); CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this); } // CCTextFieldDelegate protocol bool HelloWorld::onTextFieldAttachWithIME(CCTextFieldTTF * pSender) { if (! m_bAction) { m_pTextField->runAction(m_pTextFieldAction); m_bAction = true; } return false; } bool HelloWorld::onTextFieldDetachWithIME(CCTextFieldTTF * pSender) { if (m_bAction) { m_pTextField->stopAction(m_pTextFieldAction); m_pTextField->setOpacity(255); m_bAction = false; } return false; } bool HelloWorld::onTextFieldInsertText(CCTextFieldTTF * pSender, const char * text, int nLen) { // if insert enter, treat as default to detach with ime if ('\n' == *text) { return false; } // if the textfield's char count more than m_nCharLimit, doesn't insert text anymore. if (pSender->getCharCount() >= m_nCharLimit) { return true; } // create a insert text sprite and do some action CCLabelTTF * label = CCLabelTTF::create(text, FONT_NAME, FONT_SIZE); this->addChild(label); ccColor3B color = { 226, 121, 7}; label->setColor(color); // move the sprite from top to position CCPoint endPos = pSender->getPosition(); if (pSender->getCharCount()) { endPos.x += pSender->getContentSize().width / 2; } CCSize inputTextSize = label->getContentSize(); CCPoint beginPos(endPos.x, CCDirector::sharedDirector()->getWinSize().height - inputTextSize.height * 2); float duration = 0.5; label->setPosition(beginPos); label->setScale(8); CCAction * seq = CCSequence::create( CCSpawn::create( CCMoveTo::create(duration, endPos), CCScaleTo::create(duration, 1), CCFadeOut::create(duration), 0), CCCallFuncN::create(this, callfuncN_selector(HelloWorld::callbackRemoveNodeWhenDidAction)), 0); label->runAction(seq); return false; } bool HelloWorld::onTextFieldDeleteBackward(CCTextFieldTTF * pSender, const char * delText, int nLen) { // create a delete text sprite and do some action CCLabelTTF * label = CCLabelTTF::create(delText, FONT_NAME, FONT_SIZE); this->addChild(label); // move the sprite to fly out CCPoint beginPos = pSender->getPosition(); CCSize textfieldSize = pSender->getContentSize(); CCSize labelSize = label->getContentSize(); beginPos.x += (textfieldSize.width - labelSize.width) / 2.0f; CCSize winSize = CCDirector::sharedDirector()->getWinSize(); CCPoint endPos(- winSize.width / 4.0f, winSize.height * (0.5 + (float)rand() / (2.0f * RAND_MAX))); float duration = 1; float rotateDuration = 0.2f; int repeatTime = 5; label->setPosition(beginPos); CCAction * seq = CCSequence::create( CCSpawn::create( CCMoveTo::create(duration, endPos), CCRepeat::create( CCRotateBy::create(rotateDuration, (rand()%2) ? 360 : -360), repeatTime), CCFadeOut::create(duration), 0), CCCallFuncN::create(this, callfuncN_selector(HelloWorld::callbackRemoveNodeWhenDidAction)), 0); label->runAction(seq); return false; } bool HelloWorld::onDraw(CCTextFieldTTF * pSender) { return false; } void HelloWorld::callbackRemoveNodeWhenDidAction(CCNode * pNode) { this->removeChild(pNode, true); } void HelloWorld::keyboardWillShow(CCIMEKeyboardNotificationInfo& info) { CCLOG("TextInputTest:keyboardWillShowAt(origin:%f,%f, size:%f,%f)", info.end.origin.x, info.end.origin.y, info.end.size.width, info.end.size.height); if (! m_pTextField) { return; } CCRect rectTracked = getRect(m_pTextField); CCLOG("TextInputTest:trackingNodeAt(origin:%f,%f, size:%f,%f)", rectTracked.origin.x, rectTracked.origin.y, rectTracked.size.width, rectTracked.size.height); // if the keyboard area doesn't intersect with the tracking node area, nothing need to do. if (! rectTracked.intersectsRect(info.end)) { return; } // assume keyboard at the bottom of screen, calculate the vertical adjustment. //计算出需要y轴需要调整的距离 adjustVert = info.end.getMaxY() - rectTracked.getMinY(); CCLOG("TextInputTest:needAdjustVerticalPosition(%f)", adjustVert); // move all the children node of KeyboardNotificationLayer CCArray * children = getChildren(); CCNode * node = 0; int count = children->count(); CCPoint pos; for (int i = 0; i < count; ++i) { node = (CCNode*)children->objectAtIndex(i); pos = node->getPosition(); pos.y += adjustVert; //所有的节点都向上移动 node->setPosition(pos); } } void HelloWorld::keyboardWillHide(CCIMEKeyboardNotificationInfo &info) { CCLOG("TextInputTest:keyboardWillShowAt(origin:%f,%f, size:%f,%f)", info.end.origin.x, info.end.origin.y, info.end.size.width, info.end.size.height); CCArray * children = getChildren(); CCNode * node = 0; int count = children->count(); CCPoint pos; for (int i = 0; i < count; ++i) { node = (CCNode*)children->objectAtIndex(i); pos = node->getPosition(); pos.y -= adjustVert; //所有的节点都向下移动,恢复原来的位置 node->setPosition(pos); } }
大概就是这么多内容了吧!了解了一下CCEditBox & CCTextFieldTTF,感觉前者使用过程比较简单,后者比较复杂一些,但是可以增加一些特殊效果。
还有一点就是:使用 CCEditBox 创建的编辑框,不用额外的代码处理,点击编辑框区域就可以跳出键盘,点击非编辑框区域就可以隐藏键盘;
但是使用CCTextFieldTTF创建编辑框的时候,点击编辑框的时候,需要添加检测判断的代码,判断点击位置是否在编辑框中(在touch触摸事件中处理),如果是,才手动调用方法,弹出键盘;这个弹出键盘的处理在上面的代码中已经有涉及。同理,如果想要实现点击非编辑框区域,隐藏键盘,同样需要添加代码检测判断点击位置是否落在非编辑框区域。所以说后者的使用比较麻烦!
另外附上一个 使用 CCTextFieldTTF 创建的 带光标的输入框 文章!