论坛首页 编程语言技术论坛

[译]从Flex中owner和parent的区别来说Flex API设计思路

浏览 2249 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-12-22   最后修改:2010-12-22

英文原文: 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的皮肤文件如下:

 

<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.

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

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


(注: 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中的ButtonmyPanel.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中, SkinGroup, 和 SkinnableContainer 实现了这个接口,持有着可视化元素. 为保持一致性, MX的 Container 也实现了这个接口, 不过只是对addChild(), numChildren, 等函数的封装....

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.
 */ 
public function getElementAt(index:int):IVisualElement

//----------------------------------
//  Visual Element addition
//----------------------------------

/**
 *  Adds a visual element to this visual container. The element is 
 *  added after all other elements and on top of all other elements.  
 *  (To add a visual element to a specific index position, use 
 *  the <code>addElementAt()</code> method.)
 * 
 *  <p>If you add a visual element object that already has a different
 *  container as a parent, the element is removed from the child 
 *  list of the other container.</p>  
 *
 *  @param element The element to add as a child of this visual container.
 *
 *  @return The element that was added to the visual container.


 * 
 *  @event elementAdded ElementExistenceChangedEvent Dispatched when 
 *  the element is added to the child list.
 * 
 *  @throws ArgumentError If the element is the same as the visual container.
 */   
public function addElement(element:IVisualElement):IVisualElement;

/**
 *  Adds a visual element to this visual container. The element is 
 *  added at the index position specified.  An index of 0 represents   
 *  the first element and the back (bottom) of the display list, unless
 *  <code>layer</code> is specified.
 * 
 *  <p>If you add a visual element object that already has a different
 *  container as a parent, the element is removed from the child 
 *  list of the other container.</p>  
 *
 *  @param element The element to add as a child of this visual container.
 * 
 *  @param index The index position to which the element is added. If 
 *  you specify a currently occupied index position, the child object 
 *  that exists at that position and all higher positions are moved 
 *  up one position in the child list.
 *
 *  @return The element that was added to the visual container.
 * 
 *  @event elementAdded ElementExistenceChangedEvent Dispatched when 
 *  the element is added to the child list.
 * 
 *  @throws ArgumentError If the element is the same as the visual container.
 * 
 *  @throws RangeError If the index position does not exist in the child list.
 */
public function addElementAt(element:IVisualElement, index:int):IVisualElement;

//----------------------------------
//  Visual Element removal
//----------------------------------

/**
 *  Removes the specified visual element from the child list of 
 *  this visual container.  The index positions of any elements 
 *  above the element in this visual container are decreased by 1.
 *
 *  @param element The element to be removed from the visual container.
 *
 *  @return The element removed from the visual container.
 * 
 *  @throws ArgumentError If the element parameter is not a child of 
 *  this visual container.
 */
public function removeElement(element:IVisualElement):IVisualElement;

/**
 *  Removes a visual element from the specified index position 
 *  in the visual container.
 *
 *  @param index The index of the element to remove.
 *
 *  @return The element removed from the visual container.
 * 
 *  @throws RangeError If the index does not exist in the child list.
 */
public function removeElementAt(index:int):IVisualElement;

//----------------------------------
//  Visual Element index
//----------------------------------

/**
 *  Returns the index position of a visual element.
 *
 *  @param element The element to identify.
 *
 *  @return The index position of the element to identify.
 * 
 *  @throws ArgumentError If the element is not a child of this visual container.
 */ 
public function getElementIndex(element:IVisualElement):int;

/**
 *  Changes the position of an existing visual element in the visual container.
 * 
 *  <p>When you call the <code>setElementIndex()</code> method and specify an 
 *  index position that is already occupied, the only positions 
 *  that change are those in between the elements's former and new position.
 *  All others will stay the same.</p>
 *
 *  <p>If a visual element is moved to an index 
 *  lower than its current index, the index of all elements in between increases
 *  by 1.  If an element is moved to an index
 *  higher than its current index, the index of all elements in between 
 *  decreases by 1.</p>
 *
 *  @param element The element for which you want to change the index number.
 * 
 *  @param index The resulting index number for the element.
 * 
 *  @throws RangeError - If the index does not exist in the child list.
 *
 *  @throws ArgumentError - If the element parameter is not a child 
 *  of this visual container.
 */
public function setElementIndex(element:IVisualElement, index:int):void;

//----------------------------------
//  Visual Element swapping
//----------------------------------

/**
*  Swaps the index of the two specified visual elements. All other elements
*  remain in the same index position.
*
*  @param element1 The first visual element.
*  @param element2 The second visual element.
*/
public function swapElements(element1 :IVisualElement, element2 :IVisualElement):void;

/**
 *  Swaps the visual elements at the two specified index 
 *  positions in the visual container.  All other visual 
 *  elements remain in the same index position.
 *
 *  @param index1 The index of the first element.
 * 
 *  @param index2 The index of the second element.
 * 
 *  @throws RangeError If either index does not exist in 
 *  the visual container.
 */
public function swapElementAt(index1:int, index2:int):void;

}
}
 

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

(smithfox注: 从Spark最终SDK中的代码可以验证, 如图)


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

////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2003-2008 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package mx.core
{

/**
 *  The IVisualElement interface defines the minimum properties and methods 
 *  required for a visual element to be laid out and displayed in a Spark container.
 */
public interface IVisualElement extends ILayoutElement
{

    /**
     *  The owner of this IVisualElement object. 
     *  By default, it is the parent of this IVisualElement object.
     *  However, if this IVisualElement object is a child component that is
     *  popped up by its parent, such as the drop-down list of a ComboBox control,
     *  the owner is the component that popped up this IVisualElement object.
     *
     *  <p>This property is not managed by Flex, but by each component.
     *  Therefore, if you use the <code>PopUpManger.createPopUp()</code> or
     *  <code>PopUpManger.addPopUp()</code> method to pop up a child component,
     *  you should set the <code>owner</code> property of the child component
     *  to the component that popped it up.</p>
     *
     *  <p>The default value is the value of the <code>parent</code> property.</p>
     */
    function get owner():DisplayObjectContainer;
    function set owner(value:DisplayObjectContainer):void;
    
    /**
     *  The parent container or component for this component.
     *  Only visual elements should have a <code>parent</code> property.
     *  Non-visual items should use another property to reference
     *  the object to which they belong.
     *  By convention, non-visual objects use an <code>owner</code>
     *  property to reference the object to which they belong.
     */
    function get parent():DisplayObjectContainer;
    
    ...OTHER STUFF NOT DISCUSSED HERE...
}
}

(smithfox注: IVisualElement接口为什么是放在mx.core包内,确实有点怪, 但这是事实, 如图)

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

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

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

未完,javaeye截断了

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics