加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

【转】从Flex中owner和parent的区别来说Flex API设计思路

发布时间:2020-12-15 03:35:06 所属栏目:百科 来源:网络整理
导读:译文原地址:http://www.smithfox.com/?e=36 英文原地址:http://opensource.adobe.com/wiki/display/flexsdk/Gumbo+DOM+Tree+API 原译文的图片挂了,而那些图片其实是详细的说明,对于文章而言不可缺少的。所以转载了译文,并添加上原译文丢失的图片。(顺

译文原地址:http://www.smithfox.com/?e=36

英文原地址:http://opensource.adobe.com/wiki/display/flexsdk/Gumbo+DOM+Tree+API

原译文的图片挂了,而那些图片其实是详细的说明,对于文章而言不可缺少的。所以转载了译文,并添加上原译文丢失的图片。(顺便吐槽51CTO的水印挡住了文字,特地把某些图片扩大了点……)


目的:

在Flex 4中有许多DOM(Document Object Model)树。他们到底是怎么组织和呈现的?

定义

图形元素(graphic element)?- 就象是矩形,路径,或是图片. 这些元素不是DisplayObject的子类; 但是它们还是需要一个DisplayObject来渲染到屏幕. (译者注: "多个图形元素可以只用一个DisplayObject来渲染")

视觉元素(visual element)?- (英文有时简称为 - "element"). 可以是一个halo组件,或是一个gumbo组件,或是一个图形元素. 视觉元素实现了接口?IVisualElement.

数据项?(英文有时简称为 - "item") - 本质上Flex中的任何事物都可以被看着数据项. 通常是指非可视化项,比如 String,Number,XMLNode,等等. 一个视觉元素也能作为数据项 -- 这要看他是怎么被看待的.

组件树?- 组件树表现了MXML文档结构. 举个简单例子,一个?Panel?包含了一个?Label. 这个例子中,Panel?和?Label?都在组件树中,但是?Panel的皮肤却不是.

布局树?- 布局树呈现了运行时的布局. 在这个树中,父亲负责呈现和布局对象,孩子则是被布局的视觉元素. ?举个简单例子,sans-serif;">Label.??这个例子中,?Label?都在布局树中,?同样Panel皮肤和皮肤中的contentGroup也是.

显示树?- Flash 底层 DisplayObject 树.

本文中的全部图的图例如下:

wKiom1RXMuCSlM3GAAKdmOBWLVo576.jpg

背景:

当你用MXML创建应用程序时,幕后发生了许多的事情,会将MXML转换成Flash显示对象. 后台有三个主要因素: 皮肤,项渲染和显示对象sharing. 前两个对开发人员是非常重要的概念; 最后一个只需要框架开发人员关注,但仍然比较重要.

皮肤:

当你初始化一个?Button,其实创建了不止一个对象. 例如:

<s:Button?/>

在布局树中的结果是:

wKioL1RXNRSwp5vcAAEMhkSbc5s873.jpg

(注: TextBox 已经更名为 Label)

一个皮肤文件被实例化了,并且加入到Button的显示列表中

Button的皮肤文件如下:

<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属性就可以得到一个ButtonSkin实例来访问Rectangle?和Label.?

如要访问Label,你可以写成:myButton.skin.getElementAt(2)或是?myButton.skin.labelDisplay.

由于labelDisplay是?Button?的 skin part,所以你可可以直接写成?myButton.labelDisplay.

同样的原则也一样适用在SkinnableContainer,?SkinnableContainer是容器所以天然就有孩子,但同时他们也是SkinnableComponent,所以也有一个皮肤以及来自皮肤的孩子.

(注: SkinnableContainer是SkinableComponent的子类)

Panel为例

<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个孩子,它的组件树象这样:

wKiom1RXOKKBLWmBAAHFdJpbZoE070.jpg

(注: TextBox 已经更名为 Label)

但是,这只是组件树. 因为皮肤的原因,sans-serif;">Panel真正布局树是这样的:

wKiom1RXOWKBxo9KAAIkTGYDtJA536.jpg

在上面这张图上有许多箭头. 需要注意的有:

  • Panel的组件孩子有: button,label,和checkbox.

  • button,和checkbox的组件父亲(owner?属性) 是?Panel.

  • button,and checkbox的布局父亲 (parent?属性)?是 Panel皮肤的contentGroup.

