在cocos2d-js实现自动绑定cocostudioUI控件与事件

一.起因

在客户端游戏开发中最让人恶心的工作就是UI相关的东西,虽然有了像cocostudio这样的可视化工具,但界面中有大量需要由代码访问的控件的时候,需要写太多重复的代码例如:

    //加载UI配置文件
    var root = ccs.uiReader.widgetFromJsonFile("res/cocosui/UIEditorTest/UIButton_Editor/UIButton_Editor_1.json");
    this._mainNode.addChild(root);

    //查询back控件,并添事件响应
    var back_label = ccui.helper.seekWidgetByName(root, "back");
    back_label.addTouchEventListener(this.backEvent,this);

    //查询Button_123控件,并添事件响应
    var button = root.getChildByName(root, "Button_123");
    button.addTouchEventListener(this.touchEvent,this);</span>

上面是最为直接访问控件的方法。问题在于如果一个UI界面中有十个、二十个甚至更多的UI控件需要操作的是候,我们做UI的界面、逻辑开发的同学们是否还有时间愉快的玩耍?有没有什么方法不需要去手写这些代码,就能愉快的访问UI组件与接收UI事件响应呢?

二. 思考

对于手机游戏,特别是卡牌类的游戏来说70%~80%的客户端工作量是在UI布局与逻辑上。

上面的seekWidgetByName、getChildByName、addTouchEventListener函数将大量充斥在客户端代码中,滥竽充数着我们的代码行数。

自己曾经有过Qt的开发经验。Qt中也有自己的UI设计工具,生成xml的ui配置文件. 对于这个xml有两种使用方式:

第一种方式: 使用Qt自己的编译工具,将xml翻译生成一个c++代码文件,代码内容就是根据xml中的信息创建各种控件,设置坐标\属性\事件等。

第二种方式: 在程序中,将xml文件使用UILoader工具类加载进来,成为一个节点。然后调用Qt的函数实现信号/槽的自动关联。实现信号/槽(事件)自动绑定的原理,是要求写一个事件处理函数,格式为:

    void on_控件名_信号名(参数);  

具体如何使用就不细说了,有兴趣的朋友可以自己去看看。根据Qt的这个功能提示,我们何尝不可以在cocos2d-js自动绑定coccostudio输出的ui文件呢?

三. 名命约定

1. 代码名命约定

根据cocos2d-js代码风格,我们约定:

(1)类成员变量以下划线 "_"开头后面接以驼峰名命格式的英文单词。

例如:_loginButton 、 _closeButton 、 _nameLabel

(2)类中的私有函数也使用同样的方式。

例如:_onLoginButtonTouchBegan: function() { ...}

2. UI命名约定

在cocostudioUI编辑器中,我们遵循上述代码中成员变量的命名规范。将需要由代码访问的控件取名为"_"开头,后面接以驼峰名命格式的英文单词。 请看下图:

3. ccui控件事件命名

ccui.Widget事件注册有两种:

1). 常规的touch事件有:

ccui.Widget.TOUCH_BEGAN   触摸开始 (按下)

ccui.Widget.TOUCH_MOVED  触摸移动 (移动)

ccui.Widget.TOUCH_ENDED  触摸结束 (抬起)

ccui.Widget.TOUCH_CANCELED  触摸取消 (一般没用)

我们使用widget.addTouchEventListener(selector, target)给控件注册触摸事件,设置回调函数.

2). 控件特殊事件:

比如CheckBox的:

ccui.CheckBox.EVENT_SELECTED

ccui.CheckBox.EVENT_UNSELECTED

又如:TextField的:

ccui.TextField.EVENT_ATTACH_WITH_IME

ccui.TextField.EVENT_DETACH_WITH_IME

ccui.TextField.EVENT_INSERT_TEXT

ccui.TextField.EVENT_DELETE_BACKWARD

这类事件需要使用widget.addEventListener(selector, target)来注册。

这里的selector就是我们的回调函数,需要我们取名并实现这个函数,事件类型是通过参数来识别的:

ctor: function() {
	this._super();
	...
	button.addTouchEventListener(this._onButtonEvent, this);
},

_onButtonEvent: function(sender, type) {
	switch(type) {
		case ccui.Widget.TOUCH_BEGAN:
			...;
			return true;
		case ccui.Widget.TOUCH_MOVED:
			...;
			break;
		case ccui.Widget.TOUCH_ENDED:
			...;
			break;
	}
}

这个"_onButtonEvent"就是我们为事件函数取的名字,如果我们按:【前缀+控件名(取掉下划线)+事件名】给控件事件函数取名

举例说明:

控件名字为:_button

事件名则为:_onButtonTouchBegan、_onButtonTouchMoved、_onButtonTouchEnded

四. 代码实现

有了上面的约定,我们可以开始实现UI的绑定了。

1. 定义一个自动绑定的控件列表,我们这里列出了常用的控件类型与事件名字。

