组件失效机制(invalidation mechanism)是Flex用来提高应用性能的一项技术,组件生命周期与布局主要利用这一机制来实现,本节对该机制进行总结并探讨该机制如何实现。
1. 什么是组件失效机制
在组件的生命周期中,应用可能会改变组件的大小和位置,改变组件的属性来控制组件的显示,或者更改组件的样式和皮肤属性。比如,可能更改组件中所显示文本(Text )的字体(font)。组件中文本字体发生变化,那么组件的尺寸也可能随之变化,这就会影响到组件的布局。从前面的内容中我们可以知道,布局操作会使Flex自动调用自定义组件的commitProperties()、measure(), layoutChrome()以及updateDisplayList()等一系列方法。
通过程序来更改“字体(font)”这个属性值的执行速度远远快于Flex渲染图形和更新屏幕的速度。因此,应该在确定最终字体之后再更新布局。
另外,可能会有多个组件同时改变字体,组件的字体改变都可能会引起组件尺寸的变化,从而影响它们之间的相对位置。这时应该让Flex去协调布局操作,以消除冗余处理,而不是每个组件更新字体之后都执行一次布局操作。
Flex使用失效机制来同步组件的变更。正如前面所讲到的那样,Flex用一系列方法的调用来标记组件的某些东西已经发生变化,然后将其延迟到下一次屏幕更新时通过布局管理器统一调用组件的commitProperties()、measure()、layoutChrome()以及updateDisplayList()方法。
表2-1列出了组件中有关“失效(invalidation)”的方法。
表2-1 组件中“失效”方法列表
失效方法 |
描述 |
invalidateProperties() | 通知组件,以使下次屏幕更新时,它的commitProperties()方法能被调用 |
invalidateSize() | 通知组件,以使下次屏幕更新时,它的measure()方法能被调用 |
invalidateDisplayList() | 通知组件,以使下次屏幕更新时,它的layoutChrome()方法和updateDisplayList()方法能被调用 |
当组件调用失效方法时,它通知Flex,该组件已经被更新,需要重新渲染和布局。当多个组件调用失效方法时,Flex会协调这些更新,以使这些更新操作在下一次屏幕更新时一起执行。通常,组件使用者不必直接调用这些失效方法。这些失效方法被组件的setter方法或组件的其他方法根据需要进行调用。
2. Flex如何实现“失效机制”
Flex通过UIComponent的callLater()方法实现组件的“失效机制”。callLater()方法声明如下:
- public function callLater(method:Function, args:Array = null):void
callLater是一个非常重要的底层方法,callLater方法将给定的method及其参数args放入内部队列中,当下一次屏幕更新时调用内部队列中的方法。那么“下一次屏幕更新”指的是什么呢?通过研究callLater方法的源代码我们发现callLater方法主要做三件事情:
1) 首先将需要延迟调用的method方法及其参数args放入组件的内部队列中。
2) 为舞台(Stage)对象的ENTER_FRAME和RENDER事件设置侦听器,在这两个事件的侦听器中会调用在“内部队列”中延迟的方法。队列中的方法一旦被调用就会从队列中清除。
3) 调用舞台(stage)对象的invalidate()方法,这样Flash Player在显示列表渲染前能够派发RENDER事件。
因此,回顾图1-5所示的“Flash Player执行帧中ActionScript代码及渲染图形过程”,我们可以得出以下结论:如果在“用户动作”阶段的代码中调用callLater方法,则callLater方法中method参数所指定的方法将在“失效动作”阶段被调用。如果在“失效动作”阶段的代码中调用callLater方法,则callLater方法中的method参数所指定的方法将在下一帧ENTER_FRAME事件派发时调用。
代码清单2-22通过callLater方法实现了文字移动效果。调试状态下执行这段代码,查看控制台中的输出结果可以验证以上结论。
代码清单2-22 通过callLater方法实现文字移动效果
- <?xml version="1.0"?>
- <!-- CallLater.mxml -->
- <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
- enterFrame="this.onEnterFrame(event)">
- <mx:Script><![CDATA[
- [Bindable]
- public var text:String = "这是一串通过callLater方法实现滚动文字";
- [Bindable]
- //文字移动速度。
- public var speed:Number = 5;
- import flash.display.Stage
- public function initTicker():void
- {
- //从右侧开始移动文字
- theText.move( this.width+10, 0 );
- callLater(moveText);
- }
- private function onEnterFrame(event:Event):void
- {
- trace("----------------进入新一帧------------------------");
- }
- public function moveText():void
- {
- var xpos:Number = theText.x;
- if( xpos-speed+theText.width < 0 )
- {
- xpos = this.width+10; //从右侧开始移动文字
- }
- xpos -= speed;
- //每帧应当执行两次该代码,分别在进入帧和渲染显示列表前各执行一次。
- trace(speed, xpos);
- //该代码的调用将导致显示列表失效,从而RENDER事件被Flash派发。
- theText.move(xpos,0);
- // 如果下面代码在"失效动作"阶段执行,则moveText方法将 在进入新一帧时被调用。如果该代码在进入新一帧时被调用,也 就是在"用户动作"阶段被调用,则moveText方法将在"失效动作"阶 段执行,也就是在渲染显示列表前执行。
- callLater(moveText);
- }
- public function changeSpeed():void
- {
- speed = speedSelector.value;
- }
- ]]>
- </mx:Script>
- <mx:Panel title="步进器例子" width="400" height="200">
- <mx:Canvas creationComplete="initTicker()"
- horizontalScrollPolicy="off" backgroundColor="red" color="white" width="100%">
- <mx:Label id="theText" text="{text}" y="0"/>
- </mx:Canvas>
- <mx:HBox>
- <mx:Label text="速度:"/>
- <mx:HSlider minimum="1" maximum="10" value="{speed}"
- id="speedSelector" snapInterval="1" tickInterval="1"
- change="changeSpeed()"/>
- </mx:HBox>
- </mx:Panel>
- </mx:Application>
通过调试我们可以发现,在执行一帧的过程中,moveText()方法被执行两次,因为trace(speed, xpos)方法输出了两次。调试状态下,控制台输入如下所示:
//代码中的speed 绑定可能会有点问题。
- ----------------进入新一帧------------------------
- 5 1025
- ----------------进入新一帧------------------------
- 5 1020
- 5 1015
- ----进入新一帧------------------------------------
- 5 1010
- 5 1005
- ----------------进入新一帧------------------------
- ......