创建flex组件


  现在,考虑一下当应用程序更改标签的字体时会发生什么。记住,Flex 的 UI 依赖于异步处理,因此任何更改都不会立即生效。在更改字体时,您可以同时创建一个新的 UITextField,或者存储一个新的字体上下文和一个表明字体已经更改的标记。当下次呈现该组件时(通常发生在下一次屏幕刷新),您就可以应用新的字体。检查表明字体状态变更的标记是否改变,然后创建一个对应的 UITextField。有可能在两次屏幕刷新之间多次更改这些属性,但这让变更处理的效率下降。更好的策略是等待下一次屏幕刷新,然后再应用更改。注意,终端用户不会发现任何变化,因为屏幕的刷新频率下降了。这是针对任何异步、事件驱动 UI 的典型用例。应用程序存储需要应用的变更、对这些变更进行排序、然后在适当时间应用它们。您可能要问,什么时候才是最佳时间。这正是 LayoutManager 处理的任务。当您收到一个对组件的 commitProperties() 的回调时,就可以确定这是再次呈现该组件之前的最后一个调用;在这个调用之后 对属性所做的任何变更都不会出现在即将发生的屏幕刷新中。这些变更必须在下一次屏幕刷新和调用 commitProperties() 时才被应用。 这些都好理解,但您可能很想知道如何告知 LayoutManager 根据组件执行验证。假设某人更改了标签的字体,然后您存储了新的字体和相关标记,现在您需要让 LayoutManager 再次调用验证阶段。您可以为相应的组件调用一个称为 invalidateProperties() 的方法来实现验证。现在,我们看看更改标签的文本所需的代码。清单 2 显示了如何处理验证阶段(为了保持简洁,省略了一些代码)。 清单 2. 处理验证阶段 public function set text(value:String): void { _text = value; textChanged = true invalidateProperties(); } 注意,_text 是临时存储新的文本值的字段,而 textChanged 是用来标记更改的标记。此外还要注意为了通知 LayoutManager 开始验证阶段而进行的 invalidateProperties() 调用。清单 3 显示了 Label 类的 commitProperties() 中的对应代码片段(注意,为了保持简洁我省略了一些代码): 清单 3. 使用 commitProperties override protected function commitProperties():void{ if (textChanged) { textField.text = _text; textChanged = false } 这个被覆盖的方法演示了当标记被设置为 true 时,如何更新 UITextField(textField),以及如何将该标记重新设置为 false 以在未来进行标记。如果您希望使用 Flex 创建定制控件,那么这是必须理解的关键点。在清单 2 和清单 3 中讨论的验证阶段展示了 Flex 呈现引擎的 3 大基础支柱之一。这也是最容易被忽视的阶段,有许多高级的组件甚至没有验证阶段。性能考虑是忽略验证阶段的部分原因。在发生更改时就立即应用它们一般不会引起错误,但是无目的、不必要地多次应用变更会导致 Flex 应用程序的性能问题(一般情况是小问题,但也可能是大问题),理解这点非常重要。 开发人员通常在两种情况下编写组件。第一种情况是,编写一个与应用程序用例紧密结合的组件。通常,这种应用程序还不适合重用,因此可以适当牺牲性能以避免组件过于复杂。第二种情况是,编写适合在框架级别使用或由其他开发人员使用的组件。对于这种情况,尽可能提高组件的性能是值得的。一般在第二种情况中才需要认真处理验证阶段。 度量阶段详解 度量阶段是另一个重要的 Flex 阶段,它比提交阶段更常用。在组件通过了验证之后,LayoutManager 将继续度量组件,使它能够显示在特定上下文中。例如,LayoutManager 可能将组件显示为特定容器的子容器。您需要使用度量阶段,因为它帮助 LayoutManager 确定父容器是否包含滚动条。一些组件可以根据其子组件正常显示所需的空间调整自身的大小。考虑 VBox 示例,它是一个垂直排列其子容器的容器。如果您没有给 VBox 分配 height 或 width 值,它就在每次添加子容器时重新调整大小。它的 height 足以包含所有垂直排列的子容器,除非您设置某些限制。Flex 包含一个称为 measure() 的保护方法,您可以用它度量组件。LayoutManager 在适当的时间(一般在下一次屏幕刷新之前)调用这个方法,您需要在这个时间点度量组件并使用 measuredWidth 和 measuredHeight 设置 height 和 width。然后,父容器使用这些属性度量它本身。 首先度量子容器 与验证阶段相反,度量阶段是自下而上的。这是因为必须在度量父容器之前度量子容器。这确保当父容器收到度量调用时,已经度量了它的子容器。到目前为止,一切进展顺利。但是,现在假设您显式地指定了组件的 height 和 width。Flex 将这些存储的值指定为 explicitWidth 或 explicitHeight 属性。Flex 将考虑您在这种情况下指定的 width 和 height 值。不过,有时给容器设置显式度量是不明智的,因为您可能不能预先知道容器的所有子容器的总大小。 对于这种情况,Flex 给超出指定界限的子容器添加一个滚动条。注意,这些滚动条仅出现在扩展 Container 类的容器上。对于其他控件,滚动条可能出现,或者 Flex 会剪切掉一部分内容区域(称为内容切除)以保持您指定的大小。 显式地设置属性值 显式地设置 width 和 height 值将导致一个有趣的问题。如果您编写一个包含其他组件的组件并希望度量容器的大小,那么了解每个子容器的 width 和 height 将有帮助。您可能很想知道怎样才能知道特定组件的大小是被显式地指定的,或通过覆盖度量方法进行度量。回想一下,度量大小包含在 measuredWidth 和 measuredHeight 等属性中,而显式定义的大小包含在 explicitWidth 和 explicitHeight 中。仅有一对度量包含实数值,而另一对度量将包含 Not-a-Numbers (NaN)。Flex 通过提供两个方法解开了这个谜团:getExplicitOrMeasuredWidth() 和 getExplicitOrMeasuredHeight()。您仅需调用这些方法,而不必担心 height 和 width 值是否被度量或显示地设置。当一个组件被度量之后,就需要调用 setActualSize() 方法,它将设置 width 和 height 属性。只要显式地设置了 height 和 width,同时也就设置了这些属性。清单 4 演示了使用显式的 width 和 height 值创建文本输入组件有多么简单。清单 4 接收 4 个属性: 清单 4. 输入 width 和 height 
  这个例子包含 4 个属性:width、height、explicitWidth 和 explicitHeight。注意,measuredWidth 和 measuredHeight 保持为 NaN。 现在,看一看清单 5,其中没有设置任何显式的 height 或 width 值: 清单 5. 组件度量 在这个例子中,组件本身提供了度量。就像在 Flex 框架的另一个地方一样,这里的 "默认" width 和 height 值被设置为 measuredWidth 和 measuredHeight。显式的值仍然保留为 NaN。无论哪种情况,getExplicitOrMeasuredWidth() 和 getExplicitOrMeasuredHeight() 都返回正确的值。类似地,Flex 为 explicitMinWidth 或 measuredMaxWidth 设置最小的大小,以为组件提供最小和最大的界限。当组件的大小超出 maxWidth 指定的界限时,滚动条将显示当前视图区域内看不到的内容。注意,当设置了显式大小时,LayoutManager 将不调用 measure()。这是有意义的,因为当您显式地定义了大小之后,就没有必要再次度量组件。 调用 invalidateSize() 将告知 LayoutManager 初始化度量阶段。LayoutManager 包含 3 个不同的队列:invalidatePropertiesQueue、invalidateSizeQueue 和 invalidateDisplayListQueue。这些队列与在生命周期的某个点上调用 invalidateProperties()、invalidateSize() 和 invalidateDisplayList() 的组件对应。然后,LayoutManager 从每个队列中处理对应的组件,并调用每个组件的 validateProperties()、validateSize() 和 validateDisplayList() 方法。然后,这些方法中的默认实现 UIComponent 将调用 commitProperties()、measure() 和 updateDisplayList()。您可以覆盖这些方法以编写定制的逻辑。 度量组件可能还涉及到度量文本。然而,文本的行为与其他控件大不相同。度量文本要求您考虑升序、降序和行距等。幸运的是,Flex 提供的一个实用程序简化了这一过程。UIComponent 公开称为 measureText() 和 measureHtmlText() 的方法帮助您度量文本控件。清单 6 显示了如何度量文本字段:  清单 6. 度量文本字段
  package components { 
  import flash.text.TextLineMetrics; 
  import mx.core.UIComponent; 
  public class MyLabel extends UIComponent { 
  private var _text : String; 
  public function set text(value : 
  String) : void { 
  _text = value; 
  invalidateSize() 
  } 
  public function get text() : String { 
  return _text 
  } 
  override protected function measure(): void { 
  if (!_text) return super.measure(); 
  //measure text here! 
  var textLineMetrics : TextLineMetrics = 
  super.measureText(_text); 
  super.setActualSize(textLineMetrics.width, 
  textLineMetrics.height); 
  } 
  这段代码创建了一个简单的扩展 UIComponent 的 MyLabel 类,因此您仅需关注度量阶段。注意,您将在设置文本时调用 invalidateSize(),它通知 LayoutManager 在下一次屏幕刷新期间将组件添加到它的 invalidateSizeQueue()。然后,LayoutManager 调用组件的 validateSize() 方法,它是在 UIComponent 中定义的。最后,validateSize() 调用您曾经覆盖的 measure() 方法。这个调用使您能够设置控件的大小。您可以简单地将 width 设置为 200,并将 height 设置为 45,然后接受最后的结果。MyLabel 类的所有实例将共享相同的大小,这似乎不是好事情,因为它首先就不支持使用类似于 Flex 的健壮 UI 开发工具。但是使用了固定大小时,您就不需要覆盖 measure() 方法,甚至可以在将组件添加到其父组件时定义 width 和 height。清单 7 显示了度量标签控件的 2 个变体。
  清单 7. 度量标签
   
  layout="vertical" 
  xmlns:components="article1.components.*"> 
   
   
   
  在这个例子中,将调用 label1 的 measure() 方法,因为您没有显式地定义它的大小。然而,label2 的 measure 没有被调用,因为您显式地定义了控件的大小。注意,Flex 的 Label 类中的 measure() 实现的功能不仅仅是调用 measureText()。您需要处理左边、右边、顶部和底部的边框填充。但基本原理是一样的。
  度量阶段是 Flex 组件的生命周期中的重要阶段。尽管这是一个重要的阶段,但您很少需要关注它,除非您从头开始构建组件。大部分定制的 Flex 组件可能忽略了这个阶段,并将它留给 Flex 处理。Flex 在处理类似的琐碎事情方面表现非常出色,让您从乏味的度量任务中解脱出来。不过,如果您需要为 Flex 框架编写一个新的组件,那么就需要精通这个阶段。
  布局阶段详解
  布局阶段是 Flex 框架的 3 大生命周期阶段中的最后一个。您需要在这个阶段上花费大量时间,以致于很难将其看作是一个阶段。所有图形工作都发生在这个阶段,包括皮肤和艺术性增强等。
  假设您拥有一个经过适当验证和度量的组件。下一步就是考虑它的布局。布局阶段处理组件的许多方面。例如,您需要在这个阶段处理所有背景图像。您还需要打包在这个阶段中需要使用皮肤的所有组件。在这个阶段中,还可以将组件移动到理想位置。再次以 VBox 为例,这是一个垂直排列的容器。当您向它添加子容器时,您必须以垂直的方式排列它们。这种排列发生在布局阶段。
  Flash 坐标系的工作原理
  我将引导您了解如何使用 Flex 将子容器移动到 VBox 中,但我们首先温习一下 Flash Player 的坐标系。任何组件的左上角都由 x 和 y 值表示。每个组件的 x 和 y 值都与它的直接父组件相关。x 轴在右边为正值,但是 y 轴在下方为正值(这与传统的上方为正值相反)。因此,如果您在 VBox 中放置了一个标签,其 x 和 y 值分别为 30 和 40,标签的左上角距右边 30 像素,并且在 VBox 左上角下方 40 像素处。每个组件都有 mouseX 和 mouseY 属性,它告诉您组件的鼠标指针的相对位置。例如,如果 mouseX 为 -46,mouseY 为 78,那么鼠标指针在组件的坐标系中为向左 46 像素,向下 78 像素。除了局部坐标系之外,Flex 还有一个全局坐标系,该坐标系从整个应用程序的左上角度量 x 和 y 坐标。这些坐标称为 globalX 和 globalY。
  Flex 管理大量更改
  在其生命周期中,一个组件经历一系列的转换、旋转、缩放、剪切和拉伸,最后才显示在屏幕上。每个组件都有一个与之关联的 matrix 属性;这个属性包含关于所有这些调整的信息。因此,x和 y 坐标、globalX 和 globalY 坐标都通过这些矩阵转换关联起来。获取 x 和 y 值通常需要向 globalX 和 globalY 应用矩阵转换。从 x 和 y 坐标获取 globalX 和 globalY 坐标值需要执行反向转换。Flex 隐藏了所有这些细节,并提供两个简化这些转换过程的方法:localToGlobal() 和 globalToLocal()。您可以正确地使用这些方法,而不用担心底层转换。但是,一定要注意:函数是在点级别上工作的,而不是组件级别。让我们再看看一个基于 VBox 的例子。清单 8 在 VBox 内部创建了一个标签:
  清单 8. 在 VBox 内部创建标签
   
   
   
  box 和 myLabel 都有 localToGlobal() 方法。假设您想要获得 label 的全局位置。您的第一个想法可能是使用 label 的父容器的 localToGlobal,因为 label 的 x 和 y 坐标都与它的父容器 box 的 x 和 y 坐标相对应。不过,myLabel 拥有相同的方法,因此您也将调用该方法。结果是这两种方法都正确,但您需要使用不同的参数调用它们。您可以使用 box.localToGlobal(new Point(myLabel.x,myLabel,y)) 或 myLabel.localToGlobal(new Point(0,0))。当您调用 box.localToGlobal(new Point(myLabel.x,myLabel.y)) 时,就是请求 Flex 提供 VBox 中的点的全局位置,其坐标为 myLabel.x 和 myLabel.y。注意,这与 myLabel 无关。
  获得精确的全局坐标
  假设 myLabel 的 x 和 y 坐标分别为 40 和 30。在这个例子中,您要求获得 VBox 中的一个点 (40,30) 的精确全局位置。在这里,将对 VBox 矩阵进行转换。myLabel 的坐标系的左上角为 (0,0) -- Flex 组件都是这样。这意味着在第二个选项中(考虑到 myLabel 的坐标系),您要求 Flex 提供在 myLabel 的坐标系中为 (0,0) 的点的全局位置。不同的方法得到相同的结果,因为它们都从不同的坐标系中选择相同的点。如果没有理解这一区别,在本文的后面可能会引起错误和困惑。
  添加工具提示
  使用 Flex 时,有几个重要的原因要求您获得全局位置。例如,考虑一个典型的用例,即为组件显示工具提示。组件提示被添加到 Flex 应用程序的 systemManager,因此它们位于全局坐标系的内部。不过,您需要为位于其父组件局部坐标系中的组件显示工具提示。因此,Flex 挑选对应组件的右下角附近的点的局部坐标,然后对其应用 "局部到全局" 的转换。最后,将绘制一个工具提示组件,并将其添加为组件的子工具提示。工具提示被添加到 Flex 应用程序的 systemManager,然后放置到将被计算的全局点上。现在考虑另一种情况,在某人单击控件之后显示一个菜单,例如浏览器左上角的 File 菜单。您不能将这样的菜单添加为显示 File 选项的标签的子元素。例如,如果您将菜单添加为标签的父元素的子元素,那么 Flex 就将控件垂直放置在该点的下面,因为这是 VBox 的默认行为。如果您将菜单添加为标签的子元素,那么菜单根本就不会显示,因为标签将仅度量它所包含的文本,然后创建一个符合文本的大小。相反,您需要将菜单添加到 systemManager,它的行为类似于弹出子控件。您可以将添加到 systemManager 的组件放置到整个应用程序区域的任何位置。要将控件准确地放置在标签的下面,您需要将所需的位置转换为全局坐标。例如,在标签的坐标系中标签的底部点为 (0,label.height)。然后您可以使用标签的 localToGlobal() 转换该点,并将菜单放置到该点上。使用 move() 方法放置菜单,它将任何组件的左上角映射到所选的位置。清单 9 显示了如何将菜单添加到标签控件。
  清单 9. 将菜单添加到标签
   
   
   
   
     

猜你喜欢

转载自pbrp68pbrp.iteye.com/blog/1574447