游戏全目录
关闭
网易游戏全目录
  • 客户端游戏
  • 手机游戏
  • 游戏辅助

# UI数据绑定

# 简介

当我们想要改变UI控件的显示,例如修改label控件显示的文字,通常有两种方法,一种是调用UI API修改,一种就是使用绑定。绑定就是将Python变量的值,与UI控件的属性关联到一起。当我们修改Python变量的数据后,UI的显示会自动刷新,不用再手动调用API去设置控件属性,从而实现了数据和渲染的统一。

UI API和绑定各有优劣,UI API更容易理解,代码也更简单。而绑定(尤其是集合绑定)拥有更好的性能,例如无尽贪婪的9×9工作台界面,若使用集合绑定可以实现第一次秒开。而使用API去一个一个设置格子的话,往往要卡1秒左右才能打开。

# 绑定流程

例如现在我们想绑定label的text属性,首先要定义一个bindings属性,格式为array(object)或object,用于定义绑定规则。

接着在其中新增一个绑定对象,定义绑定变量为#abcdefg,绑定条件为always_when_visible(可见时绑定),然后把它指定到text属性上。

复制 json
"label0": {
	"bindings": [{
		"binding_name": "#abcdefg", // 引擎会去找Python里的#abcdefg
		"binding_condition": "always_when_visible"
	}],
	"text": "#abcdefg" // 把#abcdefg的值指定到text属性上
	...
},

绑定变量必须以#开头,不要和$属性变量相混淆,$变量是纯JSON层面的变量,例如你完全可以这样写

复制 json
"label0": {
	"$my_binding_name": "#abcdefg"
	"bindings": [{
		"binding_name": "$my_binding_name",
		...
	}],
	...
},

而binding_name会对应到UI类中带有ViewBinder装饰器的Python函数,游戏会以渲染帧高频调用该函数,开发者在该函数中返回对应的Python变量即可。

binding(bind_flag, binding_name = None)

复制 python
    @ViewBinder.binding(ViewBinder.BF_BindString, '#abcdefg')
    def ReturnAbcdefg(self):
        return self.oneText # 当self.oneText改变,#abcdefg就会改变,label0控件就会自动刷新

BF_BindString用于声明绑定的数据类型是str,#abcdefg对应JSON里的binding_name。

Python侧支持的绑定分为binding单个绑定和binding_collection集合绑定,已支持绑定的数据类型如下:

绑定类型 绑定方式 解释
BF_ButtonClickUp binding 绑定按钮的Up事件
BF_ButtonClickDown binding 绑定按钮的Down事件
BF_ButtonClick binding 同时绑定Up和Down事件
BF_ButtonClickCancel binding 绑定按钮的Cancel事件(按钮down其他up)
BF_InteractButtonClick binding 绑定游戏原生的按钮点击事件
BF_BindBool binding | binding_collection 绑定Bool变量
BF_BindInt binding | binding_collection 绑定Int变量
BF_BindFloat binding | binding_collection 绑定Float变量
BF_BindString binding | binding_collection 绑定String变量
BF_BindGridSize binding 绑定GridSize变量
BF_BindColor binding | binding_collection 绑定颜色变量
BF_EditChanged binding 绑定输入框输入改变事件
BF_EditFinished binding 绑定输入框输入完成事件
BF_ToggleChanged binding 开关状态改变事件
复制 python
# 绑定类型的枚举
class ViewBinder(object):
	ButtonFilter = 0x10000000
	BF_ButtonClickUp	=	0 | ButtonFilter
	BF_ButtonClickDown	=	1 | ButtonFilter
	BF_ButtonClick		= 	2 | ButtonFilter
	BF_ButtonClickCancel= 	3
	BF_InteractButtonClick = 4
	BindFilter = 0x01000000
	BF_BindBool		= 5 | BindFilter
	BF_BindInt		= 6 | BindFilter
	BF_BindFloat	= 7 | BindFilter
	BF_BindString	= 8 | BindFilter
	BF_BindGridSize = 9 | BindFilter
	BF_BindColor	= 10 | BindFilter
	EditFilter = 0x00100000
	BF_EditChanged	= 11 | EditFilter
	BF_EditFinished	= 12 | EditFilter
    ToggleFilter = 0x00010000
	BF_ToggleChanged = 13 | ToggleFilter

