Flex 的 Style 檔案可以編入 Application 也可以單獨編譯成 SWF 檔案

可是假如將 Style 檔案放在 Package Folder 之下,且檔案內的 CSS 有用到 Class Reference
那麼編譯就會出現 Error

Flex Style Error

這樣還挺不方便的,強迫一定要將 Style 放在 src 之下才行
為了改善這問題,觀察 Flex Style 產生的 AS3 檔案
發現原來它也是繼承 ModuleBase 且實作 IStyleModule 介面
所以獨立 Style 最後就是變成一個 Module
既然這樣何不自己寫一個 Style Module 呢?

嘗試過幾次之後,發現 Flex 產生的 Style Module 是有包含復原機制的
會自動產生 selectors, overrideMap, effectMap 等資訊
等到要卸載 Style Module 時,它會用這些資訊將 App Style 還原
可是假如只是一般的 Module + Style 就沒有這些東西
平鋪直敘的一行一行呼叫 StyleManager 設定好 Style
這些動作都是 Flex Compiler 自動產生的,執行過了就沒了,很難加以干預

最後想到一個很特殊的技巧,在自訂的 StyleModule 內宣告一個 StyleManager 變數成員
當作是一個 Hook 鉤子,因為它的名稱與 Flex 靜態類別 mx.styles.StyleManager 名稱重覆
所以程式碼內取用 StyleManager 時,會優先取到 StyleManager 變數成員
這樣一來所有對 StyleManager 設定 Style 動作都可以欄截下來了

protected var StyleManager:Object = {styleModule: this,
  getStyleManager: function(... args):* {
   return new StyleManagerProxy(this.styleModule);
  }};

只要再補上一個 StyleManagerProxy 類別就好
用來將收到的 Style Declaration 全部轉丟回去給自訂 StyleModule

package com.ticore.style.module {
 import mx.managers.SystemManagerGlobals;
 import mx.styles.CSSStyleDeclaration;
 import mx.styles.StyleManagerImpl;
 
 public class StyleManagerProxy extends StyleManagerImpl {
 
  protected var styleModule:Object;
 
  override public function StyleManagerProxy(styleModule:Object) {
   super(SystemManagerGlobals.topLevelSystemManagers[0]);
   this.styleModule = styleModule;
  }
 
  override public function getStyleDeclaration(selector:String):CSSStyleDeclaration {
   return null;
  }
 
  override public function setStyleDeclaration(selector:String,
     styleDeclaration:CSSStyleDeclaration, update:Boolean):void {
   styleModule.pushStyle(styleDeclaration);
  }
 
 }
}

至於其它 Style Module 部分直接從 Flex 產生的版本抄就好了
以下便是完整的自訂 StyleModule:

