惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
C
Check Point Blog
雷峰网
雷峰网
小众软件
小众软件
GbyAI
GbyAI
美团技术团队
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
Y
Y Combinator Blog
Jina AI
Jina AI
爱范儿
爱范儿
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美

博客园 - spoony

Flex4 Skinning 4: 留言板示例 Flex4 Skinning 1: 自定义一个简单按钮的皮肤 Air应用程序访问本地数据库 [通篇转载]调程序的小女孩 Adobe ColdFusion Reports(5) Adobe ColdFusion Reports(4) Adobe ColdFusion Reports(3) Adobe ColdFusion Reports(2) Adobe ColdFusion Reports(1) Adobe ColdFusion Charts(3) Adobe ColdFusion Charts(2) Adobe ColdFusion Charts(1) Adobe ColdFusion读写Microsoft Office Excel数据(3) Adobe ColdFusion读写Microsoft Office Excel数据(2) Adobe ColdFusion读写Microsoft Office Excel数据(1) Flex与DotNET WebService交互(2) Flex与DotNET WebService交互(1) Ext JS Designer 1.0.5 发布 设置Flex Builder中FlashPlayer调试版本 - spoony - 博客园
Flex4 Skinning 2: 皮肤协议
spoony · 2010-11-03 · via 博客园 - spoony

  上一篇随笔中笔者介绍了如何为按钮制作一个简单的自定义皮肤,接下来分析一下皮肤文件的组成部分,并对皮肤协议(skinning contract)中各个部分进行详细介绍。

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

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

  接下来就是states。

<!-- states -->
<s:states>
   <s:State name="disabled" />
   <s:State name="down" />
   <s:State name="over" />
   <s:State name="up" />
</s:states>

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

//--------------------------------------
//  Skin states
//--------------------------------------

/**
 *  Up State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("up")]

/**
 *  Over State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("over")]

/**
 *  Down State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("down")]

/**
 *  Disabled State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("disabled")]

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

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

<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, 代码如下:

//--------------------------------------------------------------------------
//
//  Skin parts
//
//--------------------------------------------------------------------------

[SkinPart(required="false")]

/**
 *  A skin part that defines the label of the button. 
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public 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部分改成如下代码所示:

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

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

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx" 
			   minWidth="955" minHeight="600">

	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
	
	<fx:Style>
		@namespace s "library://ns.adobe.com/flex/spark";
		@namespace mx "library://ns.adobe.com/flex/mx";
		s|Button {
			skinClass:ClassReference("com.guyue.skins.SimpleButtonSkin");
		}
	</fx:Style>
	
	<s:layout>
		<s:VerticalLayout />
	</s:layout>
	
	<s:Button label="Simple Button" />
	
	<s:Button label="I used to be a button without customerized skin. " />
	
</s:Application>

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

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

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

/**
 *  @private
 */
override protected function partAdded(partName:String, instance:Object):void
{
    super.partAdded(partName, instance);
    
    if (instance == labelDisplay)
    {
        labelDisplay.addEventListener("isTruncatedChanged",
                                      labelDisplay_isTruncatedChangedHandler);
        
        // Push down to the part only if the label was explicitly set
        if (_content !== undefined)
            labelDisplay.text = label;
    }
}

/**
 *  @private
 */
override protected function partRemoved(partName:String, instance:Object):void
{
    super.partRemoved(partName, instance);
    
    if (instance == labelDisplay)
    {
        labelDisplay.removeEventListener("isTruncatedChanged",
                                         labelDisplay_isTruncatedChangedHandler);
    }
}

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

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

//--------------------------------------------------------------------------
//
//  Skin parts
//
//--------------------------------------------------------------------------

[SkinPart(required="false", type="mx.core.IDataRenderer")]
    
/**
 *  A skin part that defines a dataTip that displays a formatted version of 
 *  the current value. The dataTip appears while the thumb is being dragged.
 *  This is a dynamic skin part and must be of type IFactory.
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
public 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的定义和交互。