这意味着即使看上去Panel的孩子应该是一个button,和一个checkbox; 但实际上真正的孩子是一个panel皮肤实例. button,和 checkbox 向下变成了皮肤中contentGroup的孩子.?

有几种方法可以访问panel中Button

myPanel.getElementAt(0)?或?myPanel.contentGroup.getElementAt(0)?或?

myPanel.skin.contentGroup.getElementAt(0).

所有?SkinnableComponent 都有?skin?属性. 在?SkinnableContainer中组件的孩子实际上下推成为skin的?contentGroup的孩子.?组件树?指向编译自MXML的语义树.?Panel?例子中,只包括Panel?和他的孩子: 一个 button,一个?label,和一个checkbox. 由于皮肤,?布局树?是布局系统所实际看到的树.contentGroup的孩子).

布局树无需和所见的Flash显示列表有什么相关性. 这是因为?GraphicElement?不是天然的显示对象. 因为考虑效率的原因,他们最小化了显示对象数目(译者注: 多个GraphicElement可以在一个DisplayObject上渲染,这样DisplayObject的总数就可以大大减少).

IVisualElementContainer?定义了content?APIs. 在Spark中,?Skin,255);">GroupSkinnableContainer?实现了这个接口,持有着可视化元素. 为保持一致性,MX的?Container?也实现了这个接口,不过只是对addChild(),numChildren 等函数的封装....

这个接口使访问树变得容易了. 本质上,这个接口为容器对外暴露有它哪些孩子提供了方法. 例如,FocusManager就是这样. 该接口使得 focus manager不依赖于Group?或是其它 Spark代码(除了这个接口),MX也不必增加太多代码. 我们讨论过要不要增加这些变异的(mutation) APIs,要不要MX也实现这些接口,但我们认为这将有助有开发人员(框架开发人员) 实现所有容器(MX和Spark). 当我们看 DataGroup and SkinnableDataContainer 代码时,你会发现他们并没有实现IVisualElementContainer接口

比如?numElements?和?getElementAt().

IVisualElementContainer?持有?IVisualElements.?IVisualElement?是可视化元素的一个新接口. 它包含了一些必要的属性和方法以使容器可以增加element. 他继承自?ILayoutElement?并增加了一些其它属性.

视觉元素的parent,也就是容器,直接负责布局. 视觉元素的owner是视觉元素的逻辑持有组件. 如果一个 Button在一个SkinnableContainer里,它的parent是contentGroup而它的owner 是这个?SkinnableContainer.

请注意 ?parent?和?owner?属性类型是?DisplayObjectContainer?而不是?IVisualElementContainer. 这是因为在MX内,这些属性就是?
DisplayObjectContainer. 此外,因为?parent?属性是继承自 Flash的?DisplayObject,我们无法改变他. 我们曾讨论过为这个属性起个新名字,但最后我们认为这样不值得.

(译者注: DisplayObjectContainer是flash.display.Sprite的父类)

MX 组件

MX 组件和有上面有着相同的概念,但是大部分隐藏在后台. Spark组件则因为皮肤化就变得更加透明.

一个MX button有一个孩子,就是TextField. 这个孩子是直接通过addChild() (没有皮肤)方法加到Button的. 例如,这个Button的TextField就是Button的孩子. 所以如果你查看Button的孩子,他将返回给你这个TextField. 如果你问这个TextField父亲,他将返回这个Button.

在Spark中,一个Button只有一个孩子,皮肤对象. 皮肤对象包含了一个Label. 如果你问Button的显示对象孩子,它将告诉你它有一个孩子:皮肤. 如果你想确认Button皮肤的孩子,你应该调用皮肤对象中的方法.

容器有些难懂,它包括了组件孩子和皮肤孩子. 在MX中,Panel的显示列表包含了皮肤孩子和一个叫"contentPane"的组件孩子. panel的所有组件孩子都放到这个contentPane. 这和Spark非常象; 然而,在MX中对开发人员隐藏了太多细节. 如果你问Panel的显示列表孩子,它其实对你撒谎了,它返回你这个contentPane孩子(Panel的组件孩子). 为访问皮肤孩子,可以通过rawChildren?属性返回孩子列表. 如果你问Panel的组件孩子的它的父亲是谁,它会告诉你是这个panel,但实际上他的父亲应该是contentPane.