# 返回值类型的枚举
class ViewRequest(object):
	Nothing = 0
	Refresh = 1 << 0
	PointerHeldEventsRequest = 1 << 1
	PointerHeldEventsCancel = 1 << 2
	Exit = 1 << 3

# 绑定对象

在继续深入学习绑定前,我们先了解bindings中的绑定对象可具有的属性。

名称 类型 默认值 描述
ignored boolean false 是否忽略该绑定
binding_type enum global 绑定类型,可选的值:
- global(全局绑定)
- view(视图绑定)
- collection(集合绑定)
- collection_details(集合详情绑定)
- none(无)
binding_condition enum none 数据绑定的条件。可选的值:
- always(持续触发)
- always_when_visible(在可见时持续触发)
- visible(当可见时触发一次)
- visibility_changed(当可见性发生改变时触发)
- once(单次触发)
- none(无)

当binding_type为global(全局绑定)时额外支持以下属性

名称 类型 默认值 描述
binding_name string 在引擎代码或Python脚本中用于对接的绑定名称,需要自己起名,支持字符串表达式
binding_name_override string "" 要映射到的的控件属性名称,具体见下文

当binding_type为collection(集合绑定)时额外支持以下属性

名称 类型 默认值 描述
binding_name string 在引擎代码或Python脚本中用于对接的绑定名称,需要自己起名,支持字符串表达式
binding_name_override string "" 要映射到的的控件属性名称,具体见下文
binding_collection_name string 在引擎或Python脚本中用于对接的集合绑定名称,需要自己起名,支持字符串表达式

当binding_type为collection_details(集合详情绑定)时额外支持以下属性

名称 类型 默认值 描述
binding_name string 在引擎代码或Python脚本中用于对接的绑定名称,需要自己起名,支持字符串表达式
binding_name_override string "" 要映射到的的控件属性名称,具体见下文
binding_collection_name string 在引擎或Python脚本中用于对接的集合绑定名称,需要自己起名,支持字符串表达式
binding_collection_prefix string "" 集合绑定的前缀

当binding_type为view(视图绑定)时额外支持以下属性

名称 类型 默认值 描述
source_control_name string "" 要查看的源控件的控件名,默认为当前控件自身
source_property_name string 要查看的源控件的绑定属性名,支持字符串表达式
target_property_name string 要将查看得到的计算值赋给的绑定属性名
resolve_sibling_scope boolean false 是否解析兄弟控件的作用域,如果为true,将在同controls中的兄弟控件/父控件中,而不是在子控件中寻找source_control_name

# 绑定的默认值

property_bag用于定义绑定变量的默认值,假如Python没有返回数据,那么变量会使用默认值。

复制
"label0": {
	"text": "#abcdefg",
	"property_bag": {
		"#abcdefg": "喜欢小星星"
	},
	...
},

这样写完后,例如Python没有返回数据,text会显示默认值喜欢小星星

# 绑定的映射

当我们在 Python 端编写了一个绑定函数,并且在 JSON 端编写了控件及其“绑定变量”时,如果二者之间的“名称”不一致,就可以在 JSON 里做一个“名字映射”,以让引擎知道“控件最终要拿哪个 Python 返回的值”来更新自身的属性值。而这个“名字映射”就由 binding_name 和 binding_name_override 共同完成。

如果直接将 binding_name 与控件需要的属性同名,那么“控件需要的属性名”和“Python 函数里声明的绑定变量名”就会一致,进而“一一对应”,不需要任何额外映射。但在实际项目中,我们可能会遇到以下需求或问题:

  1. 你的控件属性名是 #abcdefg,但 Python 中的绑定名偏偏叫 #my_python_binding,二者不一样。
  2. 界面上不止一个属性值需要使用同一个绑定变量。举例:我们想让“某个整数”既用来显示文本,又控制可见性(具体见视图绑定部分)。
  3. 你不想改 Python 里的函数名,也不想改 JSON 里已有的属性名(因为牵涉到其他位置的调用)。