package com.ticore.style.module {
 import flash.system.Security;
 
 import mx.core.mx_internal;
 import mx.modules.ModuleBase;
 import mx.styles.CSSCondition;
 import mx.styles.CSSSelector;
 import mx.styles.CSSStyleDeclaration;
 import mx.styles.IStyleManager2;
 import mx.styles.IStyleModule;
 
 public class StyleModule extends ModuleBase implements IStyleModule {
 
  private static var domainsAllowed:Boolean = allowDomains();
 
  private static function allowDomains():Boolean {
   if (Security.sandboxType != "application") {
    Security.allowDomain("*");
   }
   return true;
  }
 
  //============================================================
 
  protected var selectors:Array = [];
  protected var overrideMap:Object = {};
  protected var effectMap:Object = {};
  protected var unloadGlobal:Boolean;
 
  protected var styleManager:IStyleManager2;
 
  //============================================================
 
  public static var StyleManagerCls:Class = mx.styles.StyleManager;
 
  // StyleManager class reference hook
  protected var StyleManager:Object = {styleModule: this,
    getStyleManager: function(... args):* {
     return new StyleManagerProxy(this.styleModule);
    }};
 
  protected var _styles_:Array = [];
 
  public function pushStyle(style:CSSStyleDeclaration):void {
   _styles_.push(style);
  }
 
  //============================================================
 
  public function setStyleDeclarations(styleManager:IStyleManager2):void {
   // (trace)("StyleModule.setStyleDeclarations();", styleManager);
 
   if (!styleManager) {
    styleManager = StyleManagerCls["getStyleManager"](null);
   }
 
   this.styleManager = styleManager;
 
   // clone styles with new styleManager
   for (var i:int = 0; i < _styles_.length; ++i) {
    var oldStyle:CSSStyleDeclaration = _styles_[i] as CSSStyleDeclaration;
 
    var selector:CSSSelector = oldStyle.selector;
    var selectorString:String = selector.toString();
 
    var newStyle:CSSStyleDeclaration = styleManager.getStyleDeclaration(selectorString);
 
    if (!newStyle) {
     newStyle = new CSSStyleDeclaration(selector, styleManager);
     selectors.push(selectorString);
    }
 
    // register override map and keys
    var factory:Function = oldStyle.factory;
    var dumpObj:Object = {};
    factory.apply(dumpObj);
 
    var keys:Array = overrideMap[selectorString];
    overrideMap[selectorString] ||= keys ||= [];
 
    for (var key:* in dumpObj) {
     newStyle.mx_internal::setLocalStyle(key, dumpObj[key]);
     keys.push(key);
    }
 
    // register effects map
    var addedEffects:Array;
 
    // (trace)("effects:", oldStyle.mx_internal::effects);
    newStyle.mx_internal::effects ||= oldStyle.mx_internal::effects ||= [];
    addedEffects = newStyle.mx_internal::effects.concat();
    effectMap[selectorString] = addedEffects;
   }
  }
 
  //============================================================
  // original external style module functions
 
  public function unload():void {
   // (trace)("StyleModule.unload();");
   unloadOverrides();
   unloadStyleDeclarations();
   if (unloadGlobal) {
    styleManager.stylesRoot = null;
    styleManager.initProtoChainRoots();
   }
  }
 
  private function unloadOverrides():void {
   // (trace)("StyleModule.unloadOverrides();");
   for (var selector:String in overrideMap) {
    // (trace)("selector:", selector);
    var style:CSSStyleDeclaration = styleManager.getStyleDeclaration(selector);
    if (style != null) {
     var keys:Array = overrideMap[selector];
     var numKeys:int;
     var i:uint;
     if (keys != null) {
      numKeys = keys.length;
      for (i = 0; i < numKeys; i++) {
       // (trace)("clearOverride:", keys[i]);
       style.mx_internal::clearOverride(keys[i]);
      }
     }
     keys = effectMap[selector];
     if (keys != null) {
      numKeys = keys.length;
      var index:uint;
      var effects:Array = style.mx_internal::effects;
      for (i = 0; i < numKeys; i++) {
       // ReferenceError: Error #1069: Number 上找不到屬性 0,而且沒有預設值。
       // index = effects.indexOf(numKeys[i]);
       index = effects.indexOf(keys[i]);
       if (index >= 0) {
        effects.splice(index, 1);
       }
      }
     }
    }
   }
   overrideMap = null;
   effectMap = null;
  }
 
  private function unloadStyleDeclarations():void {
   // (trace)("StyleModule.unloadStyleDeclarations();");
   var numSelectors:int = selectors.length;
   for (var i:int = 0; i < numSelectors; i++) {
    var selector:String = selectors[i];
    styleManager.clearStyleDeclaration(selector, false);
   }
   selectors = null;
  }
 
  //============================================================
 }
}

完成之後,就可以用一般 Flex Module 方式來開發外部 Style 了

<?xml version="1.0" encoding="utf-8"?>
<module:StyleModule
  xmlns:fx="http://ns.adobe.com/mxml/2009"
  xmlns:module="com.ticore.style.module.*">
 <fx:Style source="style/Style01.css"/>
 <fx:Style source="style/Style02.css"/>
</module:StyleModule>

搞了這麼麻煩,這種自訂 Style Module 方式倒底有什麼好處呢?

  • Style Module 可以放在任何 Package Folder 下
  • 一個 Style Module 可以包多個 Style CSS 檔案
  • Style Module 可以作最佳化減少檔案大小

GitHub – Flex Style Module

以上的問題與技巧同時適用於 Flex 4, 4.5

相關連結:
Flex – Loading style sheets at run time