Dorothy Xml编写UI 1

  Dorothy编辑器的项目现在进展到了场景编辑器的部分,因为担心项目太过复杂,用之前随意地进行编码的方式可能会导致后期难以维护。尤其是之前几个编辑器的代码都是一个.lua文件动辄上千行代码,混杂着创建UI组件,设置布局大小,颜色外观,创建交互动画特效,响应事件回调,处理编辑器业务逻辑,更新存储编辑数据,根据编辑数据更新UI界面元素等等各种工作,完全无组织地混合在每一个.lua源文件中。每次后续维护都是要粗略通读一遍代码,交给别人接手更是非常地困难,怎么说这也是一个开源项目。没有良好的组织,同伴也很难加入进来。于是有了开发这个Dorothy Xml功能的想法。
  Dorothy Xml的核心功能就是把编辑器界面代码中的关注于前端展示的代码用Xml标记语言来编写,就像微软的XAML语言类似的定位。像前文提到的创建UI组件,设置布局大小、颜色外观,创建交互动画和UI动画响应消息事件,都可以用Dorothy Xml来组织和完成。Dorothy Xml基本符合Xml规范,稍微做了些修改,有很多预定义的标签,也可以自定义新的标签。
  编写Dorothy Xml,一定要使用这个编辑器,带有语法高亮和代码补全的功能,写起来会省力一些。首先来看怎么写一个自定义的按钮吧。
  先简单地设计一下我们的按钮:

  • 应该拥有一个点击区域
  • 应该拥有按钮上的显示文本
  • 至少有正常、点击两种外观
  • 按钮的外观随着点击事件触发而切换
  • 作为独立的控件可以在多处引用并支持调整显示参数

  然后就可以开始编写了。

1.拥有一个点击区域

<!-- Button.xml -->
<MenuItem Width="60" Height="60">
</MenuItem>

  MenuItem在Dorothy中就是一个纯粹用于触发点击事件的组件,没有任何外观,需要添加为Menu组件的子节点才能发挥作用,具体用法我们后面又说。

2.拥有按钮上的显示文本

<!-- Button.xml -->
<MenuItem Width="60" Height="60">
	<LabelTTF X="30" Y="30" Text="Click Me" FontName="Arial" FontSize="16"/>
</MenuItem>

  增加LabelTTF作为MenuItem的子节点,然后补全LabelTTF的一些基本参数,在CodeEditor的补全下应该不难编写。

3.有正常和点击两种外观

<!-- Button.xml -->
<MenuItem Name="menuItem" Width="60" Height="60">
	<Action>
		<Opacity Name="fadeIn" Time="0" Alpha="1"/>
		<Opacity Name="fadeOut" Time="0.5" Alpha="0.4"/>
	</Action>

	<DrawNode X="30" Y="30" Opacity="0.4">
		<Dot Radius="30" Color="0xff00ffff"/>
	</DrawNode>
	<LabelTTF X="30" Y="30" Text="Click Me" FontName="Arial" FontSize="16"/>
</MenuItem>

  画了一个半径为30的半透明实心圆作为按钮的外观。注意用作外观的<DrawNode>被写在<LabelTTF>的前面,这样他们就会先后进行渲染,使<LabelTTF>的文字覆盖在外观的<DrawNode>上。然后还写了两个动画动作并取名字为fadeIn和fadeOut,打算用来控制按钮外观的改变。动画只能写在<Action>标签中。动画还可以可以使用<Sequence><Spawn>标签嵌套写成动画序列或动画组。

4.按钮的外观随着点击事件而切换

<!-- Button.xml -->
<MenuItem Name="menuItem" Width="60" Height="60">
	<Action>
		<Opacity Name="fadeIn" Time="0" Alpha="1"/>
		<Opacity Name="fadeOut" Time="0.5" Alpha="0.4"/>
	</Action>

	<DrawNode X="30" Y="30" Opacity="0.4">
		<Dot Radius="30" Color="0xff00ffff"/>
	</DrawNode>
	<LabelTTF X="30" Y="30" Text="Click Me" FontName="Arial" FontSize="16"/>
	
	<Slot Name="TapBegan">
		menuItem:perform(fadeIn)
	</Slot>
	<Slot Name="TapEnded">
		menuItem:perform(fadeOut)
	</Slot>
</MenuItem>

  用<Slot>标签添加对MenuItem点击事件的监听,并在标签内容中调用lua代码执行动画。给最顶层组件取名为menuItem,因为lua代码中只能引用取了名字的对象。除了<Slot>可以内嵌代码外,还有<Script>标签可以在任意位置插入执行一段代码,<Call>标签在动画执行的回调动作中调用代码。这些标签中可以编写的lua代码不限行数与内容,也不用担心><操作符影响Xml标签解析的问题。

5.让按钮支持显示参数的调整

