Flex移动皮肤开发(一)
Flex 4.5提供的移动增强的皮肤特性,支持触摸交互、性能优良,并且考虑到了内存占用问题。尽管目前市场上有不少性能优异的设备,但典型的Spark皮肤(包括Flex 4引入的默认皮肤)却没有能够在移动设备上得到很好的应用。Adobe为移动优化过的皮肤在设计时就考虑到平衡两个对立的目标:性能优异却又容易创建。虽然MXML皮肤在某些情况下是有用的,但Adobe还是建议遵循以下简单的方针,确保Flex 4.5移动应用程序能够同时满足开发者和最终用户的性能要求。
这是介绍Flex 4.5移动皮肤特性系列文章中的第一篇。
本文将涵盖创建移动增强皮肤的基本知识,包括:
- Spark主题和Mobile主题的区别
- MobileSkin基类中包含的性能优化
- 用FXG替代MXML图形
- 基于Mobile主题的ButtonSkin类创建自定义的Button皮肤
该系列文章中的余下几篇将会介绍更多高级主题,包括:
- 基于MobileSkin基类构建新的皮肤
- 创建空间感知的皮肤,可以适应所有屏幕尺寸
- 使用CSS媒体查询为每个平台创建自定义的主题
Mobile主题基本是Flex 4引入的Spark主题功能集的一个子集。Mobile主题专门针对性能和内存占用问题进行的优化。
MobileSkin 基类
Spark 皮肤通常需要继承 Skin (它扩展了 Group) 或者 SparkSkin (它扩展了 Skin)。Mobile主题里的皮肤使用一个新的基类MobileSkin,它扩展了UIComponent。
状态
MobileSkin对状态的支持是它一项针对移动开发优化过的主要特性。一般情况下,使用状态特性(在MXML或者ActionScript)都会带来额外的内存和性能消耗。而MobileSkin不再用State类及子类,而是手工的在代码里处理状态改变,例如SetProperty
和 AddChild
。
布局
因为MobileSkin不是一个 Group, 它不能使用 Spark的基于约束的布局。例如 HorizontalLayout, VerticalLayout和BasicLayout 。
MobileSkin的内容在代码中人工的布局。MobileSkin加入了新的生命周期方法layoutContents(),
,它在updateDisplayList()
中被调用。这个方法用于放置皮肤的子元素。
FXG 和 MXML 图形
Mobile主题中皮肤的图形由编译过的FXG和用于绘制的ActionScript代码构成。绘制代码仅用于琐碎的图形或者需要支持样式的时候。处于性能优化的考虑,所有其他图形都使用编译过的FXG。MobileSkin基类不选择使用MXML图形,除非他们都包含在一个Group当中。
文本
Mobile主题在默认皮肤中不会使用Flash Text Engine (FTE)或者Text Layout Framework (TLF) 。这主要是基于性能的考虑,并且为了支持原生的文本输入和编辑。
Flex 4.5在mobilecomponents.swc中引入了一个新的StyleableTextField类。它扩展了TextField,并且加入了对样式的支持。它专门用于那些不准备与MXML结合使用的移动ActionScript皮肤和渲染器。
在同时使用StyleableTextField 和 Label (基于 FTE)时,开发者必须嵌入字体两次。Label 使用embedAsCFF=true
,而 TextField 和 StyleableTextField 用embedAsCFF=false
。
Adobe不建议在移动项目中使用RichEditableText组件。请用TextArea组件代替。
MXML 和 ActionScript
由于MobileSkin删除了MXML皮肤的需要主要的申明性特性(状态、布局和MXML图形),Adobe建议用ActionScript编写皮肤。没有这三个特性,用MXML做声明性的标识已经没有什么优势了。
注意: 在Flash Builder中,可以指定库和主题的SWCs的代码或者编译过的ActionScript皮肤,由设计视同渲染出MXML皮肤。但设计视图不能通过指定代码渲染ActionScript皮肤。
不支持的全局样式
由于上文提到限制,Mobile主题删除了一些样式,包括:
rollOverColor
– 不支持,因为基础可触屏的界面是目前主要环境。borderAlpha
,borderColor
,cornerRadius
– 不支持,因为这些参数是编译过的FXG的属性,他们不会在运行时发生变化dropShadowVisible
– 不支持,为性能考虑,尽量减少过滤器的使用。- Flash Builder 会根据当前选择的主题,在MXML和CSS编辑器中正确的显示或者隐藏样式属性
Mobile中避免使用的组件
由于各种原因,某些Spark组件在Mobile主题中没有皮肤。例如,有些组件在Mobile UI下没有什么作用,或者它根本就不是 Flex 4.5 版本的主要目标,可能在下一个发行版中专门为移动平台进行优化。这些组件包括:
- ComboBox and DropDownList
- NumericStepper
- ToggleButton
- VideoDisplay and VideoPlayer
- VSlider
- Panel
- TabBar (the component used in TabbedViewNavigator is actually a ButtonBar)
- TitleWindow
开始使用皮肤特性的最佳方式是,基于默认的Buton皮肤,创建一个自定义的Button皮肤。为了让问题简单化,这个例子没有考虑到屏幕DPI的问题。笔者将在系列文章的其他篇幅讨论这些问题。
首先,使用 Adobe Illustrator CS5为Button图形创建一个FXG文件。这个文件将同时展现Button组件的up
和down
皮肤状态。因为这里主要针对触摸输入,所以没有创建over
状态。
其次,通过重载MobileSkin的drawBackground()
方法,添加对chromeColor
样式的支持。或者,也可以手动在up和down的图形里添加固定的背景颜色,然后重载drawBackground()
方法,不作任何事情。disabled
状态仅仅会改变up
状态的 alpha
值。移动ButtonSkin类已经默认包含这个功能。
根据自己的喜好,可以选择Adobe Flash Professional、Adobe Illustrator或者 Adobe Fireworks构建FXG。你也可以在Flash Builder中手工编辑FXG,然后在MXML文件中移动这个FXG,再用设计视图渲染它,可视化的检查结果。
用 Illustrator 创建 FXG
这个例子用 Illustrator 创建一个药丸形状的Button图形.
- 在Illustrator中,选择 File > New。
- 为图形命名,例如RoundedButtonExport。
- 设置 New Document Profile 为Flash Catalyst。
- 设置大小为典型的手机尺寸 480 px x 800 px 。
- 点击 OK
- 用矩形工具新建一个由灰色笔触,渐变填充的矩形。
- 在Stroke面板中,找到Align Stroke,选择中间的Align Stroke To Inside选项。默认情况下,笔触都是居中对齐的。笔者将稍候介绍为什么最好避免使用这个默认值。
- 修改 X 和 Y 位置为 0.
- 选中矩形后,选择 Effect > Stylize > Round Corners。
- 指定角的半径为 22 px。
- 保存为没有私有数据的 FXG 。
清理 FXG
导出FXG文件后,你可能希望清理一些不需要的标签,例如多余的Group标签或者私有的命名空间数据。这个步骤不是必须的,不过这样做可以让你的FXG更容易让人理解。
RoundedButtonExport.fxg
<?xml version="1.0" encoding="utf-8" ?> <Graphic version="2.0" viewHeight="800" viewWidth="480" ai:appVersion="15.0.2.399" ATE:version="1.0.0" flm:version="1.0.0" d:using="" xmlns="http://ns.adobe.com/fxg/2008" xmlns:ATE="http://ns.adobe.com/ate/2009" xmlns:ai="http://ns.adobe.com/ai/2009" xmlns:d="http://ns.adobe.com/fxg/2008/dt" xmlns:flm="http://ns.adobe.com/flame/2008"> <Library/> <Group ai:seqID="1" d:layerType="page" d:pageHeight="800" d:pageWidth="480" d:type="layer" d:userLabel="Artboard 1"> <Group ai:seqID="2" d:type="layer" d:userLabel="Layer 1"> <Group ai:seqID="3" flm:knockout="false" d:type="layer" d:userLabel="RoundedButton"> <Group ai:seqID="4" flm:knockout="false"> <Path x="0.5" y="1" winding="nonZero" ai:seqID="5" data="M21.5 43C9.64502 43 0 33.355 0 21.5 0 9.64502 9.64502 0 21.5 0L197.5 0C209.355 0 219 9.64502 219 21.5 219 33.355 209.355 43 197.5 43L21.5 43Z"> <fill> <LinearGradient x="109.5" y="0" scaleX="43" rotation="90"> <GradientEntry ratio="0" color="#F0F0F0"/> <GradientEntry ratio="0.478788" color="#C8C8C8"/> <GradientEntry ratio="0.50303" color="#BBBBBB"/> <GradientEntry ratio="1" color="#F0F0F0"/> </LinearGradient> </fill> </Path> <Path winding="nonZero" ai:seqID="6" data="M198 1C209.58 1 219 10.4204 219 22 219 33.5796 209.58 43 198 43L22 43C10.4204 43 1 33.5796 1 22 1 10.4204 10.4204 1 22 1L198 1M198 0 22 0C9.8999 0 0 9.8999 0 22 0 34.1001 9.8999 44 22 44L198 44C210.1 44 220 34.1001 220 22 220 9.8999 210.1 0 198 0L198 0Z"> <fill> <SolidColor color="#DDDDDD"/> </fill> </Path> </Group> </Group> </Group> </Group> <Private/> </Graphic>
RoundedButtonCleanup.fxg
<?xml version="1.0" encoding="utf-8" ?> <Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008"> <Path x="0.5" y="1" winding="nonZero" data="M21.5 43C9.64502 43 0 33.355 0 21.5 0 9.64502 9.64502 0 21.5 0L197.5 0C209.355 0 219 9.64502 219 21.5 219 33.355 209.355 43 197.5 43L21.5 43Z"> <fill> <LinearGradient x="109.5" y="0" scaleX="43" rotation="90"> <GradientEntry ratio="0" color="#F0F0F0"/> <GradientEntry ratio="0.478788" color="#C8C8C8"/> <GradientEntry ratio="0.50303" color="#BBBBBB"/> <GradientEntry ratio="1" color="#F0F0F0"/> </LinearGradient> </fill> </Path> <Path winding="nonZero" data="M198 1C209.58 1 219 10.4204 219 22 219 33.5796 209.58 43 198 43L22 43C10.4204 43 1 33.5796 1 22 1 10.4204 10.4204 1 22 1L198 1M198 0 22 0C9.8999 0 0 9.8999 0 22 0 34.1001 9.8999 44 22 44L198 44C210.1 44 220 34.1001 220 22 220 9.8999 210.1 0 198 0L198 0Z"> <fill> <SolidColor color="#DDDDDD"/> </fill> </Path> </Graphic>
测试 FXG
导出和清理 FXG之后,你可以把它丢进一个MXML文件中,用设计视图预览,从而快速的验证它是否工作。
尝试以下步骤:
- 在Flash Builder中新建一个项目(可以命名为任何名字)。
- 在源代码文件夹中加入 FXG 文件。
- 切换到设计视图
组件面板将会把FXG文件显示为一个自定义组件。
- 拖拽这个组件到设计区域。你可以看到FXG被渲染成原本的大小。
- 在设计视图中,尝试改变FXG的宽度。
你将发现圆角在水平方向拉伸(见图1)。但你希望看到的应该是圆角的形状保持不变,而是让图形的中间部分拉伸。为此,需要为FXG加入伸缩网格信息。
手工添加scale grids
为让grid伸缩适当的伸缩,你需要添加网格的左、右、上和下的位置信息。因为使用了Round Corners风格化选项,我们知道当前的半径是22px。有了这些信息,你可以打开Flash Builder,对FXG文件做如下修改:
- 在
<Graphic>
根标签中添加scaleGridLeft="22"
和scaleGridRight="198px"
(图形的完整宽度是 220px)
你还需要确保边界的笔锋没有伸缩。
- 在同一个标签中,为这个图形添加
scaleGridTop="1px"
和scaleGridBottom="43px"
(图形的完整高度是 44px)
注意: 对于有些图形,很难找到任意路径上的精确起始点。这种情况下,请使用Illustrator的选择工具去覆盖一个锚点,从而找到它的坐标(见图2)。
在Graphic根标签添加网格数据之后,查看一下设计视图中的新FXG,你会发现没有什么变化。因为路径信息并没有填满图形的全部尺寸,所以伸缩网格没有变化。
- 为了修复这个问题,如下所示,添加一个透明的Rect占据整个图形空间。
- 将刚做的修改保存为RoundedButton.fxg.
RoundedButton.fxg
<?xml version="1.0" encoding="utf-8" ?> <Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008" scaleGridLeft="22" scaleGridRight="198" scaleGridTop=”1” scaleGridBottom=”43”> <Path x="0.5" y="1" winding="nonZero" data="M21.5 43C9.64502 43 0 33.355 0 21.5 0 9.64502 9.64502 0 21.5 0L197.5 0C209.355 0 219 9.64502 219 21.5 219 33.355 209.355 43 197.5 43L21.5 43Z"> <fill> <LinearGradient x="109.5" y="0" scaleX="43" rotation="90"> <GradientEntry ratio="0" color="#F0F0F0"/> <GradientEntry ratio="0.478788" color="#C8C8C8"/> <GradientEntry ratio="0.50303" color="#BBBBBB"/> <GradientEntry ratio="1" color="#F0F0F0"/> </LinearGradient> </fill> </Path> <Path winding="nonZero" data="M198 1C209.58 1 219 10.4204 219 22 219 33.5796 209.58 43 198 43L22 43C10.4204 43 1 33.5796 1 22 1 10.4204 10.4204 1 22 1L198 1M198 0 22 0C9.8999 0 0 9.8999 0 22 0 34.1001 9.8999 44 22 44L198 44C210.1 44 220 34.1001 220 22 220 9.8999 210.1 0 198 0L198 0Z"> <fill> <SolidColor color="#DDDDDD"/> </fill> </Path> <!-- scale grid fix --> <Rect x="0" y="0" width="220" height="44"> <fill> <SolidColor color="#000000" alpha="0"/> </fill> </Rect> </Graphic>
现在,设计视图中的Button应该伸缩自如了。(见图 3)
基于现有的Mobile主题,创建一个新的Button皮肤的过程氛围3个主要步骤。
- 新建一个spark.skins.mobile.ButtonSkin的子类。
- 在构造函数中,设置FXG类的
upBorderSkin
和downBorderSkin
属性。注意,这些是类实行,而不是FXG的实例。同样,为FXG的尺寸信息设置measuredDefaultHeight
和measuredDefaultWidth
属性(参考下面的代码片段)。 - 用CSS或者XML设置Button的
skinClass
属性。
注意: 这个项目的示例文件中包含RoundedButtonSkinProject.fxp。您可以将这个文件导入Flash Builder,查看完整的应用程序(包括皮肤在内)是如何实现的。
关于chromeColor
样式,你有3个选择:
- 不支持
chromeColor
– 重载drawBackground()
,不作任何事情 - 绘制
chromeColor
,使其符合FXG图形 – 重载drawBackground()
,用代码绘制chromeColor
。并且为FXG添加alpha值,使得chromeColor
可见。 - 为
chromeColor
填色 - 重载drawBackground()
,用工具方法applyColorTransform()
将FXG从基色转变为特定的chromeColor
。
这个例子演示了第三种方法。
下面的代码是皮肤的最终实现。
RoundedButtonSkin.as
package skins { import skins.assets.RoundedButton; import spark.skins.mobile.ButtonSkin; public class RoundedButtonSkin extends ButtonSkin { private var colorized:Boolean = false; public function RoundedButtonSkin() { super(); // replace FXG asset classes upBorderSkin = skins.assets.RoundedButton; downBorderSkin = skins.assets.RoundedButton; measuredDefaultHeight = 44; measuredDefaultWidth = 220; } override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void { // omit call to super.drawBackground() to apply tint instead and don't draw fill var chromeColor:uint = getStyle("chromeColor"); if (colorized || (chromeColor != 0xDDDDDD)) { // apply tint instead of fill applyColorTransform(border, 0xDDDDDD, chromeColor); // if we restore to original color, unset colorized colorized = (chromeColor != 0xDDDDDD); } } } }
下面的代码将定制过的RoundedButtonSkin设置为Button的默认皮肤。为了演示chromeColor
和CSS,作者在此为Buttondown状态加入了一些样式。
<fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; s|Button { skinClass: ClassReference("skins.RoundedButtonSkin"); } s|Button:down { chromeColor: #0000FF; color: #FFFFFF; textShadowColor: #000000; } </fx:Style>
在应用程序中添加一些Button后,你应该能看到他们的新皮肤在弹起和按下时的不同状态(见图4)。down状态时的皮肤表明CSS样式已经起作用了。
在处理FXG时,请记住下列注意事项。
路径 vs 原始图形
路径信息并不总是很容易找到,但有些情况下,路径还可能与一些反锯齿的元素渲染在一起。你每次需要获得的路径信息总是不同的。当你在Illustrator中难以获取路径信息时,不要立刻就期望简化原始图形。
blendMode
图形元素的blendMode
属性默认是设置为 自动的
。当它是auto
时候,Flash Player或者AIR运行时将会正确的判断出元素本身是否需要根据alpha
值使用blendMode
层。
尽可能避免笔触
通常情况下,笔触会横跨图形的边缘(例如,在Illustrator中使用"笔触居中对其")
举例来说,画一条从(0,0)到(0,100)的一条线,它有1个像素宽的笔触。该笔触将会从X轴的-.5延展到.5。但是由于在小数位的像素空间内无法绘制任何东西,Flash将会画一条反锯齿的2个像素宽的线,正好占据该小数宽度位置。为了消除反锯齿的干扰,画图形的时候使用奇数做为笔触大小,使得它能有半个像素的边界。例如,一个占据(10,10)位置的、1
个像素宽的SolidColorStroke
笔触,应该实际占位在(10.5,10.5)。
尽可能使用填充矩形,而非有笔触的矩形和线条。如果必须使用有笔触的填充
矩形
,那可以尝试把它分位两个填充矩形(假设填充万全不透明)。如果要画一个条线,尝试把它转变为一个填充矩形。例如,如果要画一条水平直线,可以创建一个高度等同于线条笔触的填充矩形。
伸缩网格
在用FXG设计图形时,必须考虑到伸缩网格的一些局限。
伸缩网格的值必须在图形的边界之内,并且不能与特性边界重合(也就是说,左边界 scaleGridLeft
<scaleGridRight
< 右边界)。
如果图形包含Group元素,就不能用伸缩网格。
如果元素使用了alpha
属性,伸缩网格也不能使用。所以,只在笔触和填充
元素上应用alpha
。
FXG规范
FXG的完整规范,请参考《FXG 2.0 - 功能与设计规范》