IVisualElementContainer接口可以让你访问孩子. 这也是Spark组件宣布谁是他的可视化孩子的方式.Group?和?SkinnableContainer?都实现了这个接口. 另外,MX的?Container?也实现了这个接口. 但那只是对显示列表APIs的一种封装,sans-serif;">IVisualElementContainer?提供了唯一的,一致的访问容器孩子的方法.

SkinnableContainer 仍然有DisplayList API(译者注: 就是在Flex 3中的操作children的函数,比如addChild). 但是,但是如果你想试图通过这些API操作 DisplayList,我们将抛出一个运行时异常.?

当你访问?numChildren?或是?getChildAt()函数,不像在MX中,Spark会如实地返回他的显示列表.?

当你调用SkinnableContainer的 "content API" (numElements,getElementAt()) ?,它将返回它的组件孩子 (contentGroup的实际的所有孩子). 要访问皮肤孩子 (就象MX组件中的"rawChildren"),你需要调用skin对象的方法. 当你问Panel组件的孩子问谁是它的parent,它会返回contentGroup?(不象MX返回这个Panel). 但是有另外一个属性会返回Panel,那就是owner. owner属性MX也有,但是在MX中和它parent属性返回的是一样. 在Spark中,owner 和 parent则指向了不同的对象.

数据项

DataGroup?和?SkinnableDataContainer?用来容纳数据项.?SkinnableContainer?用来容纳可视化元素. 一个数据容器能容纳任何东西,但特别是用来容纳非可视元素 (比如.-真正的数值). 有关数据容器重要的一点是它们支持 项渲染,就是将数据项转换为可视元素.

项渲染