<!-- Button.xml -->
<!-- params: X, Y, Radius, Text, FontSize -->
<MenuItem Name="menuItem" X="{ x }" Y="{ y }" Width="{ radius*2 }" Height="{ radius*2 }">
	<Action>
		<Opacity Name="fadeIn" Time="0" Alpha="1"/>
		<Opacity Name="fadeOut" Time="0.5" Alpha="0.4"/>
	</Action>

	<DrawNode X="{ radius }" Y="{ radius }" Opacity="0.4">
		<Dot Radius="{ radius }" Color="0xff00ffff"/>
	</DrawNode>
	<LabelTTF X="{ radius }" Y="{ radius }" Text="{ text }" FontName="Arial" FontSize="{ fontSize }"/>

	<Slot Name="TapBegan">
		menuItem:perform(fadeIn)
	</Slot>
	<Slot Name="TapEnded">
		menuItem:perform(fadeOut)
	</Slot>
</MenuItem>

  将固定的参数改为由大括号包裹的任意lua表达式{ expr },其中可以引用任意名称的参数变量,注意大括号必须紧靠前后双引号,否则会解析不正确,引用的参数变量建议写在组件的开头方便使用者进行了解。

6.在其他文件中引用按钮控件

<!-- MainScene.xml -->
<Scene>
	<Import Module="Button"/>

	<Menu X="100" Y="100" Width="50" Height="50">
		<Button X="25" Y="25" Radius="25" Text="Click" FontSize="14"/>
	</Menu>
</Scene>

  用<Import>标签导入Xml模块。Import的Module属性的中需要填入模块的完整路径,如果Button.xml的路径为View/Button.xml,则需要写为<Import Module="View.Button"/>。此外还可以在当前模块范围内重命名导入的模块,比如写成<Import Module="View.Button" Name="ButtonView"/>,之后的标签名称就得用ButtonView而不是Button了。
  导入模块可以设置的属性除了内置的Name和Ref属性(有特别功能),其他设置的属性都会作为首字母小写的参数变量传入模块中使用。所以一定不能漏填模块中使用的参数变量。
  这样我们的自定义按钮就完成了,要加载运行Dorothy Xml文件很简单,可以直接当成lua文件加载,比如这样:

local CCDirector = require("CCDirector")
local MainScene = require("MainScene") -- 会返回一个创建对象使用的函数
local scene = MainScene() -- 调用函数创建对象
CCDirector:run(scene)

  我们也可以用lua代码来创建按钮,比如把MainScene.xml换成MainScene.lua。

-- MainScene.lua
Dorothy()
local Button = require("Button")

return function()
	local scene = CCScene()

	local menu = CCMenu()
	menu.position = oVec2(100,100)
	menu.contentSize = CCSize(50,50)
	scene:addChild(menu)

	local button = Button{ x=25, y=25, radius=25, text="Click", fontSize=14 }
	menu:addChild(button)

	return scene
end

  事实上,Dorothy Xml是编译成lua代码执行的,以上代码正是MainScene.xml编译生成的结果。用Xml标签来写这样的lua逻辑要更结构清晰和语法简练得多,也提高了我们后期的维护性。

7.制作按钮模板

  如果我们想做多种外观不同但是功能基本相同的按钮,我们就可以想把一些固定的按钮功能做成通用的模板。在Dorothy Xml里模板可以这样做,我们把之前写的按钮改造成这样。

<!-- ButtonBase.xml -->
<!-- params: X, Y, Width, Height, Text, FontSize -->
<MenuItem X="{ x }" Y="{ y }" Width="{ width }" Height="{ height }">
	<Action>
		<Opacity Name="fadeIn" Time="0" Alpha="1"/>
		<Opacity Name="fadeOut" Time="0.5" Alpha="0.4"/>
	</Action>

	<Node Name="face" X="{ width*0.5 }" Y="{ height*0.5 }" Ref="True"/>
	<LabelTTF X="{ radius }" Y="{ radius }" Text="{ text }" FontName="Arial" FontSize="{ fontSize }"/>

	<Slot Name="TapBegan">
		face:perform(fadeIn)
	</Slot>
	<Slot Name="TapEnded">
		face:perform(fadeOut)
	</Slot>
</MenuItem>

  我们把固定的外观替换成了一个可以挂任意东西的节点<Node>,并取名字为face,注意我们还给它增加了一个叫Ref的属性,设置值为True。只有Ref属性为True的节点才会被导出这个组件,并且才能被外部直接访问。就是说这个组件提供了这个访问接口:

local buttonBase = require("ButtonBase")()
print(buttonBase.face) -- 可以获取到face节点

  接下来在Xml中使用这个模板可以这样。

<!-- SpriteButton.xml -->
<!-- params: X, Y, Text -->
<ButtonBase X="{ x }" Y="{ y }" Width="60" Height="60" Text="{ text }" FontSize="14">
	<Import Module="ButtonBase">

	<Item Name="face">
		<Sprite File="img.png"/>
	</Item>
</ButtonBase>

  我们套用了这个模板,并通过<Item>标签获取模板中导出的子组件,Item标签中的Name属性必须设置为与导出组件相同的名字face,并在这个子组件下添加了一个图片组件。Dorothy Xml规定<Import>标签必须写在最外层标签之内,但是导入的组件可以用作最外层标签。通过这样的写法,我们就可以在套用按钮模板功能之下,定制不同外观的按钮了。

标题目录