最近工作上,花了很多時間在處理 Flash 與 Flex 之間的合作。
目標是,美術製作的 Flash 素材,Flex 可以直接使用,不用重新依照 Flash 的設計而在 Flex 中重新以 MXML 組合 layout;並且要能支援動態載入外部 *.swf 素材,達到切換 theme 的機制。
步驟一:準備數款 Flash 設計的檔案,並輸出 *.swf
/asset/asset1.fla -> /asset/asset1.swf

/asset/asset2.fla -> /asset/asset2.swf

/asset/asset3.fla -> /asset/asset3.swf

步驟二,將 Flash 素材,轉變成 Flex 素材
/css/asset1.css -> /css/asset1.swf
.MyBtns{
class: Embed(source="../asset/asset1.swf", symbol="ui.Btns");
}
/css/asset2.css -> /css/asset2.swf
.MyBtns{
class: Embed(source="../asset/asset2.swf", symbol="ui.Btns");
}
/css/asset3.css -> /css/asset3.swf
.MyBtns{
class: Embed(source="../asset/asset3.swf", symbol="ui.Btns");
}
步驟三,開一 MXML 專案
畫面中只放一個自訂 UIComponent : MyBtnsUIComponent
Theme.xml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">
<mx:creationComplete>
<![CDATA[
StyleManager.loadStyleDeclarations("Users/ben/Documents/Flex Builder 3/Theme/css/asset1.swf");
]]>
</mx:creationComplete>
<local:MyBtnsUIComponent />
</mx:Application>
這個 MyBtnsUIComponent 作的事情是,從 CSS 中,取得 ui.Btns 類別,建立視覺物件,放到畫面上;因為從 Flash 產生的物件是 Sprite 或 MovieClip,無法直接放到 Application 中,所以要放在自訂的 UIComponent 中。
MyBtnsUIComponent.as
package
{
import flash.display.Sprite;
import flash.events.Event;
import mx.core.UIComponent;
import mx.events.FlexEvent;
public class MyBtnsUIComponent extends UIComponent
{
private var Cls:Class;
private var _view:Sprite;
private var _viewProxy:MyBtnsProxy;
public function MyBtnsUIComponent()
{
super();
this.addEventListener(FlexEvent.INITIALIZE, onInitialize, false, 0, true);
}
private function onInitialize(e:Event):void{
this.removeEventListener(FlexEvent.INITIALIZE, onInitialize);
this.styleName = "MyBtns";
}
override public function styleChanged(styleProp:String):void{
Cls = this.getStyle("class") as Class;
if(Cls!=null)
createView();
}
private function createView():void{
if(_view!=null && _view.parent!=null)
_view.parent.removeChild(_view);
_view = new Cls as Sprite;
this.addChild(_view);
if(_viewProxy==null)
_viewProxy = new MyBtnsProxy(_view);
else
_viewProxy.updateView(_view);
}
}
}
另外,設計了一個 FlashUIComponentProxy 用來包裝所有來自 Flash 的 MovieClip / Sprite。
FlashUIComponentProxy.as
package
{
import flash.display.Sprite;
import flash.events.Event;
public class FlashUIComponentProxy
{
static private var _updateController:Sprite = new Sprite();
static private var _updateProxies:Array = [];
static private function invalidateDisplayList(proxy:FlashUIComponentProxy):void{
_updateProxies.push(proxy);
if(!_updateController.hasEventListener(Event.ENTER_FRAME))
_updateController.addEventListener(Event.ENTER_FRAME, onUpdateController_update, false, 0, true);
}
static private function onUpdateController_update(e:Event):void{
_updateController.removeEventListener(Event.ENTER_FRAME, onUpdateController_update);
var ary:Array = _updateProxies.splice(0, _updateProxies.length);
for(var i:int=0; i<ary.length; i++){
(ary[i] as FlashUIComponentProxy).updateDisplayList();
}
}
protected var view:Sprite;
public function FlashUIComponentProxy(view:Sprite)
{
updateView(view);
}
private var _childProxies:Object = new Object();
public function setChildProxy(view:Sprite, ProxyCls:Class):FlashUIComponentProxy{
if(_childProxies[view.name] == null)
_childProxies[view.name] = new ProxyCls(view);
else
(_childProxies[view.name] as FlashUIComponentProxy).updateView(view);
return _childProxies[view.name] as FlashUIComponentProxy;
}
public function updateView(view:Sprite):void{
this.view = view;
makeLink();
invalidateDisplayList();
}
protected function makeLink():void{
}
protected function invalidateDisplayList():void{
FlashUIComponentProxy.invalidateDisplayList(this);
}
protected function updateDisplayList():void{
}
}
}
以這個例子來說,我有一個 Sprite 中,放了三個按鈕、一個文字欄位、一個 Sprite,因為這一整組 Sprite 會有自己的邏輯,所以建立了一個 MyBtnsProxy 來包裝。
MyBtnsProxy.as
package
{
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import mx.styles.StyleManager;
public class MyBtnsProxy extends FlashUIComponentProxy
{
private var _b1:SimpleButton;
private var _b2:SimpleButton;
private var _b3:SimpleButton;
private var _txtData:TextField;
private var _mcInner:Sprite;
private var _mcInnerProxy:MyInnerProxy;
private var _myData:String = "";
public function get myData():String{return _myData;}
public function set myData(value:String):void{
if(_myData==value)
return;
_myData=value;
this.invalidateDisplayList();
}
public function MyBtnsProxy(view:Sprite)
{
super(view);
}
override protected function makeLink():void{
_b1 = view.getChildByName("b1") as SimpleButton;
_b2 = view.getChildByName("b2") as SimpleButton;
_b3 = view.getChildByName("b3") as SimpleButton;
_txtData = view.getChildByName("txtData") as TextField;
_mcInner = view.getChildByName("mcInner") as Sprite;
_mcInnerProxy = this.setChildProxy(_mcInner, MyInnerProxy) as MyInnerProxy;
_b1.addEventListener(MouseEvent.CLICK, on_b1_click, false, 0, true);
_b2.addEventListener(MouseEvent.CLICK, on_b2_click, false, 0, true);
_b3.addEventListener(MouseEvent.CLICK, on_b3_click, false, 0, true);
}
private function on_b1_click(e:MouseEvent):void{
myData = "on_b1_click";
_mcInnerProxy.myData = "inner 1";
StyleManager.loadStyleDeclarations("Users/ben/Documents/Flex Builder 3/Theme/css/asset1.swf");
}
private function on_b2_click(e:MouseEvent):void{
myData = "on_b2_click";
_mcInnerProxy.myData = "inner 2";
StyleManager.loadStyleDeclarations("Users/ben/Documents/Flex Builder 3/Theme/css/asset2.swf");
}
private function on_b3_click(e:MouseEvent):void{
myData = "on_b3_click";
_mcInnerProxy.myData = "inner 3";
StyleManager.loadStyleDeclarations("Users/ben/Documents/Flex Builder 3/Theme/css/asset3.swf");
}
override protected function updateDisplayList():void{
_txtData.text = myData;
}
}
}
在上述的 Sprite 中,又另外有一個叫作 mcInner 的 Sprite,內含一個文字欄位,所以這裡也建立了另一個 MyInnerProxy 來包裝這個 Sprite。
MyInnerProxy.as
package
{
import flash.display.Sprite;
import flash.text.TextField;
public class MyInnerProxy extends FlashUIComponentProxy
{
private var _myData:String = "";
public function get myData():String{return _myData;}
public function set myData(value:String):void{
if(_myData==value)
return;
_myData=value;
this.invalidateDisplayList();
}
private var _txtInner:TextField;
public function MyInnerProxy(view:Sprite)
{
super(view);
}
override protected function makeLink():void{
_txtInner = view.getChildByName("txtInner") as TextField;
}
override protected function updateDisplayList():void{
_txtInner.text = myData;
}
}
}
以上,是所有程式碼,執行畫面如下:
按下按鈕一,動態載入 asset1.swf

按下按鈕二,動態載入 asset2.swf

按下按鈕三,動態載入 asset3.swf

整套程式的目的在於,動態換掉視覺後,文字欄位、資料的部分仍要能保存下來;決定是否要建立一個 FlashUIComponentProxy 來包裝一個 Sprite 在於這個 Sprite 有多複雜。