深入Flex4 -- 瞭解Element和Child的異同(先放着回去看)

當我瞭解到Flex4那些對我諸多裨益的新特性後, 我便決定轉而使用它。剛開始的時候,我試圖利用在Flex前作中的認識和既有經驗來快速進入狀態。但很快我便發現有時即使面對一些顯而易見的問題我也不得 不求助於API文檔或者運行一些示例程序來弄清這種問題的來龍去脈。根據以往經驗,Flex3 的Halo在處理顯示列表的時候隱藏了大量的實現細節和不良設計。然而一旦你開始使用新的Spark架構後,你就得以近距離的面對這些實現細節—Halo 究竟在私底下幹了什麼,而且你會體會到爲什麼說Spark對於顯示列表的處理更爲“直白”。 

“elements”是一個關鍵性的問題。elements是何物?它同child是否是一回事?剛開始的時候我曾武斷的認爲elements不 過是children的另一種說法。通過反覆梳理組件中所有的elements和children,我發覺在新的容器類(也包括一些經過改良的傳統容器) 某些似乎是理所當然應該具備的方法消失了。如果沒有getElements(),我該如何獲取elements的數目呢?我能否把 getChildren() 的返回結果作爲IVisualElement來對待。這令我十分糾結。 

困擾的我於是開始認真閱讀學習API文檔,Flex的源碼以及相關的博客文章。我也曾嘗試解讀一些博主發佈的關於Flex4新特性的幻燈片。然而事實證明脫離講解而孤立的看幻燈片作用相當有限。 

最後,我拼湊了一些言簡意賅的示例。這些示例將帶領我瞭解有關elements的全新知識,告訴我那些在新的Spark容器背後發生的故事。 

言歸正傳,首先從問題開始。問題一,“應該如何獲得Spark 容器的全部elements?”我曾想當然的認爲是通過一個類似Flex3中的getChildren() 的方法。然而實際上我們需要通過兩個Property來達到這個目的:numElements & numChildren 。可以通過對numElements計數的循環語句配合getElementAt() 來實現遍歷容器elements或特定訪問。這種方式還比較直觀。問題二,“element和child的區別何在?”,讓我們來看看兩者的差異。 

語義上,element簡單的說就是實現了IVisualElement接口的任意型別。child是指擴展了DisplayObject類的任 意型別。判斷某個組件是element還是child亦或兩者都是的關鍵在於以下幾點。UIComponent(所有Flex組件的基類:譯者注)是由 DisplayObject擴展而來,故所有UIComponent都是DisplayObject,也就是說UIComponent都是 children。UIComponent同時也實現了IVisualElement接口,因而所有的UIComponent也可以被作爲 elements看待。但這並不是說所有的DisplayObjects(文中所言的DisplayObject一般指擴展於DisplayObject 的子類,譯者注)都是elements。容器中的DisplayObject對象是該無疑是容器的child。而只有當此DisplayObject對象 同時也實現了IVisualElement接口時它纔是容器的element。那麼對容器而言,DisplayObject什麼情況下是child,什麼 情況下又是element?通過示例來認識這個問題。 

在首個示例中,我們使用了傳統的Halo容器(這裏我們使用的Panel)。Panel擴展與DisplayObject類,所以它可以使用 addChild() 方法。進一步而言,Panel也是Container類的子類(mx.core.Container實現了 IVisualElementContainer接口),它具有addElement() 方法。Container類的IVisualElementContainer接口實現只是基於顯示列表API的門面,所以理論上它和同樣實現了 IVisualElementContainer接口的新式Spark容器具有相同的方法集合。 



於是看起來我們可以任意添加children或element到容器中了。事實卻不是這樣。並非任意型別的element都能被添加(此處 element泛指實現了IVisualElement接口的類)容器中。視覺元素(VisualElements)和圖形元素 (GraphicElements)有一些區別視覺元素(VisualElements)實現了IVisualElement接口,而圖形元素 (GraphicElements)實現的是IVisualElement接口的子接口IGraphicElement。IGraphicElement 接口擴展的新特性爲容器獲取信息提供了額外渠道。某些elements(圖形元素是其中之一)無法直接添加至Halo的Panel編譯器會告知“這樣的對 象需事先包裝進一個Group容器中”(實際上錯誤提示應該是在運行時出現,不關編譯器什麼事:譯者注)。原因馬上揭曉。 

接下來的示例中,Panel中有若干個UIComponent,其中包括另一個Halo Panel,一個Spark Panel,幾個Halo Button和幾個Spark Button,以及一個包含有子組件的SkinnableContainer(注意: 包含於SkinnableContainer的組件是隻屬於SkinnableContainer的children,不是上級容器Panel的 children)。所有組件都繼承於DisplayObject,所以它們都是“children”。點擊“show children”後可以清楚的瞭解這一點。進一步而言,所有的組件也都是“element”,因爲UIComponent實現了 IVisualElement接口。 

看下一個示例。這次我們探討的容器上Spark Group。與前Halo Panel類似,Group繼承於DisplayObjectContainer,它具有addChild() 方法,它同時也實現了IVisualElement接口,所以我們可以用addElement() 方法來IVisualElement對象(elements)。而且Group也接受圖形元素(GraphicElements),比如 spark.primitives.Rect。要知道Rect是無法直接添加到Halo Panel中的。Group是怎麼做到這一點的?原因就在於Group知道如何使用一種優化的方式來呈現圖形元素(GraphicElements)。什 麼意思?往下讀。 

