Flex4 Skinning 2: 皮肤协议

本文详细介绍了如何为按钮制作自定义皮肤,包括皮肤文件的组成部分、状态、皮肤协议以及与组件之间的交互。通过实例展示了如何在皮肤文件中定义状态、设置外观,并解释了组件与皮肤之间的数据传输方式。最后讨论了静态和动态SkinPart的使用,以及如何在开发过程中灵活运用皮肤文件实现外观和逻辑的分离。
摘要由CSDN通过智能技术生成
 上一篇随笔中笔者介绍了如何为按钮制作一个简单的自定义皮肤,接下来分析一下皮肤文件的组成部分,并对皮肤协议(skinning contract)中各个部分进行详细介绍。

  首先可以看到根标签为Skin, 接下来是HostComponent元数据,这个可以在我们新建文件的时候指定,可以参照上一篇随笔的图1。

1<fx:Metadata>
2  [HostComponent("spark.components.Button")]
3</fx:Metadata>

  接下来就是states。

1<!-- states -->
2<s:states>
3   <s:State name="disabled" />
4   <s:State name="down" />
5   <s:State name="over" />
6   <s:State name="up" />
7</s:states>

  来查看一下按钮(spark.components.Button)组件的源代码,准确说应该是按钮(spark.components.Button)类的父类按钮基类(spark.components.supportClasses.ButtonBase), 找到元数据中带有SkinState元数据的部分,可以看到该类具有四个skin state: up, over, down disabled, 这与在皮肤文件中定义的states部分相对应。代码如下:

01//--------------------------------------
02//  Skin states
03//--------------------------------------
04  
05/**
06 *  Up State of the Button
07 *  
08 *  @langversion 3.0
09 *  @playerversion Flash 10
10 *  @playerversion AIR 1.5
11 *  @productversion Flex 4
12 */
13[SkinState("up")]
14  
15/**
16 *  Over State of the Button
17 *  
18 *  @langversion 3.0
19 *  @playerversion Flash 10
20 *  @playerversion AIR 1.5
21 *  @productversion Flex 4
22 */
23[SkinState("over")]
24  
25/**
26 *  Down State of the Button
27 *  
28 *  @langversion 3.0
29 *  @playerversion Flash 10
30 *  @playerversion AIR 1.5
31 *  @productversion Flex 4
32 */
33[SkinState("down")]
34  
35/**
36 *  Disabled State of the Button
37 *  
38 *  @langversion 3.0
39 *  @playerversion Flash 10
40 *  @playerversion AIR 1.5
41 *  @productversion Flex 4
42 */
43[SkinState("disabled")]

  States是Flex4中skinning contract的重要组成部分。皮肤(skin)文件和相对应的控件组件(component)相互交互用到的正是skinning contract. 如同Button有四个state一样,每个skinning component都有一系列相对应的state, 可以通过component当前所处的state来对外观进行不同的设置。

  组件的includeIn和excludeFrom属性为不同state的外观定义提供了很大便利。通过这两个属性可以在某特定的state下设置该state需要的外观。另外值得注意的是也可以通过“属性名.state=属性值”来进行更进一步的设置。如:

1<s:SolidColor color="0x77CC22" color.over="0x92D64E" color.down="0x67A41D" />

  以上代码段表示当按钮处于over state的时候,颜色为"0x92D64E",其它state时颜色为"0x77CC22"。

  SkinState元数据为皮肤文件中定义哪些state做了直接说明,如果皮肤中没有定义相关的state, 而且这个state被设置为required=true, 那么编译就会报错。

  子类会从父类中集成state, 比如前面提到的四个skin state: up, over, down, disabled的定义并不存在于spark.components.Button中,而是在ButtonBase中。不过我们可以通过Exclude元数据对其进行过滤。

  在开发过程中,如果改变state, 需要用到component的getCurrentSkinState()和invalidateSkinState()方法。getCurrentSkinState()用来追踪component的各个属性,并确定skin应该处于的state. invalidateSkinState()方法会使改变前的state失效并且根据getCurrentSkinState()方法返回值来设置当前的state.

  有时候skin需要引用相关component的内容。如果设置了HostComponent元数据,那么在skin里面就可以直接调用。在skin和component之间交互数据有两种方式。推式(push method)是指component把数据推入到skin中。相反的,拉式(pull method)是指skin向component索要属性值数据。关于这两种数据传输方式并没有固定的规定,但是一般情况下,如果component中定义SkinPart为required=true, 则采用推式(push method)。

  接下来我们可以在spark.components.supportClass.ButtonBase类定义中可以找到标有SkinPart的成员变量labelDisplay, 代码如下:

01//--------------------------------------------------------------------------
02//
03//  Skin parts
04//
05//--------------------------------------------------------------------------
06  
07[SkinPart(required="false")]
08  
09/**
10 *  A skin part that defines the label of the button. 
11 *  
12 *  @langversion 3.0
13 *  @playerversion Flash 10
14 *  @playerversion AIR 1.5
15 *  @productversion Flex 4
16 */
17public var labelDisplay:TextBase;

  Skin part也是skin contract的重要组成部分。任何复杂的component都是由简单的组合而成。比如前面提到的按钮,就是由文本和矩形组成。文本就是按钮的skin part. 它由component声明,可以是可选的。定义的方式是使用SkinPart元数据,可以把这个元数据标记在任何公共的属性上,而属性的名称就成了这个part的名称。比如在按钮当中,labelDisplay就是一个SkinPart, 它被定义为可选的,类型为TextBase.

  在component中定义了skin part后,需要在skin文件中进行对应的实现,比如我们可以将上一篇随笔中定义的label的id属性设置为labelDisplay, 这样Flex就会通过id属性自动将这个label与Button的labelDisplay相匹配。

  将皮肤文件的Label部分改成如下代码所示:

1<!-- text -->
2<s:Label id="labelDisplay" text="{hostComponent.label}" color="0x131313" 
3         textAlign="center" verticalAlign="middle" 
4         horizontalCenter="0" verticalCenter="1" 
5         left="12" right="12" top="6" bottom="6" />

  可以发现,此时skin文件要想引用对应的component中的数据只需要使用hostComponent属性就可以了,这是因为我们在最开始就已经对元数据HostComponent进行了设置。修改相应的主应用程序,代码如下所示:

01<?xml version="1.0" encoding="utf-8"?>
02<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
03               xmlns:s="library://ns.adobe.com/flex/spark" 
04               xmlns:mx="library://ns.adobe.com/flex/mx" 
05               minWidth="955" minHeight="600">
06  
07    <fx:Declarations>
08        <!-- Place non-visual elements (e.g., services, value objects) here -->
09    </fx:Declarations>
10      
11    <fx:Style>
12        @namespace s "library://ns.adobe.com/flex/spark";
13        @namespace mx "library://ns.adobe.com/flex/mx";
14        s|Button {
15            skinClass:ClassReference("com.guyue.skins.SimpleButtonSkin");
16        }
17    </fx:Style>
18      
19    <s:layout>
20        <s:VerticalLayout />
21    </s:layout>
22      
23    <s:Button label="Simple Button" />
24      
25    <s:Button label="I used to be a button without customerized skin. " />
26      
27</s:Application>

  再次运行程序,可以发现现在两个按钮公用同样的皮肤,而且skin的定义在主应用程序端是完全透明的,这样外观和逻辑就实现了分离。如图3.

 

Figure 3. 使用样式可以同时为多个按钮设置皮肤

  上面提到的TextBase是确定的类型,而且在声明周期中只会存在唯一的实例,称为静态的。当加载静态SkinPart的皮肤时,SkinnableComponent基类负责将所有的静态part从skin加载到component中。加载完毕之后,component就可以直接使用这些实例了。当part加载的时候,partAdded()会被调用,当part移除的时候partRemoved()方法会被调用。因此我们可以在这两个方法中添加相关事件处理逻辑。

01/**
02 *  @private
03 */
04override protected function partAdded(partName:String, instance:Object):void
05{
06    super.partAdded(partName, instance);
07      
08    if (instance == labelDisplay)
09    {
10        labelDisplay.addEventListener("isTruncatedChanged",
11                                      labelDisplay_isTruncatedChangedHandler);
12          
13        // Push down to the part only if the label was explicitly set
14        if (_content !== undefined)
15            labelDisplay.text = label;
16    }
17}
18  
19/**
20 *  @private
21 */
22override protected function partRemoved(partName:String, instance:Object):void
23{
24    super.partRemoved(partName, instance);
25      
26    if (instance == labelDisplay)
27    {
28        labelDisplay.removeEventListener("isTruncatedChanged",
29                                         labelDisplay_isTruncatedChangedHandler);
30    }
31}

  以上便是Button类中相关的代码,可以看出我们能够动态的为component中skin part添加或减少相关事件监听器。

  但是有些时候我们需要动态的使用多个SkinPart的实例,比如spark.components.supportClasses.SliderBase这个类的所有子类的dataTip属性。这种SkinPart称为动态的。具体的代码如下所示:

01//--------------------------------------------------------------------------
02//
03//  Skin parts
04//
05//--------------------------------------------------------------------------
06  
07[SkinPart(required="false", type="mx.core.IDataRenderer")]
08      
09/**
10 *  A skin part that defines a dataTip that displays a formatted version of 
11 *  the current value. The dataTip appears while the thumb is being dragged.
12 *  This is a dynamic skin part and must be of type IFactory.
13 *  
14 *  @langversion 3.0
15 *  @playerversion Flash 10
16 *  @playerversion AIR 1.5
17 *  @productversion Flex 4
18 */
19public var dataTip:IFactory;

  可以看出dataTip属性的类型为Ifactory, 这表示开发人员可以动态的在component中对其实例利用createDynamicPartInstance()方法初始化,利用removeDynamicPartInstance()方法来移除。相应的partAdded()和partRemoved()方法与静态SkinPart处理相似。在一个component可以有多个动态part实例,何时调用createDynamicPartInstance()完全由开发人员根据需要来决定。

  卸载skin基本上是自动进行的。程序运行时skin发生变化的时候就会调用detachSkin()方法。所有skin part相关的partRemoved()方法也会自动调用。开发人员可以在这两个方法里添加相应的事件监听。

  有些复杂的component 可能需要在skin创建后某个不确定的时间与skin part进行交互。Component需要知道partAdded()方法可能在skin初始化很久以后才会被调用。这样的skin part被称为延迟的(deferred part)。一个典型的例子就是TabNavigator, 当用户点击了某个Tab之后,component才会知道新的part然后调用partAdded()方法。

  图4是Adobe官方给出的skin parts生命周期。

 

Figure 4.

  本篇随笔中提到的state, skin part, data构成了skin contract. 他们共同实现了component及其skin的定义和交互。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值