DataGroup?有能力将随意的非可视化元素呈现到屏幕. 因此,项渲染器正好可以加到布局树中. 某些情况下,甚至于可视化元素,比如?UIComponents?和?GraphicElements,也被包装成项渲染器. 为向开发人员展现这个设计思路,我们考虑一下以下几个可选方案:

  1. DataGroup 和 SkinnableDataContainer 设计成叶子节点,他们的实际可视化孩子不能被访问

  2. DataGroup 和 SkinnableDataContainer 实现IVisualElementContainer接口. 当问屏幕上有几个可视化元素时,我们只返回当前屏幕正在被渲染的那些元素. Mutation APIs RTE(RuntimeException).

  3. DataGroup 和 SkinnableDataContainer 实现我们决定向DataGroup增加?"只读"的 element APIs,象numElements,sans-serif;">getElementAt(),和?getElementIndex(). 还有另一个API,sans-serif;">getItemIndicesInView()决定哪些数据项在屏幕显示.

    象MX一样,项渲染器的?owner?属性总是和组件的 owner属性是一样的. 项渲染器的?parent?属性负责渲染.

    这两个图显示了项渲染的运行.

    wKioL1RXO4uAOJ0RAAGvdJw_Wqc820.jpg


    你会注意到DataGroup?在组件树中没有孩子. 这是因为它被看着是渲染数据的叶子节点.?

    下图是DataGroup的布局树例子:

    wKiom1RXO5TDdpUkAAFxWFom22I761.jpg

    (注: TextBox 已更名为 Label)

    上面例子中,字符串不是一个可视化的元素并且需要一个项渲染器. 创建一个项渲染器包装这个字符串对象. 它的owner属性就是DataGroup. 因为设置了一个 itemRendererFunction 对象,所以 Employee Object 和其它的字符串一样都会得到处理.

    用例:

    开发人员通常只和组件树打交道. 布局和效果就像FocusManager一样和布局树打交道. 只有像Group的 DisplayObject的sharing code这样的底层的代码才和显示树打交道.

    API 说明

    public?interface?IVisualElementContainer?{
    ????public?function?get?numElements():int;
    ????public?function?getElementAt(index:int):IVisualElement;
    ????public?function?getElementIndex(element:IVisualElement):int;
    ????public?function?addElement(element:IVisualElement):IVisualElement;
    ????public?function?addElementAt(element:IVisualElement,?index:int):IVisualElement;
    ????public?function?removeElement(element:IVisualElement):IVisualElement;
    ????public?function?removeElementAt(index:int):IVisualElement;
    ????public?function?setElementIndex(element:IVisualElement,?index:int):void;
    ????public?function?swapElements(element1:IVisualElement,?element2:IVisualElement):void;
    ????public?function?swapElementsAt(index1:int,?index2:int):void;
    }
    ?
    public?interface?IVisualElement?extends?ILayoutElement?{
    ????owner:DisplayObjectContainer;
    ????parent:DisplayObjectContainer;
    ????...other?stuff?not?discussed?here...
    }
    ?
    public?class?UIComponent?implements?IVisualElement?{
    ????owner:DisplayObjectContainer;
    ????parent:DisplayObjectContainer;
    ????...other?stuff...
    }
    ?
    public?class?GraphicElement?implements?IVisualElement?{
    ????owner:DisplayObjectContainer;
    ????parent:DisplayObjectContainer;
    ????...other?stuff...
    }
    ?
    [DefaultProperty("content")]
    public?class?Group?extends?GroupBase?implements?IVisualElementContainer?{
    ????[write-only]?mxmlContent:Array;
    ????layout:ILayout;
    ????public?function?get?numElements():int;
    ????public?function?getElementAt(index:int):IVisualElement;
    ????public?function?getElementIndex(element:IVisualElement):int;
    ????public?function?addElement(element:IVisualElement):IVisualElement;
    ????public?function?addElementAt(element:IVisualElement,?index2:int):void;
    }
    ?
    public?class?Skin?extends?Group?{
    }
    ?
    public?class?SkinnableComponent?extends?UIComponent?{
    ????function?get?skin():Skin;
    ????[CSS]?function?set?skinClass:Class;
    }
    ?
    [DefaultProperty("content")]
    public?class?SkinnableContainer?extends?SkinnableContainerBase?implements?IVisualElementContainer?{
    ????[write-only]?mxmlContent:Array;
    ????public?function?get?numElements():int;
    ????public?function?getElementAt(index:int):IVisualElement;
    ????public?function?getElementIndex(element:IVisualElement):int;
    ????public?function?addElement(element:IVisualElement):IVisualElement;
    ????public?function?addElementAt(element:IVisualElement,?index2:int):void;
    ????[SkinPart]?contentGroup:Group;
    }
    ?
    public?class?Container?extends?UIComponent?implements?IVisualElementContainer?{
    ????public?function?get?numElements():int;
    ????public?function?getElementAt(index:int):IVisualElement;
    ????public?function?getElementIndex(element:IVisualElement):int;
    ????public?function?addElement(element:IVisualElement):IVisualElement;
    ????public?function?addElementAt(element:IVisualElement,?index2:int):void;
    }
    ?
    ?
    [DefaultProperty("dataProvider")]
    public?class?DataGroup?extends?UIComponent?{
    ????dataProvider:IList;
    ????itemRenderer/itemRendererFunction;
    ????layout:ILayout;
    ?
    ????public?function?get?numElements():int;
    ????public?function?getElementAt(index:int):IVisualElement;
    ????public?function?getElementIndex(element:IVisualElement):int;
    ?
    ????public?function?getItemIndicesInView():Vector.;
    }
    ?
    [DefaultProperty("dataProvider")]
    public?class?SkinnableDataContainer?extends?SkinnableContainerBase?{
    ????dataProvider:IList;
    ????layout:ILayout;
    ????itemRenderer/itemRendererFunction;
    ????[SkinPart]?dataGroup:DataGroup;
    }



    遍历这些树的样例代码

    public?function?walkTree(element:IVisualElement,?proc:Function):void
    {
    ????proc(element);
    ????if?(element?is?IVisualElementContainer)
    ????{
    ????????var?visualContainer:IVisualElementContainer?=?IVisualElementContainer(element);
    ????????for?(var?i:int?=?0;?i?<?visualContainer.numElements;?i++)
    ????????{
    ????????????walkTree(visualContainer.getElementAt(i));
    ????????}
    ????}
    ?????????
    }
    ?
    public?function?walkLayoutTree(element:IVisualElement,?proc:Function):void
    {
    ????proc(element);
    ????if?(element?is?SkinnableComponent)
    ????{
    ????????var?skin:Skin?=?SkinnableComponent(element).skin;
    ????????walkTree(skin);
    ????}
    ????else?if?(element?is?IVisualElementContainer)
    ????{
    ????????var?visualContainer:IVisualElementContainer?=?IVisualElementContainer(element);
    ????????for?(var?i:int?=?0;?i?<?visualContainer.numElements;?i++)
    ????????{
    ????????????walkTree(visualContainer.getElementAt(i));
    ????????}
    ????}
    ????//?expand?this?to?MX?and?IRawChildrenContainer?
    }
    ?
    public?function?walkUpTree(element:IVisualElement,?proc:Function):void
    {
    ????while?(element!=?null)
    ????{
    ????????proc(element);
    ????????element?=?element?.owner;
    ????}
    ?
    }
    ?
    public?function?walkUpLayoutTree(element?:IVisualElement,?proc:Function):void
    {
    ????while?(element?!=?null)
    ????{
    ????????proc(element?);
    ????????element?=?element?.parent;
    ????}
    }


    更多关于 parent/owner

    有一种看待parent?属性的方法是: "谁布局我". 如果你是一个DisplayObject,这同时也对应着你的物理显示列表parent. (GraphicElements这里做了一点伪装,因为他们并不是显示对象,但也同一个概念).

    owner属性的用途:

    • 它能告诉你在组件树(或是SkinnableContainer中的elements)中谁是你的父亲

    • 它能告诉项渲染器哪个数据容器负责他们

    • 它还用在弹出窗口,象?DateField,它告诉你谁在负责这个弹出窗口.

    看待owner属性的方式就是: 一个元素的?owner?指向了负责它的组件.

    需要做的一些变化(译者注: 这个是Flex 4的设计规格,所以应该是说给adobe开发人员听的)

    GraphicElement增加parentowner属性.?

    在适当的地方将这些属性(项渲染器和SkinnableContainer)衔接起来.

    建议主要还是创建和实现这些接口.

    已经分开了Group和DataGroup. 这就可以按完全独立的规范性工作项目来运作.

    最后,还需要做些虚拟化规范相关的工作,以实现怎样呈现这样已经渲染过的元素.

    重要的/有争议的 观点:

    • 这些不同的DOM树有些让人迷惑,我们需要向Flex开发人员做些明确的解释.

    • 我们引入owner?属性是因为这样人们可以遍历逻辑DOM树. 我们引入?parent?属性是因为这样人们可以遍历布局DOM树. 我们考虑过是否不需要parent属性,因为它的类型是DisplayObjectContainer,在将来的某个时候,parent 节点不必一定是DisplayObjectContainers. 但现在还是,sans-serif;">parent这个名字比其它名字要适合一些. 如果我们决定使容器变成非DisplayObjects,那时我们可能会贯彻到底,将所有的DisplayObject都变成是可选的l.

    • Walking the layout tree requires knowledge of?SkinnableComponent?and?Skin. This means Mustella (or other places) will need to bring these classes in (or treat them as untyped).

    • MX也实现IVisualElementContainer接口.

    • owner?属性看上去有3个不同的用途.

    • Scroller?也实现?IVisualElementContainer?接口以宣布它有一个孩子. 我们考虑过为"decorators(装饰)" 创建一个单独的接口,但我们倾向更通用这样也能处理?HDividedBoxes?. 这些"getter"方法将能在Scroller使用,其它的就抛出运行时异常.

    • 我们考虑过在gumbo容器中支持flash原生display objects,但最后还是否决了.

    • 我们需要支持新的组件工具包和那些用老的组件工具包制作的swc. 一种解决方案是always link in UIMovieClip and the other FCK classes. 这些新定义的类将会实现IVisualElement?和?IVisualElementContainer接口. 因为这些类是新定义的,它们将会覆盖老版本的基类. 另一个解决方案是只更新组件工具包而不再支持老的scw. 我们需要更多的PM的决定; 但是不管怎么样,这样类是需要更样的.

    • 我们同时有多套Flash DisplayObjectContainer APIs,他们分别继承自Group/DataGroup/SkinnableComponent (addChild(),getChildAt(),等). 为处理这个问题,所有mutation(变异的) APIs调用 (addChild(),removeChild(),swapChildren(),等...) 都将抛出运行时异常. 只有允许调用"getters" 类的方法. 我们也试图在正常API?(getChildAt,numChildren,etc...)调用时也抛出异常,但会有架构方面的问题,比如UIComponent的 removeChildAt 方法就依赖于这些API.?如果这原来是一个优先事项,我们可以在这些方法中都加入运行异常并且提供新的方法,比如 $getChildAt_SkinnableComponent之类. 然后我们在这些新API的基础上改动所有framework的代码. 这样做又有新的问题:?[Child APIs vs. Item APIs].

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读