从Flex中owner和parent的区别来说Flex API设计思路
[译] Flex 4 Gumbo DOM Tree API - Functional and Design Specification
英文原文: Flex 4 Gumbo DOM Tree API - Functional and Design Specification
翻译的原创链接: http://www.smithfox.com/?e=36 转载请注明, 文中如果有什么错误的地方或是讲的不清楚的地方,欢迎大家留言.
这是一篇难得的Flex功能和架构技术SPEC, 耐心看完绝对有收获.
为了振作你看这个文章的兴趣, 假设你应聘Flex工作被问到了下面的几个问题:
1. Flex中owner和parent有什么区别?
2. addChild和addElement两套函数有什么不同,(不是指怎么使用不同, 而是指框架内部的设计有什么不同)?
3. <s:Rect>是GraphicElement吗, 他们为什么可以放在<s:Group>内?
4. SkinnableComponent, SkinnableContainer, Group, DataGroup以及SkinnableDataContainer有什么区别?
5. 最关键的是: 你知道smithfox吗?(哈哈)
目的
在Flex 4中有许多DOM(Document Object Model)树。他们到底是怎么组织和呈现的?
定义
图形元素(graphic element) - 就象是矩形, 路径, 或是图片. 这些元素不是DisplayObject的子类; 但是它们还是需要一个DisplayObject来渲染到屏幕. (smithfox注: "多个图形元素可以只用一个DisplayObject来渲染")
视觉元素(visual element) - (英文有时简称为 - "element"). 可以是一个halo组件, 或是一个gumbo组件, 或是一个图形元素. 视觉元素实现了接口 IVisualElement.
数据项 (英文有时简称为 - "item") - 本质上Flex中的任何事物都可以被看着数据项. 通常是指非可视化项,比如 String, Number, XMLNode, 等等. 一个视觉元素也能作为数据项 -- 这要看他是怎么被看待的.
组件树 - 组件树表现了MXML文档结构. 举个简单例子, 一个 Panel 包含了一个 Label. 这个例子中, Panel 和 Label 都在组件树中, 但是 Panel的皮肤却不是.
布局树 - 布局树呈现了运行时的布局. 在这个树中, 父亲负责呈现和布局对象, 孩子则是被布局的视觉元素. 举个简单例子, 一个 Panel 包含了一个 Label. 这个例子中, Panel 和 Label 都在布局树中, 同样Panel的皮肤和皮肤中的contentGroup也是.
显示树 - Flash 底层 DisplayObject 树.
本文中的全部图的图例如下:
背景:
当你用MXML创建应用程序时, 幕后发生了许多的事情,会将MXML转换成Flash显示对象. 后台有三个主要因素: 皮肤,项渲染和显示对象sharing. 前两个对开发人员是非常重要的概念; 最后一个只需要框架开发人员关注, 但仍然比较重要.
皮肤:
当你初始化一个 Button, 其实创建了不止一个对象. 例如:
<s:Button />
在布局树中的结果是:
(注: TextBox 已经更名为 Label)
一个皮肤文件被实例化了,并且加入到Button的显示列表中.Button的皮肤文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
<
s:Skin
xmlns:fx
=
"http://ns.adobe.com/mxml/2009"
xmlns:s
=
"library://ns.adobe.com/flex/spark"
minWidth
=
"23"
minHeight
=
"23"
>
<
fx:Metadata
>
[HostComponent("mx.components.Button")]
</
fx:Metadata
>
<
s:states
>
<
s:State
name
=
"up"
/>
<
s:State
name
=
"over"
/>
<
s:State
name
=
"down"
/>
<
s:State
name
=
"disabled"
/>
</
s:states
>
<!-- background -->
<
s:Rect
left
=
"0"
right
=
"0"
top
=
"0"
bottom
=
"0"
width
=
"70"
height
=
"23"
radiusX
=
"2"
radiusY
=
"2"
>
<
s:stroke
>
<
s:SolidColorStroke
color
=
"0x5380D0"
color.disabled
=
"0xA9C0E8"
/>
</
s:stroke
>
<
s:fill
>
<
s:SolidColor
color
=
"0xFFFFFF"
color.over
=
"0xEBF4FF"
color.down
=
"0xDEEBFF"
/>
</
s:fill
>
</
s:Rect
>
<!-- label -->
<
s:Label
id
=
"labelDisplay"
/>
</
s:Skin
>
|
尽管Button看上去是一个叶子结点, 但因为皮肤的存在, 实际上他包含了孩子. 为访问这些元素,所有SkinnableComponent对象都定义了skin属性. 这样就可以通过Button.Skin实例来访问Rectangle 和Label. 如要访问Label, 你可以写成:myButton.skin.getElementAt(2)或是 myButton.skin.labelDisplay.由于labelDisplay是 Button 的 skin part, 所以你可可以直接写成 myButton.labelDisplay.
同样的原则也一样适用在SkinnableContainer. SkinnableContainer是容器所以天然就有孩子, 但同时他们也是SkinnableComponent,所以也有一个皮肤以及来自皮肤的孩子.
(smithfox注: SkinnableContainer的确是继承自SkinableComponent, 见图)
还是以Panel为例:
1
2
3
4
5
|
<
s:Panel
>
<
s:Button
/>
<
s:Label
/>
<
s:CheckBox
/>
</
s:Panel
>
|
panel 有三个孩子: 一个button, 一个label, 和一个checkbox. 用定义在SkinnableContainer上的content APIs可以访问他们. 这些content APIs很像flash DisplayObjectContainer 的 APIs, 包括addElement(), addElementAt(), getElementAt(), getElementIndex(), 等等.... 所有方法的完整列表在稍后文档中列出.
因为 panel有3个孩子, 它的组件树象这样:
(注: TextBox 已经更名为 Label)
但是, 这只是组件树. 因为皮肤的原因, Panel真正布局树是这样的:
(注: TextBox 已经更名为 Label)
在上面这张图上有许多箭头. 需要注意的有:
- Panel的组件孩子有: button, label, 和checkbox.
- button, label, 和checkbox的组件父亲(owner 属性) 是 Panel.
- button, label, and checkbox的布局父亲 (parent 属性) 是 Panel皮肤的contentGroup.
这意味着即使看上去Panel的孩子应该是一个button, 一个label, 和一个checkbox; 但实际上真正的孩子是一个panel皮肤实例. button, label, 和 checkbox 向下变成了皮肤中contentGroup的孩子. 有几种方法可以访问panel中的Button: myPanel.getElementAt(0) or myPanel.contentGroup.getElementAt(0) or myPanel.skin.contentGroup.getElementAt(0).
所有 SkinnableComponent 都有 skin 属性. 在 SkinnableContainer中组件的孩子实际上下推成为skin的 contentGroup的孩子. 组件树 指向编译自MXML的语义树.Panel 例子中, 只包括Panel 和他的孩子: 一个 button, 一个 label, 和一个checkbox. 由于皮肤, 布局树 是布局系统所实际看到的树.Panel 例子中,包括 这个panel, panel的皮肤, 以及这个皮肤的所有孩子(皮肤中的contentGroup的孩子).
布局树无需和所见的Flash显示列表有什么相关性. 这是因为 GraphicElement 不是天然的显示对象. 因为考虑效率的原因, 他们最小化了显示对象数目(smithfox注: 多个GraphicElement可以在一个DisplayObject上渲染, 这样DisplayObject的总数就可以大大减少).
(smithfox注: GraphicElement是spark的类, 确实是少有继承层次非常少的对象, 如图:)
IVisualElementContainer 定义了content APIs. 在Spark中, Skin, Group, 和 SkinnableContainer 实现了这个接口,持有着可视化元素. 为保持一致性, MX的 Container 也实现了这个接口, 不过只是对addChild(), numChildren, 等函数的封装....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
package
mx.core
{
public
interface
IVisualElementContainer
{
//----------------------------------
// Visual Element iteration
//----------------------------------
/**
* The number of elements in this group.
*
* @return The number of visual elements in this group
*/
public
function
get
numElements():
int
;
/**
* Returns the visual element that exists at the specified index.
*
* @param index The index of the element to retrieve.
*
* @return The element at the specified index.
*
* @throws RangeError If the index position does not exist in the child list.
*/
|