这些场景下,binding_name_override 就能发挥作用:

• binding_name:用来告诉引擎“我要引用 Python 端哪个绑定变量”。
• binding_name_override:用来告诉引擎“我把这个绑定变量的值,最终要赋给 UI 的哪个属性”。

假设你原本写了一个 Python 绑定函数,返回一个字符串,名为:

复制 python
@ViewBinder.binding(ViewBinder.BF_BindString, '#my_python_binding')
def ReturnMyStr(self):
    return self.someText

而在某个 JSON 中,你的控件 label 想直接显示这个字符串。那么最直接的做法就是:

复制 json
"label0": {
  "text": "#my_python_binding",
  "bindings": [{
    "binding_name": "#my_python_binding",
    "binding_condition": "always_when_visible"
  }]
}

这样就能让“label0 的 text 属性”直接绑定到“Python 的 #my_python_binding”。这里没有用到 binding_name_override,因为 JSON 的 binding_name 和 label 的 text 属性使用的都是 #my_python_binding,一致即可。

假如在某个 JSON 中,label 的 text 写了 #abcdefg,但是 Python 端的函数叫 #my_python_binding,你又不打算重命名两端已有的名字,那么可以这样写:

复制 json
"label0": {
  "text": "#abcdefg", // 这里的#abcdefg需要手动指定给text属性
  "bindings": [{
    "binding_name": "#my_python_binding", // 引擎会去找 Python 里的 #my_python_binding
    "binding_name_override": "#abcdefg", // 把该值映射给本控件的 #abcdefg
    "binding_condition": "always_when_visible"
  }]
}

这里的#abcdefg需要手动指定给text属性,但是对于一些基础变量,如alpha、visible,以及渲染器专用绑定字段#item_id_aux、#item_custom_color等,还有之前在UI说明文档中,启用use_anchored_offset后新增的一些绑定变量,并不需要手动指定给对应的属性,可以直接绑定。

复制 json
"label0": {
	"bindings": [{
		"binding_name": "#xxxxx_visible", //对应的Python绑定名称
		"binding_name_override": "#visible", //直接映射给visible属性
		"binding_condition": "always"
	}],
	...
},

如上方代码所示,如需使用绑定控制label0的显示和隐藏,直接将Python侧的某个变量#xxxxx_visible映射给#visible即可,并不需要定义"visible": "#visible",这里我们使用binding_name_override。当然你也可以像之前绑定text一样,将一个专门的绑定变量指定给visible,反正都是多写一行。

有了 binding_name_override,你就可以让“多端命名不统一”在 JSON 层面灵活处理,而不必修改所有 Python 文件或所有 UI JSON 文件。

# 集合绑定

假如我们现在有一个grid用于背包,里面有36个格子,每个格子都可能显示不同的物品。如果使用单个绑定来控制里面的物品渲染,那就得为每个控件编写对应的绑定变量和回调函数,36个格子就得写36个函数,这无疑是极其低效的。

所以mojang开发了集合绑定,专门用来解决grid的绑定问题。和单个绑定不同的是,我们只需要定义一个绑定变量和一个回调函数,即可控制格子里的所有物品。之后网易又开发了stack_grid控件,其实就是一维的grid,也可以使用集合绑定。而剩余的其他控件不能使用,也无需使用集合绑定。

集合绑定的原理是,通过在绑定时新增一个index参数,之后引擎在调用绑定方法时,会把当前要渲染的index传给开发者,开发者根据不同的index给引擎返回不同的数据。引擎在每个渲染帧会将grid的所有index从小到大都调用一遍,比如有36个格子,每帧就会调用36遍,index从0到35,这样利用一个index避免了写36次绑定。