相對於典型的視覺元素(VisualElements),圖形元素(GraphicElements)與容器的關係更爲緊密。其關鍵在於 IGraphicElement接口。上面曾經提到,這個擴展於IVisualElement的接口(此即圖形元素(GraphicElements)可 以通過Group的addElement() 方法來添加至其上的原因所在)。然而由於圖形元素(GraphicElements)不是DisplayObject,所以他們在被“投映”到某個作爲他 父對象的DisplayObject前是無法被顯示出來的。基於這個原因,當添加一個“Rectangle”到Group時,需要有 DisplayObject來繪製這個Rectangle。更有效率一點的做法是Group儘可能的複用同一個DisplayObject來繪製多個圖形 元素(GraphicElements)。容器可以使用任何實現了ISharedDisplayObject接口的DisplayObject來繪製圖形 元素(GraphicElements)。第一個示例中的Halo Panel無法使用這種方式來繪製圖形元素(GraphicElements),編譯器會報錯:“必須將其包裝至一個合適的容器中”。而Group支持這 種優化方式,所以能添加圖形元素(GraphicElements)。 

另外需要注意的一點是,有些圖形元素(GraphicElements)的繪製由Group提供DisplayObject來完成,也有的是自行 創建專屬的DisplayObject來完成繪製。IGraphicElement接口甚至允許把對象自己創建的DisplayObject交由容器管理 (換而言之就是以child形態添加的DisplayObject會以IGraphicElement的面貌來繪製自己)。 

這意味着什麼?這意味着在接下來的示例中,children的數目和elements的數目是不一樣的。這個示例使用了與第一個示例相同的組件集 合外,還增加了4個矩形圖形元素(GraphicElements)。所有子對象皆爲IVisualElement,但不是都可以稱爲children。 幾個矩形是圖形元素(GraphicElements),它們並不繼承於DisplayObject。Group不在乎這點,它知道添加 DisplayObject來繪製這些圖形元素(GraphicElements)。由於幾個矩形的尺寸和角度有所不同,所以Group會創建2個新的 DisplayObject來繪製這4個矩形。很酷吧! 



現在來看示例三。我們用一個SkinnableContainer替換先前的Group。SkinnableContainer有和先前相同的子 組件集,它還能利用Skin來增強視覺效果。Skin是SkinnableContainer唯一的child。SkinnableContainer的 默認Skin類由一個矩形和一個被稱爲ContentGroup的Group組成。該Group的作用在於規劃出容器內組件的添加位置。 

這個示例證明了這樣的事實,即使SkinnableContainer擁有10個elements,但它只有唯一的child:它自己的 Skin。而且這個Skin也只有唯一的child:名爲ContentGroup的Group組件。你也許會感到奇怪:爲什麼Skin的 children不是2個:其一是ContentGroup,另一個是用於繪製作爲邊框的Rectangle的DisplayObject?這是因爲 Skin類繼承自Group類,而Group只在它確實需要繪製其包容的圖形元素(GraphicElements)時纔會添加 DisplayObject,目前的情況下不需要。Skin類具備直接在其上繪製Rect圖形元素(GraphicElements)的能力,這歸功於 Skin類的上級類Group實現了ISharedDisplayObject接口。這意味着它在需要時能作爲共享的DisplayObject來繪製圖 形元素(GraphicElements)。Skin負責管理用於呈現圖形元素(GraphicElements)的DisplayObject,在當前 示例中,Skin自己就是用於繪製的DisplayObject!如果你的自定義Skin中有其它的Rectangle,並將該Skin賦予 SkinnableContainer,這種情況下Skin會判斷是否需要更多的DisplayObject來繪製額外的Rectangle。這時你可能 會發現在Skin的children列表中有更多的child。 

值得注意的是,示例中SkinnableContainer,它的Skin以及Skin的ContentGroup這三者的element列表的 數目是相同的。通過SkinnableContainer的源碼可以知道,numElement的值實際上來源於與之對應的 CurrentContentGroup的numElement。所以基本上對SkinnableContainer的elements的檢索是被重定向 到它的ContentGroup上的。SkinnableContainer的Skin也有類似行爲。它繼承於Group,Group的 numElement的值取自其內部的mxmlContent屬性。該屬性是一個保存了Group可視內容children的數組。這兩個屬性與 Panel的RawChildren屬性十分相似,它用於返回Panel上的所有children而不是getChildren()方法返回的僅僅你添加 到Panel上的那些。 



通過以上閱讀,也許起不到撥雲見日的效果。但可以讓你明白釐清以下七個類/接口的繼承結構和相互關係是十分有必要的: 
1. DisplayObject 
2. UIComponent 
3. Container 
4. IVisualElement 
5. IGraphicElement 
6. IVisualElementContainer 
7. ISharedDisplayObject 

一旦你掌握它們之間的關係,你就能明白elements 和children的不同。可以肯定的是我在某些問題的認識和闡述上存在很多謬誤之處。如果你發現了這樣的問題望不吝賜教,在評論處寫下您的正確觀點吧。 

轉:http://www.blogjava.net/oathleo/archive/2011/10/14/361252.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章