About

<#TEMPLATE_INCLUDE_NINEPAGE_ABOUTME#>
  • Feb

    2

    如果想要修改Feathers 示例主题的原始设计,可以下载源文件。每一个主题文件是一个 FLA 文档,使用 Animate CC 打开(Adobe 公司已经抛弃了 Flash CC这个名称,已经改名为 Animate CC)。

    可以修改原始的矢量稿,然后重新导出 sprite sheet。可能还需要下载其它用于创建纹理集图像的打包 PNG 的工具,类似 Texture Packer

    Feb

    2

    Feathers 2.0 开始包含了一个全新的皮肤架构。用于旧主题扩展的 DisplayListWatcher 类仍然存在,可以在一定时间内继续使用它(3.0 开始冒似不存在了……)。如果开发者想利用新的样式提供者带来的好处,那么不得不对主题文件做一些修改。

    扩展新类:StyleNameFunctionTheme

    原生旧主题是扩展f eathers.core.DisplayListWatcher 类:

    // legacy
    public class CustomTheme extends DisplayListWatcher

    要创建一个更符合“现代化”的主题,请扩展 feathers.themes.StyleNameFunctionTheme:

    // modern
    public class CustomTheme extends StyleNameFunctionTheme

    这么改完之后如果直接编译可能会报错,需要对主题文件稍作修改。

    替换 setInitializerForClass() 方法:

    新的 StyleNameFunctionTheme 对象依旧会调用函数设置组件的属性,类似旧主题,开发者依旧可以使用字符串作为样式名称区分相同类型的组件却不同的外观,但 API 有些改变。

    旧主题中,开发者使用 setInitializerForClass() 方法,并传入一个组件类和一个设置组件皮肤用的函数,有时候还会传入第三个参数用于区分相同组件的不同外观。

    // legacy
    this.setInitializerForClass( Button, setButtonStyles );
    this.setInitializerForClass( Button, setCustomButtonStyles, "my-custom-button" );

    在新主题中,开发者首先需要获得一个组件类的全局样式提供者引用,然后才能设置它的默认外观与特定的外观:

    // modern
    this.getStyleProviderForClass(Button).defaultStyleFunction = setButtonStyles;
    this.getStyleProviderForClass(Button).setFunctionForStyleName( "my-custom-button", setCustomButtonStyles );

    一个“快速偷懒”的方法:

    如果旧主题文件中有大量的 setInitializerForClass() 方法需要替换,这可能会是耗时和繁琐的。 如果需要快速的升级主题,并且懒的清理和整理旧代码内容,可以将以下函数复制到新主题类中:

    public function setInitializerForClass(type:Class, styleFunction:Function, styleName:String = null):void
    {
        var styleProvider:StyleNameFunctionStyleProvider = this.getStyleProviderForClass(type);
        if(styleName)
        {
            styleProvider.setFunctionForStyleName(styleName, styleFunction);
        }
        else
        {
            styleProvider.defaultStyleFunction = styleFunction;
        }
    }

    这样它的签名方法就与旧主题文件的 setInitializerForClass() 一样,但使用的却是新主题中的全局样式提供程序。

    替换 setInitializerForClassAndSubclasses() 方法:

    这个旧方法没替代的新方法。这个旧方法主要是为了解决旧有皮肤机制中的一些限制:比如默认一个子类在没有设置皮肤的情况下,不会使用父类的皮肤。而新的主题机制中默认子组件如果没有提供皮肤,会使用和父类相同的皮肤(除非子类自己不要父类的皮肤)。所以这个旧方法已经没有存在的必要了。

    旧主题开发时编写的代码:

    // legacy
    this.setInitializerForClassAndSubclasses( Scroller, setScrollerStyles );

    而在新主题开发时,应该直接调用对应的函数,比如开发者对 Scroller 类设置了一些通用的样式,就可以在 List 组件样式设置的函数中直接调用对应用的函数(如上面的 setScrollerStyles() 函数):

    // modern
    protected function setListStyles( list:List ):void
    {
        this.setScrollerStyles( list );

        // set other styles here
    }

    现在当 List 类(其它的任何子类)实例化设置样式的时候,setScrollerStyles() 也将会被调用。

    替换 exclude() 方法:

    在旧的主题机制下,如果希望一个组件不接受默认的皮肤设置,将该组件传递给 DisplayListWatcher 对象的 exclude() 方法:

    // legacy
    theme.exclude( button );

    在新的主题机制,直接清除样式提供程序就可以:

    // modern
    button.styleProvider = null;

    确保在组件初始化之前清除样式提供程序(皮肤设置是在初始化的步骤中)。默认情况下,当组件被添加到舞台时会执行初始化。

    将“name”替换为“style name”:

    为了解决开发人员使用 getChildByName() 的一些问题,Feathers 不再使用 name 和 nameList 属性来表示主题应该给组件一个可替代的视觉外观,而是使用 styleNameList 或 styleName 属性:

    // 旧
    button.nameList.add( "my-custom-button" );
    // or
    button.name = "my-custom-button";

    // 新
    button.styleNameList.add( "my-custom-button" );
    // or
    button.styleName = "my-custom-button";

    nameList 依旧存在于Feathers 2.0 和 2.1 版本,但从 2.2 开始它不存在了。在仍然存在的版本中,它只是映射到 styleNameList 属性,以便旧代码继续工作。

    name 属性不再用于对 Feathers 的样式化,它不会被映射到 styleName 属性,这是为了防止开发者在使用 getChildByName() 时出现潜在问题。

    一些可替换的样式名称(被定义为常量)也已经被改名,例如包含 NAME 常量名的已经改变为 STYLE_NAME 常量名,例如:Button.ALTERNATE_NAME_DANGER 被改为了 Button.ALTERNATE_STYLE_NAME_DANGER 。类似的 Slider.DEFAULT_CHILD_NAME_THUMB 被改为了 Slider.DEFAULT_CHILD_STYLE_NAME_THUMB。

    对应的,一些父组件上对子组件公开的 API 也有类似的改变,Name 替换为了 StyleName,例如:customThumbName 属性被改为了 customThumbStyleName 属性。

    Feb

    2

    在《创建自定义主题》资料中已经有提到主题文件中有两种方法管理资源文件(类似纹理图像、位图字体等):一种是静态嵌入资源,另一种是运行时动态加载资源。

    静态嵌入资源:

    这种方法是将主题相关的资源文件编译到最终的 SWF 文件(或 SWC 文件中),将所有资源都放在了一个位置。这种方式的资源都是在启动应用时预加载的,可以直接实例化并使用。

    Feathers 的初学者最简单的方法使用示例主题,只需要将 SWC 文件放在项目的库链接中,在需要的时候实例化它:

    new MetalWorksMobileTheme();

    但是这种方法需要更多的内存,因为 SWF 是一个压缩文件, Flash 运行时会将整个SWF 解压到内存中,当实例化嵌入式资源时,这些资源会从运行时的 “SWF 的库”复制到内存,所以实际是使用了两倍的内存。

    SWF 的库不同于 Flash IDE 的库:Flash IDE 的库存放的是所有有用或不用的元件,而 SWF 的库是在运行时解压到内存中所有被编译过的资源文件,它同样是以一个“资源库”的概念存在。

    示例主题嵌入了比较小的纹理集,因此对应用程序来说这种影响可能不会太大,但当开发一个大型项目时用到的资源量如果非常巨大,那么就需要很多内存(因为内存是使用量是翻倍的)。这种静态嵌入资源的方式一般只适合初学者为方便开发、或小资源项目、或快速测试功能与模块时使用。

    运行时动态加载资源:

    通过 Starling 的 AssetManager 类运行时动态加载资源,可以将资源与代码分离,资源文件可以是通过网络 URL 加载、或本地文件系统、或打包到 AIR 程序内的文件。这种方式使用内存较小,因为它只会在内存中出现一次。但编写的代码量稍稍多一些,复杂一些。

    首先需要指定资源的位置,如果是 AIR 程序,可以将资源文件一起打包,如果使用 MetalWorksMobileThemeWithAssetManager 类,需要以下两个文件:

    images/metalworks.xml
    images/metalworks.png

    如下的样例代码中,需要告知主题文件,images 文件夹放在 File.applicationDirectory 中:

    var theme:MetalWorksMobileThemeWithAssetManager =
        new MetalWorksMobileThemeWithAssetManager( File.applicationDirectory.url );

    资源文件并不是必须放在 File.applicationDirectory 中的,开发者也可以将资源文件放在任意子目录中。Flash Builder 和其它 IDE都可以指定打包文件的位置(既便是纯粹的命令行 ADT 打包也可以指定)。

    然后为主题对象添加一个侦听器:

    theme.addEventListener( Event.COMPLETE, theme_completeHandler );

    当资源管理器完成加载时,主题会派发这个事件,表现主题已经准备好为组件提供皮肤了(包括加载的纹理)。换句话说,开发者应该一直等待这个事件派发,然后才可以实例化组件显示用户界面。

    事件的侦听器可能如下所示:

    private function theme_completeHandler( event:Event ):void
    {
        // the theme is ready!

        this.button = new Button();
        this.button.label = "Click Me";
        this.addChild( button );
    }

    其它主题也一样会有类似的要资源文件需求。只需要开发者记住将主题文件当成指向的目录,如在上面的 MetalWorksMobileThemeWithAssetManager 主题例子中,指向的目录引用并不是 images,而是它的父目录。

    在未末的 Feathers 版本中,这个主题可能会需要 images 目录旁边的额外目录中的资源,因此希望保持其灵活性。例如,MinimalMobileThemeWithAssetManager 需要以下文件: 

    images/minimal.xml
    images/minimal.png
    fonts/pf_ronda_seven.fnt

    从上面的层级可以看到,图像与字体并不在同一个目录中, 图像和字体的父目录是该主题文件的真正“根”目录。

    使用自定义资源管理器加载资源:

    默认情况下,主题会创建自己的资源管理器对象(AssetManager 对象)。如果开发者想加载主题文件所不知道的额外的文件,可以选择将自己创建的资源管理器传递给示例主题的构造函数: 

    var assets:AssetManager = new AssetManager();
    assets.enqueue( File.applicationDirectory.resolvePath( "./images/custom-asset.png" ) );

    var theme:MetalWorksMobileThemeWithAssetManager =
        new MetalWorksMobileThemeWithAssetManager( File.applicationDirectory.url, assets );

    theme.addEventListener( Event.COMPLETE, theme_completeHandler );

    在传递到主题的构造函数前,添加任何额外需要加载的资源文件。

    传递给主题构造函数的 AssetManager 对象决对不要调用 loadQueue() 方法,因为主题对象会自动调用这个方法(Starling 的 AssetManager 对象不会派发 Event.COMPLETE 事件给主题),当所有资源加载完成时主题会派发 Event.COMPLETE 事件。开发者如果需要知道何时资源文件被完全加载,只需在主题对象上侦听Event.COMPLETE 事件。

    Feb

    2

    样式提供程序(Style providers,就是指用于设置样式的方法或函数这样的片段代码,引用这段代码程序的变量也可以称为“样式提供者”)是主题文件的基础模块,它可以为每个组件进行单独的设置样式,也可以用于全局性的样式设置,在组件实例化的时候会自动设置皮肤。

    一般来说,大多数 Feathers 用户可能用不着看这篇贴子,基本上都是通过主题文件管理皮肤,但也可以深入的了解一样它背后的原理。

    为同一类型的组件设置不同的皮肤样式:

    如平时我们有多个按钮,需要有相同的外观皮肤,开发者可能就会寻找一种通用的方法,避免一次又一次的复制和粘贴皮肤相关的代码。所以 Feathers 组件支持一种被称为样式提供程序的东西,用于组件实列化的时候自动对组件设置皮肤。

    创建一个样式提供程序者(FunctionStyleProvider 对象):

    function skinButton( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xcccccc );
        button.downSkin = new Quad( 20060, 0x999999 );
    }
    var customButtonStyles:FunctionStyleProvider = new FunctionStyleProvider( skinButton );

    这段代码中创建了一个方法,接受一个需要被样式化的组件。customButtonStyles 就可以被称为样式提供者,它是一个 FunctionStyleProvider 对象;而 skinButton() 方法被称为样式提供程序,是一个函数或方法。

    通知组件去使用一个样式提供者,就像设置组件的 styleProvider 属性一样简单:

    var button1:Button = new Button();
    button1.label = "Cancel";
    button1.styleProvider = customButtonStyles;
    this.addChild( button1 );

    var button2:Button = new Button();
    button2.label = "Delete";
    button2.styleProvider = customButtonStyles;
    button2.y = 100;
    this.addChild( button2 );

    这段代码中有两个按钮,使用同一段样式提供程序,所以会有相同的皮肤。

    自动为相同类型的组件设置皮肤:

    在上面的代码,创建了一个 FunctionStyleProvider 对象作为局部变量或实例变量,并简单地在两个按钮上设置 styleProvider 属性。这在只有一个类的情况下用着还行,但通常一个组件会出现在很多个其它类文件中,而且这么一个一个的高就比较麻烦了。所以我们需要一种全局性的样式提供者(global style provider)。

    每一个组件类(比如  (Button、Slider、List 等)都会有一个静态 globalStyleProvider 属性,在下面的示例中,我们将为所有按钮设置全局样式提供程序:

    function skinButton( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xcccccc );
        button.downSkin = new Quad( 20060, 0x999999 );
    }
    Button.globalStyleProvider = new FunctionStyleProvider( skinButton );

    和前面一样,创建了一个 FunctionStyleProvider 对象,但赋值给了 Button 的静态 globalStyleProvider 属性。

    现在,创建按钮时,就不再需要为每个按钮单独设置 styleProvider 属性了:

    var button1:Button = new Button();
    button1.label = "Cancel";
    this.addChild( button1 );

    var button2:Button = new Button();
    button2.label = "Delete";
    button2.y = 100;
    this.addChild( button2 );

    现在当按钮创建的时候,Feathers 会自动设置它的 styleProvider 属性为 Button.globalStyleProvider 的值。

    trace( button1.styleProvider == Button.globalStyleProvider )//输出 true

    忽略单个组件的全局样式:

    但有时候会有例外的情况,开发者并不希望使用全局样式提供程序。用自己的皮肤替换默认皮肤的最简单的方法是清除按钮的 styleProvider 属性,并从头开始:

    var button1:Button = new Button();
    button1.label = "Click Me";

    //不使用默认样式提供程序
    button1.styleProvider = null;

    //从零开始设置新的皮肤
    button1.defaultSkin = new Quad( 20060, 0xff0000 );
    button1.downSkin = new Quad( 20060, 0x000000 );
    button1.fontStyles = new TextFormat( "_sans"36, 0xffffff );
    button1.padding = 10;

    this.addChild( button1 );

    现在,上面例子的按钮不再有默认的样式提供程序,所以新创建的皮肤就不会被默认样式提供程序覆盖了。styleProvider 也可以不设为 null ,而是直接传入一个新的自定义的 FunctionStyleProvider 对象。

    同一类型的组件使用多种不同的全局样式:

    如果有许多个同类型的按钮组件,希望其中一个有不同的外观,可以为它们提供不同的样式提供程序,如:

    function skinNormalButton( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xcccccc );
        button.downSkin = new Quad( 20060, 0x999999 );
    }
    function skinWarningButton( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xff0000 );
        button.downSkin = new Quad( 20060, 0xcc0000 );
    }

    var button1:Button = new Button();
    button1.label = "Cancel";
    button1.styleProvider = new FunctionStyleProvider( skinNormalButton );
    this.addChild( button1 );

    var button2:Button = new Button();
    button2.label = "Delete";
    button2.styleProvider = new FunctionStyleProvider( skinWarningButton );
    button2.y = 100;
    this.addChild( button2 );

    但和前面说的一样,剩下的相同外观的按钮每个设置一次,依旧会很麻烦。如果能够像前面那样直接使用一个 Button.globalStyleProvider 全局样式提供者就可以方便很多。幸运的是,FunctionStyleProvider 并不是唯一可用的样式提供者,还有一个名为 StyleNameFunctionStyleProvider 类,允许为组件定义多个外观相关的样式提供程序。如:

    function skinNormalButton( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xcccccc );
        button.downSkin = new Quad( 20060, 0x999999 );
    }
    function skinWarningButton( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xff0000 );
        button.downSkin = new Quad( 20060, 0xcc0000 );
    }

    var buttonStyleProvider:StyleNameFunctionStyleProvider = new StyleNameFunctionStyleProvider();
    buttonStyleProvider.defaultStyleFunction = skinNormalButton;
    buttonStyleProvider.setFunctionForStyleName( "warning-button", skinWarningButton );
    Button.globalStyleProvider = buttonStyleProvider;

    在上面的代码中,skinNormalButton 函数被用于 Button 的默认皮肤设置,然后调用了 setFunctionForStyleName() 方法,将 skinWarningButton() 函数关联到一个名为 “warning-button” 的样式名称上。

    样式名称是一个字符串,用来区分同一个类型的组件不同的外观。将样式名称添加到组件的 styleNameList 属性中,如:

    var button1:Button = new Button();
    button1.label = "Cancel";
    this.addChild( button1 );

    var button2:Button = new Button();
    button2.label = "Delete";
    button2.styleNameList.add( "warning-button" );
    button2.y = 100;
    this.addChild( button2 );

    如上代码中,将 “warning-button” 样式名称添加到第二个按钮的 styleNameList 对象中,StyleNameFunctionStyleProvider 对象会根据这个样式名称来确定第二个按钮的样式提供程序是 setWarningButtonStyles() 方法,而不是 setNormalButtonStyles() 方法。

    为了在多个文件中输入字符串时出现“输入性的”(手误)错误时可以被编译器捕获,推荐样式名称最好定义为静态常量。

    样式提供程序与主题文件:

    样式提供程序是主题文件的基础模块,主题文件允许您将所有的全局样式代码合并到一个主题文件类中。 通常,当应用程序首次启动时,主题会被用于实例化。

    Feathers UI 库中提供的示例主题使用了 StyleNameFunctionStyleProvider 对象,为所有组件提供了样式提供程序,一些函数被传递到 defaultStyleFunction 属性,以便在组件没有任何样式名称时提供默认样式。一些函数被传递给 setFunctionForStyleName() 方法,与样式名关联起来。关于示例主题相关的资料参考《“扩展示例主题”一些备注

    词汇表:

    样式提供程序(style provider provides):它为组件初始化时提供皮肤化设置。

    全局样式提供者(global style provider):它为同一类型的组件自动化的提供皮肤化设置。在组件初始化之前,如果有需要,可以轻松的将全局样式提供程序替换为自定义的样式提供程序。

    样式名称(style name):通过在组件上设置样式名称,我们可以通知样式提供者为单个组件提供可选的皮肤化行为。

    主题文件(theme):主题允许我们将所有的全局样式代码放在同一个位置(通常只在一个类中,可以在应用程序启动时用于主题实例化)。

    Feb

    2

    不少应用程序、游戏会需要完完全全全新的外观与字体,示例主题会无法满足需求。这种时候就会需要从零开始创建一个自定义主题(其实我个人还是推荐“扩展示例主题”,或者重构示例主题,示例主题中已经包含了一些组件的布局设置与“防抖动阔值”设置,删除代码比编写代码肯定要快)。

    创建自定义主题类:

    创建一个全新的自定义主题类,需要扩展 StyleNameFunctionTheme 类: 

    package
    {
        public class CustomTheme extends StyleNameFunctionTheme
        {
            public function CustomTheme()
            {
                super();
            }

            private function initialize():void
            {
            }
        }
    }

    添加一个 initialize() 方法,当主题的其它部份可以用的时候填充到这个方法体内。在调用这个方法前,需要先决定资源是如何管理的:资源可以是被动态加载的或静态嵌入的。所以在资源文件没有被全部加载前,不会立即调用这个方法。

    管理资源:

    有两个方法可以管理主题包含的资源, 每个都有自己的优点和缺点,需要选择哪一个视具体的开发情况。

    第1种:静态嵌入资源。

    在编译时嵌入资源方法最简单,这种方法可以直接实例化资源,而不需要侦听等待资源的加载相关的事件。缺点就是它需要更多的内存,在移动开发大型项目时可能会不适用,因为称动设备的内存比较有限,嵌入的资源数量会受到限制,过多的资源被静态嵌入后,启动 APP 有可能会引起一些中低端机型的 APP 闪退或无 响影。所以这种方法只适合小型项目或嵌入的资源比较少的情况。

    在 CustomTheme 类中嵌入纹理集(texture atlas,一个纹理集需要两个文件:一个图像文件与一个 XML 文件):

    [Embed(source="/../assets/images/atlas.png")]
    private static const ATLAS_BITMAP:Class;

    [Embed(source="/../assets/images/atlas.xml",mimeType="application/octet-stream")]
    private static const ATLAS_XML:Class;

    在主题文件中定义一个成员变量持有纹理集(TextureAtlas)的引用: 

    private var atlas:TextureAtlas;

    然后再创建一个 createTextureAtlas() 方法,实例化纹理后保持引用:

    private function createTextureAtlas():void
    {
        var atlasTexture:Texture = Texture.fromEmbeddedAsset( ATLAS_BITMAP );
        var atlasXML:XML = XML( new ATLAS_XML() );
        this.atlas = new TextureAtlas( atlasTexture, atlasXML );
    }

    在 CustomTheme 构造方法中调用 createTextureAtlas() 方法,然后就可以直接调用 initialize()。

    public function CustomTheme()
    {
        super();
        this.createTextureAtlas();
        this.initialize();
    }

    如果主题需要嵌入其它资源,如其它更多的纹理集、位图字体等,都类似,全部需要在 initialize()  方法前实例化它们。

     第2种:运行时动态加载资源。

    Starling 的资源管理类(AssetManager 类)支持运行时动态加载资源文件(可以一次加载多个资源文件),它还提供了把位图自动转换成对应的纹理集或注册位图字体等功能。这种方式最大的好处是同样的资源数量,会比嵌入资源的方式使用更少的内存,这意味着我们可以打包入更多的资源。加载资源的缺点就是代码稍稍复杂一些,因为需要 AssetManager 类在没有加载完资源之前,无法使用它们,所以就需要侦听加载相关的事件。

     在打包 AIR 应用程序时,有时候资源文件不会被自动打包,所以需要检查一下资源是否被打勾了。

     在主题类中定义两个变量,一个用于资源引用,一个用于加载资源:

    private var atlas:TextureAtlas;
    private var assets:AssetManager;

     添加一个 loadAssets() 方法,用于实例化 AssetManager() 和列队需要加载的资源:

    private function loadAssets():void
    {
        this.assets = new AssetManager();
        this.assets.enqueue( "atlas.png" );
        this.assets.enqueue( "atlas.xml" );
        this.assets.loadQueue( this.assets_onProgress );
    }

     在末尾我们调用 AssetManager 对象的 loadQueue() 方法开始加载资源,再加一个回调函数作为参数,可以用于判定资源的加载情况(回调函数的参数是0-1.0之间的 Number 类型):

    private function assets_onProgress( progress:Number ):void
    {
        if( progress < 1.0 )
        {
            return;
        }
        this.atlas = this.assets.getTextureAtlas( "atlas" );
        this.initialize();
        this.dispatchEventWith( Event.COMPLETE );
    }

    当资源加载完成后,就可以从 AssetManager 对象访问资源纹理集了(TextureAtlas 对象),通过 getTextureAtlas() 方法得到纹理集的引用赋值给主题类的实例变量保持引用(纹理集的名称不需要扩展名,所以上面直接写了 atlas,而不是 atlas.png 或 atlas.xml)。

    资源文件加载完成后,立即调用了 initialize() 方法,如果主题文件需要更多的资源文件,都需要在调用 initialize() 之前加载完。

    最后,为了通知应用程序继续执行后面的代码,派发 Event.COMPLETE 事件:

    this.theme = new CustomTheme();
    this.theme.addEventListener( Event.COMPLETE, theme_completeHandler );

     一旦侦听器被调用,应用程序中就可以开始自由的实例化组件了,主题文件会对组件自动设置样式。

    组件样式化:

    一旦资源文件被加载完成,就可以在应用程序中设置组件的样式化相关的函数了。在这些函数中,样式仅仅是直接简单的设置组件的属性。函数体内的代码就跟没有使用主题文件,直接通过“皮肤”设置组件的代码类似。以 Button 为例:

    private function setButtonStyles( button:Button ):void
    {
        button.defaultSkin = new Image( this.atlas.getTexture( "button-up" ) );
        button.downSkin = new Image( this.atlas.getTexture( "button-down" ) );

        button.padding = 20;
        button.gap = 15;

        button.fontStyles = new TextFormat( "_sans"18, 0x333333 );
    }

    在前面的 initialize() 方法体内,调用一个新的 initializeStyleProviders() 方法(下面定义):

    private function initialize():void
    {
        this.initializeStyleProviders();
    }

    initializeStyleProviders() 方法的定义,并设置样式提供程序:

    private function initializeStyleProviders():void
    {
        // button
        this.getStyleProviderForClass( Button ).defaultStyleFunction = this.setButtonStyles;
    }

    getStyleProviderForClass() 方法返回一个 StyleNameFunctionStyleProvider 对象。

    StyleNameFunctionStyleProvider 对象有两个主要功能,它的 defaultStyleFunction 属性会被用于一类组件的默认皮肤(用的最多的情况)。比如上面的样式代码,当我们生成一个按钮时,就会调用 setButtonStyles() 样式化按钮。但有时候一个组件的不同风格在应用中也会出现,比如同样是按钮,可能是特别醒目,用于警告功能。Button.ALTERNATE_STYLE_NAME_DANGER_BUTTON 常量就可以用来定义一个警告按钮。在开发应用时,可以向组件添加此样式名称,以告诉主题应该采用不同的样式:

    button.styleNameList.add( Button.ALTERNATE_STYLE_NAME_DANGER_BUTTON );

    对应的在主题文件内,设置一个新的样式提供程序,一个名为 setDangerButtonStyles() 的方法:

    private function setDangerButtonStyles( button:Button ):void
    {
        button.defaultSkin = new Image( this.atlas.getTexture( "danger-button-up" ) );
        button.downSkin = new Image( this.atlas.getTexture( "danger-button-down" ) );

        button.padding = 20;
        button.gap = 15;

        button.fontStyles = new TextFormat( "_sans"18, 0x333333 );
    }

    在按钮的样式提供者(StyleNameFunctionStyleProvider)调用 setFunctionForStyleName() 方法注册新的样式提供程序:

    this.getStyleProviderForClass( Button )
        .setFunctionForStyleName( Button.ALTERNATE_STYLE_NAME_DANGER_BUTTON, this.setDangerButtonStyles );

     如果一个组件拥有此样式名称,就会调用此 setDangerButtonStyles() 样式提供程序,而不会使用前面的 setButtonStyles() 样式提供程序。

    样式化子组件:

    有一些组件是带有子组件的,子组件也需要样式化皮肤设置。比如,一个 Slider 组件有一个滑块,这个滑块它是一个 Button 组件。像这种子组件可以通过父组件中的样式化名称来定义区别,例如叫 Slider.DEFAULT_CHILD_STYLE_NAME_THUMB,用来显示滑块的缩略图。

    this.getStyleProviderForClass( Button )
        .setFunctionForStyleName( Slider.DEFAULT_CHILD_STYLE_NAME_THUMB, this.setSliderThumbStyles );

    含有子组件的父组件,通常会提供一个设置子组件样式名称的属性,如果需要除默认值以外的其他值。比如一个 Slider 对象,可以设置 customThumbStyleName 属性:

    slider.customThumbStyleName = "custom-thumb";

     然后,我们可以像设置其它新的样式程序一样,在主题文件中使用该自定义样式名称:

    this.getStyleProviderForClass( Button )
        .setFunctionForStyleName( "custom-thumb", this.setSliderThumbStyles );

    设置全局性的默认值:

    某些全局性的属性通常也是通过主题文件设置的。例如在自定义主题文件中,设置 FeathersControl.defaultTextRendererFactory  FeathersControl.defaultTextEditorFactory 决定 TextInput 默认的文本渲染器与编辑器(还有类似模态化的叠加层等设置往往也是全局性的默认值)

    创建一个 initializeGlobals() 方法:

    private function initializeGlobals():void
    {
        FeathersControl.defaultTextRendererFactory = function():ITextRenderer
        {
            return new TextBlockTextRenderer();
        };

        FeathersControl.defaultTextEditorFactory = function():ITextEditor
        {
            return new StageTextTextEditor();
        };
    }

    在 initialize() 方法中,在调用 initializeStyleProviders() 方法前先调用这种全局性的设置方法:

    private function initialize():void
    {
        this.initializeGlobals();
        this.initializeStyleProviders();
    }

    Feb

    1

    在进行移动开发的时候,个人比较推荐“扩展示例主题”,因为一个示例主题全部代码量和资源算在内只有几百KB,相对于一个 AIR SDK 打包移动APP,动则增加几十M 的容量来说,简直就是小意思了,但却能节省大量的时间。Feathers UI 库中提供的示例主题其实已经从设计时就开始考虑了扩展性,所以既便是在桌面开发中也同样推荐,会非常便于后期维护。除非是桌面 Web 型应用,并且对文件大小非常非 常的严格,那么只能从头开始完完全完的自定义一个主题文件

    添加替换的样式函数

    假设 MetalWorksMobileTheme 主题类符合我们大多数需求,但只有少数 UI 需要进行设整,那么就可以扩展 MetalWorksMobileTheme 类:

    package com.example
    {
        import feathers.themes.MetalWorksMobileTheme;

        public class CustomTheme extends MetalWorksMobileTheme
        {
            public function CustomTheme()
            {
                super();
            }
        }
    }

    当首次启动应用时,实例化主题类:

    new CustomTheme();

    然后,在 CustomTheme 类中添加一个 setCustomButtonStyles 方法,为按钮提供不同的皮肤(假设有个按钮需要修改皮肤):

    protected function setCustomButtonStyles( button:Button ):void
    {
        button.defaultSkin = new Quad( 20060, 0xff0000 );
        button.downSkin = new Quad( 20060, 0x000000 );
        button.fontStyles = new TextFormat( "_sans"36, 0xffffff );
        button.padding = 10;
    }

    在它的超类中定义了一个 initializeStyleProviders() 方法,在该方法中所有的全局样式提供者为 Feathers 组件提供了注册,所以通过覆盖该方法可以通知对应类的全局样式提供者。每一个官方示例主题都是类似的结构(如果是第三方提供的主题文件可能会有所不同):

    override protected function initializeStyleProviders():void
    {
        super.initializeStyleProviders()// 别忘了这个

        this.getStyleProviderForClass( Button )
            .setFunctionForStyleName( "custom-button", this.setCustomButtonStyles );
    }

    通过 getStyleProviderForClass() 方法访问 Button 类的全局样式提供者。如果全局样式提供者(global style provider)还没有被创建过,就会自动创建该对象。因为 MetalWorksMobileTheme 类是 StyleNameFunctionTheme 类的子类,通过 getStyleProviderForClass() 方法返一个 StyleNameFunctionStyleProvider 对象,该对象允许开发者将样式名称与方法关联起来,可以为同一类型的组件提供多个皮肤。

    上面例子中调用了 setFunctionForStyleName() 方法,并传了 “custom-button” 作为样式名称,然后将它与 setCustomButtonStyles 方法名关联起来。

    接下来,对按钮的代码进行一些调整:

    var specialButton:Button = new Button();
    specialButton.label = "Special!";
    specialButton.styleNameList.add( "custom-button" );
    this.addChild( specialButton );

    将  “custom-button” 字符串添加到按钮的 styleNameList,现在按钮就会使用前面自定义的 setCustomButtonStyles() 函数来代替 MetalWorksMobileTheme 定义的默认函数。

    在主题文件中为自定义类提供皮肤:

    默认情况下,自定义类会从它的超类继承样式提供程序。如创建了一个自定义的 CustomButton 类继承了 Button 类,子类就会自动使用 Button.globalStyleProvider。如果希望子类拥有和父类类似的外观,那就不需要做什么。

    但是,如果希望子类使用与其父类不同的默认样式提供程序,则需要向组件的类中添加几个属性。 首先,它需要一个静态全局样式提供者:

    public class CustomComponent extends FeathersControl
    {
        public static var globalStyleProvider:IStyleProvider;
    }

     然后需要覆盖 defaultStyleProvider 实例方法:

    override protected function get defaultStyleProvider():IStyleProvider
    {
        return CustomComponent.globalStyleProvider;
    }

    对绝大多数组件,defaultStyleProvider getter 都应该简单的返回静态全局样式提供者。

    默认样式提供者也是可以自定义的,例如,ToggleButton 组件在 ToggleButton.globalStyleProvider 为 null 时就返回 Button.globalStyleProvider:

    override protected function get defaultStyleProvider():IStyleProvider
    {
        if( ToggleButton.globalStyleProvider )
        {
            return ToggleButton.globalStyleProvider;
        }
        return Button.globalStyleProvider;
    }

    类似的,自定义组件也可以从超类获得可替换的默认样式提供者。

    要将自定义组件添加到主题文件,与前面一样,也是通过覆盖 initializeStyleProviders() 方法:

    override protected function initializeStyleProviders():void
    {
        super.initializeStyleProviders()//别忘了这个!

        this.getStyleProviderForClass(CustomComponent).defaultStyleFunction = 
            this.setCustomComponentStyles;
    }

    可以在 StyleNameFunctionStyleProvider 对象上设置 defaultStyleFunction。如果自定义组件有可替换的备用样式,同样可以像前面介绍的那样对 Button 的全局样式提供者使用 setFunctionForStyleName() 方法。

    Feb

    1

    Feathers 主题或者主题文件(themes)是一个类,集中在同一个文件位置实现多个组件的皮肤样式设置与管理。在主题文件中提供的样式是全局的,任何 Feathers 组件初始化的时候,会自动设置样式。在应用开发时使用主题文件是非常容易、方便和有用的,它可以使用相同的样式皮肤化任何组件。如果想要特殊化一个组件的样式,而不同于默认全局的样式,也可以非常方便的从主题文件中“排除”,如果喜欢默认样式,但想有一些微小的调整,都可以轻松实现。

    主题文件并不是必须的,可以通过 Feathers 组件直接逐个的设置每个组件的样式属性。但使用主题文件会带来很大的好处,就是可以将皮肤样式外观的设置集中到一处,而不会影响其它具体的业务逻辑,减少代码混乱。也可以使用预制的主题(样例主题),进行前期的快速功能开发,后期再调换成自己设计的主题,对其余的部份不会有什么影响。

    初始化主题:

    使用 Feathers UI 库中的示例主题 MetalWorksMobileTheme 类为例,在 Starling 的根容器中立即实例化主题:

    import starling.display.Sprite;
    import feathers.themes.MetalWorksMobileTheme;

    public class Main extends Sprite
    {
        public function Main()
        {
            new MetalWorksMobileTheme();
            super();
        }
    }

    实例化主题的代码在 super() 方法前,如有必要情况,主题就可以对根容器也进行皮肤样式化处理(根容器不一定就是直接扩展 Starling 的 Sprite 类,可能就是实现了一个自定义的 Feathers 组件)。

    此后,当 Feathers 组件被 addChild 到舞台时,主题文件就会自动检测添加的组件,自动给组件添加皮肤。

    自定义特定组件实例:

    1、如果使用主题文件,那么最好将样式化相关的代码都放在主题文件内(脱藕性),但也可以在任何组件上自定义样式,以使其与默认的皮肤样式不同。如自定义一个按钮的标签字体样式: 

    var button:Button = new Button();
    button.label = "Click Me";
    button.fontStyles = new TextFormat( "_sans"20, 0x4c4c4c );
    this.addChild(button);

    如上代码,样例按钮的背景皮肤、padding 等会按着主题文件中的样式进行设置,但 fontStyles 会以这个自定义的 starling.text.TextFormat 样式进行设置。

    相比较而言,在旧版本的 Feathers 中,在主题文件外自定义样式有一些麻烦。如上面的代码,这样直接简单的设置样式有可能会被主题文件替换。但从 Feathers 3.1 开始,这样直接设置样式属性不会再和主题文件中的代码冲突。

    2、还有一些情况,某个组件就是不想使用主题文件中的任何样式,是完完全全自定义的。有下面几种方法可以实现:

    a、清除样式提供者。最直接暴力的方法就是将 styleProvider 属性设为 null,告诉组件不使用主题。

    var button:Button = new Button();
    button.styleProvider = null; //不使用主题文件中的代码!
    button.label = "Click Me";
    button.fontStyles = new TextFormat( "_sans"20, 0x4c4c4c );
    button.defaultSkin = new Quad( 10030, 0xcccccc );
    button.padding = 10;
    this.addChild(button);

    当 styleProvider 属性设为 null 时,按钮的所有样式属性如背景纹理、padding、 layouts、字体等等都需要手动设置了,因为它不会再接受主题文件中的样式代码。

    b、使用扩展的自定义样式名称(相对于前面的方法,这是比较推荐的方法)。这通常会涉及到主题类的扩展,将皮肤样式相关的代码集中放在主题文件中,可以让代码更清晰。

    内置的可交替使用的(备用)样式名称(Built-in alternate style names):

    一些组件提供了一组内置的备用样式名称(style names),样式名称可以被添加到 styleNameList 属性中,用以通知主题文件特定实例的皮肤与默认的皮肤应该有所不同。备用的皮肤在不改变组件功能的基础上,用以在视觉上区分组件实例。例如,希望某个按钮比其它按钮更明显,给它一个名为“call-to-action”的样式名称,让主题给它更明显的颜色用以在视觉上区分其它按钮。

    另一个例子,如 GroupedList 组件有一个标准的普通外观皮肤,看起来就像是 List 组件,但它有 headers 与 footers。还有一个 “inset” 样式,控制组中第一个和最后一个项渲染器的外观带有圆角。如果希望观看两种不同的风格样式,可以在 Component Explorer 中浏览 GroupedList 组件。

    Component Explorer 是类似 Feathers 移动版本的组件 DEMO,使用了 MetalWorksMobileTheme 主题。

    备用的样式名称一般是直接在组件定义的类上定义为 public 的静态常量,如 GroupedList 类定义了 ALTERNATE_STYLE_NAME_INSET_GROUPED_LIST 常量。inset 样式就可以被添加到 styleNameList 中,如:

    var list:GroupedList = new GroupedList();
    list.styleNameList.add( GroupedList.ALTERNATE_STYLE_NAME_INSET_GROUPED_LIST );
    this.addChild( list );

    如果正在使用的主题文件没有为组件的备用样式名称提供对应的皮肤,组件并不会因为找不到皮肤而看不到或产生异常,而是主题会自动回退到显示默认的皮肤。主题文件的程序员并不需要编写特别的代码,它已经是主题系统的核心部分。这样只是为了确保开发者在切换不同的主题文件时,可以让组件的功能正常运行,而不产生问题。如果自定义自己的主题文件,确保为每个备用样式名称提供对应的皮肤是最好的做法。

    文件大小警告:

    示例主题文件包含了每个可用的 feathers 组件,这意味着如果使用示例主题文件,所有的羽毛组件不管用到的还是没用到的都将被编译到正在开发的应用中去。如果要保存文件大小,应该考虑修改主题文件的源代码,以删除项目中未使用的所有组件的引用。另一种更好的方式就是完完全全的从头开始创建一个自定义的主题文件

    Feb

    1

    Feathers3 皮肤资料备注

    • 0 Comments
    • Flash Platform

    所有的 Feathers 组件都支持大量的样式属性以允许用户自定义设计。许多组件包含了多个状态的背景皮肤属性,还有不同的布局属性,如 padding、alignment、字体样式等。此外,还有一些组件在移动开发或桌面开发时分别会有不同的选项。

    实际上 Feathers 没有默认皮肤,它鼓励开发者自己开发设计独一无二的皮肤外观。但也为了方便开发者,Feathers 提供了几个不同风格的示例主题,如果有必要或为了方便开发,开发者也可以直接扩展这些示例主题自定义不同的皮肤。

    基本资料:

    所有的皮肤、布局选项、字体样式等其它有用的样式属性,都是以 public 的属性方式提供了 API。自定义样式化的皮肤和布局时就像设置属性一样简单。以一个按钮为例:

    var button:Button = new Button();
    button.label = "Click Me";
    button.defaultSkin = new Quad( 10030, 0xc4c4c4 );
    button.fontStyles = new TextFormat( "Helvetica"20, 0x3c3c3c );
    this.addChild( button );

    这里只是一个基本示例,还有其它更多的布局或图标相关的样式属性,可以参考具体的 API 手册。其中, starling.text.TextFormat 可以为任何 Feathers 组件用于自定义字体样式,无论是使用位图字体、嵌入字体或设备字体等。

    组件有多个状态

    有些组件类似 Button、TextInput 等有多个状态,可以为不同的状态设置不同的皮肤外观。比如通过 setTextureForState() 设置不同状态时的背景皮肤纹理:

    var skin:ImageSkin = new ImageSkin( upTexture );
    skin.setTextureForState( ButtonState.DOWN, downTexture );
    button.defaultSkin = skin;

    传递一个默认皮肤纹理给 ImageSkin 构造函数,然后再通过 setTextureForState() 方法设置不同状态的纹理。如果我们没有为某个状态设置皮肤纹理,那么默认的皮肤纹理就会被使用,比如上面的代码中没有设置 HOVER 状态的皮肤,那么 HOVER 状态时就会显示默认的皮肤纹理。如果要设转上老母一个状态,可能类似的代码如下面这样子:

    var skin:ImageSkin = new ImageSkin( upTexture );
    skin.setTextureForState( ButtonState.DOWN, downTexture );
    skin.setTextureForState( ButtonState.HOVER, hoverTexture );
    skin.setTextureForState( ButtonState.DISABLED, disabledTexture );
    button.defaultSkin = skin;

    和上面设置不同状态的纹理类似,可以通过 setFontStylesForState() 方法设置不同状态的字体样式。

    var upFontStyles:TextFormat = new TextFormat( "Helvetica"20, 0x3c3c3c );
    var downFontStyles:TextFormat = new TextFormat( "Helvetica"20, 0xff0000 );

    button.fontStyles = upFontStyles;
    button.setFontStylesForState( ButtonState.DOWN, downFontStyles );

    如上代码中,fontStyles 是默认的字体样式,而 setFontStylesForState() 方法可以自定义不同状态的字体样式(new 一个 TextFormat 即可)。

    设置子组件的外观皮肤样式:

    一些“复杂的” Feathers 组件会包含其它 Feathers 组件作为子组件。例如一个 Panel 组件,包含了一个 header 对象,像这样的 header 子组件是可以设置样式的,但它的样式属性并不会直接通过父组件直接暴露给开发者。

    样式化一个子组件比较简单的方法是自定义工厂方法,直接返回一个样式化的子组件,如 Panel 的 header 对象是通过 headerFactory 属性的工厂方法创建的:

    var panel:Panel = new Panel();
    panel.headerFactory = function():IFeathersControl
    {
        var header:Header = new Header();
        header.backgroundSkin = new Quad( 20050, 0x3c3c3c );
        header.fontStyles = new TextFormat( "Helvetica"20, 0xd4d4d4 );
        return header;
    };
    this.addChild( panel );

     如果开发者正在使用一个主题( theme) 文件,可能会希望将皮肤样式外观有关的代码都放在主题文件中,那样的话就不推荐使用生成子组件的工厂方法,而应该为子组件创建新的样式名称,然后将代码移动到主题文件中。

    复杂的组件允许开发者自定义子组件的样式名称。在这个 Panel 的例子中,可以设置 customHeaderStyleName 属性,如:

    var panel:Panel = new Panel();
    panel.customHeaderStyleName = "custom-panel-header";
    this.addChild( panel );

     在主题文件中,创建一个对应的方法用于对子组件进行样式化设置:

    private function setCustomPanelHeaderStyles( header:Header ):void
    {
        header.backgroundSkin = new Quad( 20050, 0x3c3c3c );
        header.fontStyles = new TextFormat( "Helvetica"20, 0xd4d4d4 );
    }

     最后“告诉”主题文件,当子组件有自定义的样式名称时,对应的调用哪个方法:

    getStyleProviderForClass( Header ).setFunctionForStyleName( "custom-panel-header", setCustomPanelHeaderStyles );

    Feb

    1

    Feathers3 "Tool-tips" 一些备注

    • 0 Comments
    • Flash Platform

    Tool-tips (提示功能)最常见的情况是在桌面开发的应用中,当鼠标悬停在某个 UI 按钮上面时,会出现一个提示文本。Feathers 也可提供了这样的功能,通过 ToolTipManager 可以方便的实现。(虽然移动开发也能使用,但移动设备因为没有鼠标指针,所以一般不会用这样的功能,就算用了在一般情况下也是看不到提示的)。

    启用 Tool-Tip Manager:

    要启用提示功能,应用首次启动时只需要一行代码:

    ToolTipManager.setEnabledForStage( this.stage, true );

    然后只需要设置组件的 toolTip 属性即可。如果开发的是一个桌面 AIR 程序,具有多个窗口,每个窗口都有一个独立的舞台对象,所以需要为每个应用窗口单独调用 setEnabledForStage() 方法。  

    var button:Button = new Button();
    button.label = "Click Me";
    button.toolTip = "Some useful information";

    所有的 Feathers 组件都有一个 toolTip 属性。

    Feb

    1

    Feathers 提供了拖放管理器(DragDropManager )提供拖放功能(Drag 与 Drop 功能, 也被简称为 “D&D” 功能,如果有使用过 Flex 的 D&D 功能,应该会很熟悉基本流程)。基本流程如下:

    1、首先需要在对应的对象上实现 IDragSource 与 IDropTarget 接口。

    2、调用 DragDropManager.startDrag() 方法:

    传入第一个参数是 IDragSource 对象。

    第二个参数是启动拖动操作的 Touch 对象。

    第三个参数是 DragData 对象,它存储正在拖动的数据,可以指定数据格式,用于指定不同的目标可以接受不同的数据类型。

    3、当被拖动的对象经过 IDropTarget 对象时,IDropTarget 对象会派发 DragDropEvent.DRAG_ENTER 事件时(DragDropManager 会自动派发这个事件,开发者只需要侦听这个事件就可以),DragData 包含了目标对象可以接受的数据格式,会调用 DragDropManager.acceptDrag() 方法。

    4、如果用户在 IDropTarget 对象上面有释放操作(比如停止触摸或释放鼠标),IDropTarget 对象会派发 DragDropEvent.DRAG_DROP 事件,如果正是需要的目标对象,需要在侦听器中处理释放(Drop)操作。

    5、无论释放操作是否在需要的目标对象上,IDragSource 对象都会派发 DragDropEvent.DRAG_COMPLETE 事件,事件对象的 isDropped 属性是会指示出释放操作是否成功。如果数据需要在释放操作后被删除,就可以在侦听器中处理,检查 isDropped 属性是否是成功的接受了释放对象,还是取消了。

    当在调用 DragDropManager.startDrag() 方法时,可以使用一个具体的图像作为拖动数据的“代理显示”。在拖动行为过程中这个图像会跟随着手指或鼠标移动。

    数据格式类似是在拖动对象与目标对象之间的一种契约协议,可能是代理显示的图像,也可能是纯粹的数据项,比如 List 对象中的项数据。如果只是简单的拖动一个显示对象(没有任何其它数据关联),开发者可以将显示对象作为拖动“数据”,类似代码:

    var dragData:DragData = new DragData();
    dragData.setDataForFormat("display-object-drag-format", theDisplayObject);

    可以使用任意的字符串“display-object-drag-format”,只需要拖动源与释放目标能相互识别即可:

    在  DragDropEvent.DRAG_ENTER 事件中,检测是否可以接受被拖动的对象:

    function(event:DragDropEvent, dragData:DragData):void
    {
        if(dragData.hasDataForFormat("display-object-drag-format"))
        {
            DragDropManager.acceptDrag(this);
        }
    }

    在 DragDropEvent.DRAG_DROP 事件侦听器中,使用 dragData.getDataForFormat() 方法来检索显示对象或其它数据。