binding_collection(bind_flag, collection_name, binding_name = None)

复制 python
	# 演示了从物品信息字典列表里,取出count并转为str返回给引擎,用于显示物品格子右下角数字
    @ViewBinder.binding_collection(ViewBinder.BF_BindString, "abcdefg_item", "#abcdefg")
    def ReturnItemCount(self, index):
        return str(self.itemDictList[index]['count'])

使用集合绑定首先要给grid定义一个集合名,即collection_name,如abcdefg_item。

复制
"inventory_grid": {
	"type": "grid",
	"collection_name": "abcdefg_item",
	"grid_dimensions": [9, 3],
	"grid_item_template": "xxxxxxx.xxxx"
}

然后在grid_item_template对应的控件里进行集合绑定,比如现在我们有一个物品格子,里面包含一个右下角的文本控件用于展示物品数量,可以这样写:

复制
"controls": [{
	"label0": {
		"anchor_from": "bottom_right",
		"anchor_to": "bottom_right",
		"text": "#abcdefg",
		"bindings": [{
			"binding_type": "collection",
			"binding_collection_name": "abcdefg_item",
			"binding_name": "#abcdefg",
			"binding_condition": "always_when_visible"
		}],
		...
	},
	...
}]

这样就对应了上方的Python代码。以上仅作示例,实际开发建议直接继承common.container_item即可。至于物品格子中的其他控件,如耐久条、物品渲染、在编写时binding_collection_name都是一致的,这样index就都是一致的。而binding_name则会用于控制不同的属性,所以会不一样。

# 视图绑定

视图绑定用于绑定变量的灵活复用,因为无论是全局绑定还是集合绑定,最终绑定到的变量,只有控件本身可以读取。而视图绑定可以读取本控件或其他控件中的变量,并在计算后设置到本控件的变量中,使得绑定能够更加灵活。

编写视图绑定时,首先要确定想获取哪个控件的变量,然后把它的路径填在source_control_name里,不填则默认为视图绑定所在的控件自身。然后把需要读取的变量名称填在source_property_name里(支持表达式),然后要把要覆盖值的变量写在target_property_name里。这里和binding_name_override的区别在于,一旦定义了binding_name_override重载了binding_name,那么控件本身binding_name对应的变量会消失,不能被视图绑定找到。但source_control_name对应到target_property_name,并不会导致source_control_name的变量被清除。

