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媒体查询为每个平台创建自定义的主题

比较Spark和Mobile 主题

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

Button皮肤教程概述

开始使用皮肤特性的最佳方式是,基于默认的Buton皮肤,创建一个自定义的Button皮肤。为了让问题简单化,这个例子没有考虑到屏幕DPI的问题。笔者将在系列文章的其他篇幅讨论这些问题。

首先,使用 Adobe Illustrator CS5为Button图形创建一个FXG文件。这个文件将同时展现Button组件的updown皮肤状态。因为这里主要针对触摸输入,所以没有创建over状态。

其次,通过重载MobileSkin的drawBackground()方法,添加对chromeColor样式的支持。或者,也可以手动在up和down的图形里添加固定的背景颜色,然后重载drawBackground() 方法,不作任何事情。 disabled 状态仅仅会改变up 状态的 alpha值。移动ButtonSkin类已经默认包含这个功能。

教程第一步:创建FXG图形

根据自己的喜好,可以选择Adobe Flash Professional、Adobe Illustrator或者 Adobe Fireworks构建FXG。你也可以在Flash Builder中手工编辑FXG,然后在MXML文件中移动这个FXG,再用设计视图渲染它,可视化的检查结果。

用 Illustrator 创建 FXG

这个例子用 Illustrator 创建一个药丸形状的Button图形.

  1. 在Illustrator中,选择 File > New。
  2. 为图形命名,例如RoundedButtonExport
  3. 设置 New Document Profile 为Flash Catalyst。
  4. 设置大小为典型的手机尺寸 480 px x 800 px 。
  5. 点击 OK
  6. 用矩形工具新建一个由灰色笔触,渐变填充的矩形。
  7. 在Stroke面板中,找到Align Stroke,选择中间的Align Stroke To Inside选项。默认情况下,笔触都是居中对齐的。笔者将稍候介绍为什么最好避免使用这个默认值。
  8. 修改 X 和 Y 位置为 0.
  9. 选中矩形后,选择 Effect > Stylize > Round Corners。
  10. 指定角的半径为 22 px。
  11. 保存为没有私有数据的 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文件中,用设计视图预览,从而快速的验证它是否工作。

尝试以下步骤:

  1. 在Flash Builder中新建一个项目(可以命名为任何名字)。
  2. 在源代码文件夹中加入 FXG 文件。
  3. 切换到设计视图

组件面板将会把FXG文件显示为一个自定义组件。

  1. 拖拽这个组件到设计区域。你可以看到FXG被渲染成原本的大小。
  2. 在设计视图中,尝试改变FXG的宽度。

你将发现圆角在水平方向拉伸(见图1)。但你希望看到的应该是圆角的形状保持不变,而是让图形的中间部分拉伸。为此,需要为FXG加入伸缩网格信息。

Flex移动皮肤-第一部分:增强的皮肤功能基础_第1张图片
图 1.缺少伸缩网格数据

手工添加scale grids

为让grid伸缩适当的伸缩,你需要添加网格的左、右、上和下的位置信息。因为使用了Round Corners风格化选项,我们知道当前的半径是22px。有了这些信息,你可以打开Flash Builder,对FXG文件做如下修改:

  1. <Graphic>根标签中添加scaleGridLeft="22"scaleGridRight="198px" (图形的完整宽度是 220px)

你还需要确保边界的笔锋没有伸缩。

  1. 在同一个标签中,为这个图形添加scaleGridTop="1px"和 scaleGridBottom="43px" (图形的完整高度是 44px)


注意: 对于有些图形,很难找到任意路径上的精确起始点。这种情况下,请使用Illustrator的选择工具去覆盖一个锚点,从而找到它的坐标(见图2)。

Flex移动皮肤-第一部分:增强的皮肤功能基础_第2张图片
图 2.找到锚点坐标

在Graphic根标签添加网格数据之后,查看一下设计视图中的新FXG,你会发现没有什么变化。因为路径信息并没有填满图形的全部尺寸,所以伸缩网格没有变化。

  1. 为了修复这个问题,如下所示,添加一个透明的Rect占据整个图形空间。
  2. 将刚做的修改保存为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)

Flex移动皮肤-第一部分:增强的皮肤功能基础_第3张图片
图 3.添加了伸缩网格后的组件。

教程步骤2: 创建皮肤类

基于现有的Mobile主题,创建一个新的Button皮肤的过程氛围3个主要步骤。

  1. 新建一个spark.skins.mobile.ButtonSkin的子类。
  2. 在构造函数中,设置FXG类的 upBorderSkin 和downBorderSkin属性。注意,这些是类实行,而不是FXG的实例。同样,为FXG的尺寸信息设置measuredDefaultHeight 和 measuredDefaultWidth 属性(参考下面的代码片段)。
  3. 用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);
        }
    }
}
}


教程步骤3: 测试

下面的代码将定制过的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样式已经起作用了。

Flex移动皮肤-第一部分:增强的皮肤功能基础_第4张图片
图 4.拥有新皮肤的Button。

FXG 提示与技巧

在处理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 - 功能与设计规范》

你可能感兴趣的:(Flex,mobile,Adobe,button,actionscript,图形)