//触摸事件
sz.UILoader.touchEvents = ["TouchBegan", "TouchMoved", "TouchEnded"];
//控件事件列表
sz.UILoader.widgetEvents = [
    //Button
    {widgetType: ccui.Button, events: sz.UILoader.touchEvents},
    //ImageView
    {widgetType: ccui.ImageView, events: sz.UILoader.touchEvents},
    //TextFiled
    {widgetType: ccui.TextField, events: ["AttachWithIME", "DetachWithIME", "InsertText", "DeleteBackward"]},
    //CheckBox
    {widgetType: ccui.CheckBox, events: ["Selected", "Unselected"]},
    //ListView
    {widgetType: ccui.ListView, events:["SelectedItem"]},
    //Panel
    {widgetType: ccui.Layout, events: sz.UILoader.touchEvents},
    //BMFont
    {widgetType: ccui.TextBMFont, events: sz.UILoader.touchEvents},
    //last must null
    null
];

这个sz.UILoader.widgetEvents数组可以根据需要自己添加需要绑定的组件。

2.逻辑流程

1). 使用loader载入ui文件并传入target为当前layer。所有事件和控件变量都将绑定到target上。

2). 遍历载入后的子节点(childNode),检查名字前缀是否以”_”开头。并且该节点类型是否在widgetEvents数组中。

3). 将childNode绑定到target上。

4). 提取childNode事件函数名,检查target是否有这些函数存在。

5). 为widgetNode注册事件响应。

6). 加载类接收到事件响应,转发事件到对应的target的事件处理函数上。

3.UI加载类的具体实现

sz.UILoader = cc.Class.extend({
    _eventPrefix: null,
    _memberPrefix: null,
    /**
     * 加载UI文件
     * @param target将  jsonFile加载出的节点绑定到的目标
     * @param jsonFile  cocostudio UI编辑器生成的json文件
     */
    widgetFromJsonFile: function(target, jsonFile, options) {
        cc.assert(target && jsonFile);
        if (!options) {
            options = {};
        }

        this._eventPrefix  =  options.eventPrefix || sz.UILoader.DEFAULT_EVENT_PREFIX;
        this._memberPrefix = options.memberPrefix || sz.UILoader.DEFAULT_MEMBER_PREFIX;
        var rootNode = ccs.uiReader.widgetFromJsonFile(jsonFile);
        if (!rootNode) {
            cc.log("Load json file failed");
        }

        target.rootNode = rootNode;
        target.addChild(rootNode);
        this._bindMenbers(rootNode, target);
    },

    /**
     * 递归对rootWidget下的子节点进行成员绑定
     * @param rootWidget
     * @param target
     * @private
     */
    _bindMenbers: function(rootWidget, target) {
        var widgetName,
            children = rootWidget.getChildren();

        var self = this;
        children.forEach(function(widget) {
            widgetName = widget.getName();

            //控件名存在,绑定到target上
            var prefix = widgetName.substr(0, self._memberPrefix.length);
            if (prefix === self._memberPrefix) {
                target[widgetName] = widget;
                self._registerWidgetEvent(target, widget);
            }

            //绑定子控件,可以实现: a._b._c._d 访问子控件
            if (!rootWidget[widgetName]) {
                rootWidget[widgetName] = widget;
            }

            //如果还有子节点,递归进去
            if (widget.getChildrenCount()) {
                self._bindMenbers(widget, target);
            }

        });
    },

    /**
     * 获取控件事件
     * @param widget
     * @returns {*}
     */
    _getWidgetEvent: function(widget) {
        var bindWidgetEvent = null;
        var events = sz.UILoader.widgetEvents;
        for (var i = 0; i < events.length; i++) {
            bindWidgetEvent = events[i];
            if (widget instanceof bindWidgetEvent.widgetType) {
                break;
            }
        }
        return bindWidgetEvent;
    },

    /**
     * 注册控件事件
     * @param target
     * @param widget
     * @private
     */
    _registerWidgetEvent: function(target, widget) {
        var name = widget.getName();

        //截取前缀,首字母大写
        var newName = name[this._memberPrefix.length].toUpperCase() + name.slice(this._memberPrefix.length + 1);
        var eventName = this._eventPrefix + newName + "Event";
        var isBindEvent = false;

        if (target[eventName]) {
            isBindEvent = true;
        } else {
            //取事控件件名
            var widgetEvent = this._getWidgetEvent(widget);
            if (!widgetEvent) {
                return;
            }
            //检查事函数,生成事件名数组
            var eventNameArray = [];
            for (var i = 0; i < widgetEvent.events.length; i++) {
                eventName = this._eventPrefix + newName + widgetEvent.events[i];
                eventNameArray.push(eventName);
                if (cc.isFunction(target[eventName])) {
                    isBindEvent = true;
                }
            }
        }

        //事件响应函数
        var self = this;
        var eventFunc = function(sender, type) {
            var callBack;
            if (eventNameArray) {
                var funcName = eventNameArray[type];
                callBack = target[funcName];
            } else {
                callBack = target[eventName];
            }

            if (self._onWidgetEvent) {
                self._onWidgetEvent(sender, type);
            }

            if (callBack) {
                return callBack.call(target, sender, type);
            }
        };

        //注册事件监听
        if (isBindEvent) {
            widget.setTouchEnabled(true);
            if (widget.addEventListener) {
                widget.addEventListener(eventFunc, target);
            } else {
                widget.addTouchEventListener(eventFunc, target);
            }
        }
    }

});
sz.uiloader = new sz.UILoader();

