最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
cocos2dx 3.0中弹出键盘后屏蔽其它层
在日常使用手机或平板进行触摸输入时,键盘弹出时会显示一个层,有时还会弹出提示框,这其实就是一种模态窗口。在未对当前对话框进行确认时,用户无法继续往下操作。本文将探讨如何合理设计这样的功能,不过不会直接提供完整的解决方案和大量源码,而是会指导你根据自身需求进行完善,正所谓“授人以鱼不如授人以渔”。
功能分析
我们要设计一个对话框,该对话框具备以下特点:
- 有几个可定制数量的按钮。
- 带有标题,能让用户一眼看出其用途。
- 内部包含详细的提示文字。
- 是模态窗口,即用户在处理该对话框之前无法操作其他部分。
- 窗口大小可变,以适应不同的屏幕尺寸。
- 具备弹出效果,为用户提供良好的体验。
为了方便使用,我们将接口设计得尽量简洁。以下是接口函数的定义(本文所用到的源代码可以从这里获取):
class PopupLayer: public CCLayer{
public:
PopupLayer();
~PopupLayer();
virtual bool init();
CREATE_FUNC(PopupLayer);
// 需要重写触摸注册函数,重新给定触摸级别
virtual void registerWithTouchDispatcher(void);
// 重写触摸函数,永远返回 true ,屏蔽其它层,达到 “模态” 效果
bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
// 构架,并设置对话框背景图片
static PopupLayer* create(const char* backgroundImage);
// 它可以显示标题,并且设定显示文字大小
void setTitle(const char* title, int fontsize = 20);
// 文本内容,padding 为文字到对话框两边预留的距离,这是可控的,距上方的距离亦是如此
void setContentText(const char* text, int fontsize = 20, int padding = 50, int paddintTop = 100);
// 回调函数,当点击按钮后,我们关闭弹出层的同事,需要一个回调函数,以通知我们点击了哪个按钮(如果有多个)
void setCallbackFunc(CCObject* target, SEL_CallFuncN callfun);
// 为了添加按钮方面,封装了一个函数,传入些必要的参数
bool addButton(const char* normalImage, const char* selectedImage, const char* title, int tag = 0);
// 为了在显示层时之前的属性生效,选择在 onEnter 里动态展示
virtual void onEnter();
virtual void onExit();
};
从使用角度来看,定义上述函数可供外部调用,以完成基本的功能需求。当然,还有一些隐藏函数,如 setContentSize。接下来,我们将进行下一步设计。
onEnter 动态组建弹出层
前文提到,所需的模态窗口大小是可变的,可通过 ContentSize 来获取。若未设置 ContentSize,窗口大小将与传入图片相同;反之,则将窗口设定为指定大小。对于完成这样一个功能,有以下两种方式:
实时设置,实时刷新
例如,在 static PopupLayer* create(const char* backgroundImage) 的实现中,创建一个精灵并设置好图片,然后添加到当前层。若调用了 setContentSize,则在此方法中获取精灵后修改其大小(虽然本文未实现 setContentSize 方法,但不影响解说)。
保留属性,动态组建
在 static PopupLayer* create(const char* backgroundImage) 方法内部,仅保存图片的名称,setContentSize 也只需专注于自身的工作。最后,在合适的执行时期,根据这两个参数动态创建符合需求的精灵,而 onEnter 方法是执行此操作的理想时机。
下面通过一个例子来说明“实时刷新”与“动态组建”的区别。定义中有一个 addButton 方法,用于在当前对话框中添加一个或多个按钮。按钮的位置会随按钮个数的不同而变化,若只有一个按钮,将居中显示;若有两个按钮,则按三等份距离分布。
- 实时刷新:每次调用
addButton方法时,需要创建一个新的按钮并设置正确的位置。在设置新位置时,需要判断是否已存在按钮,并修改前面按钮的位置。若代码逻辑复杂、依赖属性较多,这种方式将难以控制。 - 动态组建:在
addButton方法中,只需使用一个集合(类似机制)来保存所有的按钮信息。然后在onEnter方法中,读取这些信息并实时添加按钮至界面。此时,属性基本已经确定(如按钮个数),只需计算一次各按钮的位置,逻辑处理更加清晰。
需要注意的是,动态组建并不一定比实时刷新好。例如,在 Cocos2d - x 的 CCControl 框架中,使用了实时刷新的解决方案,在设定一个控件的属性之后,需要根据属性更新控件的布局等。对于“实时刷新”与“动态组建”这两种方式,需要根据实际情况进行选择,以简化开发。在本文中,基本采用动态组建的方式设计,因为在弹出层运行时,所有的属性就已经确定,无需再根据属性实时刷新。
弹出层的触摸优先级,操作与显示相一致
-128 是 CCMenu 的默认触摸级别。若不使用自带的菜单项,处理触摸级别相对简单,选择一个稍大的值即可,但需要自己实现按钮处理逻辑。使用 CCMenu 的好处是它封装了方便的点击操作。为了简化开发,本文基于 CCMenu 实现对话框按钮的操作。
那么,弹出层的触摸级别应该设置为多少呢?这里有一个原则:“操作与显示相一致”。假设为了屏蔽弹出层以外所有层的操作,将弹出层级别设置为 -129,这样虽然保证了它是模态的(一般而言,除了 CCMenu 的级别,用户自定义也无需这么大),但如果当前弹出层上方还有其他层(如多层的 UI 设计),且这些层上也有 CCMenu 按钮,就会出现显示在弹出层上方层中的 CCMenu 按钮无法点击的情况,这显然不合理。实际上,弹出层的 CCMenu 也可能无法点击,不过可以通过将弹出层的触摸消息传递给层中的 CCMenu 来解决。
因此,一味追求最大的触摸优先级并不可取,它无法很好地实现“操作与显示相一致”。而优先级小于 CCMenu 又不能屏蔽下方的其他 CCMenu 操作。所以,将弹出层的触摸级别设置为 -128 是合理的,对于相同级别的元素(弹出层和 CCMenu),能够保证显示在最上方的优先处理。如果弹出层上方还有层,这些层的按钮应该是可以点击的。我们要做的是通过逻辑控制,确保弹出层节点最后添加到场景的最上方。
关于“操作与显示相一致”的解决方案,可以参考一叶的另外两篇文章:多层 UI 触摸事件的轻量级设计 和 CCScrollView 实现帮助界面、关卡选择。这两篇文章中没有使用 CCMenu,而是独立创建了一套规则。
回调函数的实现方案
由于使用了 CCMenu 作为按钮,我们可以将 CCMenu 的回调函数作为参数传入,但在事件处理完之后,还需要关闭当前层,因此需要进一步封装。将弹出层的按钮回调函数设置为内部实现,然后回调给它的上层,最后由对话框层完成关闭操作。回调一般有两种方式:
delegate 回调
需要定义接口,由上层继承并实现接口,然后将自身作为参数传入弹出层,由弹出层调用 delegate 的接口方法实现。在 Cocos2d - x 中,很多地方都使用了这种方式。
函数绑定
就像 CCMenu 那样绑定函数。
在本文中,选择了后者。如果当前弹出层固定有一个或两个按钮,使用 delegate 实现函数回调会使回调步骤更清晰,但这里设计的是按钮个数可变、功能也不尽相同,因此使用回调函数绑定 CCNode 参数,以其 tag 标示点击的是哪个按钮。void setCallbackFunc(CCObject* target, SEL_CallFuncN callfun); 的定义类似于 CCMenu 的回调机制,一个 CCNode 作为参数,其 tag 用于标示当前点击的是哪个按钮。
以下是使用示例代码:
void Popup::popupLayer(){
// 定义一个弹出层,传入一张背景图
PopupLayer* pl = PopupLayer::create("popuplayer/BackGround.png");
// ContentSize 是可选的设置,可以不设置,如果设置把它当作 9 图缩放
pl->setContentSize(CCSizeMake(400, 350));
pl->setTitle("吾名一叶");
pl->setContentText("娇兰傲梅世人赏,却少幽芬暗里藏。不看百花共争艳,独爱疏樱一枝香。", 20, 60, 250);
// 设置回调函数,回调传回一个 CCNode 以获取 tag 判断点击的按钮
// 这只是作为一种封装实现,如果使用 delegate 那就能够更灵活的控制参数了
pl->setCallbackFunc(this, callfuncN_selector(Popup::buttonCallback));
// 添加按钮,设置图片,文字,tag 信息
pl->addButton("popuplayer/pop_button.png", "popuplayer/pop_button.png", "确定", 0);
pl->addButton("popuplayer/pop_button.png", "popuplayer/pop_button.png", "取消", 1);
// 添加到当前层
this->addChild(pl);
}
void Popup::buttonCallback(cocos2d::CCNode *pNode){
// 打印 tag 0, 确定,1 ,取消
CCLog("button call back. tag: %d", pNode->getTag());
}
// 弹出效果 关键代码,当前层直执行这个动作
CCAction* popupLayer = CCSequence::create(CCScaleTo::create(0.0, 0.0),
CCScaleTo::create(0.06, 1.05),
CCScaleTo::create(0.08, 0.95),
CCScaleTo::create(0.08, 1.0), NULL);
这里有一张截图(未给出具体截图),其中 popup 按钮是一个 CCMenu,弹出层中的两个按钮也是 CCMenu,层本身的触摸优先级别为 -128,它们处于同一级别。因此,要遵循“操作与显示相一致”的原则,注意逻辑控制,确定何时弹出层以及由哪个节点弹出层(一般由场景基层负责),以确保弹出层显示在最上方。
总结
通过以上的讨论和实践,我们完成了对话框的基本模型,实现了以下功能:
- 一个可以弹出的对话框。
- 模态窗口的实现(需要逻辑控制)。
- 支持多个按钮,位置自适应,并提供回调函数。
- 提供标题和内容设置。
- 支持九图,可控制适应弹出框大小。
当然,该实现还有许多未考虑到的功能或不完善的地方,需要用户自行扩展和定制。例如,这样的层至少应该是单例的,任何时候只应存在一个,可以使用单例模式实现;对于弹出层的内容,目前只有标题和内容,且标题位置固定,按钮的位置也可以更灵活地设置等。