探究 Flex 組件的生命週期

  簡介: 最爲新一代 RIA 技術的典型框架,Adobe Flex 既有傳統桌面程序的交互相應性強,健壯性以及容易編程調試的特點,又有着 Web 程序容易部署,更豐富多彩的 UI,靈活的分佈式應用等優勢。而 Flex Framework 提供的豐富的組件庫,以及健壯、規範的組件開發流程,更使得他成爲了展現層技術的首選。其中要想正確高效地開發 Flex 組件,對組件生命週期的必不可少。掌握組件的生命週期,可以方便 UI 展示、優化組件執行性能,避免內存泄露。本文將從開發人員的角度,向您介紹 Flex 組件的生命週期中的重要階段,以及每個階段在編碼過程中應該注意的問題。學習的目的是爲了最終指導實踐,所以本文還會通過一個具體的例子,講解生命週期現實應用。也許之前您一直在開發 Flex 組件,但是學習本文之後,您會做的更好。
  學習本文,您需要有一定的 Flex 編程經驗,對 Actionscript 語言有一定的瞭解。
  開始之前
  在開始本篇文章之前,首先問大家兩個問題:第一、什麼是 Flex;第二、爲什麼選擇 Flex。作爲一個有着 Flex 開發經歷的編程人員,您可能會給出很多答案:Flex 是支持 RIA 技術的開發框架;Flex 是一個開發和部署 RIA 的平臺;Flex 是 Adobe 公司的開發工具 Flex Builder 等等,其實這些解釋都不錯,因爲 Flex 本來就是對這一系列開發技術產品的概括詞。那麼爲什麼選擇 Flex 呢 ? 您肯定會給出很多 Flex 的優點,比如說:開發方便,因爲它提供了大量的組件庫可以使用;跨平臺和瀏覽器,因爲它運行在 Flash player 播放器中之中;健壯性:Flex2 及其以後的版本都基於 ActionScript 3.0,作爲 Flash Player9 的衍生產品,實現了從解釋到編譯語言的飛躍,具有更高的性能和安全性。不錯,這些印象都很對,因爲 Adobe 公司對 Flex 設計的初衷就是,提供一個透明的平臺,使得每一個基於 flash player 的開發人員(比如說 Flash 開發人員)都可以開發應用程序;又使得每一個應用程序的開發人員(比如說熟悉 java 或者 .NET 的程序員)都可以像開發普通應用那樣地開發 RIA 應用。 Flex 本質 提起 Flex 我們不得不追述其發展歷史以及兩個很重要的名詞或者說技術,那就是 Flash 和 Flash Player。Flash 是 Adobe 推出的基於時間軸的交互式矢量圖和 Web 動畫的標準。一般被大量應用於互聯網網頁的矢量動畫設計。而 Flash Player,則是運行 Flash 程序的播放器,特別是 flash player 9 之後,他通過 Action Script 3.0 和新一代的虛擬機 AVM2 帶來了更加強大的運行時功能。下面我們就來簡單類比和對比下這三者之間的關係,從而得出 Flex 的本質到底是什麼。
  Flex、Flash 和 Flash Player
  Flex 和 Flash 程序有很多相同點:他們都基於 Action Script 語言;並且都會被編譯成一個 .swf 文件。Flash Player 是運行 Flash 和 Flex 應用的播放器或者說運行時環境。他通過讀取 swf 文件裏的二進制內容同 Flex 或者 Flash 應用進行交互,從而來指示他們執行某些操作,比如說加載圖片,繪製圖形,發送 HTTP 請求等等。Flash Player 提供了所有可以執行的操作的 API,而 Flash 和 Flex 應用只能夠做 Flash Player 允許他們做的事情。所以 Flash player 環境是 Flex 和 Flash 應用開發的基礎,Flex 的很多類都是 Flash player API 的子類。
  Flex 和 Flash 應用都運行在相同的 Flash Player 裏,具有着同樣的用戶體驗。因爲無論哪種應用程序,他們都只包含指令,需要由 Flash Player 來執行這些指令。所以,Flash 和 Flex 程序的區別並不是內容不同,而是他們創建內容的方式不同。
  基於幀的 Flex
  基於以上闡述,我們知道了 Flex 程序的本質就是 Flash,他是面相程序員的 Flash 變種。Flex 程序的根 mx.managers.SystemManager 就是 Flash 類 flash.display.MovieClip 的擴展。所以,Flex 也可以說成是基於時間軸的,只不過他在時間軸上的幀只有兩幀:第一幀是預加載,由 Flex 框架控制,也就是我們在 Application 運行之初看到的進度條,被稱之爲 Preloader( 如圖 1 所示 );第二幀,就是我們真正的應用程序。瞭解這點對於我們理解 flex 組件的生命週期至關重要,因爲幀的執行模式有一個重要的特點,那就是在一幀中會先執行腳本後渲染屏幕,即通常稱爲的 Elastic Racetrack 模型(如圖 2 所示)。 這個模型預示着過長的代碼執行會影響屏幕渲染的用戶體驗。Flex 組件構建在幀模型基礎上的,需要同樣遵行幀的這種執行模式。 圖 1. Flex 程序第一幀-- Preloader
  
  圖 2. Elastic Racetrack 幀加載模型
  
  Flex 組件生命週期概述
  書歸正傳,下面我們就來介紹 Flex 的生命週期。
  首先,Flex 組建的生命週期是什麼?它是指 Flex 框架如何同每一個 Flex 組件進行交互,通過什麼方法來來控制 Flex 組件的產生、刷新和銷燬,以及各個組件又是如何和外界進行通訊的。概括地說,Flex 組件的生命週期可以總結爲兩個模式、三個時期、四個方法、五個事件、七個階段。
  同客觀世界的物質一樣,Flex 組件也有着從出生到銷燬的三個時期:初生階段、生長階段、銷燬階段。每個階段又會經歷若干個重要步驟包括:構造、準備、裝配、驗證、提交、交互、拆卸和回收。其中,有些步驟組件在一生中只會經歷一次,有的則會伴隨生命週期重複若干。這些步驟會通過四個重要方法負責實現。那麼 Flex 組件如何通知 Flex Engineer 當前處於哪個步驟,又如何告訴變成開發人員當前的狀態如何呢?他會通過派發五個不同的事件來進行內外交互;並通過驗證 - 提交模式(invitation-validation pattern)來響應更新,從而實現了一個延遲渲染的鬆耦合的事件模型。 爲了能夠更加容易理解一下的講解,讓我們來舉例說明,我們來從 Flex 組件的基類 UIComponent 出發創建一個簡單的圖片查看器 ImageViewer(如下圖 3 所示), 然後以此爲例分別講解各個時期裏,Flex 框架對該組建都做了什麼。 圖 3. 一個簡單的 Flex 組件 ImageViewer
  
  出生時期
  組件的出生階段包括三個步驟:構造,配置,裝配和初始化。(分別對應了 4 個 protected 方法,constructor、createChildren,commitProperties,measure 和 updatedisplayList)
  構造函數(Constructor) 編程建議一: 通常對組件本身(並不是組件子元素)的事件監聽器會在構造函數裏註冊。我們通常也不再構造函數裏添加子元素 , 在下面的篇章裏會講解我們爲什麼不這樣做。
  首先組件從構造函數(construct)開始出生。當您使用 new 操作符(如清單 1 所示)或者在 mxml 裏聲明一個組件的時候,構造函數就被調用了。通過調用構造函數,一個組件類的實例被創建在了內存裏。但是僅此而已。因爲他是組件生命週期的最早部分,此時組件中的子元素還沒有被創建,屬性值也不明,所以,通常我們在這一階段裏所做的事情很少;但是他也是一個爲組件添加時間監聽器的好地方,因爲一個組件的構造函數會且只會被調用一次。 清單 1. 構造函數被調用 private var myImageView : ImageViewer = new ImageViewer(); 配置階段(get & set) 初生階段的第二個步驟是配置。在組件中定義的一些特性(properties)會在這個階段通過 set 方法被賦值。但是請注意,此時組件的子元素仍然沒有被創建,所以如果組件的某個屬性的 set 方法裏涉及到了對子元素或者其屬性的操作的話,請格外留意。如清單 2 所示,假設我們把 ImageViewer 組件放到一個 Panel 容器裏 (),此時代碼的執行順序如下: 清單 2. 配置階段的執行順序 輸出順序: Panel : constructor Panel.with : setter ImageViewer Calendar : constructor ImageViewer.imageHeight :setter 所以 Adobe 建議開發人員在配置階段只緩存特性值,而把業務邏輯推遲到到以後的驗證階段(參見清單 3)。這就是之前提到的驗證 - 提交模式(invitation-validation pattern),關於這一概念我們會在下面的章節做詳細說明。 編程建議二: 使用組件的 set 方法是緩存特性值。將真正的業務邏輯推遲到提交階段。
  清單 3. 例子 ImageViewer 的 set imageHeight() 方法 public function set imageHeight(value:Number): void { if (_imageHeight != value) { _imageHeight = value; imageHeightChanged = true ; this .invalidateDisplayList() dispatchEvent( new Event( "imageHeightChanged" )); } } 裝配階段(addChild) 組件被構造以及賦值之後,並不會自動進入整個生命週期的循環,他必須經過裝配時期,及組件自身裝配到顯示列表(Display List)上。組件只有通過 addChild 或者 addChildAt 方法被轉配到 Display List 上,纔會依次進入到以下的生命週期時期,否則得話,以後的步驟和方法都不會被調用。爲此我們可以做這樣一個實驗。我們在組件代碼裏添加 trace(清單 4),然後再分別執行應用程序 Test1.mxml(清單 5)和 Test1.mxml(清單 6),再來 對兩個輸出。 清單 4. 驗證裝配階段 ImageViewer 組件的執行順序 package dw.flex.sample
  {
  import mx.core.UIComponent;
  public
  class ImageViewer extends UIComponent
  {
  public
  function ImageViewer()
  {
  trace( "constructor" );
  super();
  }
  override
  protected
  function createChildren(): void
  {
  trace( "createChildren" );
  super.createChildren()
  }
  override
  protected function commitProperties(): void
  {
  trace( "commitProperties" );
  super.createChildren()
  }
  override
  protected function measure(): void
  {
  trace( "measure" );
  super.createChildren()
  }
  override
  protected function updateDisplayList(w:Number, h:Number): void
  {
  trace( "createChildren" );
  super.updateDisplayList(w,h)
  }
  }
  }
  package dw.flex.sample { import mx.core.UIComponent; public class ImageViewer extends UIComponent { public function ImageViewer() { trace( "constructor" ); super(); } override protected function createChildren(): void { trace( "createChildren" ); super.createChildren() } override protected function commitProperties(): void { trace( "commitProperties" ); super.createChildren() } override protected function measure(): void { trace( "measure" ); super.createChildren() } override protected function updateDisplayList(w:Number, h:Number): void { trace( "createChildren" ); super.updateDisplayList(w,h) } } } 清單 5. 應用程序 Test1 及其輸出
  應用程序 Test1.mxml 輸出: 編程建議三:推遲組件的裝配(使用 addChild 方法將組建添加到父節點上),除非您真的需要他。
  清單 6. 應用程序 Test2 及其輸出
  應用程序 Test2.mxml
  
  
  
  
  
   輸出: constructor createChildren commitProperties measure createChildren 初始化階段(Initialization)
  初始化階段發生在裝配之後,這個階段包含了 3 個子階段,派發 3 個事件。
  組件派發 Preinitialize 事件。這個階段意味着組件已經被添加到了顯示列表(DisplayList)上。
  調用 protected 方法 createChildren() 來生成子元素。在這個階段裏,您可以覆蓋這個方法添加需要的子元素。
  派發 initialize 事件。這意味着組件及其所有的子元素都已經被創建、配置裝備到了 DisplayList 上。
  進入第一次驗證和提交階段。Flex 框架會通過 layoutManager 引擎來依次調用組件的三個驗證方法 invalidateProperties,invalidateSize() 和 invalidateDisplayList(). 以及其其分別對應的三個提交方法 CommitProperties(),measure() 和 updateDisplayList()。關於這三組方法究竟都是做什麼的,我們會在以下的驗證階段詳細介紹。
  最後配發 creationComplete 事件。
  至此,組件的初始化階段完成,同時也意味着 Flex 組件的出生時期結束了。總結以上個步驟,我們用圖 4 來標註組件出生時期的流程圖。 編程提示四:覆蓋 createChildren 方法來添加子節點,此方法會且只會被執行一次。儘量把創建動態形式和數據驅動(data-driven)形式的子元素推遲到 CommitProperties() 方法裏添加或者執行。
  圖 4. 組件出生時前交互流程
  
  生活時期
  組件經過出生時期之後就進入了生活時期,這個時期意味着組件可以被看見,被操作,甚至被刪除。並且作爲一個成熟的個體,組件可以和外界進行交互,對更新請求進行響應。Flex 組件使用 Invalidation Validation 模式來響應更新請求。
  驗證 - 提交模式(Invalidation -Validation Pattern)
  Flex 的生命週期是建立的幀模型基礎之上的,所以同樣遵循着先執行腳本後渲染屏幕的規則。爲了達到流暢的視覺體驗,我們通常期望能夠儘量減少代碼執行的消耗,而給畫面渲染留下充足的時間,爲了實現這點 Flex 使用了 Invalidation Validation 模式,來實現資源的有效利用。 圖 5. Invalidation-Validation 模式
  
  Invalidation Validation 模式即驗證提交 - 模式提供了一個低耦合的處理過程,將數據的改變(舊數據的實效)和數據的處理(對新數據的重新使用)區分開來,這樣做的好處是: 只在有屏幕刷新需求的時候的時候進行相應數據操作和計算;
  並且避免了不必要的重複渲染。
  以清單 7 爲例,通過 Invalidation Validation 模式,第二行的代碼的結果永遠不會生效,即不會也從來沒有顯示到屏幕上。 清單 7. 相同屬性被多次賦值的例子 myImageView.height = 50; myImageView. height = 100; 那 Flex 框架是如何實現這一模式的呢?讓我們來看一下 Flex 是如何將這一模式應用到組件上的。
  首先我們來看一下 Flex 可視化組件基類 UIComponent 是如和處理 set width() 方法的。 清單 8. UIComponent 的 set width ()方法 public function set width(value:Number):void { if(explicitWidth != value) { explicitWidth = value; invalidateSize(); } // 其他代碼省略 } 從清單 8 我們可以看出,UIComponent 在首先會去判斷屬性賦值是否改變,從而避免重複賦值帶來的消耗,然後調用 protected 方法 invalidateSize()。
  invalidateSize()是 UIComponent 提供的驗證方法之一,它用來校驗與組件尺寸大小相關的屬性,當此方法被調用後,Flex 組件引擎 LayoutManager 會首先檢查該屬性的更新請求是否已經調用過 invalidateSize()方法,如果調用過,說明此類型的更改請求已經記錄在案,無需贅述。如果沒有,則會將此次請求記錄到 LayoutManager 校驗隊列上進行等待。那麼對 width 屬性值的更新什麼時候生效呢? LayoutManager 會在監聽到下一次 RENDER 事件派發的時候,將組件推送到提交階段。
  在提交階段,LayoutManager 得到更新請求隊列,調用 commitProperties()方法使得屬性 width 的最新值(假設記錄在了 _width 私有變量上)得以生效。Flex 就是通過這種屬性值延遲生效的方法來保證每次用於渲染畫面的請求都是最新的,從而避免了不必要的資源浪費。 因此,在我們的例子 ImageViewer 裏,我們也使用了類似機制。(如清單 9 所示),只不過這裏我們調用的是 invalidateProterties()方法。與 invalidateSize()類似,組件的 scale 屬性值會在提交階段通過調用 commitProperties()方法生效。 清單 9. ImageViewer 的 set scale 方法 public function set scale(value : Number): void { if (_scale != value) { _scale = value; scaleChanged = true ; invalidateProperties(); dispatchEvent( new Event( "scaleChanged" )); } } 驗證階段(Invalidation)
  驗證階段是組件在生活要經歷的第一步,具體地說是響應更新請求的第一步。作爲 invalidation-validation 循環的一份子他會在組件的生命週期中多次執行。Flex 組件基類 UIcomponent 提供了三種驗證方法:invalidateProperties()、invalidateSize()和 invalidateDisplayList() 來分別響應組件對值相關,佈局相關和顯示相關屬性的更新請求。
  組件會通過兩種方式進入到驗證階段:
  第一種方式是在初始化階段,通過父節點調用 childrenCreated() 方法,組件進入第一次 Invalidation validation 循環。這一次,三種驗證方法都會被自動調用以確保組件初始化成功。這類調用在組件的生命週期內只會經歷一次。
  第二種方式是響應更新請求。組件的任何一方面特性發生更新請求後,都會進入到驗證階段。而此時,用戶需要根據特性的不同類別來自行決定調用哪種驗證方法。還是以清單 9 爲例,通過調用 invalidateProperties()方法,確保了在接下來的提交階段,更新會對值計算生效。
  在這個時期,我們通常不去做太多的事情,因爲此時只是驗證階段,更新並不生效。我們所要做的只是記錄此次更新,並且等待進入提交階段。 編程提示五:響應組件更新請求時,調用三種驗證方(invalidateProperties()、invalidateSize()、 invalidateDisplayList()),而不要顯試調用提交方法(commitProperties(),measure()和 updateDisplayList())。Flex 框架會在最合適的時候自行調用提交方法。
  提交階段(Validation)
  通過以上的介紹,我們已經很清楚組件更新會在提交階段實際生效。和驗證階段的隨時更新隨時校驗不同,Flex 框架爲提交階段設計了線性執行的順序,將三個提交方法分爲了三個階段。順序調用三個提交方法:commitProperties(),measure()和 updateDisplayList()。這樣做的目的和每個方法的功能有關。commitProperties()方法主要負責屬性值的計算和生效,因此首先執行,因爲這些自己算過的值可能用於佈局,也可能用於顯示。這裏也可以用來移除不需要的子節點,但是您需要通過標誌位來判斷子節點是否存在。
  measure()方法用於根據組件的內容大小計算組件尺寸。由於尺寸的計算是自下而上的,所組件子元素的大小改變都會隱試的觸發此方法。
  UpdatedisplayList() 方法用來更新顯示列表(Display List)的佈局以及渲染。組件的佈局是一個自上而下的過程,所以在這個方法裏您需要對不僅僅是組件設置佈局屬性,而且也要對子元素進行相應的調整。這裏也是使用 Flash 繪圖 API 的好地方。
  這裏,我們所要做的就是覆蓋(override)這幾個方法,在正確的時間、正確的地方做正確的事。 編程提示六:通過挑選並重寫合適的提交方法,您可以把複雜的邏輯處理延遲到提交階段通過設置標誌位,可以進一步優化提交階段的更新性能。
  交互階段(Interaction)
  交互嚴格地說組件生命週期中的某種行爲,他是促使組件更新,推動驗證 - 提交循環的動力。
  Flex 使用事件驅動的交互模式,來實現一種完全鬆耦合的體系結構。簡單地說,任何組件如果想要告訴外界當前發生了什麼事或者即將發什麼事的話,他可以派發一個事件,那麼在該類事件可及範圍內的任何組件都可以通過註冊該事件的監聽器的方式來對此類事件進行相應。關於 Flex 在事件機制處理方面信息的由於超出了本文的範圍,這裏不再多講,感興趣的讀者可以關注後續教程《探索 Flex 的事件機制》,或者閱讀資料部分的相應文檔。
  一般來說,組件的交互行爲有以下幾種:
  用戶同組件的交互,比如說:輸入數據,縮放大小等等。
  通過派發和響應事件。
  應用(Application)或者父節點(parent)與組件的交互。比如說 ItemRenderer 實例和宿主對象之間的 data 傳遞。(關於 ItemRenderer 機制和實踐講解,也會有後續教程加以探討) 銷燬時期
  任何事物都會有一個歸宿,Flex 組件也不例外。當某個組件不再需要的時候,我們需要把他銷燬。(這聽起來有點殘酷,但是我們不得不這麼做)
  拆卸階段(removeChild)
  銷燬組件的一種方法是通過調用組件父親節點(parent)的 removeChild()方法,該方法會將他從顯示列表(Display List)上拆卸掉,並且檢查是否存在對此組件的引用,如果沒有的話,組件會被標記爲"可以回收",這預示着組件進入到了回收階段,並且可以被 AVM 垃圾回收。
  回收階段(GC)
  剛纔我麼提到了通過 removeChild()方法將組建拆卸掉以後,組件可以被垃圾回收。這意味着該組件的實例會被從內存中完全刪除掉,並且釋放資源。但是請注意,垃圾回收並不一定在此刻馬上發生,AVM 有着自己的垃圾回收時間。因此這個打了標籤的組件需要等待回收時刻的到來。
  拆卸階段並不是組件銷燬的必經階段。當某個組件被拆卸掉之後,如果該組件包含了子組件,而他們都不存在外界引用的話,所有的元素都會被標記爲"可以回收",也就是說該系統中的子組件並不需要進入到拆卸階段,也可以在回收時刻到來的時候被 AVM 回收掉。那麼開發人員所需要注意的就是,在這個時刻發生之前,將引用去除掉。 實際編程中的應用
  到目前爲止我們學習的多是 Flex 生命週期的理論。學習理論的最終目的就是知道編程實踐,現在我們來看一下生命週期是如何在例子 ImageViewer 裏是被貫徹執行的。
  關於 createChildren()方法
  清單 10 顯示了組件 ImageViewer 的 createChildren() 方法。正如大家注意的那樣,在創建每一個子節點的時候,首先判斷該節點是否存在是一個很好的習慣,這樣可以防止子節點在某種情況下已被實例化。是的,這種情況是可能發生的。以清單 11 裏的 Table 類爲例, Table 裏的子節點 row 有一個 set()方法,而我們從"初生階段"裏知道,裝配階段(set 方法調用)要早於初始化階段,那麼請大家思考一下清單 12 的執行結果會是如何。 清單 10. ImageViewer 的 createChildren() 方法 override
  protected
  function createChildren(): void
  {
  super.createChildren();
  if (! this.border){
  createBorder();
  }
  if (!controlBar){
  controlBar = new UIComponent();
  }
  if (!zoomInButton){
  zoomInButton = new Button();
  zoomInButton.label = "+" ;
  zoomInButton.addEventListener(MouseEvent.CLICK,
  zoomInButtonClickHandler);
  controlBar.addChild(zoomInButton);
  }
  if (!zoomOutButton){
  zoomOutButton = new Button();
  zoomOutButton.label = "-" ;
  zoomOutButton.addEventListener(MouseEvent.CLICK,
  zoomOutButtonClickHandler);
  controlBar.addChild(zoomOutButton);
  }
  // added controlBar the last time
  addChild(controlBar);
  }
  override protected function createChildren(): void { super.createChildren(); if (! this.border){ createBorder(); } if (!controlBar){ controlBar = new UIComponent(); } if (!zoomInButton){ zoomInButton = new Button(); zoomInButton.label = "+" ; zoomInButton.addEventListener(MouseEvent.CLICK, zoomInButtonClickHandler); controlBar.addChild(zoomInButton); } if (!zoomOutButton){ zoomOutButton = new Button(); zoomOutButton.label = "-" ; zoomOutButton.addEventListener(MouseEvent.CLICK, zoomOutButtonClickHandler); controlBar.addChild(zoomOutButton); } // added controlBar the last time addChild(controlBar); } 清單 11. Table 類的例子 public
  class Table extends TableBase
  {
  private
  var _row : Row;
  public
  function
  getrow():Row
  {
  return
  this._row;
  }
  public
  function
  setrow(r : Row): void
  {
  _row = r;
  this.invalidateDisplayList();
  }
  public
  function Table()
  {
  super();
  }
  override
  protected
  function createChildren(): void
  {
  super.createChildren();
  if (!_row){
  _row = new Row();
  }
  this.addChild(_row);
  }
  public class Table extends TableBase { private var _row : Row; public function getrow():Row { return this._row; } public function setrow(r : Row): void { _row = r; this.invalidateDisplayList(); } public function Table() { super(); } override protected function createChildren(): void { super.createChildren(); if (!_row){ _row = new Row(); } this.addChild(_row); } 清單 12. Table 和 Row 的初始化順序 var t : Table = new Table(); t.row = new Row(); addChild(t); 我們也看到 controlBar 在該方法的末尾才被添加到 Display List 上,正如之前提到的那樣,我們只在需要的時候裝配他。同時,此時也是爲子節點添加監聽器的好地方。
  關於 commitProperties()方法
  下面我們來看一下 ImageViewer 組件在 commitProperties()裏都做了什麼(如清單 13 所示)。
  CommitProperties()是驗證方法 invalidateProperties()所對應的提交方法,也是初始化階段會被調用的第一個提交方法。他的目的是使得任何通過 set 方法提交的數值更改生效。所以您可以看到在清單 9 的 set scale()方法裏,按照 invalidation-validation 模式,我們調用了 invalidateProperties()方法從而將值的生效延遲到了 commitProperties()裏,並且爲了做到 "局部更新",我們使用了標誌位 scaleChanged。
  另外,這裏這裏一個奇怪的地方,那就是爲什麼在 commitProperties()會有添加子節點的操作呢?這也是我們要特意舉例說明的一個地方。
  對於有些子節點,他的誕生可能是和某些屬性值相關聯的,也就是我們在編程提示四里提到的動態創建或者數據驅動的子節點。這些子節點,由於他們並不是隨着組件的產生而產生,而是要受屬性值的變化而產生或者變化,甚至在某些情況下,根本就不需要存在。所以我們應該在值的提交階段,也就是 commitProperties()方法調用的時候,當新值真正生效的時候再去創建它。 清單 13. ImageViewer 的 commitProperties()方法 override
  protected
  function commitProperties(): void
  {
  super.commitProperties();
  if (sourceChanged){
  if (! this.image){
  image = new Image();
  this.addChild(image);
  image.source = this._source;
  } else {
  image.source = this._source;
  }
  sourceChanged = false ;
  }
  if (scaleChanged){
  this.imageWidth = this.imageWidth * this.scale;
  this.imageHeight = this.imageHeight *+ this.scale;
  scaleChanged = false ;
  }
  }
  override protected function commitProperties(): void { super.commitProperties(); if (sourceChanged){ if (! this.image){ image = new Image(); this.addChild(image); image.source = this._source; } else { image.source = this._source; } sourceChanged = false ; } if (scaleChanged){ this.imageWidth = this.imageWidth * this.scale; this.imageHeight = this.imageHeight *+ this.scale; scaleChanged = false ; } } 關於 measure() 方法
  measure()方法是組件自動計算尺寸大小的地方,在例子 ImageViewer 的 measure()方法裏(如清單 14 所示),我們做的事很少。這是因爲,我們 ImageViewer 被設計爲:需要顯式指定大小(當然這裏只是爲了舉例方便,您可以根據需要,製作可以自動度量大小的組件)。即在應用時設置 width 和 height 值。如清單 15 所示:
  在這種情況家,measure()方法不會被調用。所以需要提請讀者注意的就是,一旦您的組件在組裝階段被設置了 with 和 height 屬性值,那麼請不要期望在 measure 裏會執行您的業務邏輯。 清單 14. ImageViewer 的 measure() 方法 override protected function measure(): void { super.measure(); measuredWidth = Math.max(image.width, controlBar.getExplicitOrMeasuredWidth()) ; measuredHeight = image.height + controlBar.getExplicitOrMeasuredHeight(); measuredMinWidth = measuredWidth; measuredMinHeight = measuredHeight; } 清單 15. 使用 ImageViewer 的應用 Sample.mxml 關於 updateDisplayList ()方法
  updateDisplayList()方法用於對組件內容進行佈局以及渲染,一切屏幕上可見的內容都需要在這裏被處理,所以 updateDisplayList()可以說是最繁忙的一個提交方法,他所包含的實現可以非常多。清單 16 中,我們省略了部分代碼。只留下了需要講解的部分。
  在 measure()方法裏我們可以獲取 Flex 自動計算的尺寸(如果被調用的話),這些數據需要在 updateDisplayList() 方法裏被應用處理,所以我們會大量使用 setActualSize()方法,特別是子元素比較多的時候。
  作爲提交方法,updateDisplayList()的最重要的職責之一就相對應的 invalidateDisplayList()方法的更新請求進行響應。比如說清單 16 裏方法就對清單 17 裏的 set imageWidth()方法進行了相應。並且就像在 commitProperties()部分裏介紹的那樣,這裏同樣使用了"標誌位"方法來進行"局部跟新"。
  局部更新是普遍應用於提交方法裏的一種技巧,因爲我們知道這三個提交方法是公用的,任何更新的提交都會在這幾個方法裏被處理。而每次更新都可能只是局部的更改,所以是當地使用標誌位方法進行局部更新可以有效地優化代碼的執行。
  最後要提到的是,渲染屏幕的職責也決定了 updateDisplayList()方法是調用 Flash Player 繪圖 API 的好地方。所以我們在清單 16 中特意使用了 drawRect()方法爲圖片家了一個邊框。 清單 16. ImageViewer 的 updateDisplayList () 方法。 override
  protected
  function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number): void
  {
  super.updateDisplayList(unscaledWidth, unscaledHeight);
  // 省略部分代碼
  zoomInButton.setActualSize(50, 20);
  zoomOutButton.setActualSize(50, 20);
  var tmpx : Number = 20;
  controlBar.setActualSize(tmpx, 20);
  controlBar.move(0, unscaledHeight-25);
  zoomInButton.move(tmpx, 0);
  tmpx +=60;
  zoomOutButton.move(tmpx, 0);
  if (imageHeightChanged ||imageWidthChanged){
  image.width = this.imageWidth;//w;
  image.height = this.imageHeight;//h - 20;
  var tempX : Number = x+ (w-imageWidth) * 0.5;
  var tempY : Number = y + (h-imageHeight) * 0.5;
  image.x = tempX;
  image.y = tempY;
  imageHeightChanged = false ;
  imageWidthChanged = false ;
  var g:Graphics = graphics;
  g.clear();
  g.beginFill(0x6F7777);
  g.drawRect(tempX-1, tempY-1, imageWidth+2, imageHeight+2);
  g.endFill();
  }
  }
  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number): void { super.updateDisplayList(unscaledWidth, unscaledHeight); // 省略部分代碼 zoomInButton.setActualSize(50, 20); zoomOutButton.setActualSize(50, 20); var tmpx : Number = 20; controlBar.setActualSize(tmpx, 20); controlBar.move(0, unscaledHeight-25); zoomInButton.move(tmpx, 0); tmpx +=60; zoomOutButton.move(tmpx, 0); if (imageHeightChanged ||imageWidthChanged){ image.width = this.imageWidth;//w; image.height = this.imageHeight;//h - 20; var tempX : Number = x+ (w-imageWidth) * 0.5; var tempY : Number = y + (h-imageHeight) * 0.5; image.x = tempX; image.y = tempY; imageHeightChanged = false ; imageWidthChanged = false ; var g:Graphics = graphics; g.clear(); g.beginFill(0x6F7777); g.drawRect(tempX-1, tempY-1, imageWidth+2, imageHeight+2); g.endFill(); } } 清單 17. ImageViewer 的 set imageWidth () 方法。 public function set imageWidth(value:Number): void { if (_imageWidth != value) { _imageWidth = value; imageWidthChanged = true ; this.invalidateDisplayList() dispatchEvent( new Event( "imageWidthChanged" )); } } 結束語
  本文對 Flex 生命週期的各個階段進行了分類和講解,根據各個時期的特點提出了一些編程方面的提示,並且通過實例分析將這些編程提示應用到實踐中,希望對您的 Flex 開發工作有所幫助。 下載
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章