所有东西都已经准备好了, 我们看看怎么在具体的代码中使用:

GameLayer = cc.Layer.extend({

	ctor: function() {
		this._super();
		//加载UI文件,绑定控件和事件到this
		sz.uiloader.widgetFromJsonFile(this, "res/DemoLogin.ExportJson");
		//立即可以访问控件的属性方法了
		cc.log(this._closeButton.getName());
	},

	/*
	 * _closeButton的TouchBegan事件处理函数
	 */
	_onCloseButtonTouchBegan: function(sender) {
		cc.log("_onCloseButtonTouchBegan");
	},
});

现在看看我的客户端代码是不是比之前的简洁多了!

完整代码可以到githut下载: https://github.com/ShawnZhang2015/UILoader

时间: 2024-10-10 05:01:32

在cocos2d-js实现自动绑定cocostudioUI控件与事件的相关文章

在cocos2d-js实现自动绑定cocostudioUI控件与事件(二)

前两天有个刚学习使用cocos2d-js的同事问我,怎么实现一个功能:点击一个按钮UI显示计数加1,按住不放UI计数就不停的加. 这个功能不就是个长按事件吗?我给他描述了下实现长按事件的思路: 1. 在控件touchBegan时,使用 一次性定时器scheduleOnce传入touchLong函数,设定1秒后执行. 2.  touchLong触发时,开启schedule传入addOnce函数(ui显示计数加1),设定每0.1秒执行一次. 3.  在touchEnded时执行unschedule函

在cocos2d-js实现自动绑定cocostudioUI控件与事件(三)

一.为cc.Node类型节点注册触摸事件 演示常规方式为cc.Node类型注册触摸事件 ctor: function() { ... this._label = new ... cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, onTouchBegan: this.onTouchBegan, onTouchMoved: this.onTouchMoved, o

纯js模拟 radio和checkbox控件

代码待优化,功能实现了,不兼容ie8以上, 相同name的radio可以实现切换的操作, 分享代码,共同学习进步 <!doctype html> <html> <head> <meta charset="utf-8"> <title></title> <style> .radiobox, .checkbox { width: 10px; height: 10px; padding: 2px; borde

java android布局里的控件值 反射绑定给实体类,实体类绑定给控件,表单提交绑定很有用

注意了:根据实际情况,添加实体里字段的类型,控件类型的判断才可使用.这里控件只有TextView EditText 实体类字段只有String int类型,带值的控件添加tag ,值和实体类的字段值一致 package ice.ui.service; import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import jav

Js获取file上传控件的文件路径总结

总结一个获取file上传控件文件路径的方法 firefox由于保护机制只有文件名,不能获取完整路径. document.getElementById('file').onchange = function(){ alert(getFullPath(this)) } function getFullPath(obj){ if(!obj){return;} if(!-[1,]){obj.select();return document.selection.createRange().text;} r

winfrom中DataGridView绑定数据控件中DataGridViewCheckBoxColumn怎么选中

for (int i = 0; i < this.dataGridView1.Rows.Count; i++) { this.dataGridView1.Rows[i].Cells["CheckBoxCulums"].Value = this.checkBox1.Checked; } winfrom中DataGridView绑定数据控件中DataGridViewCheckBoxColumn怎么选中,布布扣,bubuko.com

pb自动注册ole控件

方法一:  1.手工注册OCX控件 将该控件随程序一起发布,然后,将此文件拷到windows\system,或者直接放在本运行目录,然后执行dos命令,run( "regsvr32   *.ocx ") *表示具体的文件.然后写注册表,将控件注册标志置为1,在程序开始运行时,先检查该标志,是否需要进行注册 2.自动注册OCX控件 在OCX控件中一般都包含一个DLLRegisterServer函数,可以用此函数来实现OCX控件自动注册. 例如:我们要在应用中自动注册ActiveMovie

WPFS数据绑定(要是后台类对象的属性值发生改变,通知在“client界面与之绑定的控件值”也发生改变须要实现INotitypropertyChanged接口)

WPFS数据绑定(要是后台类对象的属性值发生改变,通知在"client界面与之绑定的控件值"也发生改变须要实现INotitypropertyChanged接口) MainWindow.xaml <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="

【Javascript】JS获取ASP.NET CheckBoxList控件的Text和Value

由于在客户端用js是无法直接获取到ASP.NET的控件CheckboxList的值的,所以采用以下解解方案: 服务器端代码: public void LoadAllTags() { var tagList = tagBO.GetAllTags(); cbTagList.DataSource = tagList; cbTagList.DataTextField = "TagName"; cbTagList.DataValueField = "Id"; cbTagLis