例如耐久条渲染器(参考common.durability_bar),我们省略其他代码,这里利用视图绑定,实现了当物品不存在耐久值概念时(即#progress_bar_current_amount < 1.0)就隐藏耐久条的效果,而#progress_bar_total_amount永远设为1.0,Python只传剩余耐久比例给#progress_bar_current_amount,这样就能避免Python判断,节约了性能。

复制 json
"durability_bar": {
	"type": "custom",
	"renderer": "progress_bar_renderer",
	"property_bag": {
		"#progress_bar_total_amount": 1.0,
		...
	},
	"bindings": [
		{
			"binding_type": "view",
			"source_property_name": "(#progress_bar_current_amount < 1.0)", //需要加括号
			"target_property_name": "#touch_progress_bar_visible"
		}
		...
	]
}

如果定义了source_control_name,当resolve_sibling_scope为false(默认)时,只能选择绑定所在的本控件或controls里的子控件,如果改成true,就能在同controls中的兄弟控件和父控件中查找source_control_name了。

例如如下代码,实现了利用自定义变量#lock_mode的值,动态显示物品槽位右上角的黄色和红色角标(表示是否为锁定物品 (opens new window)

复制
"item_lock": {
	"type": "panel",
	"bindings": [{
		"binding_type": "view",
		"source_property_name": "(not (#lock_mode = 0))",
		"target_property_name": "#visible"
	}],
	"property_bag": {
		"#lock_mode": 0
	},
	"controls": [
		{"lock_red@common.container_item_lock_red": {
			"bindings": [{
				"binding_type": "view",
				"source_control_name": "item_lock",
				"resolve_sibling_scope": true,
				"source_property_name": "(#lock_mode = 1)",
				"target_property_name": "#visible"
			}]
		}},
		{"lock_yellow@common.container_item_lock_yellow": {
			"bindings": [{
				"binding_type": "view",
				"source_control_name": "item_lock",
				"resolve_sibling_scope": true,
				"source_property_name": "(#lock_mode = 2)",
				"target_property_name": "#visible"
			}]
		}}
	]
}

这里使用了表达式,这样Python端只需要传回itemDict的userData里的item_lock对应的值即可,不需要考虑具体控件的显示和隐藏。

# 效果展示

如图所示为季度mod西游:大闹天宫中图鉴的效果展示,下面我们将一步步拆解,通过一个demo复刻来详细介绍如何使用ui数据绑定。这部分是之前的人写的,讲的不是很清楚,看看就好。

分页

# toggle控件

toggle控件继承自原版的common.toggle,可以理解为开关或者选项控件,可用于设置中的单选、复选。其中我们是选用单选toggle来实现分页效果。具体mod及源码可参考示例中的DataBindingMod。

# stack_grid控件

该控件结合了stack_panel和grid,可以理解它就是一个一维(或横向扩展或纵向扩展)的grid。通过绑定collection可以快速实现类似记分牌、列表展示等功能。本例中三级分页tab列表便采用了该控件。

# UI节点树

实际上在本例中,红框中的三个部分都采用了toggle,实现了三级分页效果:

tab

整体UI的节点树如下所示:

  • guideBookPanel 根目录
    • bookToggleGroup 一级分页
      • 生物图鉴
      • 物品图鉴
    • guidePanelWrap 中间panel的根节点,生物图鉴和物品图鉴对应的是两个panel,根据当前选择的tab来显示对应panel并隐藏另一个panel
      • monsterHeaderContentPanel 生物图鉴panel
        • monsterToggleGroup 二级分页
          • 被动生物
          • 中立生物
          • 攻击生物
          • 可驯服
        • monsterGuideContentLeft 左半边panel,用于scrolling_panel展示三级分页
          • monsterToggleGrid 三级分页,通过stack_grid实现动态数据绑定的三级分页列表
            • monsterToggleBtn 三级分页tab的模板控件
        • monsterGuideContentRight 右半边panel,用于展示三级分页的详细内容,实现细节与本文内容无关,不做详细介绍,可根据自己需求进行修改
      • itemHeaderContentPanel 物品图鉴panel
        • ...物品图鉴结构同生物图鉴,不做重复介绍

# Tab分页的实现

上述效果展示中的例子,因为是一个三级分页,我们分两部分来介绍,首先来介绍一下第一级和第二级这种固定的分页,以第二级的tab为例,其JSON代码如下:

复制 json
{
    "namespace": "guideBookUI",
	"locationToggleGroup": {
        "anchor_from": "top_middle",
        "anchor_to": "top_middle",
        "type": "stack_panel",
        "orientation": "horizontal", // 扩展方向,默认是垂直,这里我们需要水平
        "layer": 1,
        "size": [ "100%", 22 ],
        "controls": [
            {
                "toggle0@guideBookUI.typeToggleBtn": {
                    "$buttonLabel": "被动生物",
                    "$toggle_group_forced_index": 0
                }
            },
            {
                "content_padding_1": {
                   "size" : [ 4, "100%" ],
                   "type" : "panel"
                }
            },
            {
                "toggle1@guideBookUI.typeToggleBtn": {
                    "$buttonLabel": "中立生物",
                    "$toggle_group_forced_index": 1
                }
            },
            {
                "content_padding_2": {
                   "size" : [ 4, "100%" ],
                   "type" : "panel"
                }
            },
            {
                "toggle2@guideBookUI.typeToggleBtn": {
                    "$buttonLabel": "攻击生物",
                    "$toggle_group_forced_index": 2
                }
            },
            {
                "content_padding_3": {
                   "size" : [ 4, "100%" ],
                   "type" : "panel"
                }
            },
            {
                "toggle3@guideBookUI.typeToggleBtn": {
                    "$buttonLabel": "可驯服",
                    "$toggle_group_forced_index": 3
                }
            }
        ]
    },
    "typeToggleBtn@common.toggle": {
        "$checked_img": "textures/ui/guide_book/checked",
        "$default_texture": "textures/ui/guide_book/btn02_unuse",
        "$pressed_texture": "textures/ui/guide_book/btn02_click",
        "$locked_texture": "textures/ui/guide_book/btn02_unuse",
        "$default_font_color": [ 0.674, 0.482, 0.361 ],
        "$pressed_font_color": [ 0.494, 0.188, 0.02 ],
        "$locked_font_color": [ 0.674, 0.482, 0.361 ],
        "$unchecked_control": "mod7CommonUI.toggle_unchecked_state", // 未选中状态
        "$checked_control": "mod7CommonUI.toggle_checked_state", // 选中状态
        "$unchecked_hover_control": "mod7CommonUI.toggle_unchecked_state", // 未选中时hover状态
        "$checked_hover_control": "mod7CommonUI.toggle_checked_state", // 选中时hover状态
        "$unchecked_locked_control": "mod7CommonUI.toggle_locked_state", // 未选中且锁定状态(不可交互)
        "$unchecked_locked_hover_control": "mod7CommonUI.toggle_locked_state", // 未选中且锁定时hover状态(不可交互)
        "$checked_locked_control": "mod7CommonUI.toggle_locked_state", // 选中且锁定状态(不可交互)
        "$checked_locked_hover_control": "mod7CommonUI.toggle_locked_state", // 选中且锁定时hover状态(不可交互)
        "anchor_from": "top_left",
        "anchor_to": "top_left",
        "size": [ 54, 22 ],
        "$radio_toggle_group": true, // 是否单选,true的时候同一个toggle_name的被视为同一个group,同一个group同一时刻只能有一个toggle被选中
        "$toggle_name": "#typeTab", // toggle绑定的group名
        "$toggle_group_default_selected": 0
    }
}

这里直接继承了ui_common.json中微软封装的toggle组件,具体字段的含义可参考注释,其中$toggle_name所绑定的参数在Python中进行管理:

复制 python
@ViewBinder.binding(view_binder.BF_ToggleChanged, "#typeTab")
def OnTypeTabChecked(self, args):
    # 二级tab切换时触发,将三级分页默认切换至第一个tab详情页
    self.mCurTypeTab = args["index"]
    self.OnMonsterToggleChecked({ "index": self.mCurMonsterIndex })

从JSON中我们可以看到toggle总共有八种状态,各种状态的详细效果可以打开游戏设置界面查看,左侧的按钮就是toggle。主要有选中、未选中两种基础状态,每种状态又有hover和非hover,然后还有锁定、未锁定,所以总共就有2^3=8种状态组合,需要不同的控件展示。由于我们这里比较简单,只考虑了选中、未选中以及锁定三种状态,所以只实现了对应的三种控制panel,其JSON如下:

复制 json
{
    "namespace" : "mod7CommonUI",
	"toggle_checked_state": {
        "$pressed_texture|default": "",
        "$pressed_font_color|default": [ 1, 1, 1 ],
        "type": "image",
        "texture": "$pressed_texture",
        "nineslice_size": 4,
        "controls": [
            {
                "toggleText": {
                    "type": "label",
                    "$buttonLabel|default": "测试",
                    "color": "$pressed_font_color",
                    "text": "$buttonLabel",
                    "layer": 3
                }
            },
            {
                "checkedImg": {
                    "type": "image",
                    "$checked_img|default": "",
                    "texture": "$checked_img",
                    "size": [ 12, 6 ],
                    "layer": 3,
                    "anchor_from": "bottom_middle",
                    "anchor_to": "bottom_middle"
                }
            }
        ],
        "layer": 10
    },
    "toggle_unchecked_state": {
        "$default_texture|default": "",
        "$default_font_color|default": [ 0, 0, 0 ],
        "type": "image",
        "texture": "$default_texture",
        "nineslice_size": 4,
        "controls": [
            {
                "toggleText": {
                    "type": "label",
                    "$buttonLabel|default": "测试",
                    "color": "$default_font_color",
                    "text": "$buttonLabel",
                    "layer": 3
                }
            }
        ],
        "layer": 1
    },
    "toggle_locked_state": {
        "$locked_texture|default": "",
        "$locked_font_color|default": [ 0, 0, 0 ],
        "type": "image",
        "texture": "$locked_texture",
        "nineslice_size": 4,
        "controls": [
            {
                "toggleText": {
                    "type": "label",
                    "offset": [ 6, 0 ],
                    "$buttonLabel|default": "测试",
                    "color": "$locked_font_color",
                    "text": "$buttonLabel",
                    "layer": 3
                }
            },
            {
                "lockedImg": {
                    "type": "image",
                    "size": [ 12, 12 ],
                    "offset": [ -8, 0 ],
                    "texture": "textures/ui/lock",
                    "color": "$locked_font_color",
                    "layer": 1
                }
            }
        ],
        "layer": 2
    }
}

这里把三种状态的label统一通过$buttonLabel来管理,这样在子类中(上面的每个具体的toggle)只用管理这一个参数就行。另外各个状态的贴图和字体颜色也是分开控制的,细心的朋友可能发现了选中状态多了个checkedImg,锁定的状态多了个lockedImg,前者是用来实现选中时按钮下方的小箭头的,而后者则是用来实现锁的图标的。是否锁定只需要设置其enabled字段就可以,也可以在Python中通过调用SetTouchEnable接口来动态设置。效果如图所示:

toggle_state

以上即为最简单的分页实现。

# 自适应tab列表

接下来以第三级分页来介绍数据绑定,我们先来看看对应的JSON:

复制 json
{
    "namespace": "guideBookUI",
    "itemToggleGrid": {
        "anchor_from": "top_middle",
        "anchor_to": "top_middle",
        "bindings": [
            {
                "binding_condition": "always",
                "binding_name": "#itemToggleGrid.item_count", // 我们通过该变量名复写引擎中对应的变量名
                "binding_name_override": "#StackGridItemsCount" // 这是引擎中的变量名,用于控制stack_grid的数量
            }
        ],
        "collection_name": "itemToggles", // 绑定的集合名
        "controls": [
            {
                "itemToggleBtn@guideBookUI.itemToggleBtn" : {} // stack_grid中的template,改变#StackGridItemsCount的值会动态克隆所有controls
            }
        ],
        "item_count": 10,
        "layer": 0,
        "orientation": "vertical", // 扩展方向,垂直
        "property_bag": {
            "#itemToggleGrid.item_count": 10 // 默认有10个item,实际根据绑定值返回实时更新
        },
        "size": [ "100%", "default" ],
        "type": "stack_grid",
        "visible": true
    },
    "itemToggleBtn@common.toggle": {
        "$unchecked_control": "guideBookUI.itemUncheckedState",
        "$checked_control": "guideBookUI.itemCheckedState",
        "$unchecked_hover_control": "guideBookUI.itemUncheckedState",
        "$checked_hover_control": "guideBookUI.itemCheckedState",
        "$unchecked_locked_control": "guideBookUI.itemUncheckedState",
        "$unchecked_locked_hover_control": "guideBookUI.itemUncheckedState",
        "$checked_locked_control": "guideBookUI.itemUncheckedState",
        "$checked_locked_hover_control": "guideBookUI.itemUncheckedState",
        "$toggle_grid_collection_name": "itemToggles", // toggle列表绑定的collection,在common.toggle中定义
        "$item_collection_name": "itemToggles", // 文字绑定的collection,在下方的itemLabel中定义
        "anchor_from": "top_left",
        "anchor_to": "top_left",
        "size": [ "100%", 16 ],
        "$radio_toggle_group": true,
        "$toggle_name": "#itemToggleGroup",
        "$toggle_group_default_selected": 0
    },
    "itemCheckedState": {
        "type": "image",
        "texture": "textures/ui/white_bg",
        "color": [ 0.7764, 0.7764, 0.7764 ],
        "nineslice_size": 1,
        "controls": [
            {
                "itemLabel@guideBookUI.itemLabel": {}
            }
        ],
        "layer": 2
    },
    "itemUncheckedState": {
        "type": "image",
        "texture": "textures/ui/white_bg",
        "color": [ 0.3647, 0.3647, 0.3647 ],
        "$label_color": [ 0.776, 0.776, 0.776],
        "nineslice_size": 1,
        "controls": [
            {
                "itemLabel@guideBookUI.itemLabel": {}
            }
        ],
        "layer": 2
    },
    "itemLabel": {
        "bindings": [
            {
                "binding_collection_name" : "$item_collection_name",
                "binding_name" : "#label_name.text",
                "binding_type" : "collection"
            }
        ],
        "$label_color|default": [ 1, 1, 1 ],
        "color": "$label_color",
        "property_bag": {
            "#label_name.text" : "ha ha"
        },
        "layer": 3,
        "text" : "#label_name.text",
        "type" : "label"
    },
}

我们先看toggleBtn的实现,与上面的二级tab相比,三级tab没有锁定的状态,因此只用了选中和未选中两种状态panel。两个panel简单改变了不同的背景颜色和字体颜色,重点在itemLabel,通过绑定collection,在Python中可以绑定对应的数据:

复制 python
@ViewBinder.binding_collection(view_binder.BF_BindString, "itemToggles", "#label_name.text")
def OnRefreshItemLabel(self, index):
    # 这里返回的文字会显示在对应index的itemLabel上
	return self.mItemData[self.mCurItemTab][index]["name"] if len(self.mItemData[self.mCurItemTab]) > index else ""

我们再来看stack_grid的实现,重点就在于collection_name的绑定和#StackGridItemsCount的绑定,这里通过#itemToggleGrid.item_count来复盖#StackGridItemsCount并在Python中实现动态绑定:

复制 python
@ViewBinder.binding(view_binder.BF_BindInt, "#itemToggleGrid.item_count")
def OnItemGridResize(self):
    # 将stack_grid的size和对应的data数量绑定起来,实现数据驱动,有多少内容显示多少
    return len(self.mItemData[self.mCurItemTab])

最后就是点击item列表之后的回调了:

复制 python
@ViewBinder.binding(view_binder.BF_ToggleChanged, "#itemToggleGroup")
def OnItemToggleChecked(self, args):
    # 当点击#itemToggleGroup中的toggle时会调用该函数,传入的args中有对应点击的index,在该回调函数中完成分页内容的设置
    toggleIndex = args["index"]
    if toggleIndex >= len(self.mItemData[self.mCurItemTab]):
    	toggleIndex = 0
    self.mCurItemIndex = toggleIndex
    itemDict = self.mItemData[self.mCurItemTab][toggleIndex]
    self.SetSprite(self.mItemIcon, itemDict["icon"])
    self.SetText(self.mItemName, itemDict["name"])
    self.SetText(self.mItemDesc, itemDict["desc"])
    self.set_control_visible(self.mItemInfoWrap, True)

# 总结

# 单选toggle

  • 通过绑定BF_ToggleChanged实现点击对应$toggle_name的toggle的回调,在回调中展示对应分页内容
  • $radio_toggle_group设置为true时为单选,同$toggle_name的toggle在同一时间只有一个toggle处于开启状态

# stack_grid数据绑定

  • 通过绑定"#itemToggleGrid.item_count"来实时更新列表的size
  • 通过集合绑定来实时更新每个index的内容,文中列举的是label的文字,同理也可以绑定image的贴图
  • demo中绑定的数据内容放在modCommon/dataBindingCfg.py中,可自行修改数据查看效果

困难

120分钟