安卓 Flash 高级教程(二)
四、图形和动画
图形对所有年龄段的人来说都是一种有趣的“取悦大众”的方式。如果您和我们一样喜欢图形,您会很高兴地发现,在桌面浏览器上运行的基于 Flex 的图形代码示例也可以在移动设备上运行。此外,当您为移动设备创建基于图形的应用时,您可以利用与触摸相关的事件和手势(在第二章中讨论过)。
本章的第一部分向你展示了如何渲染各种二维形状,如矩形、椭圆、贝塞尔曲线和路径。本章的第二部分包含一个使用线性渐变和径向渐变渲染几何对象的代码示例。本章的第三部分提供了一个代码示例,说明如何使用滤镜效果,包括Blur、DropShadow和Glow。
您还将看到移动代码示例,说明如何对本章第一部分中讨论的图形形状执行变换(平移、缩放、旋转和剪切)。接下来,您将学习如何渲染图表和图形(使用 MX 组件),然后是本章的最后一个示例,它向您展示了如何创建一个草图绘制程序,将本章前面介绍的各种图形相关概念联系在一起。这个草图程序还包括触摸事件,在 JPG 文件上绘制草图的能力,以及一个保存选项,使您可以在移动设备上将草图保存为 JPG。
阅读完本章后,您将对移动设备的图形相关功能有一个很好的认识,并且本章中的一些代码示例可能会启发您编写自己美观的图形代码!
为 2D 形状使用火花原语
本节中的移动代码示例演示了如何呈现各种 2D 形状,如矩形、椭圆形、贝塞尔曲线、多边形和路径。此外,一些代码示例包含采用各种阴影技术的多个图形图像,这将使您能够对图形图像的代码进行并排比较。
绘制矩形和椭圆形
让我们从渲染两个矩形和一个椭圆开始,这是两个大家都很熟悉的 2D 形状。使用移动应用模板创建一个名为 RectEllipse1 的新 Flex 移动项目,并添加如清单 4–1 所示的代码。
清单 4–1。 渲染两个矩形和一个椭圆
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Rectangle and Ellipse"> <s:Rect id="rect1" x="10" y="10" width="250" height="200"> <s:fill> <s:SolidColor color="0xFF0000"/> </s:fill> <s:stroke> <s:SolidColorStroke color="0xFFFF00" weight="4"/> </s:stroke> </s:Rect>
<s:Ellipse id="ellipse1" x="10" y="220" width="250" height="200"> <s:fill> <s:SolidColor color="0x0000FF"/> </s:fill> <s:stroke> <s:SolidColorStroke color="0xFF0000" weight="4"/> </s:stroke> </s:Ellipse>
<s:Rect id="rect2" x="10" y="460" width="250" height="100"> <s:fill> <s:SolidColor color="0xFFFF00"/> </s:fill> <s:stroke> <s:SolidColorStroke color="0x0000FF" weight="8"/> </s:stroke>
fx:Declarations </fx:Declarations> </s:View>`
清单 4–1 以一个 XML Rect元素开始,该元素指定了属性id、x、y、width和height的值。注意,XML Rect元素包含一个 XML fill元素和一个 XML stroke元素,而不是一个fill属性和一个stroke属性,这与 SVG 不同,SVG 通过属性指定fill和stroke值。但是,XML stroke元素包含一个 XML SolidColorStroke子元素,它将color和weight指定为属性,而不是 XML 元素的值。注意,SVG 使用了一个stroke和一个stroke-width属性,而不是一个color属性和一个weight属性。
清单 4–1 还包含一个 XML Ellipse元素,它定义了一个椭圆,具有与 XML Rect元素几乎相同的属性和值,但是生成的输出是一个椭圆而不是矩形。
第二个 XML Rect元素类似于第一个Rect元素,但是颜色不同,在屏幕上的位置也不同。
图 4–1 显示了两个矩形和一个椭圆。
图 4–1。 两个矩形和一个椭圆
使用线性和径向渐变
Flex 移动应用支持线性渐变和径向渐变。顾名思义,线性渐变以线性方式计算起始色和结束色之间的中间色。例如,如果线性渐变从黑色变化到红色,那么初始颜色是黑色,最终颜色是红色,颜色的阴影线性“过渡”在黑色和红色之间。
径向梯度不同于线性梯度,因为过渡以径向方式发生。想象一颗扔进池塘的鹅卵石,观察半径增加的圆圈的波纹效果,这让你对径向渐变是如何渲染的有所了解。
作为一个示例,下面的移动代码呈现一个具有线性渐变的矩形和一个具有径向渐变的椭圆。使用移动应用模板创建一个名为 LinearRadial1 的新 Flex 移动项目,并添加如清单 4–2 所示的代码。
清单 4–2。 使用线性渐变和径向渐变
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" title="Linear and Radial Gradients">
<s:Panel title="Linear and Radial Gradients"> <s:Group> <s:Rect id="rect1" x="10" y="10" height="250" width="300"> <s:fill> <s:LinearGradient> <s:GradientEntry color="0xFF0000" ratio="0" alpha=".5"/> <s:GradientEntry color="0xFFFF00" ratio=".33" alpha=".5"/> <s:GradientEntry color="0x0000FF" ratio=".66" alpha=".5"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x000000" weight="2"/> </s:stroke> </s:Rect>
<s:Ellipse id="ellipse1" x="10" y="270" height="300" width="250"> <s:fill> <s:RadialGradient> <s:GradientEntry color="0xFF0000" ratio="0" alpha="1"/> <s:GradientEntry color="0xFFFF00" ratio=".9" alpha="1"/> </s:RadialGradient> </s:fill>
<s:stroke>
<s:SolidColorStroke color="0x000000" weight="2"/>
</s:stroke>
</s:Ellipse>
</s:Group> </s:Panel>
fx:Declarations </fx:Declarations> </s:View>`
清单 4–2 包含一个 XML Panel元素,该元素包含一个 XML Group元素,其属性指定面板的布局。XML Group元素包含两个 XML 子元素:一个 XML Rect元素和一个 XML Ellipse元素。XML Rect元素定义了一个带有线性渐变的矩形,如下所示:
` <s:Rect id="rect1" x="10" y="10" height="100" width="200"> <s:fill> <s:LinearGradient> <s:GradientEntry color="0xFF0000" ratio="0" alpha=".5"/> <s:GradientEntry color="0xFFFF00" ratio=".33" alpha=".5"/> <s:GradientEntry color="0x0000FF" ratio=".66" alpha=".5"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x000000" weight="2"/> </s:stroke> </s:Rect>`
前面的 XML Rect元素指定了属性id、x、y、width和height的值。接下来,XML Rect元素包含一个 XML fill元素(正如您在前面的示例中看到的),该元素又包含一个 XML LinearGradient元素,该元素指定了三个 XML GradientEntry元素,每个元素都为ratio和alpha属性指定了一个十进制值(在0和1之间)。XML Rect元素的最后一部分包含一个 XML stroke元素,该元素包含一个 XML SolidColorStroke元素,该元素指定属性color和weight的值。
清单 4–2 还包含一个 XML Ellipse元素,它定义了一个带有径向渐变的椭圆。这段代码包含与 XML Rect元素几乎相同的属性和值,除了它表示一个椭圆而不是矩形。
图 4–2 显示了一个带有线性渐变的矩形和一个带有径向渐变的椭圆。
图 4–2。??【线性渐变的矩形】和径向渐变的椭圆
绘制三次贝塞尔曲线
Flex 支持三次贝塞尔曲线(有两个端点和两个控制点)和二次贝塞尔曲线(有两个端点和一个控制点)。您可以轻松识别三次贝塞尔曲线,因为它以字母“C”(或“C”)开头,二次贝塞尔曲线以字母“Q”(或“Q”)开头。大写字母“C”和“Q”指定“绝对”位置,而小写字母“C”和“Q”指定相对于 XML Path元素中前面一点的位置。
三次或二次贝塞尔曲线的点中列出的第一个点是第一个控制点,在三次贝塞尔曲线的情况下,后面是另一个控制点,然后是第二个端点。二次和三次贝塞尔曲线中的第一个端点是 XML Path元素中指定的前一个点;如果未指定点,则将原点(0,0)用作第一个端点。
您也可以使用字母“S”(对于三次贝塞尔曲线)或字母“T”(对于二次贝塞尔曲线)来指定贝塞尔曲线序列。
使用移动应用模板创建一个名为 BezierCurves1 的新 Flex 移动项目,并添加如清单 4–3 所示的代码,该代码显示了四条贝塞尔曲线的代码:一条三次贝塞尔曲线、一条二次贝塞尔曲线、两条组合的三次贝塞尔曲线以及一条组合的三次和二次贝塞尔曲线。
清单 4–3。 渲染三次和二次贝塞尔曲线
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Cubic and Quadratic Bezier Curves">
<s:Panel width="500" height="500" title="Cubic and Quadratic Bezier Curves"> <s:Path data="C 100 150 200 20 300 100"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="#FFFFFF" alpha="0.5"/> <s:GradientEntry color="#FF0000" alpha="0.5"/> </s:LinearGradient> </s:fill> <s:stroke> <s:SolidColorStroke color="0x0000FF" weight="4"/> </s:stroke> </s:Path>
<s:Path data="Q 250 200 100 300"> <s:fill> <s:RadialGradient rotation="90"> <s:GradientEntry color="#000000" alpha="0.8"/> <s:GradientEntry color="#0000FF" alpha="0.8"/> </s:RadialGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0xFF0000" weight="8"/> </s:stroke> </s:Path>
<s:Path data="C 100 300 200 20 300 100 S 250 200 300 250"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="#FF0000" alpha="0.5"/> <s:GradientEntry color="#FFFF00" alpha="0.5"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x00FF00" weight="2"/>
</s:stroke>
</s:Path>
<s:Path data="C 250 400 200 150 350 100 T 250 250 400 280"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="#FFFF00" alpha="0.5"/> <s:GradientEntry color="#FF0000" alpha="0.5"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x000000" weight="4"/> </s:stroke> </s:Path> </s:Panel> </s:View>`
清单 4–3 包含一个 XML Panel元素,该元素又包含四个 XML Path元素,这些元素指定带有各种阴影的贝塞尔曲线。第一个 XML Path元素指定了一条三次贝塞尔曲线,如下所示:
<s:Path data="C 100 300 200 20 300 100 S 250 200 300 250"> [other elements omitted] </s:Path>
此三次贝塞尔曲线的第一个端点是(0,0),因为没有指定点;控制点为(100,300)和(200,20);并且目的地端点是(300,100)。
这个 XML Path元素包含一个 XML LinearGradient元素,该元素从白色到红色变化,不透明度为0.5,后跟宽度为4的蓝色笔划,如下所示:
<s:LinearGradient rotation="90"> <s:GradientEntry color="#FFFFFF" alpha="0.5"/> <s:GradientEntry color="#FF0000" alpha="0.5"/> </s:LinearGradient> </s:fill> <s:stroke> <s:SolidColorStroke color="0x0000FF" weight="4"/> </s:stroke>
第二个 XML Path元素指定了一条二次贝塞尔曲线,该曲线的第一个端点是(0,0 ),因为没有指定点;这条二次贝塞尔曲线的单个控制点是(250,200);而目的地端点是(100,300)。这个 XML Path元素包含一个 XML LinearGradient元素,从黑色到蓝色变化,不透明度为0.8。
第三个 XML Path元素指定了一条与第二条三次贝塞尔曲线“连接”的三次贝塞尔曲线,如下所示:
<s:Path data="C 100 300 200 20 300 100 S 250 200 300 250"> [other elements omitted] </s:Path>
这条三次贝塞尔曲线的两个控制点是(100,300)和(20,300),目的端点是(300,100)。这个 XML Path元素的第二部分指定了一条二次贝塞尔曲线,它的控制点是(250,200),目标端点是(300,250)。
这个 XML Path元素包含一个指定从黄色到红色的线性渐变的 XML LinearGradient元素,后面是一个指定黑色和宽度为4单位的 XML stroke元素。
最后一个 XML Path元素指定了一条三次贝塞尔曲线,后跟第二条三次贝塞尔曲线,如下所示:
<s:Path data="C 250 300 200 150 350 100 T 250 250 400 280"> [other elements omitted] </s:Path>
这条三次贝塞尔曲线的控制点是(250,300)和(200,150),目的端点是(350,100)。这个 XML Path元素的第二部分指定了一条二次贝塞尔曲线,它的控制点是(250,250),目标端点是(400,280)。
这个 XML Path元素包含一个 XML LinearGradient元素,它指定从黄色到红色的线性渐变,不透明度为0.5,后面是一个 XML stroke元素,它指定黑色和线宽为4单位。
图 4–3 显示了三次、二次和组合贝塞尔曲线。
图 4–3。 三次、二次和组合贝塞尔曲线
另一个路径元素示例
在前面的例子中,您看到了如何使用Path元素来呈现一组贝塞尔曲线。元素还可以让你组合其他的 2D 形状,比如线段和带有线性渐变和径向渐变的贝塞尔曲线。使用移动应用模板创建一个名为 Path1 的新 Flex 移动项目,并添加如清单 4–4 所示的代码。
清单 4–4。 结合线段和贝塞尔曲线
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Path-based Lines and Bezier Curves">
<s:Panel width="500" height="500" title="Path-based Lines and Bezier Curves"> <s:Path data="M 50 50 L150 50 350 150 50 150z C 250 300 200 150 350 100 T 250 250 400 500"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="#FF0000" alpha="1"/> <s:GradientEntry color="#0000FF" alpha="1"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x000000" weight="8"/> </s:stroke> </s:Path> </s:Panel> </s:View>`
清单 4–4 中的 XML Panel元素包含一个 XML Path元素,它使用线段来呈现一个梯形,后跟一对三次贝塞尔曲线。XML Path元素的data属性如下所示:
<s:Path data="M 50 50 L150 50 350 150 50 150z C 250 300 200 150 350 100 T 250 250 400 280">
data属性的第一部分(以字母M开始)指定一个梯形;data属性的第二部分(以字母C开始)呈现一条三次贝塞尔曲线;data属性的第三部分(以字母T开始)指定了另一条三次贝塞尔曲线。
图 4–4 显示了一条梯形和两条三次贝塞尔曲线。
图 4–4。 基于路径的梯形和贝塞尔曲线
使用火花过滤器
Flex 滤镜效果对于在基于 Flex 的应用中创建丰富的视觉效果非常有用,这些效果可以真正增强应用的吸引力。Spark 原语支持多种滤镜,包括Blur滤镜,一个DropShadow滤镜,一个Glow滤镜,都属于spark.filters包。
使用移动应用模板创建一个名为 RectLGradFilters3 的新 Flex 移动项目,并添加如清单 4–5 所示的代码。
清单 4–5。 用火花滤镜画矩形
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Rectangle: Gradient and Filters">
<s:Rect id="rect1" x="50" y="50" height="300" width="250"> <s:fill> <s:LinearGradient> <s:GradientEntry color="0xFF0000" ratio="0" alpha=".5"/> <s:GradientEntry color="0xFFFF00" ratio=".33" alpha=".5"/> <s:GradientEntry color="0x0000FF" ratio=".66" alpha=".5"/> </s:LinearGradient> </s:fill> <s:stroke> <s:SolidColorStroke color="0xFF0000" weight="2"/> </s:stroke> <s:filters> <s:DropShadowFilter distance="80" color="#0000FF"/> <s:BlurFilter/> <s:GlowFilter/> </s:filters> </s:Rect> </s:View>`
清单 4–5 包含一个 XML Rect元素,它定义了一个用线性渐变渲染的矩形。ratio属性是一个介于0和1之间的十进制数,它指定了颜色过渡从起点到终点的距离的分数。在清单 4–5 中,第一个GradientEntry元素有一个 ratio 属性,其值为0,这意味着矩形用颜色0xFF0000(红色的十六进制值)呈现。第二个GradientEntry元素有一个 ratio 属性,它的值是0.33,这意味着矩形是用颜色0xFFFF00(黄色的十六进制值)从初始位置到目的地的 33%的位置呈现的。第三个GradientEntry元素有一个值为0.66的ratio属性,因此矩形从初始位置到目的位置的 66%处用颜色0x0000FF(蓝色的十六进制值)呈现。
alpha属性是不透明度,是介于0(不可见)和1(完全可见)之间的十进制数。清单 4–5 中的三个GradientEntry元素有一个0.5的 alpha 属性,所以矩形是部分可见的。尝试比率属性和 alpha 属性的不同值,以便可以找到创建令人愉悦的视觉效果的组合。
XML Rect元素的最后一部分包含一个 XML stroke元素,该元素指定红色和描边宽度2,后跟三个火花过滤器,如下所示:
<s:filters> <s:DropShadowFilter distance="80" color="#0000FF"/> <s:BlurFilter/> <s:GlowFilter/>
</s:filters>
本例中的三个 Spark 过滤器具有直观的名称,表明当您将它们包含在代码中时可以创建的效果。第一个 Spark 过滤器是一个向 XML Rect元素添加“投影”的DropShadowFilter。第二个火花过滤器是一个BlurFilter,它增加了模糊效果。第三个也是最后一个火花过滤器是一个GlowFilter,它创建了一个辉光过滤器效果。
Figure 4–5 显示了一个带有线性渐变和三个火花过滤器的矩形。
图 4–5。 一个带有线性渐变和三个火花滤镜的矩形
将变换应用于几何形状
本章的这一节介绍了如何将变换应用于几何对象,包括本章上一部分讨论的对象。Spark 原语支持以下效果和变换:
AnimateAnimateColorAnimateFilterAnimateShaderTransitionAnimateTransformFadeMoveResizeRotateScaleWipeCrossFade
这些 Spark 原语在spark.effects包中,它们可以应用于 Spark 组件,也可以应用于 MX 组件;mx.effects包(包含在 Flex 4 SDK 中)包含可以应用于 MX 组件的相应功能。
以下小节包含一个 Flex 代码示例,它说明了如何在 Flex 中创建缩放效果。
创建缩放效果
缩放效果(即,扩展或收缩形状)对于面向游戏的应用非常有用,并且在基于 Flex 的应用中非常容易创建。使用移动应用模板创建一个名为 ScaleEffect1 的新 Flex 移动项目,并添加如清单 4–6 所示的代码。
清单 4–6。 用线性渐变创建缩放效果
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Scale Effect">
fx:Library
<fx:Definition name="MyRect1">
<s:Rect x="50" y="50" height="40" width="20">
<s:fill>
<s:LinearGradient>
<s:GradientEntry color="0xFF0000"
ratio="0" alpha=".5"/>
<s:GradientEntry color="0xFFFF00"
ratio=".33" alpha=".5"/>
<s:GradientEntry color="0x0000FF"
ratio=".66" alpha=".5"/>
</s:LinearGradient>
</s:fill>
<s:stroke>
<s:SolidColorStroke color="0xFF0000" weight="1"/>
</s:stroke>
<s:filters>
<s:BlurFilter/>
<s:GlowFilter/>
</s:filters> </s:Rect>
</fx:Definition>
<fx:Definition name="MyEllipse1"> <s:Ellipse x="200" y="200" height="40" width="80"> <s:fill> <s:LinearGradient> <s:GradientEntry color="0xFF0000" ratio="0" alpha=".5"/> <s:GradientEntry color="0xFFFF00" ratio=".33" alpha=".5"/> <s:GradientEntry color="0x0000FF" ratio=".66" alpha=".5"/> </s:LinearGradient> </s:fill> <s:stroke> <s:SolidColorStroke color="0xFF0000" weight="1"/> </s:stroke> <s:filters> <s:DropShadowFilter distance="20" color="#FF0000"/> </s:filters> </s:Ellipse> </fx:Definition> </fx:Library>
<s:Group> <fx:MyRect1 scaleX="6" scaleY="4"/> <fx:MyEllipse1 scaleX="3" scaleY="8"/> <fx:MyRect1 scaleX="2" scaleY="2"/> <fx:MyEllipse1 scaleX="2" scaleY="2"/> </s:Group> </s:View>`
清单 4–6 包含一个 XML Definition元素,它指定一个带有矩形定义的 XML Rect元素,以及另一个 XML Definition元素,它指定一个带有椭圆定义的 XML Ellipse元素。XML Group元素包含两个对矩形的引用和两个对椭圆的引用,如下所示:
<s:Group> <fx:MyRect1 scaleX="6" scaleY="4"/> <fx:MyEllipse1 scaleX="3" scaleY="8"/> <fx:MyRect1 scaleX="2" scaleY="2"/> <fx:MyEllipse1 scaleX="2" scaleY="2"/> </s:Group>
第一个 XML 元素通过为属性scaleX和scaleY指定值6和3来缩放先前定义的矩形。第二个 XML 元素通过为属性scaleX和scaleY指定值3和8来缩放先前定义的矩形。
图 4–6 显示了两个缩放的矩形和两个缩放的椭圆。
图 4–6。 两个缩放的矩形和椭圆
在 Spark 中创建动画效果
本节包含的移动代码展示了如何将动画效果应用到几何对象上,包括本章上一部分讨论的那些对象。动画效果的火花原语如下:
AnimateAnimateColorAnimateFilterAnimateShaderTransitionAnimateTransformCrossFadeFadeMoveResizeRotateScaleWipe
以下部分提供了移动代码示例,说明如何使用 XML Animate元素以及如何并行和顺序定义动画效果。
使用动画元素
对于面向游戏的应用来说,动画效果显然非常受欢迎,而且它们也可以有效地用于其他类型的应用。同时,请记住,在以业务为中心的应用中谨慎使用动画效果可能是个好主意。
使用移动应用模板创建一个名为 AnimPropertyWidth 的新 Flex 移动项目,并添加如清单 4–7 所示的代码。
清单 4–7。 动画显示矩形的宽度
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Animate Rectangle Width">
fx:Declarations <s:Animate id="MyAnimate1"> <s:motionPaths> <s:MotionPath property="width"> <s:keyframes> <s:Keyframe time="0" value="200"/> <s:Keyframe time="2000" value="400"/> </s:keyframes> </s:MotionPath> </s:motionPaths> </s:Animate> </fx:Declarations>
<s:VGroup>
<s:Rect id="rect1" x="10" y="50" height="300" width="200">
<s:fill>
<s:LinearGradient>
<s:GradientEntry color="0xFF0000"
ratio="0" alpha=".5"/>
<s:GradientEntry color="0xFFFF00"
ratio=".33" alpha=".5"/>
<s:GradientEntry color="0x0000FF"
ratio=".66" alpha=".5"/>
</s:LinearGradient>
</s:fill>
<s:stroke> <s:SolidColorStroke color="0xFF0000" weight="2"/>
</s:stroke>
</s:Rect>
<s:Button id="MyButton1" label="Animate Width" click="MyAnimate1.play([rect1])" bottom="150" right="50"> </s:Button> </s:VGroup> </s:View>`
清单 4–7 包含一个 XML Declarations元素,该元素又包含一个定义动画特定细节的 XML Animate元素。XML Animate元素有一个值为MyAnimate1的id属性,该属性在本节稍后描述的点击处理事件中被引用。
清单 4–7 包含一个 XML VGroup元素,该元素又包含一个 XML Rect元素,其内容类似于您在本章中已经看到的例子。
清单 4–7 包含一个 XML Button元素,使您能够开始动画效果。每当用户单击或点击这个按钮时,代码将执行事件处理程序,其id属性为MyAnimate1,这在前面的代码示例中已定义。动画效果很简单:矩形宽度在两秒钟(2000 毫秒)内从 200 个单位增加到 400 个单位。
图 4–7 和图 4–8 显示了当用户点击按钮时,一个矩形在屏幕上水平移动的两个快照。
图 4–7。 一个带有动画的矩形(初始位置)
图 4–8。 一个带动画的矩形(最终位置)
动画:并行和顺序
Flex 支持两种动画效果。并行动画效果涉及同时发生的两个或多个动画效果。另一方面,顺序动画效果涉及两个或更多按顺序出现的动画效果,这意味着在任何给定时间只出现一个动画效果。记住这一点,使用移动应用模板创建一个名为 SequentialAnimation1 的新 Flex 移动项目,并添加如清单 4–8 所示的代码。
清单 4–8。 创造连续动画效果
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Sequential Animation">
fx:Declarations <s:Sequence id="transformer1" target="{button1}"> <s:Move xFrom="50" xTo="150" autoCenterTransform="true"/> <s:Rotate angleFrom="0" angleTo="360" autoCenterTransform="true"/> <s:Scale scaleXFrom="1" scaleXTo="2" autoCenterTransform="true"/> </s:Sequence>
<s:Sequence id="transformer2" target="{button2}">
<s:Move xFrom="50" xTo="150" autoCenterTransform="true"/>
<s:Scale scaleXFrom="1" scaleXTo="2"
autoCenterTransform="true"/>
<s:Rotate angleFrom="0" angleTo="720"
autoCenterTransform="true"/>
</s:Sequence>
</fx:Declarations>
<s:Rect id="rect1" x="10" y="10" width="400" height="400"> <s:fill> <s:SolidColor color="0xFF0000"/> </s:fill> <s:stroke> <s:SolidColorStroke color="0x0000FF" weight="4"/> </s:stroke> </s:Rect>
<s:Button id="button1" x="50" y="100" label="Transform Me" click="transformer1.play()"/>
<s:Button id="button2" x="50" y="200" label="Transform Me Too" click="transformer2.play()"/> </s:View>`
清单 4–8 包含一个 XML Declarations元素,该元素又包含两个 XML Sequence元素,这两个元素指定了三种转换效果。动画效果从 XML Move元素开始(提供翻译效果),然后是 XML Rotate元素(提供旋转效果),最后是 XML Scale元素(提供缩放效果)。当用户点击第一个 XML Button元素时,这将调用 XML Sequence元素中定义的动画效果,该元素的id属性的值为transformer1。
类似的注释也适用于第二个 XML Sequence元素和第二个按钮,只是动画效果包含两次完整的旋转,而不是一次旋转。
请注意,通过用 XML Parallel元素替换 XML Sequence元素,您可以轻松地将动画效果从顺序改为并行,如下所示:
<s:Parallel id="transformer" target="{button}"> <s:Move xFrom="50" xTo="150" autoCenterTransform="true"/> <s:Rotate angleFrom="0" angleTo="360" autoCenterTransform="true"/> <s:Scale scaleXFrom="1" scaleXTo="2" autoCenterTransform="true"/> </s:Parallel>
图 4–9 和图 4–10 显示了两个按顺序经历动画效果的按钮。由于截图只捕获了初始和最终的动画效果,因此在移动设备上启动这个移动应用,这样您还可以看到滑动效果和旋转效果。
**图 4–9。**一个带有连续动画的按钮(初始)
*
图 4–10。 一个带有连续动画的按钮(后)
创建 3D 效果
Flex 支持多种 3D 效果,包括移动、旋转和缩放 JPG 文件。3D“移动”效果包括移动 JPG 图像以及减小图像的尺寸,而 3D 缩放效果包括将 JPG 图像的宽度和高度从起始值(通常为 1)增加(或减小)到最终值(可以大于或小于 1)。3D“旋转”效果包括旋转 JPG 图像,使其看起来像在三维空间中旋转。
清单 4.9 中的以下代码示例向您展示了如何在基于移动设备的应用中创建移动、旋转和缩放 JPG 文件的 3D 效果。
Figure 4–11 显示了卡桑德拉·陈(斯蒂芬·陈的女儿)的 JPG 形象Cassandra4.jpg,该形象用于说明这三种 3D 动画效果的代码示例中。
**图 4–11。**3D 特效的 JPG
清单 4–9。 制作 3D 动画效果
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Creating 3D Effects">
fx:Declarations
<s:Move3D id="moveEffect" target="{targetImg}" xBy="100" zBy="100" repeatCount="2" repeatBehavior="reverse"
effectStart="playMoveButton.enabled=false"
effectEnd="playMoveButton.enabled=true;"/>
<s:Rotate3D id="rotateEffect" target="{targetImg}" angleYFrom="0" angleYTo="360" repeatCount="4" repeatBehavior="reverse" effectStart="playRotateButton.enabled=false;" effectEnd="playRotateButton.enabled=true;"/>
<s:Scale3D id="atScale" target="{targetImg}" scaleXBy="-.45" repeatCount="2" repeatBehavior="reverse" effectStart="playScaleButton.enabled=false" effectEnd="playScaleButton.enabled=true;"/> </fx:Declarations>
<s:VGroup width="100%" height="100%" > <s:Image id="targetImg" horizontalCenter="0" verticalCenter="0" source="@Embed(source='img/Cassandra4.jpg')"/>
<s:HGroup> <s:Button id="playMoveButton" left="10" bottom="25" label="Move" click="moveEffect.play();"/>
<s:Button id="playRotateButton" left="110" bottom="25" label="Rotate" click="rotateEffect.play();"/>
<s:Button id="playScaleButton" left="222" bottom="25" label="Scale" click="atScale.play();"/> </s:HGroup> </s:VGroup>
</s:View>`
清单 4–9 包含一个 XML Declarations元素,该元素包含三个 3D 效果元素,以及三个 XML Button元素,用户可以单击这些元素来创建 3D 效果。XML Move3D元素通过属性xBy和zBy指定目标位置,还有一个值为 2 的repeatCount(执行动画效果两次),以及一个值为reverse(每次都返回到原始位置)的repeatBehavior。相应的 XML Button元素包含一个值为Move的label属性和一个值为moveEffect.play()的click属性,后者调用在 XML Declarations元素中定义的 XML MoveEffect元素中指定的移动动画效果。
旋转效果通过 XML Rotate3D元素处理,其属性angleYFrom和angleYTo分别指定0和360的开始和结束角度(即一次完整的旋转)。这种旋转效果会出现四次。XML Button元素包含一个值为Rotate的label属性和一个值为rotateEffect.play()的click属性,该属性调用在 XML Declarations元素中定义的 XML Rotate3D元素中指定的缩放动画效果。
缩放效果(这是第三个也是最后一个效果)是通过 XML Scale3D元素处理的,该元素包含几个属性,这些属性的值指定了同一个 JPG 图像的动画行为的细节。id属性的值为atScale,用于在代码的其他地方引用这个元素。属性target引用了 XML 元素,其id的值为targetImg,引用了 JPG 图像。scaleXBy属性的值为-0.25,它将 JPG 图像缩小 25%。repeatCount属性的值为4,repeatBehavior属性的值为reverse,表示动画效果出现四次,从左到右来回交替。另外两个属性是effectStart和effectEnd,它们指定动画开始和结束时的行为,在本例中是禁用然后启用 playButton。
注意,XML Image元素指定了Cassandra4.jpg,的位置,它位于这个移动项目的顶层目录的images子文件夹中。出于布局目的,XML Image元素在 XML VGroup元素中指定,该元素还包含一个 XML HGroup元素,该元素包含三个 XML Button元素。
Figure 4–12 显示了经过 3D“移动”效果后的 JPG。
图 4–12。 一个 3D 移动后的 JPG 效果
图 4–13 显示了经过 3D“旋转”效果后的 JPG。
图 4–13。 一个 3D 旋转后的 JPG 效果
图 4–14 显示了经过 3D“缩放”效果后的 JPG。
图 4–14。 一个 JPG 经过 3D 缩放后的效果
创建火花皮肤
当您希望在移动应用的某些方面创建更丰富的视觉效果时,自定义外观非常有用。例如,您可以创建多个自定义外观,将图形效果(包括您在本章前面学习的那些)应用于按钮。我们将要讨论的代码示例清楚地展示了创建 Spark 自定义皮肤效果的过程。
清单 4–10 到 4–12 分别显示CustomSkinHomeView.mxml、ButtonSkin1.mxml和ButtonSkin2.mxml中的代码内容。
在本节讨论 MXML 文件之前,让我们看一下下面的将文件ButtonSkin1.mxml(在skins包中)添加到项目中的步骤列表。
- 将新文件夹
skins添加到您的项目中。 - 右键单击您的项目,并导航到
New->MXML Skin。 - 指定
skins作为新皮肤的包名。 - 指定
ButtonSkin1作为皮肤的名称。 - 指定
spark.components.Button作为组件的名称。 - 取消选择标签“
Create as a copy of:”左侧的复选框。
对定制皮肤ButtonSkin2.mxml重复前面的一组步骤,并对您想要添加到这个项目中的任何额外的定制皮肤重复这些步骤。现在让我们看看CustomSkin.mxml的内容,显示在清单 4–10 中
清单 4–10。 创建自定义火花皮肤
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Custom Skins"> <s:VGroup> <s:Label text="This is a Normal Button:" x="10" y="0"/> <s:Button label="Button1" x="10" y="25"/>
<s:Label text="First Skinned Button:" x="10" y="60"/> <s:Button skinClass="skins.ButtonSkin1" x="10" y="85"/>
<s:Label text="Second Skinned Button:" x="10" y="100"/> <s:Button skinClass="skins.ButtonSkin2" x="10" y="125"/>
<s:Label text="Third Skinned Button:" x="10" y="140"/> <s:Button skinClass="skins.ButtonSkin1" x="10" y="165"/>
<s:Label text="Fourth Skinned Button:" x="10" y="180"/> <s:Button skinClass="skins.ButtonSkin2" x="10" y="205"/> </s:VGroup> </s:View>`
清单 4–10 包含一个 XML VGroup元素,该元素包含十个“成对的”XML 元素,用于呈现一个标准 XML Label元素和一个标准 XML Button元素,其中第一个是一个普通的按钮,如下所示:
<s:Label text="This is a Normal Button:" x="10" y="0"/> <s:Button label="Button1" x="10" y="25"/>
前面的 XML 元素很简单:第一个是标签(“This is a Normal Button”),第二个呈现按钮。
第一对包含皮肤按钮的 XML 元素显示标签“First Skinned Button:”,第二个元素基于包skins中 Flex 皮肤ButtonSkin1的内容呈现一个 XML Button元素。类似地,下一对包含皮肤按钮的 XML 元素显示标签“Second Skinned Button:”,这一对中的第二个元素基于包skins中 Flex 皮肤ButtonSkin2的内容呈现一个 XML Button元素。类似的注释也适用于其他两个自定义按钮。
现在让我们看看清单 4–11 中ButtonSkin1.mxml的内容,它包含了渲染第二个按钮(这是第一个皮肤按钮)的数据。
清单 4–11。创建带有图形的按钮皮肤
`<s:Skin xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" >
fx:Metadata [HostComponent("spark.components.Button")] </fx:Metadata>
<s:states> <s:State name="disabled" /> <s:State name="down" /> <s:State name="over" /> <s:State name="up" /> </s:states>
<s:Rect id="rect1" x="0" y="0" height="40" width="100"> <s:fill> <s:LinearGradient> <s:GradientEntry color="0xFF0000" ratio="0" alpha=".5"/> <s:GradientEntry color="0xFFFF00" ratio=".33" alpha=".5"/> <s:GradientEntry color="0x0000FF" ratio=".66" alpha=".5"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x000000" weight="2"/> </s:stroke> </s:Rect> </s:Skin>`
清单 4–11 包含一个 XML Skin根节点,其中有三个 XML 子元素定义了定制皮肤的行为。第一个子元素是 XML Metadata元素,如下所示:
<fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata>
前面的 XML 元素指定了Button类的包名,这也是您在项目中添加自定义皮肤ButtonSkin1.mxml时指定的名称。
第二个子元素是 XML states元素,如下所示:
<s:states> <s:State name="disabled" /> <s:State name="down" /> <s:State name="over" /> <s:State name="up" /> </s:states>
前面的 XML states元素包含对应于一个按钮状态和三个鼠标相关事件的四个子元素,如果您想要处理这些状态,您可以包含额外的代码。第三个子元素是 XML Rect元素,它为阴影效果和黑色边框指定了线性渐变。
清单 4–12。 创建第二个按钮皮肤
`<s:Skin xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" >
fx:Metadata [HostComponent("spark.components.Button")] </fx:Metadata> <s:states> <s:State name="disabled" /> <s:State name="down" /> <s:State name="over" /> <s:State name="up" /> </s:states>
<s:Path data="M 0 0 L 100 0 L 100 40 L 0 40 Z "> <s:fill> <s:SolidColor color="#FF0000" alpha="1"/> </s:fill> <s:stroke> <s:SolidColorStroke color="#0000FF" weight="4"/> </s:stroke> </s:Path> </s:Skin>`
注意,清单 4–12 和清单 4–11 的唯一区别是 XML Path元素而不是 XML Rect元素。
XML Path元素很简单:它包含一个数据属性,该属性的值是一组指定矩形的线段,矩形的颜色是#FF0000(红色),边框是#0000FF(蓝色),宽度是4。
如你所见,Flex 使得定义自定义皮肤变得非常容易。然而,更复杂(也更有趣)的自定义皮肤通常指定鼠标事件(如鼠标按下、鼠标抬起等)的行为以及相应的状态变化方面的触摸事件。您可以“绑定”在这些事件期间执行的 ActionScript 函数(由您编写),以便更改应用各个方面的可视显示。
Figure 4–15 显示了一个标准的 Flex 按钮和四个使用自定义皮肤的按钮。
图 4–15。 一个标准按钮和四个带有自定义火花皮肤的按钮
在 Spark 中生成 2D 图表和图形
Flex 4 为以下 2D 图表和图形提供了良好的支持:
- 面积图
- 柱形图
- 条形图
- 曲线图
- 饼图
- 散点图
在下面的示例中,您将学习如何编写用于呈现 2D 条形图和 2D 饼图的移动代码示例,您还将看到具有动画效果并可以处理鼠标事件和触摸事件的代码示例。请注意,Flex 使用术语“条形图”表示水平条形图(即每个条形元素从左到右水平呈现),术语“柱形图”指垂直条形图。
创建 2D 条形图
条形图非常受欢迎,尤其是在面向业务的应用中,因为它们使您能够轻松地看到数据的趋势,否则可能很难从数据的表格显示中看出这些趋势。在接下来的示例中,您将学习如何创建一个移动应用,该应用从 XML 文档中读取基于 XML 的数据,然后在 2D 条形图中呈现这些数据。这些数据是为了举例说明,显然你会使用你自己的真实数据,而不是包含在清单 4–12 中的“虚构”数据。还要记住,本书中示例的完整来源可以从本书的网页上在线获得。
现在,使用移动应用模板创建一个名为 BarChart1 的新 Flex 移动项目,添加一个名为chartdata的新顶级文件夹,然后在这个名为ChartData.xml的文件夹中添加一个新的 XML 文档,其中包含了清单 4–13 中显示的数据。
**清单 4–13。**定义基于 XML 的图表数据
<?xml version="1.0"?> <chartdata> <data> <month>January</month> <revenue>1500</revenue> </data> <data> <month>February</month> <revenue>1400</revenue> </data> [data omitted for brevity] <data> <month>November</month> <revenue>1900</revenue> </data> <data> <month>December</month> <revenue>1800</revenue> </data> </chartdata>
清单 4–13 包含一个 XML chartdata元素,该元素包含 12 个 XML data元素,每个元素保存一年中某个月的图表相关数据。清单 4–13 中的每个 XML data元素包含一个 XML month元素和一个 XML revenue元素。例如,第一个 XML data元素指定了一个值为1500的revenue元素和一个值为January的month元素(没有指定货币单位)。
现在让我们看一下清单 4–14,它包含使用清单 4–13 中基于 XML 的数据呈现条形图的代码。
清单 4–14。 创建条形图
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:s="library://ns.adobe.com/flex/spark"
` title="Bar Chart">
fx:Declarations <fx:Model id="chartModel" source="chartdata/ChartData.xml"/> <s:ArrayCollection id="chartData" source="{chartModel.data}"/> <mx:NumberFormatter id="nf" precision="1" rounding="nearest"/> </fx:Declarations>
fx:Style @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; mx|ColumnChart { font-size:12; font-weight:bold; } </fx:Style>
<mx:ColumnChart dataProvider="{chartData}" height="70%" width="100%"> mx:horizontalAxis <mx:CategoryAxis dataProvider="{chartData}" categoryField="month"/> </mx:horizontalAxis> mx:series <mx:ColumnSeries xField="month" yField="revenue"/> </mx:series> </mx:ColumnChart> </s:View>`
清单 4–14 定义了 XML 文档ChartData.xml在 XML Model元素中的位置,以及由基于 XML 的数据组成的ArrayCollection和一个简单的数据格式化程序。清单 4–14 包含一个 XML Style元素,该元素为两个 CSS 属性font-size和font-weight指定值,分别为12和bold,用于在饼图中呈现文本。
XML ColumnChart元素指定了一个柱形图,以及属性dataProvider、height和weight的适当值,它们的值分别是chartData、75%和80%。注意,chartData是一个在 XML Declarations元素中定义的ArrayCollection变量,而chartData是用 XML 文档ChartData.xml中指定的数据值填充的。
height和weight属性的值被指定为呈现饼图的屏幕尺寸的百分比;根据您希望条形图占据屏幕的百分比来调整这些属性的值(50%表示半宽或半高,25%表示四分之一宽或四分之一高,依此类推)。
XML ColumnChart元素包含两个重要的元素。首先,有一个 XML horizontalAxis元素指定了水平轴的month值(在ChartData.xml中指定)。其次,有一个 XML series元素,引用条形图水平轴的month值和垂直轴的revenue值。
图 4–16 显示了一个基于 XML 文件ChartData.xml中数据的条形图,显示在清单 4–13 中。
**图 4–16。**2D 条形图
请记住图 4–16 缺少一些有用的信息,例如收入的货币、当前年份、公司的名称和位置以及收入数据的地区(或国家)。如果您添加此类额外信息,请对清单 4–14 中的代码进行适当的修改,以确保修改后的代码指定了访问收入相关数据的正确路径。
创建 2D 饼图
饼图也很受欢迎,因为它以一种更容易理解数据元素之间关系的方式显示数据。我们将创建一个饼图,它使用了清单 4–13 中的 XML 文档ChartData.xml中的数据,这些数据与上一个示例中用于呈现条形图的数据相同。使用移动应用模板创建一个名为 PieChart1 的新 Flex 移动项目,并添加如清单 4–15 所示的代码。
清单 4–15。 创建饼状图
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:s="library://ns.adobe.com/flex/spark" title="Pie Chart">
fx:Declarations <fx:Model id="chartModel" source="chartdata/ChartData.xml"/> <s:ArrayCollection id="chartData" source="{chartModel.data}"/> <mx:NumberFormatter id="nf" precision="1" rounding="nearest"/> </fx:Declarations>
fx:Style @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; mx|PieChart { font-size:12; font-weight:bold; } </fx:Style>
fx:Script </fx:Script>
<mx:PieChart dataProvider="{chartData}" height="50%" width="80%" horizontalCenter="0" verticalCenter="0"> mx:series <mx:PieSeries field="revenue" labelFunction="getWedgeLabel" labelPosition="callout" explodeRadius="0.05"/> </mx:series> </mx:PieChart> </s:View>`
清单 4–15 包含一个 XML Declarations元素和一个 XML Style元素,它们与清单 4–14 相同。定义私有函数 getWedgeLabel的 XML Script元素返回由每个饼图楔形区的name:value对组成的字符串,如下所示:
<fx:Script> <![CDATA[ private function getWedgeLabel (item:Object, field:String, index:Number, percentValue:Number):String { return item.month+": "+item.revenue; } ]]> </fx:Script>
XML PieChart元素指定了一个饼图,以及其值指定如何呈现饼图的属性。例如,height和width属性都有值80%,这意味着图表的高度和宽度是屏幕尺寸的 80%。根据您希望饼图占据屏幕的百分比来调整这些属性的值(就像您对条形图所做的那样)。
XML PieChart元素还包含一个 XML PieSeries元素,该元素又包含四个属性,使您能够指定如何呈现饼图数据和饼图扇区。field属性的值为revenue,这意味着 XML revenue元素的数据值呈现在饼图中。
labelFunction属性的值为getWedgeLabel,这是一个 ActionScript 函数(在前面的fx:Script元素中定义),它指定了饼图中每个饼图“楔形”的标签。
labelPosition属性的值为callout,这意味着每个饼图扇区的标签都呈现在饼图扇区之外,从饼图扇区到其标签之间有一条“断开的”线段。注意,labelPosition属性可以有另外三个值:inside、outside或insideWithCallout。尝试这些值,看看它们如何改变饼图的呈现。
最后,explodeRadius属性的值为0.05,它呈现的饼图在相邻的饼图扇区之间留有空间,产生了一种“爆炸”效果。
图 4–17 显示了一个 2D 饼图。
图 4–17。??【2D】饼状图
使用带火花的 FXG
第三章包含了对 FXG 的简要介绍,这一节包含了一个代码示例,演示了如何将清单 4–1(包含了渲染矩形和椭圆的代码)转换成使用 FXG 的 Flex 项目。
使用移动应用模板创建一个名为 FXG1 的新 Flex 移动项目,创建一个名为components的顶层文件夹,然后在这个名为RectEllipse1.fxg的文件夹中创建一个文件,其内容如清单 4–16 所示。
清单 4–16。 使用 FXG 定义图形元素
`
`
XML Graphic元素包含两个 XML 元素,它们的数据值与清单 4–1 中的 XML Rect元素和 XML Ellipse元素相同,除此之外还有以下区别:
- 元素不包含命名空间前缀。
- 这些元素属于默认的名称空间。
- 颜色属性使用“#”符号而不是“0x”前缀。
清单 4–17 展示了如何引用清单 4–16 中定义的元素。
清单 4–17。 引用 FXG 组件
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:comps="components.*">
<s:VGroup> <comps:RectEllipse1 id="rect1"/> </s:VGroup> </s:View>`
清单 4–17 包含一个引用 FXG 文件RectEllipse1.fxg的名称空间,该文件位于components子目录中。XML VGroup元素在comps名称空间中包含一个 XML RectEllipse1元素,该元素引用一个 XML 元素,该元素的id属性的值为rect1,该值在 FXG 文件RectEllipse1.fxg中定义,如清单 4–16 所示。
图 4–18 显示一个椭圆和两个矩形,与图 4–1 中的相同。
图 4–18。??【一个长方形和一个椭圆】??
从这个例子可以推测,FXG 使您能够模块化 Flex 项目中的代码。此外,以下 Adobe 产品使您能够将项目导出为 FXG 文件,然后您可以将这些文件导入 Flex 项目:
- Adobe Photoshop 中
- Adobe Illustrator 中
- Adobe Fireworks
你可以在第九章中看到 FXG 文件更复杂的例子。
一个素描程序
您将在本节中看到的移动代码向您展示了如何创建一个草图程序,该程序将本章前面介绍的各种图形相关概念结合在一起,并提供触摸事件、在 JPG 文件上绘制草图以及在移动设备上将草图保存为 JPG 的能力。
使用 Mobile Flex 应用模板创建一个名为 Sketch1 的新 Flex 移动项目,并添加显示在清单 4–18 中的代码。出于讨论的目的,代码以较小的代码块呈现,请记住完整的代码可以从本书的网站下载。
清单 4–18。 渲染并保存草图
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="HomeView"> fx:Script <![CDATA[ import flash.ui.Multitouch; import flash.ui.MultitouchInputMode; import flash.events.TouchEvent;
import mx.graphics.ImageSnapshot; import mx.graphics.SolidColor; import mx.graphics.codec.JPEGEncoder;
private var colors:Array = [0xFF0000, 0x00FF00, 0xFFfF00, 0x0000FF]; private var singleTapCount:int = 0; private var touchMoveCount:int = 0; private var widthFactor:int = 0; private var heightFactor:int = 0; private var currentColor:int = 0; private var rectWidth:int = 20; private var rectHeight:int = 20;
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
function touchMove(event:TouchEvent):void { //event.stopImmediatePropagation(); ++touchMoveCount;
if (event.isPrimaryTouchPoint) { currentColor = colors[touchMoveCount%colors.length]; } else { currentColor = colors[(touchMoveCount+2)%colors.length]; }
var myRect:Rect = new Rect(); myRect.x = event.localX; myRect.y = event.localY; myRect.width = rectWidth; myRect.height = rectHeight; myRect.fill = new SolidColor(currentColor);
var myGroup1:Group = event.target as Group; myGroup1.addElement(myRect); }`
清单 4–18 以一个 XML Script元素开始,该元素包含各种导入语句和在一些 ActionScript 3 方法中使用的恰当命名的变量的定义(例如,用于跟踪触摸事件)。
MultiTouch.inputMode设置为多点触摸模式,因此当您在屏幕上拖动多个手指时,会呈现多个矩形。如果你需要刷新一下关于多点触摸的记忆,你可以阅读第二章中的相关章节。
函数touchMove包含处理移动事件的代码。该函数首先递增变量touchMoveCount,然后使用该变量作为数组colors的索引,从而呈现一组矩形,其颜色遍历该数组。该函数中的其余代码在触摸事件的位置创建一个小矩形。这实际上是图形渲染代码的“心脏”,但是其他函数处理其他事件。
下一个代码块包含函数touchEnd()的代码,它实际上是可选的,但是它向您展示了一个在这个事件处理程序中可以做什么的例子。
` function touchEnd(event:TouchEvent):void { ++touchMoveCount;
if (event.isPrimaryTouchPoint) { currentColor = colors[touchMoveCount%colors.length]; } else { currentColor = colors[0]; }
widthFactor = (touchMoveCount%3)+1; heightFactor = (touchMoveCount%3)+2;
var myRect:Rect = new Rect(); myRect.x = event.localX; myRect.y = event.localY; myRect.width = rectWidthwidthFactor; myRect.height = rectHeightheightFactor; myRect.fill = new SolidColor(currentColor);
var myGroup1:Group = event.target as Group; myGroup1.addElement(myRect); }`
函数touchEnd中处理“touch up”事件的代码递增变量touchMoveCount,然后使用该变量作为数组colors的索引,但是在这种情况下,执行一些简单的算法来呈现不同尺寸的矩形。
`function touchSingleTap(event:TouchEvent):void { var myRect:Rect = new Rect(); myRect.x = event.localX; myRect.y = event.localY;
++singleTapCount;
if (event.isPrimaryTouchPoint) {
currentColor = colors[singleTapCount%colors.length];
myRect.width = rectWidth3;
myRect.height = rectHeight2;
} else {
currentColor = colors[(singleTapCount+1)%colors.length];
myRect.width = rectWidth2;
myRect.height = rectHeight3; }
myRect.fill = new SolidColor(currentColor);
var myGroup1:Group = event.target as Group; myGroup1.addElement(myRect); }`
处理单击事件的逻辑在函数touchSingleTap中。该函数递增变量touchSingleTapCount,然后应用一些简单的逻辑来确定在单击事件位置呈现的矩形的尺寸。
` function touchMoveHandlerImage(event:TouchEvent):void { touchMove(event); }
function touchTapHandlerImage(event:TouchEvent):void { touchSingleTap(event); }
private function saveImageToFileSystem():void { var jPEGEncoder:JPEGEncoder = new JPEGEncoder(500); var imageSnapshot:ImageSnapshot = ImageSnapshot.captureImage(imgPanel, 0, jPEGEncoder);
var fileReference:FileReference = new FileReference(); fileReference.save(imageSnapshot.data, "fingersketch.jpg"); } ]]> </fx:Script>`
两个函数touchMoveHandlerImage和touchTapHandlerImage(顾名思义)处理 JPG 文件fingersketch.jpg的移动事件和单击事件,该文件存储在 Flex 应用的images子目录中。这两个函数包含一行调用相应函数touchMove和touchTapHandler的代码,这在本节前面已经讨论过了。
每当您单击 Save Sketch 按钮时,就会调用函数saveImageToFileSystem,它包含将当前草图保存到移动设备的文件系统的代码。将出现一个弹出对话框,其中包含 JPG 文件的默认位置和名称,在保存当前草图之前,您可以更改这两个位置和名称。
` <s:Panel id="imgPanel" title="Finger Sketching For Fun!" width="100%" height="100%" > <s:Button id="saveImage" left="150" bottom="5" label="Save Sketch" click="saveImageToFileSystem();"/>
<s:Group name="myGroup1" width="500" height="500"
touchMove="touchMove(event)"
touchEnd="touchEnd(event)"
touchTap="touchSingleTap(event)">
<s:Ellipse id="ellipse1" x="10" y="10" width="100" height="50">
<s:fill> <s:SolidColor color="0xFFFF00"/> </s:fill>
<s:stroke> <s:SolidColorStroke color="red" weight="5"/> </s:stroke> </s:Ellipse>
<s:Rect id="rect1" x="110" y="10" width="100" height="50">
<s:fill> <s:SolidColor color="0xFF0000"/> </s:fill>
<s:stroke> <s:SolidColorStroke color="blue" weight="5"/> </s:stroke>
</s:Rect>
<s:Ellipse id="ellipse2" x="210" y="10" width="100" height="50">
<s:fill> <s:SolidColor color="0xFFFF00"/> </s:fill>
<s:stroke> <s:SolidColorStroke color="red" weight="5"/> </s:stroke>
</s:Ellipse>
<s:Rect id="rect2" x="310" y="10" width="100" height="50">
<s:fill> <s:SolidColor color="0xFF0000"/> </s:fill>
<s:stroke> <s:SolidColorStroke color="blue" weight="5"/> </s:stroke>
</s:Rect>
<s:Path data="C100 300 200 20 300 100 S 250 200 300 250"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="#FF0000" alpha="0.8"/> <s:GradientEntry color="#0000FF" alpha="0.8"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x00FF00" weight="2"/> </s:stroke> </s:Path>
<s:Path data="C 350 300 200 150 350 100 T 250 250 400 280"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="#FFFF00" alpha="0.5"/> <s:GradientEntry color="#FF0000" alpha="0.5"/> </s:LinearGradient> </s:fill>
<s:stroke> <s:SolidColorStroke color="0x000000" weight="4"/> </s:stroke> </s:Path> </s:Group>
<s:Image id="img" width="480" height="320" source="img/fingersketch.jpg" touchMove="touchMoveHandlerImage(event)" touchTap="touchTapHandlerImage(event)" horizontalCenter="-10" verticalCenter="60"/> </s:Panel> </s:View>`
代码的下一个主要部分由一个 XML Panel元素组成,该元素包含一个用于保存当前草图的 XML Button元素,随后是一个 XML Group元素,该元素指定与触摸相关的事件处理程序touchMove、touchEnd和touchTap,它们在本示例的前面已经讨论过。
XML Group元素还包含各种图形对象的定义,包括椭圆、矩形和贝塞尔曲线,这在本章前面已经学过。这些图形对象显然是可选的,它们只是为了让你知道如何制作一个对用户有吸引力和视觉吸引力的草图程序。
XML Image元素指定了 JPG 文件fingersketch.jpg,它位于这个 Flex 应用的images子目录中。XML Image元素指定了用于触摸动作事件的函数touchMoveHandlerImage和用于轻击相关事件的函数touchTapHandlerImage。尝试使用属性horizontalCenter和verticalCenter的不同值,这将改变 JPG 图像的水平和垂直布局位置。
图 4–19 显示了在移动设备中启动草图程序后的草图样本。
图 4–19。 一张样图
总结
在本章中,您学习了如何使用 Spark 组件在面向移动设备的图形应用中呈现各种 2D 图形形状。如果您已经熟悉渲染基于 Flex 的图形,那么您可以快速轻松地利用现有知识来创建使用图形的移动应用。
您使用的图形图像和图形效果取决于应用特定的要求,您可以在自己的移动项目中使用的一些效果包括:
- 渲染基本形状,如矩形、椭圆形和线段
- 为需要更多“艺术”非线性视觉效果的移动应用渲染二次和三次贝塞尔曲线
- 应用线性渐变、径向渐变和滤镜效果来产生更丰富、更引人注目的视觉效果
- 应用变换(平移、缩放、旋转和剪切)
- 创建自定义皮肤来替换“标准”按钮
- 结合触摸事件使用并行或顺序动画效果
使用条形图和饼图执行有效的数据可视化*
五、应用部署和发布
至此,我们已经向您展示了如何在 Flash 平台上构建引人入胜的应用,这些应用利用了 Flash Professional 和 Flash Builder 中的移动功能。然而,为了展示您新开发的应用,您需要知道如何准备您的应用进行部署,将它们安装在开发设备上,并将您的应用部署到 Android Market,最终用户可以在那里下载它们。
在本章中,我们将首先向您展示如何安装和配置 Android SDK,并在 Android 模拟器中运行。这是在一系列不同设备类型和操作系统组合上试验您的应用的好方法,这通常需要专门的设备测试实验室。
接下来,我们将向您展示如何从 Flash Professional 和 Flash Builder 部署您的应用,并使用您在前面章节中开发的一些应用作为示例。这是对高级主题的补充,如证书创建、命令行部署和 Android 模拟器的打包。
最后,我们向您展示如何将您的应用发布到 Android Market 和 Amazon Appstore。一旦您成功发布了一个应用,它将像商店中的任何其他本机应用一样出现,因为它是在 Flash 平台上构建的,这一事实对您的最终用户是完全透明的。
设置 Android 模拟器
如果您没有现成的 Android 设备,或者正在寻找一种方法在新的或不同的硬件上部署和测试您的代码,Android Emulator 是一个很好的选择。SDK 附带的 Android 模拟器尽可能接近于运行真实的东西,包括运行完整的 Android 操作系统堆栈,并支持类似的开发人员与 USB 连接设备的交互。
Table 5–1 比较了在设备上运行、在仿真器中运行和在 AIR Debug Launcher (ADL)中运行的体验。
正如你所看到的,ADL 是一种在开发过程中测试 Flash 应用的方便方法,但它不是一个完整的 Android 环境。相比之下,Android 模拟器在虚拟设备上运行完整版本的 Android 操作系统,因此您可以测试您的应用在不同的操作系统版本和屏幕组合上的表现。
在桌面模拟器中运行时有一些限制。最值得注意的是,你没有多点触摸支持。此外,一些 Android 按钮和功能只能通过命令行选项或按键绑定来使用,这将在下一节“模拟器按键绑定”中详细介绍
尽管有这些限制,Android 模拟器是在多种不同设备和 Android 操作系统版本上测试您的应用的一种非常经济有效的方式,也是一种您不想失去的工具。
安装 Android SDK
在设备上安装和运行 Flash 的先决条件是安装 Android SDK。为了运行模拟器,您需要下载并安装 Java SDK 和 Android SDK。您可以在此下载适用于您的平台的最新版本的 Java:
http://java.sun.com/javase/downloads
注: Java 预装在 Mac OS X 上
Android SDK 可免费用于个人和商业用途,并可通过以下网址从 Google 下载:
http://developer.android.com/sdk
初始下载相对较小,可以解压缩到您选择的目录中。要完成安装,您必须在主目录中运行 SDK 安装程序。这将提示您一个软件包安装对话框,如图 Figure 5–1 所示。
图 5–1。 AIR for Android 软件包安装对话框
您可以选择想要的软件包,方法是分别选择它们并单击“接受”按钮,或者只需单击“全部接受”。一旦您点击安装按钮,您接受的软件包将被下载和安装。
注意: Windows 用户必须安装一个额外的 USB 连接包,才能使用带有 USB 连接线的手机。如果你使用的是谷歌 Android 开发者手机,你可以在这里找到驱动:[developer.android.com/sdk/win-usb.html](http://developer.android.com/sdk/win-usb.html).
一个可选的步骤是安装 Eclipse 和 Eclipse 的 Android 开发工具(ADT)插件。正如下一章所讨论的,如果你想做任何原生 Android 开发,这是很有帮助的。ADT Eclipse 插件可以从以下 URL 下载:
http://developer.android.com/sdk/eclipse-adt.html
也可以使用您选择的 IDE 来开发原生 Android 应用。您只需使用 Android SDK 附带的命令行工具来编译和打包您的项目。
创建 Android 虚拟设备
Android 模拟器的核心是 Android 虚拟设备(AVDs)。每个 AVD 指定该设备特有的设置,包括 API 版本、屏幕大小和硬件属性。使用 AVDs,您可以拥有自己的私有虚拟设备实验室,针对要测试应用的每个目标设备进行不同的配置。
首先,您需要创建第一个 AVD 来运行 Flash 平台应用。这是在 Android SDK 和 AVD 管理器中完成的,在第一次安装 SDK 时运行。
您可以通过导航到sdk/tools目录并启动android可执行文件,从命令行重新启动 Android SDK 和 AVD 管理器。
片刻之后,Android 将启动 SDK 管理器。在这里,您可以通过执行以下步骤来创建一个新的 AVD。
- 导航到虚拟设备窗格。
- 点击 New…按钮打开如图 Figure 5–2 所示的 AVD 创建对话框。
- 在名称输入字段中指定“MyAndroidDevice”。
- 在尺寸输入字段中输入“50”。
- 从目标下拉列表中选择“Android 2.3.3 - API Level 10”(或更高版本)。
- 选择名为“WVGA854”的内置皮肤。
- 单击创建 AVD 按钮。
图 5–2。 对话框创建新的 Android 虚拟设备
步骤 3 中 AVD 的名称只是一个建议,所以您可以用另一个名称替换这个字符串。
要启动新创建的 AVD,请在列表中选择它,然后单击 Start…按钮。它会显示标准的 Android 启动屏幕,然后是一个锁定屏幕。拖动锁形符号解锁仿真器后,您将看到熟悉的主屏幕,如图图 5–3 所示。
图 5–3。??【Android 2 . 3 . 3】运行在桌面上的仿真器
模拟器的 Android 标准皮肤在左边显示你的设备屏幕,在右边显示完整的 Android 按钮和按键。有些键,比如拨号和挂断键,可能并不是在每个 Android 设备上都可以找到,但是模拟器仍然可以让你测试你的应用在这些键被按下时的行为。
几乎所有你能在普通 Android 设备上做的事情都可以在模拟器上实现,所以在继续安装你自己的应用之前,先熟悉一下用户界面。您可以启动预装的应用,如浏览器、联系人或电子邮件,或者来自 Android Market 的新应用。默认情况下,模拟器映像带有所有启用的开发选项,如 USB 调试、“保持清醒”和模拟位置,但熟悉设置应用也是值得的。
在仿真器内安装空气
当您通过 USB 调试在物理设备上运行时,如果您的 AIR SDK 包含较新版本,Flash Builder 将自动提示您升级已安装的 AIR 版本。您还可以选择直接从 Android Market 下载并安装 AIR 的发布版本,这正是在您运行没有安装 AIR 的 Flash Android 应用时会发生的情况。
然而,在模拟器的情况下,你不能直接在 Android Market 之外使用 AIR 的版本,因为它们不兼容。此外,由于 Flash Builder 不直接与 Android 模拟器集成,因此您也不能使用自动更新机制来安装 AIR。
解决方法是从 SDK 手动安装 AIR 运行时。AIR SDK 可以在 Flash Builder 安装的sdks/<version>文件夹中找到(截至发稿时,最新版本为 4.5.0)。在 AIR SDK 文件夹中,您可以在以下位置找到模拟器运行时:
runtimes/air/android/emulator/Runtime.apk
**注意:**设备和模拟器有单独的 AIR 运行时,因此请确保为此选择模拟器运行时。
可以从命令行使用 Android Debug Bridge (ADB)程序安装该文件。ADB 是 Android SDK 附带的工具之一,可以在platform-tools文件夹中找到。清单 5–1 展示了在 Mac OS X 的默认安装位置安装模拟器 APK 的命令
***Listing 5–1.** Installation Command for the AIR Emulator Runtime*
adb install "/Applications/Adobe Flash Builder 4.5/sdks/4.5.0/runtimes/air/android/emulator/Runtime.apk"
在 Windows 上,该命令非常相似,只是 Flash Builder 安装的路径位置不同。
**提示:**您还可以使用 AIR 调试工具(ADT)来安装 AIR 运行时。配置 ADT 将在本章后面的“设置 ADT”一节中介绍。使用 ADT 安装 AIR 运行时的命令如下:
adt -installRuntime -platform android
仿真器按键绑定
在 Android 模拟器中运行时,您可以选择使用普通的桌面键盘作为输入。除了 Android 设备上有几个特殊的键没有到桌面键盘的正常映射之外,这种方式工作得相当好。例如电源按钮、音量和相机按钮。
为了便于在 Android 设备上按下这些按钮,默认的设备皮肤在物理仿真器面板上包含了这些按钮,因此您可以用鼠标单击它们。然而,不能保证将来你自己安装的 Android 皮肤或自定义皮肤会有完整的按钮。一个这样的皮肤,如图图 5–4 所示,给你一个接近照片质量的 Nexus S 设备外观,1但是缺少一些按钮。
图 5–4。 Nexus S 安卓模拟器皮肤
1 黑科·伯伦斯的 Nexus S Skin:[heikobehrens.net/2011/03/15/android-skins/](http://heikobehrens.net/2011/03/15/android-skins/)
为了克服这个限制,Android 模拟器提供了完整的按键绑定。Table 5–2 列出了从普通 Android 键到桌面键盘修饰键的映射,当使用任何仿真器皮肤时,您都可以键入这些修饰键。
除了重新映射 Android 按钮之外,模拟器还有一些隐藏的功能,只能通过按键绑定来访问。Table 5–3 显示了一些特殊的键绑定,在仿真器中测试应用时,您会发现它们很有用。
为了充分利用前面的键绑定,您需要知道如何从命令行启动模拟器并传入参数。从命令行启动 Android 模拟器是对emulator可执行文件的直接调用,该文件可以在sdk/tools目录中找到:
emulator -avd <Virtual Device Name>
您替换的虚拟设备名称与 Android 工具中定义的名称完全相同,如前一节所示。然后,您可以添加任何想要使用的附加选项,例如-trace或-onion。
部署 AIR 应用
如果您一直使用设备通过 USB 测试您的应用,那么您已经在开发过程中进行了有限形式的部署。然而,您可能使用的是调试版本,当您的最终用户获得一个完全打包的应用时,不必担心对他们来说很重要的许多事情,比如权限、适当的证书和图标。
在这一节中,我们将更详细地研究应用描述符,演示如何微调您的应用部署,以改善用户体验并为您的公司形象树立品牌。
设置 ADT
虽然可以通过 Flash Professional 和 Flash Builder 完成整个发布工作流,但对于自动化和脚本编写而言,能够从命令行完成相同的活动非常有用。Adobe AIR SDK 提供了一个名为 AIR Developer Tool (ADT)的命令行,它允许您从脚本或构建文件执行任何操作。
要从命令行使用 ADT,必须预先设置以下内容:
- 为您的平台安装 AIR SDK(这是使用 Flash Builder 自动安装的)。
- 安装有效的 Java 运行时环境(JRE)。
- 将 Java 运行时添加到您的
PATH环境变量中。- 对于 Windows,这将是
%JRE_HOME%\bin。 - 对于麦克·OS X 来说,这将是
$JRE_HOME/bin. - 其中
JRE_HOME是 JRE 安装位置的完全限定路径
- 对于 Windows,这将是
- 将 AIR SDK 添加到您的
PATH环境变量中。- 对于 Windows,这将是
%AIR_SDK%\bin。 - 对于麦克·OS X 来说,这将是
$AIR_SDK/bin。 - 其中
AIR_SDK是 AIR SDK 安装位置的完全限定路径
- 对于 Windows,这将是
设置完成后,您可以使用 ADT 从命令行完成许多不同的打包和部署活动。其中包括以下内容:
- 创建签名证书 : ADT 允许您从命令行创建代码签名证书,这些证书可以在打包应用时使用。
- 打包应用:通过传入一个项目文件列表和一个有效的证书,你可以打包一个 APK 文件部署到 Android 设备上。这支持创建一个有或没有调试符号的 APK 文件,也允许你以 Android 模拟器为目标。
- 安装应用:打包步骤创建的 APK 文件可以安装在设备上。这需要一个到 Android SDK 的路径。
- 启动应用 : ADT 也可用于在设备上启动应用。这也需要一个通往 Android SDK 的路径。
在本章中,我们将利用 ADT 来展示 Flash 工作流的自动化潜力。
应用权限
用户对您的应用的第一印象将是它在安装时请求的不同权限的列表。因此,您应该确保您所请求的权限对您的应用有意义,并且是您可以交付功能的最小集合。
请求太大的权限可能会让用户暂停安装应用。例如,Twitter 客户端没有理由需要写入外部存储,因此请求该权限可能会阻止精明的用户出于安全考虑安装您的应用。
**提示:**您可能已经默认启用的权限之一是INTERNET权限。应用的 USB 调试需要该权限,因此在开发过程中启用该权限非常重要。大多数应用还需要访问 Internet 运行时,因此很可能您发布的应用版本也需要此权限;但如果没有,记得禁用这个。
在 Flash Professional 中更改权限
Flash Professional 有一个专用的用户界面来管理所有部署选项,包括权限。若要打开“设置”面板,请从“文件”菜单中选择“Air for Android 设置…”。然后点击 Permissions 选项卡,您将得到一个对话框,其中每个权限都有复选框,如图 Figure 5–5 所示。
图 5–5。??【Flash 职业权限】选项卡中的空中安卓设置对话框
您还可以通过选择顶部的复选框来手动设置应用描述符文件中的权限。如果您想这样做,请参阅“手动更改应用描述符中的权限”一节。
在 Flash Builder 中设置初始权限
Flash Builder 允许您在首次创建项目时设置权限。为此,点击新建移动项目向导第二页中的权限选项卡,如图 Figure 5–6 所示。
图 5–6。 新建项目向导中的 Flash Builder 权限选项卡
请注意,当您进入对话框时,INTERNET权限是预先选定的。这是 USB 设备调试工作所必需的。如果您需要任何额外的权限,可以在开始项目之前设置它们。
一旦创建了项目,就不能再通过项目设置对话框更改权限。相反,您可以按照下一节中的说明直接编辑为您创建的应用描述符文件。
手动更改应用描述符中的权限
如果您选择手动管理权限(对于 Flash Professional)或在项目创建后修改权限(对于 Flash Builder),则您需要知道如何修改应用描述符文件来更改权限。
应用描述符文件通常位于项目的源根目录中,并按照惯例<project-name>-app.xml命名。它被格式化为 XML 标记的标记,其中包含您可以以声明方式控制的所有不同应用设置的部分。权限设置可以在文件底部的android标签下找到,如清单 5–2 所示。
清单 5–2。 示例 AIR 应用描述符的权限部分
<android> <manifestAdditions> <manifest android:installLocation="auto"> <![CDATA[ <uses-permission android:name="android.permission.**PERMISSION_NAME** " /> ]]> </manifest> </manifestAdditions> </android>
对于您想要启用的每个权限,您可以复制uses-permission标记并用适当的权限名称替换PERMISSION_NAME占位符。清单 5–3 以适当的格式显示了所有可用的 Android 权限的例子,直接包含在应用描述符中。
清单 5–3。 全套可用安卓权限
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
在这些权限中,有一些可以作为一个组来启用和禁用,例如:
- AIR 的
SystemIdleModeAPI 需要DISABLE_KEYGUARD和WAKE_LOCK权限。 - AIR 的
NetworkInfoAPI 同时需要ACCESS_NETWORK_STATE和ACCESS_WIFI_STATE
因此,如果您计划使用这两种 API 中的任何一种,请确保同时启用这两种权限。
图标和资源
每次用户打开你的应用,他们都会看到你选择的 Android 启动图标,所以这是很专业的,并且代表你的应用,这一点很重要。
从 Android 2.0 开始,他们标准化了面向前的图标设计,建议选择应用的一个方面,并用全尺寸的描述来强调这一点。图 5–7 突出显示了一些启动器图标,它们是推荐的 Android 外观和感觉的模型示例。
图 5–7。 示例 Android 应用启动器图标 2
为了更容易地构建符合这些标准的应用图标,Android 团队提供了一个包,其中包含不同大小图标的样本材料和模板。您可以从以下网址下载 Android 图标模板包:
http://developer.android.com/guide/practices/ui_guidelines/icon_design.html#tem platespack
该软件包包括 Photoshop 模板,您可以使用这些模板在边框内精确排列图形,以及配置为应用适当效果(如图标投影)的滤镜。
图 5–8 显示了本书示例中使用的 Pro Android Flash 图标的 Photoshop 文件。它采用“网络水果”图形作为封面艺术的中心,并使用这一单一元素作为书籍的代表性图标。
2 转载自 Android 开源项目创建和共享的作品,并根据知识共享 2.5 归属许可中描述的条款使用:[developer.android.com/guide/practices/ui_guidelines/icon_design_launcher.html](http://developer.android.com/guide/practices/ui_guidelines/icon_design_launcher.html)
**图 5–8。**Adobe Photoshop 中的安卓图标模板
因为我们使用的形状是圆形的,所以它可以接触到外部的蓝色边界(1/6 的间隔)。方形图标不应超出橙色边界线(2/9 英寸的距离)。我们也使用推荐的高密度投影设置,2 像素距离,5 像素大小,90 度角。Table 5–4 列出了不同密度图标的图标尺寸、边框大小和阴影设置。
要完成图标的准备工作,请隐藏用于创建图标的任何参考图层,并将其保存为透明的可移植网络图形(PNG)文件。如果您使用的是 Photoshop,最好的方法是使用“文件”菜单中的“存储为 Web 和设备所用格式”命令。这可以确保您的图像文件尽可能小,删除任何不必要的头信息。
一旦你创建了你的图形,你可以将它们包含在你的应用描述符中,这样它们将与你的应用捆绑在一起,并显示在你部署的应用的启动器和菜单中。Flash Professional 有一个配置页面,允许您选择图标并将它们链接到您的应用,如图 5–9 所示。
**图 5–9。**Flash 专业设置对话框的图标选择选项卡
您选择的每个图形文件都将被移动到名为AppIconsForPublish的文件夹中,该文件夹位于您的项目文件位置。部署后,这些图标将被复制到生成的.apk文件中,并作为相应的密度素材进行链接。
如果您使用 Flash Builder 或手动管理应用描述符,则必须手动编辑 XML。在文本编辑器中打开应用描述符后,添加一个icon部分,列出应用支持的不同密度图标的绝对或相对路径。清单 5–4 展示了应用描述符的icon部分应该是什么样子。
清单 5–4。 空中应用描述符的示例icon部分
<icon> <image36x36>ProAndroidFlashIcon36.png</image36x36> <image48x48>ProAndroidFlashIcon48.png</image48x48> <image72x72>ProAndroidFlashIcon72.png</image72x72> </icon>
icon标签应该直接位于文件的外部application标签之下。在本例中,所有图标资源都与应用描述符文件位于同一个文件夹中,因此路径是一个简单的文件名。您可以将文件命名为任何名称,只要它们与描述符文件中的内容相匹配。
代码签名证书
Android 要求所有部署的应用都要签名。为了将应用部署到 Android Market,您不必从证书颁发机构购买昂贵的代码签名证书。所需的只是一个简单的自签名证书,因为谷歌负责检查在其市场上销售应用的各方的身份。
Flash Professional 和 Flash Builder 都提供用户界面来快速轻松地创建证书。您也可以使用 AIR Developer Tool (ADT)从命令行创建证书。所有这些机制创建的证书都是相同的,可以在工具之间互换使用。
若要在 Flash Professional 中创建证书,请从“文件”菜单中打开 Air for Android 设置…。在此对话框的“部署”选项卡上,您可以单击“创建…”按钮,通过弹出窗口生成新的证书,如图 5–10 所示。
图 5–10。 Flash 职业证书创建对话框
我们将在前面更详细地讨论创建证书的字段,但是如果您是为了开发目的而创建证书,那么您可以输入您想要的任何内容。Flash Professional 要求您在继续之前填写所有字段。
Flash Builder 有一个相同的表单,可以从 Google Android 数字签名部分的项目属性对话框中访问。再次点击 Create…按钮打开证书创建对话框,如图 Figure 5–11 所示。
图 5–11。 Flash Builder 证书创建对话框
Flash Builder 对话框与 Flash Professional 对话框几乎相同,只是省略了有效期。这将自动默认为 25 年。
使用 ADT 创建证书
若要从命令行创建证书,可以使用 AIR 开发工具(ADT)。有关设置 ADT 的更多信息,请参阅本章前面的“设置 ADT”一节。
要通过命令行创建代码签名证书,您需要键入以下命令:
adt -certificate -cn <name> ( -ou <org-unit> )? ( -o <org-name> )? ( -c <country> )? ( - validityPeriod <years> )? <key-type> <pfx-file> <password>
括号中的参数是可选的,可以省略。您可以自己选择的值用尖括号括起来。所有参数的描述和有效值在表 5–5 中列出。
注意: Android Market 要求证书的有效期必须超过 2033 年 10 月 22 日。因此,建议有效期至少为 25 年。
例如,以下命令将创建一个有效的 Android 代码签名证书:
adt -certificate -cn ProAndroidFlash -validityPeriod 25 1024-RSA proandroidflash.p12 superSecretPassword
然后,您可以通过运行checkstore命令来验证证书是否有效:
adt -checkstore -storetype pkcs12 -keystore proandroidflash.p12 -storepass superSecretPassword
如果证书创建成功,该命令将返回"valid password"。
从 Flash Professional 发布
一旦您设置了适当的权限、图标和证书,从 Flash Professional 发布应用就像按一个按钮一样简单。事实上,您可以选择几个按钮或菜单项:
- Android 设置对话框中的发布按钮
- “发布设置”对话框中的“发布”按钮
- “文件”菜单中的“发布”菜单项
这三个位置在图 5–12 中描述。除了在错误输入或信息不完整的情况下(例如证书密码丢失),您将被重定向到 AIR for Android 设置对话框,它们的工作方式完全相同。
图 5–12。 AIR for Android 部署设置对话框
在 AIR for Android 设置对话框的部署选项卡上有几个部署选项,我们还没有谈到,但它们在发布时很重要。首先是选择一个Device、Emulator或Debug版本。如果您正在创建一个供最终用户使用的 Android 包,请确保选择一个Device版本。
如果您计划在 Android 模拟器中测试您的应用,那么Emulator版本是必需的,所以如果这是您计划测试您的应用的方式,请确保选择此选项。然而,记住切换回一个Device版本以分发给最终用户。
一个Debug版本是当你通过 USB 调试测试你的应用时通常会构建的项目类型。与Device版本相比,这种版本的性能较慢,并且在错误条件下的行为略有不同,因此不建议将其用于分发目的。
您还可以选择应用下载 AIR 运行时的位置。目前支持的两个应用商店是谷歌 Android Market 和亚马逊 Appstore。如果您计划将应用部署到这两个应用商店,则应该使用不同的 AIR 运行时设置为每个商店创建单独的版本。
最后一组选项将自动在第一个连接的 USB 设备上安装和启动您的应用包。这些对于以发布形式测试新构建的应用非常方便。如果您正在运行模拟器,Flash Professional 会将其视为连接的设备,并且还可以自动部署到该设备。
点击发布,一个 Android 包文件(APK)将为你的应用创建。APK 文件是一个自包含的安装包,您可以将其部署到设备或通过应用商店发布。APK 文件的位置和名称在“发布设置”对话框的“输出文件”栏中设定。
从 Flash Builder 导出发布版本
Flash Builder 还能够为您的应用打包 APK 文件。从项目菜单中选择导出发布版本…开始导出过程,之后您将看到一个向导对话框,如图 5–13 所示。
图 5–13。??【Flash Builder 导出发布构建向导】
向导的第一页允许您选择平台、文件名和签名选项。对于移动应用,您通常希望选择第一个选项来为每个目标平台签署包,在我们的例子中只有一个。要进入第二页,请单击“下一步”按钮。
第二页包含几个选项卡,其中包含数字签名、包内容和部署的选项。如果您已经按照本章前面的讨论设置了您的签名,除了可能输入密码之外,您不必在第一个选项卡上进行任何更改。Package Contents 选项卡显示了所有资源的列表,将包含在 APK 文件中。除非您想明确排除任何文件,如未使用的图形,否则您不需要在此进行任何更改。最后,最后一个选项卡有一个选项,可以在连接的移动设备上自动部署和运行(如果可用),这是默认选择的。
单击“完成”按钮后,Flash Builder 将打包一个用于发布的 APK 文件,并可能在已安装的设备上部署和启动该文件。
在 Android 模拟器中运行 Flex 应用
与 Flash Professional 发布过程相比,您可能会注意到在 Flash Builder 对话框中没有提到 Android 模拟器。此外,如果您尝试在 Android 模拟器上安装由 Flash Builder 创建的 APK 文件,安装将会失败并出现错误。
但是,您可以从命令行使用 AIR Developer Tool (ADT)手动创建同一应用的模拟器友好版本。有关设置 ADT 的更多信息,请参阅本章前面的“设置 ADT”一节。
作为一个例子,清单 5–5 向您展示了如何为第一章中构建的手势检查项目构建一个仿真器友好的 APK。请确保在 Flash Builder 已经构建了 SWF 文件后,从bin-debug文件夹执行此命令。
***Listing 5–5.** Command to Build an APK File for the GestureCheck Project*
adt -package -target apk-emulator -storetype pkcs12 -keystore <certificate file> GestureCheck.apk GestureCheck-app.xml GestureCheck.swf ProAndroidFlashIcon*.png
这将构建一个与 Android 模拟器兼容的 APK 文件。在模拟器运行时,您可以使用 ADT 工具通过执行以下命令来安装它:
adt -installApp -platform android -package GestureCheck.apk
**提示:**您也可以使用 Android 调试桥(ADB)安装 APK 文件,如下所示:
adb install GestureCheck.apk
您的应用现在将出现在模拟器的应用菜单中。您可以通过菜单手动启动应用,或者使用以下命令以编程方式启动应用:
adt -launchApp -platform android -appid com.proandroidflash.GestureCheck.debug
请注意,appid与您的应用描述符中的id used相同,只是多了一个“.”。debug”追加到末尾。
**提示:**您也可以使用 ADB 启动 AIR 应用,如下所示:
adb shell am start -a android.intent.action.MAIN -n air.com.proandroidflash.GestureCheck.debug/.AppEntry
Figure 5–14 展示了一个在普通 Android 模拟器上运行的手势检查应用的真实例子。
图 5–14。 运行在桌面模拟器上的 Android 2 . 3 . 3
如前所述,Android 模拟器不支持多点触摸事件,这一点通过查看手势检查应用在模拟器中运行时的输出可以明显看出。
从命令行部署
虽然 Flash Professional 和 Flash Builder 使从工具内部部署应用变得非常方便,但是能够从命令行执行相同的部署通常也很有用。这允许您创建一个可重复的、自动化的过程,该过程是为您的开发工作流的确切需求而定制的。
我们将使用的命令行工具称为 AIR Developer Tool (ADT ),它可以自动执行多种不同的任务,从证书创建到应用打包再到设备部署。有关设置 ADT 的更多信息,请参见前面的“设置 ADT”一节。
ADT 中用于包装空气应用的主要标志是-package。这表明您将为桌面、移动或其他部署打包 AIR 应用。以下是用 ADT 打包 Android 应用的全部论据:
adt -package -target ( apk | apk-debug | apk-emulator ) ( -connect <host> | -listen <port>? )? ( -airDownloadURL <url> )? SIGNING_OPTIONS <output-package> ( <app-desc> PLATFORM-SDK-OPTION? FILE-OPTIONS | <input-package> PLATFORM-SDK-OPTION? )
Table 5–6 讨论了这些参数以及有效值。
虽然打包选项的排列看起来令人望而生畏,但是您只需要所需的参数就可以完成大多数任务。例如,下面将使用应用描述符中的信息打包一个简单的应用:
adt -package -target apk -storetype pkcs12 -keystore cert.p12 Sample-app.xml Sample.swf
这是从描述符和 SWF 文件打包应用的最小参数集。要构建调试版本,您应该执行以下操作:
adt -package -target apk-debug -listen -storetype pkcs12 -keystore cert.p12 Sample- app.xml Sample.swf
这将在启动时监听端口7936上的 USB 调试接口。
如果您知道要为同一个项目创建多个部署,AIR Intermediate (AIRI)文件会非常方便。您可以从 Flash Builder 导出一个 AIRI 文件,也可以在命令行上使用以下语法的prepare命令创建一个文件:
adt -prepare Sample.airi Sample-app.xml Sample.swf
然后,您可以使用package命令的input-package变体部署到多个不同的目标:
adt -package -target apk -storetype pkcs12 -keystore cert.p12 Sample-android.apk Sample.airi adt -package -target apk -airDownloadURL http://www.amazon.com/gp/mas/dl/android?p=com.adobe.air -storetype pkcs12 -keystore cert.p12 Sample-amazon.apk Sample.airi
这将创建两个不同的 APK 文件,一个准备部署到 Android Market,另一个使用不同的 AIR 下载 URL 从 Amazon Appstore 获取运行时。
命令行部署练习
本练习将指导您逐步完成打包、签名、安装和启动 Flash Capability Reporter 示例的过程。
以下是练习的先决条件:
- AIR SDK 已安装并位于路径中
- Java 运行时环境(JRE)已安装并位于路径中
- 安装了 Android SDK
首先打开命令提示符或终端。您应该能够键入不带选项的命令adt,并获得命令参数的帮助。如果它找不到adt命令或者抱怨java不在路径中,验证你已经正确地更新了你的 path 环境变量。
创建代码签名证书
要创建代码签名证书,您可以发出以下命令,其中尖括号中的值应该替换为您的名称和密码:
adt -certificate -cn <YourName> -validityPeriod 25 1024-RSA exercise.p12 <YourPassword>
如果命令成功完成,它将返回退出代码 0。
打包应用
要打包该应用,请确保您已经在 Flash Professional 中运行了该应用一次,以便可以创建电影(* .swf)和应用描述符(* -app.xml)文件。然后使用以下命令将其打包成一个 APK 文件:
adt -package -target apk -storetype pkcs12 -keystore exercise.p12 FlashCapabilityReporter.apk FlashCapabilityReporter-app.xml FlashCapabilityReporter.swf AppIconsForPublish/
这将创建一个包含应用可部署版本的 APK 文件。
安装并启动应用
另外,您可以使用以下命令将应用安装并启动到通过 USB 连接的设备上:
adt -installApp -platform android -package FlashCapabilityReporter.apk adt -launchApp -platform android -appid com.proandroidflash.FlashCapabilityReporter
如果成功,Flash Capability Reporter 应用将安装并运行在您的 Android 手机上。
将 AIR 应用发布到 Android Market
Android Market 是谷歌为 Android 设备创建和运营的应用商店。与苹果应用商店或亚马逊应用商店等其他应用商店相比,安卓市场非常开放。它没有一个限制性的筛选过程,它允许最终用户试用一个应用一天,如果他们不喜欢它,可以选择全额退款。
谷歌向开发者收取 25 美元的费用,以便创建一个可以用来提交无限数量的应用的帐户。据谷歌称,这项费用旨在通过防止应用垃圾来提高市场质量。
本节概述了将 Adobe AIR 应用发布到 Android marketplace 的三步流程。
第一步:创建一个 Android Market 开发者账户
要创建 Android Market 开发者帐户,请访问以下网站:
http://market.android.com/publish/Home
您将被要求登录您想要链接到您的 Android Market 开发者帐户的 Google 帐户。如果你已经有一个谷歌帐户,你可以使用它,否则可以免费创建一个新帐户。但是,请记住,一旦您链接了您的 Google 帐户,在不创建新的 Android Market 开发者帐户的情况下,将来将无法更改它。
接下来,提供所需的信息,如您的全名、网站 URL 和电话号码,并按照提示操作,直到您到达 Google Checkout 网站,以便提交您的注册费。要完成这个过程,请在结帐后按照链接返回注册页面,并同意 Android Market 许可证。
第二步:打包你的应用
为了上传到 Android 商店,你需要一个被打包成 APK 文件的签名应用。您可以从 Flash Professional、Flash Builder 或命令行执行此操作,如前几章所述。
提交申请时,你需要记住以下几点:
- 确保您提交的是应用的发布版本(而不是调试或仿真版本)。
- 如果不需要调试所需的
INTERNET权限,记得禁用它。 - 确保在所有标准 Android 尺寸(36×36、48×48 和 72×72)的应用中包含自定义图标。
- 确保您将 AIR 运行时 URL 设置为 Android Market(这是默认设置)。
一旦你建立了 APK 文件,你就可以开始发布你的应用到 Android Market 了。
步骤 3:上传您的 Adobe AIR 应用
Android Market 应用提交过程是完全自动化的,包括每个步骤的详细说明。Figure 5–15 展示了一个例子,展示了如果您使用第一章中的手势检查示例应用,提交过程会是什么样子。
图 5–15。 安卓市场提交流程
大部分的申请提交过程只需上传你的 APK 文件。这包括选择图标、设置权限和选择支持的平台。
除了 APK 文件之外,你还需要提交至少两张你的申请截图以及一个大尺寸图标(512 像素见方)。要截屏你的应用,你可以在桌面上的模拟器中运行它,然后把图片裁剪到合适的大小,或者使用截屏工具直接在你的 Android 设备上拍照。
在填写必填字段后,您可以提交您的应用,它将立即在 Android Market 中可用。图 5–16 显示了一个成功的应用部署到 Android Market 的成功结果。
图 5–16。 成功部署安卓市场应用
将 AIR 应用发布到亚马逊应用商店
亚马逊应用商店是第二个购买 Android 设备应用的市场。它与亚马逊的店面紧密集成,允许你从一个界面购买 Android 应用以及书籍、CD 和其他产品。此外,它使用亚马逊的专利一键式购买系统来简化在移动设备上购买应用的过程。
通过亚马逊应用商店发布应用的费用要高得多,每年订阅费用为 99 美元。幸运的是,亚马逊已经免除了同时注册亚马逊开发者计划的开发者第一年的费用。
发布到亚马逊 Appstore 的要求和流程与 Android Market 非常相似。设置您的帐户、打包您的应用并将其上传到商店的三个步骤仍然适用。
提交到亚马逊 Appstore 时,一定要设置 AIR 运行时 URL 指向亚马逊 Appstore 进行下载。这可以通过 Flash Professional UI 中的部署设置来完成,也可以通过命令行将 ADT 的-airDownloadURL property设置为以下内容来完成:
http://www.amazon.com/gp/mas/dl/android?p=com.adobe.air
Figure 5–17 展示了 Amazon Appstore 应用提交的一个例子。
图 5–17。 亚马逊应用商店提交流程
作为开发人员,在向 Amazon Appstore 提交应用时,您会注意到以下一些主要差异:
- 您需要提交三张您的应用截图,并且它们的大小必须精确到 854×480 或 480×854。
- 除了 512×512 的图标,亚马逊 Appstore 还要求 114×114 的图标。
- 你不能立即看到你的应用出现在亚马逊应用商店,你必须等待它通过审查过程。
尽管存在这些差异,但大多数应用提交过程都非常相似,这使得将您的 AIR Android 应用部署到这两个应用商店非常容易。
总结
本章结束了 Flash 平台的端到端移动应用开发过程。现在,您已经知道如何将应用从初始阶段发展到最终用户可以从市场上下载的完全发布的 Android 应用。
在本章中,您学习了如何执行以下操作:
- 设置 Android 模拟器并配置 Android 虚拟设备
- 配置应用在安装时请求的权限
- 指定启动器图标和其他资源作为应用的一部分
- 从 Flash Professional、Flash Builder 和命令行发布 AIR 包
- 在设备上或 Android 模拟器中测试您的应用包
- 将您的应用发布到 Android Market 和 Amazon Appstore
在接下来的几章中,我们将进一步深入探讨与 Android 的原生集成、针对移动设备的性能调整以及与设计师的合作。
六、Adobe AIR 和原生 Android 应用
您已经学习了如何创建有趣的基于 Flex 的移动应用,在本章中,您将了解 Adobe AIR 中可用的其他有用功能,以及如何将 Android 特定的功能合并到 Adobe AIR 移动应用中。
首先,您将学习如何执行 Adobe AIR 中提供的两项操作:如何在 AIR 应用中启动本机浏览器,以及如何在 SQLite 数据库中存储特定于应用的数据。本章的下一部分深入探讨了 Android 的基础知识,你需要理解本章后面讨论的代码示例。本节将向您展示如何创建一个简单的原生 Android 应用,并讨论 Android 应用中的主要文件。您还将了解重要的 Android 特定概念,如活动、Intent s 和Service s。
本章的第三部分包含一个 Adobe AIR mobile 应用的示例,该应用调用外部 API 来提供用户已向外部服务注册的网站的状态信息。我们的 Adobe AIR 应用将每个网站的状态存储在 SQLite 数据库中,然后在数据网格中显示状态详细信息。这个移动应用还允许用户点击一个按钮,向原生 Android 代码发送更新,然后在 Android 通知栏中显示更新。本章的最后一部分包含将 Adobe AIR mobile 应用与本机 Android 代码集成所需的步骤。
关于本章的内容,有几点需要记住。首先,Android 内容旨在帮助您了解如何将原生 Android 功能集成到 Adobe AIR 应用中。因此,只涵盖了 Android 主题的一个子集,这不足以成为一名熟练的 Android 应用开发人员。其次,Adobe AIR 是一个不断发展的产品,因此 Adobe AIR 目前不可用的一些 Android 功能可能会在未来的版本中可用。第三,Adobe AIR 应用与原生 Android 功能的集成不受 Adobe 官方支持;因此,如果您在整合过程中遇到困难,没有正式的支持机制来帮助您解决这些困难。
另一个需要考虑的问题与 Adobe AIR mobile 应用的目标设备所支持的 Android 版本有关。例如,支持 Android 2.2 的移动设备的数量目前远远大于支持 Android 2.3.x 或 Android 3.0 的移动设备,这两种移动设备目前都仅限于几款平板电脑(如三星 Galaxy Tab 10.1 和摩托罗拉 Xoom)和一款智能手机(三星 Galaxy S II)。
另一方面,如果 Adobe AIR 支持您创建移动应用所需的所有功能和特性,那么您就不需要本章中说明如何将 Adobe AIR 应用与 Android 应用合并的任何代码示例。如果是这种情况,你可以跳过这些材料而不失去连贯性。
在 Adobe AIR 中调用 URI 处理程序
目前,Adobe AIR 中有五个与 URI 相关的处理程序,使您能够在 Adobe AIR mobile 应用中执行以下操作:
- 电话(打电话)
- sms(发送文本消息)
- mailto(发送电子邮件)
- 市场(进行市场搜索)
- http 和 https(启动 web 浏览器)
每个处理程序的代码都非常简单,这使得在 Adobe AIR mobile 应用中嵌入这些处理程序非常简单。需要记住的一点是,Adobe AIR 不支持“地理”URI,但是您仍然可以导航到 maps.google.com,并且会提示用户在地图应用的浏览器会话版本中打开该 URL。这种“变通方法”使您能够在 Adobe AIR mobile 应用中支持与地图相关的功能。
使用移动应用模板创建一个名为 URIHandlers 的新 Flex 移动项目,并添加如清单 6–1 所示的代码。
清单 6–1。 调用 URI 处理程序
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Home"> fx:Script <![CDATA[ import flash.sensors.Geolocation;
[Bindable] public var tel:String; [Bindable] public var sms:String; [Bindable] public var mailto:String; [Bindable] public var search:String; [Bindable] public var http:String; [Bindable] public var geo1:String;
private var geo:Geolocation;
private function onTel():void { navigateToURL(new URLRequest("tel:"+tel)); }
private function onSMS():void { navigateToURL(new URLRequest("sms:"+sms)); }
private function onMailto():void { navigateToURL(new URLRequest("mailto:"+mailto+"?subject=Hello%20AIR")); }
private function onSearch():void { navigateToURL(new URLRequest("market://search?q=iReverse")); }
private function onHTTP():void { navigateToURL(new URLRequest(http)); }
private function onGeo():void { this.geo = new Geolocation(); this.geo.addEventListener(GeolocationEvent.UPDATE, onLocationUpdate); }
private function onLocationUpdate(e:GeolocationEvent):void { this.geo.removeEventListener(GeolocationEvent.UPDATE,onLocationUpdate); var long:Number = e.longitude; var lat:Number = e.latitude; navigateToURL(new URLRequest("maps.google.com/")); } ]]> </fx:Script>
<s:VGroup> <s:Form backgroundColor="0xFFFFFF" width="300"> <s:FormItem> <s:HGroup left="0"> <s:TextInput width="180" height="50" text="{tel}"/> <s:Button id="telID" width="250" height="50" label="(Call)" click="onTel();"/> </s:HGroup> </s:FormItem>
<s:FormItem>
<s:HGroup left="0"> <s:TextInput width="180" height="50" text="{sms}"/>
<s:Button id="smsID" width="250" height="50" label="(Text)" click="onSMS();"/> </s:HGroup>
</s:FormItem>
<s:FormItem> <s:HGroup left="0"> <s:TextInput width="180" height="50" text="{mailto}"/> <s:Button id="mailtoID" width="250" height="50" label="(EMail)" click="onMailto();"/> </s:HGroup> </s:FormItem>
<s:FormItem> <s:HGroup left="0"> <s:TextInput width="180" height="50" text="{search}"/> <s:Button id="searchID" width="250" height="50" label="(Search Market)" click="onTel();"/> </s:HGroup> </s:FormItem>
<s:FormItem> <s:HGroup left="0"> <s:TextInput width="180" height="50" text="{http}"/> <s:Button id="httpID" width="250" height="50" label="(Go)" click="onHTTP();"/> </s:HGroup> </s:FormItem>
<s:FormItem> <s:HGroup left="0"> <s:TextInput width="180" height="50" text="{geo1}"/> <s:Button id="geoID" width="250" height="50" label="(Geo)" click="onGeo();"/> </s:HGroup> </s:FormItem> </s:Form> </s:VGroup> </s:View>`
清单 6–1 包含一个带有各种输入字段的表单,每个输入字段都有一个关联的事件处理程序,它使用不同的参数调用内置方法navigateToURL()。例如,当用户输入一个 URL,然后点击相关的按钮,方法onHTTP()用下面的代码行启动一个 URL:
navigateToURL(new URLRequest(http));
Figure 6–1 显示了一个带有各种输入字段的表单,说明了如何在 AIR mobile 应用中使用与 URI 相关的功能。
图 6–1。 使用 URI 相关功能
在 Adobe AIR 中启动自定义 HTML 页面
Adobe AIR 使您能够启动定制的 HTML 页面(如前面所示),还可以导航到任意 HTML 页面(如清单 6–2 所示)。
使用 ActionScript 移动应用模板创建一个名为 StageWebViewHTML1 的新 Flex 移动项目,并添加如清单 6–2 中所示的代码。
清单 6–2。 启动一个硬编码的 HTML 页面
`package { import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.geom.Rectangle; import flash.media.StageWebView;
public class StageWebViewHTML1 extends Sprite { public function StageWebViewHTML1() { super();
// support autoOrients
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE; var webView:StageWebView = new StageWebView();
webView.stage = this.stage;
webView.viewPort = new Rectangle( 0, 0,
stage.stageWidth,
stage.stageHeight );
// create an HTML page
var htmlStr:String = "" +
"" +
"" +
"
An HTML Page in Adobe AIR
" + "Hello from the Author Team:
" + ""+ "
Stephen Chin
" + "Dean Iverson
" + "Oswald Campesato
" + "Paul Trani
" + ""+ "
"+ "
This is the key line of code:
"+ "webView.loadString( htmlStr, 'text/html'; );
"+ "'htmlStr' contains the HTML contents
"; "" + "";// launch the HTML page webView.loadString( htmlStr, "text/html" ); } } }`
清单 6–2 包含几个import语句和自动生成的代码,变量htmlStr是一个字符串,其中包含一行代码启动的 HTML 页面的内容:
webView.loadString( htmlStr, "text/html" );
如果您计划在移动应用中调用硬编码的 HTML 页面,请尝试使用不同的 HTML5 标签来创建 HTML 页面所需的样式效果。
图 6–2 显示了来自清单 6–2 的输出,它呈现了一个带有硬编码内容的 HTML 页面。
图 6–2。 启动一个硬编码的 HTML 页面
在 Adobe AIR 中导航到 HTML 页面
在前面的示例中,您学习了如何启动硬编码的 HTML 页面,在本节中,您将学习如何导航到任何 HTML 页面,然后在 Adobe AIR mobile 应用中启动该 HTML 页面。
使用 ActionScript 移动应用模板创建一个名为 StageWebViewLaunch2 的新 Flex 移动项目,并添加如清单 6–3 所示的代码。
清单 6–3。 启动用户指定的 URL
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" title="HomeView" >
<fx:Script source="StageWebViewLaunch.as"/>
<s:Label x="10" y="50" width="150" height="50" text="Enter a URL: "/> <s:TextInput x="180" y="50" width="290" height="50" text="{url}"/>
<s:Button x="10" y="120" width="300" height="50"
label="Launch the URL" click="StageWebViewExample()" />
</s:View>`
清单 6–3 包含一个允许用户输入 URL 的输入字段,以及一个通过 ActionScript3 文件StageWebViewLaunch.as中定义的方法StageWebViewExample()启动指定 URL 的按钮。
现在创建文件StageWebViewLaunch.as并插入清单 6–4 中的代码。
清单 6–4。 启动 URL 的 ActionScript3 代码
`import flash.media.StageWebView; import flash.geom.Rectangle;
import flash.events.ErrorEvent; import flash.events.Event; import flash.events.LocationChangeEvent;
[Bindable] public var url:String = "www.google.com";
private var webView:StageWebView = new StageWebView();
public function StageWebViewExample() { webView.stage = this.stage; webView.viewPort = new Rectangle( 0, 0, stage.stageWidth, stage.stageHeight );
webView.addEventListener(Event.COMPLETE, completeHandler); webView.addEventListener(ErrorEvent.ERROR, errorHandler); webView.addEventListener(LocationChangeEvent.LOCATION_CHANGING, locationChangingHandler); webView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, locationChangeHandler); // launch the user-specified URL webView.loadURL( url ); }
// Dispatched after the page or web content has been fully loaded protected function completeHandler(event:Event):void { dispatchEvent(event); }
// Dispatched when the location is about to change protected function locationChangingHandler(event:Event):void { dispatchEvent(event); }
// Dispatched after the location has changed protected function locationChangeHandler(event:Event):void { dispatchEvent(event); }
// Dispatched when an error occurs
protected function errorHandler(event:ErrorEvent):void { dispatchEvent(event);
}`
清单 6–4 定义了引用用户指定的 URL 的Bindable变量url,后面是包含特定于 URL 的功能的变量webView。方法StageWebViewExample()定义了各种事件处理程序,所有这些程序都调用内置方法dispatchEvent(),然后用户指定的 URL 通过这行代码启动:
webView.loadURL( url );
图 6–3 显示了谷歌主页,这是清单 6–4 中的默认 URL。
图 6–3。 启动用户指定的网址
在 Adobe AIR 中访问 SQLite
Adobe AIR 支持访问存储在移动设备上的 SQLite 数据库中的数据。您也可以直接从原生 Android 代码访问 SQLite 数据库,但 Adobe AIR 提供了更高的抽象级别(代码也更简单)。
使用移动应用模板创建一个名为 SQLite1 的新 Flex 移动项目,并添加如清单 6–5 所示的代码。
清单 6–5。 查看 SQLite 数据库中的数据
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="HomeView" creationComplete="start()">
<fx:Script source="SQLiteAccess.as"/>
<s:Label x="10" y="10" width="400" height="50" text="Create New Person and Click 'Add'"/> <s:Label x="10" y="50" width="150" height="50" text="First name:"/> <s:TextInput x="250" y="50" width="200" height="50" id="first_name"/>
<s:Label x="10" y="100" width="150" height="50" text="Last name:"/> <s:TextInput x="250" y="100" width="200" height="50" id="last_name"/>
<s:Button x="10" y="160" width="200" height="50" label="Add" click="addItem()"/>
<s:Button label="Remove Selected Person" height="50" x="10" y="230" click="remove()" enabled="{dg.selectedIndex != -1}"/>
<s:DataGrid id="dg" left="10" right="10" top="300" bottom="80" dataProvider="{dp}"> <s:columns> <s:ArrayList> <s:GridColumn headerText="Index" dataField="id" width="100" /> <s:GridColumn headerText="First name" dataField="first_name" width="150" /> <s:GridColumn headerText="Last name" dataField="last_name" width="150" /> </s:ArrayList> </s:columns> </s:DataGrid> </s:View>`
清单 6–5 包含一个 XML 脚本元素,该元素引用一个 ActionScript3 文件,该文件包含访问 SQLite 数据库的方法,其内容将显示在清单 6–6 中。标签和文本输入字段使用户能够输入将被添加到我们的数据库中的每个新人的名字和姓氏。
有一个 XML Button元素用于通过addPerson()方法添加一个新人员,还有一个 XML Button用于通过removePerson()方法从我们的数据库中删除一个现有人员。两种方法都在SQLiteAccess.as中定义。变量dp是一个Bindable变量,包含数据网格中显示的数据,由于用户可以添加和删除数据行,dp是一个Bindable变量。
因为我们要访问存储在 SQLite 数据库中的数据,所以我们需要在 ActionScript3 中定义几个方法来管理数据库内容的创建、访问和更新。在与文件SQLite1HomeView.mxml相同的目录下创建一个名为SQLiteAccess.as的新 ActionScript3 文件,并添加如清单 6–6 所示的代码。
清单 6–6。 在 ActionScript3 中定义数据库访问方法
`import flash.data.SQLStatement; import flash.errors.SQLError; import flash.events.Event; import flash.events.SQLErrorEvent; import flash.events.SQLEvent; import flash.events.TimerEvent; import flash.filesystem.File; import flash.utils.Timer;
import mx.collections.ArrayCollection; import mx.utils.ObjectUtil;
import org.osmf.events.TimeEvent;`
清单 6–6 包含各种与 SQL 相关的import语句和一个Timer类,当我们试图读取更新后的数据库的内容时,将会用到这个类。因为在任何给定的时间点只能执行一条 SQL 语句,所以Timer类给了我们“稍后再试”的能力(以毫秒为单位)。
`// sqlconn holds the database connection public var sqlconn:SQLConnection = new SQLConnection();
// sqlstmt holds SQL commands public var sqlstmt:SQLStatement = new SQLStatement();
// a bindable ArrayCollection and the data provider for the datagrid [Bindable] public var dp:ArrayCollection = new ArrayCollection();
// invoked after the application has loaded private function start():void { // set 'people.db' as the file for our database (created after it's opened) var db:File = File.applicationStorageDirectory.resolvePath("people.db");
// open the database in asynchronous mode sqlconn.openAsync(db);
// event listeners for handling sql errors and 'result' are
// invoked whenever data is retrieved from the database
sqlconn.addEventListener(SQLEvent.OPEN, db_opened);
sqlconn.addEventListener(SQLErrorEvent.ERROR, error);
sqlstmt.addEventListener(SQLErrorEvent.ERROR, error); sqlstmt.addEventListener(SQLEvent.RESULT, result);
}`
变量sqlconn和sqlstmt使我们能够连接到 SQLite 数据库并执行 SQL 查询。start()方法将数据库名称指定为people.db,然后打开一个异步连接。请注意用于处理数据库相关操作和错误的各种事件处理程序。
`private function db_opened(e:SQLEvent):void { // specify the connection for the SQL statement sqlstmt.sqlConnection = sqlconn;
// Table "person_table" contains three columns: // 1) id (an autoincrementing integer) // 2) first_name (the first name of each person) // 3) last_name (the last name of each person) sqlstmt.text = "CREATE TABLE IF NOT EXISTS person_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, first_name TEXT, last_name TEXT);";
// execute the sqlstmt to update the database sqlstmt.execute();
// refresh the datagrid to display all data rows refreshDataGrid(); }
// function to append a new row to person_table // each new row contains first_name and last_name private function addPerson():void { sqlstmt.text = "INSERT INTO person_table (first_name, last_name) VALUES('"+first_name.text+"','"+last_name.text+"');"; sqlstmt.execute();
refreshDataGrid(); }`
方法db_opened()指定数据库的名称和包含我们的个人相关数据的表,然后执行refreshDataGrid()方法,该方法检索数据库的最新内容,以便在我们的移动应用的数据网格中显示该数据。请注意,在addPerson()插入一个新的人之后,refreshDataGrid()方法被调用,这样 datagrid 将自动显示新添加的人。
`// function to refresh the data in datagrid private function refreshDataGrid(e:TimerEvent = null):void { // timer object pauses and then attempts to execute again var timer:Timer = new Timer(100,1); timer.addEventListener(TimerEvent.TIMER, refreshDataGrid);
if ( !sqlstmt.executing ) {
sqlstmt.text = "SELECT * FROM person_table"
sqlstmt.execute();
} else {
timer.start(); }
}
// invoked when we receive data from a sql command private function result(e:SQLEvent):void { var data:Array = sqlstmt.getResult().data;
// fill the datagrid dp = new ArrayCollection(data); }
// remove a row from the table private function removePerson():void { sqlstmt.text = "DELETE FROM person_table WHERE id="+dp[dg.selectedIndex].id; sqlstmt.execute(); refreshDataGrid(); }
// error handling method private function error(e:SQLErrorEvent):void { // Alert.show(e.toString()); }`
方法refreshDataGrid()首先检查 SQL 语句当前是否正在执行;如果是,那么它暂停指定的毫秒数(在本例中是 100),然后从person_table中检索所有行(这将包括新添加的人员)。方法result()用刷新的数据集填充变量dp。removePerson()方法删除用户在数据网格中点击选中的行。
Figure 6–4 显示了一组存储在移动设备上的 SQLite 数据库中的行。
**图 6–4。**SQLite 数据库中的一组记录
学习 Android 的基本概念
Android 是一个用于开发 Android 移动应用的开源工具包,在撰写本文时,Android 3.0(“蜂巢”)是最新的主要版本;Android 的最新版本是 3.1。注意,Adobe AIR 2 . 5 . 1 for mobile applications 至少需要 Android 2.2,您可以在支持 Android 2.2 或更高版本的移动设备上安装 Adobe AIR 应用。
以下部分提供了一些关于 Android 3.0 的主要特性、从哪里下载 Android 以及 Android 中的关键概念的信息。当你读完这一节,你将理解如何创建原生 Android 应用。
Android 3.0 的主要特性
Android 的早期版本提供了对 UI 组件和事件处理程序、音频和视频、管理文件系统上的文件、图形和动画效果、数据库支持、web 服务以及电话和消息(SMS)的支持。
Google Android 3.0(2011 年初发布)为 Android 2.3(2010 年 12 月发布)中的功能提供了向后兼容的支持。Android 3.0 提供了比 2.3 版本更好的功能,以及以下新功能:
- 平板电脑的新用户界面
- 状态和通知的系统栏
- 应用控制的操作栏
- 可定制的主屏幕
- 复制/粘贴支持
- 更多连接选项
- SVG 支援
- 通用远程功能
- 谷歌文档集成
- 内置远程桌面
- 改进的媒体播放器
- 更好的 GPS 支持
- 改进的多任务处理
- 跟踪设备
- 电池寿命/电源管理改进
Android 3.0 中的优秀功能改进包括更长的电池寿命、更快的图形渲染和更丰富的媒体功能(例如,延时视频、HTTP 直播和 DRM)。此外,Android 3.1 支持另一组新功能,包括 USB 附件的 API 和来自鼠标、轨迹球和操纵杆的新输入事件。但是,在本章中,我们将重点关注 Android 功能的一小部分,为了将 Adobe AIR 应用与原生 Android 应用合并,我们需要了解这些功能,因此我们不会深入研究 Android 3.x 的功能。导航到 Android 主页以获取有关|Android 3.x 中支持的新的和改进的功能套件的更多信息。
安卓下载/安装
为了在 Eclipse 中开发基于 Android 的移动应用,您需要下载并安装 Java、Eclipse 和 Android。注意 Java 是预装在 Mac OSX 上的,Java 也可以通过基于 Linux 的包管理器下载。你可以在这里下载适合你平台的 Java:[java.sun.com/javase/downloads](http://java.sun.com/javase/downloads)。
如果您有一台 Windows 机器,您需要将环境变量JAVA_HOME设置为解压缩 Java 发行版的目录。
Android SDK 可以在这里下载:[developer.android.com/sdk/index.html](http://developer.android.com/sdk/index.html)。
对于 Windows 平台,Android 发行版是一个具有以下类型名称的文件(这在本书出版时可能略有不同):android-sdk_r06-windows.zip。
完成 Java 和 Eclipse 安装后,按照 Android 安装步骤在您的机器上安装 Android。
你还需要创建一个 AVD (Android 虚拟设备),具体操作步骤在第五章中。
Android 中的关键概念
尽管 Android 应用是用 Java 编写的,但 Java 代码被编译成 Dalvik 可执行文件,该文件(以及其他素材)是部署到 Android 设备的.apk应用文件的一部分。
除了支持标准 Java 语言特性之外,Android 应用通常还包含以下特定于 Android 的概念:
- 活动
Intent年代Service年代Broadcast接收者
在您掌握了前面列表中的概念之后,您可以了解Intent Filter和Content Provider(对这两个主题的全面讨论超出了本章的范围)以及如何使用它们来提供更细粒度的基于Intent的功能以及跨 Android 应用共享数据的能力。
每个 Android 应用的属性都在 XML 文档AndroidManifest.xml中指定,该文档是在创建每个 Android 应用时自动生成的。这个清单文件包含关于活动、Intent、Service、Broadcast接收者和许可的信息,这些信息是相关联的 Android 应用的一部分。
一个 Android 应用可以包含多个 Android Activities,每个 Android Activity可以包含多个Intent和Intent Filter,此外,一个 Android 应用可以包含一个 Android Service和一个 Android Broadcast receiver,它们都被定义为AndroidManifest.xml中 Android activity元素的兄弟元素。
清单 6–7 提供了您可能在AndroidManifest.xml项目文件中找到的内容的概要。在这种情况下,项目文件包含两个 Android 活动、两个 Android Service和两个 Android Broadcast接收者的“存根”。XML 元素的属性已经被省略,这样您就可以看到 Android 应用的整体结构,在本章的后面,您将看到一个完整的AndroidManifest.xml内容的例子。
清单 6–7。 一个轮廓AndroidManifest.xml
<manifest xmlns:android=http://schemas.android.com/apk/res/android> <application> <activity> <intent-filter> </intent-filter> </activity> <activity> </activity> <service> </service> <service> <intent-filter> </intent-filte> </service> <receiver> </receiver> <receiver> <intent-filter> </intent-filter> </receiver> </application> </manifest>
清单 6–7 包含两个 Android activity元素、两个 Android service元素和两个 Android receiver元素。在这三对中的每一对中,都有一个包含 Android intent-filter元素的元素,但是请记住,AndroidManifest.xml的内容可能有许多变化。项目文件的确切内容取决于您的 Android 应用的功能。
Android 还支持 Java 本地接口(JNI ),允许 Java 代码调用 C/C++ 函数。但是,您还需要下载并安装 Android 原生开发工具包(NDK),它包含一组工具,用于创建包含可从 Java 代码调用的函数的库。如果你需要比单独使用 Java 更好的性能(尤其是图形性能),你可以使用 JNI,但是这个主题的细节已经超出了本章的范围。
安卓活动
Android Activity对应于应用的一个屏幕或一个视图,Android 应用的主要入口点是一个 Android Activity,它包含一个onCreate()方法(覆盖其超类中的相同方法),每当您启动 Android 应用时都会调用该方法。当你启动你的 Android 应用时,它的 Android Activity会自动启动。例如,清单 6–8 显示了HelloWorld.java的内容,这是在本章稍后创建基于 Eclipse 的“Hello World”Android 应用时自动生成的。
清单 6–8。*HelloWorld.java*的内容
`package com.apress.hello;
import android.app.Activity; import android.os.Bundle;
public class HelloWorld extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }`
在 Android 应用的项目创建步骤中,您指定包名和类名;其余生成的代码对于每个 Android 项目都是一样的。
注意,HelloWorld扩展了android.app.Activity,并且它也覆盖了onCreate()方法。正如你可能猜到的,Android Activity是一个包含一组方法(如onCreate())的 Android 类,你可以在你的 Android 应用中覆盖这些方法。一个Activity包含一个或多个属于 Android 应用的View。
Android View是用户在屏幕上看到的东西,包括 Android 应用的 UI 小部件。HelloWorld Android 应用包含一个 Android 类,它扩展了 Android Activity类,并用您的自定义代码覆盖了onCreate()方法。注意,Android 应用也可以扩展其他 Android 类(比如Service类),它们也可以创建线程。
一个 Android 应用可以包含不止一个 Android Activity,正如您已经知道的,每个Activity都必须在 XML 文档AndroidManifest.xml中定义,XML 文档是每个 Android 应用的一部分。
HelloWorld Android 项目包含 XML 文档AndroidManifest.xml,其中 Android 类HelloWorld注册在 XML activity元素中,如清单 6–9 所示。
清单 6–9。*AndroidManifest.xml*的内容
` <manifest xmlns:android="schemas.android.com/apk/res/and…" package="com.apress.hello" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".HelloWorld" android:label="@string/app_name">
`
请注意句号(“.”)位于清单 6–9 中的 Android Activity HelloWorld之前。这个句点是强制的,因为字符串.HelloWorld被附加到包名com.apress.hello(也在清单 6–9 中指定),所以这个 Android 项目中HelloWorld.java的完全限定名是com.apress.hello.HelloWorld。
安卓意图
Android UI(用户界面)由Intent和View组成。抽象地说,Android Intent表示在 Android 应用中要执行的动作(通常由动词描述)的细节。
一个Intent本质上是 Android 活动(或Service s)之间的通知。一个Intent使一个 Android Activity能够向其他 Android 活动发送数据,也能够从其他 Android 活动接收数据。
Android Intent类似于事件处理器,但是 Android 提供了处理多个Intent的额外功能,以及使用现有Inten ts 与启动新Intent的选项。Android Intent s 可以启动一个新的 Android Activity,它们还可以广播消息(由 Android Broadcast接收器处理)。下面的代码片段说明了如何通过一个Intent来启动一个新的Activity:
Intent intent = new Intent(action, data); startActivity(intent);
Android Activities 和Intent s 提供了一组松散耦合的资源,让人想起 SOA(面向服务的架构)。Android Activity的对等物是一个 web 服务,Android Activity可以处理的Intent类似于 web 服务提供给世界的“操作”或方法。其他 Android 应用可以显式地调用这些方法中的一个,或者它们可以发出一个“通用”请求,在这种情况下,“框架”决定哪些 web 服务将处理该通用请求。
你也可以广播Intent以便在组件之间发送消息。下面的代码片段说明了如何广播一个Intent:
Intent intent = new Intent(a-broadcast-receiver-class); sendBroadcast(intent);
这种类型的功能为 Android 应用提供了更大的灵活性和“开放性”。
Android 意图的类型
Android 有几种类型,每一种提供的功能都略有不同。定向的*Intent是具有一个接收者的Intent,而广播的*Intent可以被任何进程接收。一个显式?? 指定了需要调用的 Java 类。一个隐式 Intent是一个不指定 Java 类的Intent,这意味着 Android 系统将决定哪个应用将处理隐式Intent。如果有几个应用可以响应隐式的Intent,Android 系统会让用户选择其中一个应用。**
**Android 也有Intent Filter s 的概念,用于Intent分辨率。一个Intent Filter表示一个机器人Activity(或机器人Service)可以“消费”的Intent,细节在 XML intent-filter元素中指定。注意,如果应用不提供Intent Filter,那么它只能被显式的Intent调用(而不能被隐式的Intent)。
在文件AndroidManifest.xml的一个 Android Activity中指定了一个Intent Filter,如清单 6–10 所示。
清单 6–10。 安卓中的一个Intent Filter的例子
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
清单 6–10 显示了AndroidManifest.xml内容的一个片段,如清单 6–9 所示。清单 6–10 中的 XML action元素指定默认值android.intent.action.MAIN,XML category元素指定android.intent.category.LAUNCHER(也是默认值),这意味着父Activity将显示在应用启动器中。
一个Intent Filter必须包含一个 XML action元素,并且可以选择包含一个 XML category元素或者一个 XML data元素。如您所见,清单 6–10 包含指定默认动作的强制 XML action元素,以及可选的 XML category元素,但不包含可选的 XML data元素。
一个Intent Filter是定义特定动作的一组信息;XML data元素指定要操作的数据,XML category元素指定执行动作的组件。
在一个Intent Filter中可以指定这三个 XML 元素的各种组合,因为其中两个元素是可选的,Android 使用一种基于优先级的算法来确定将为您在AndroidManifest.xml中定义的每个Intent Filter执行什么。如果您需要更详细地了解Intent Filter s,请查阅 Android 文档以获取更多信息。
如果你有兴趣了解免费提供的 Android Intent s,你可以访问 OpenIntents,这是一个由其他人捐赠的各种 Android Intent s 组成的开源项目,它的主页在这里:[www.openintents.org/en/](http://www.openintents.org/en)。
OpenIntents 提供各种类别的 Android 应用,如实用程序、商业应用、教育和娱乐。这些应用以.apk文件的形式提供,有时这些应用的源代码也是免费的。此外,OpenIntents 提供了游戏引擎、图表包等 Android 库的链接,以及访问 CouchDB 服务器、Drupal、脸书等服务。OpenIntents 库可从这里获得:[www.openintents.org/en/libraries](http://www.openintents.org/en/libraries)。
OpenIntents 还提供了一个可以被 Android 活动调用的公开可用的 Android Intent的注册表,以及它们的类文件和它们的服务描述。有关更多信息,请访问 OpenIntents 主页。
安卓服务
Android Service可用于处理后台任务和其他不涉及视觉界面的任务。由于 Android Service运行在主进程的主线程中,Android Service通常会在需要执行工作时启动一个新线程,而不会阻塞 Android 应用的 UI(在主线程中处理)。因此,Android 应用可以通过该服务公开的一组 API“绑定”到一个Service。
Android Service是通过AndroidManifest.xml中的 XML service元素定义的,如下所示:
<service android:name=".subpackagename.SimpleService"/>
清单 6–11 显示了ServiceName.java的内容,它为自定义 Android Service类的定义提供了“框架”代码。
清单 6–11。*SimpleService.java*的内容
`public class SimpleService extends Service { @Override public IBinder onBind(Intent intent) { return null; }
@Override protected void onCreate() { super.onCreate(); startservice(); // defined elsewhere }
@Override protected void onCreate() {
// insert your code here
}
@Override protected void onStart() { // insert your code here } }`
例如,如果您需要定期执行某件事情,那么您可以包含一个Timer类的实例,该实例根据您的应用的需要来调度和执行一个TimerTask。
安卓广播接收器
Android Broadcast接收器的目的是“听”Android Intent s. 清单 6–12 在本章稍后讨论的基于小部件的 Android 应用的AndroidManifest.xml文件中显示了 Android Broadcast接收器的定义。
清单 6–12。 为AndroidManifest.xml 中的接收人录入样本
<!-- Broadcast Receiver that will process AppWidget updates --> <receiver android:name=".MyHelloWidget" android:label="@string/app_name"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/hello_widget_provider" /> </receiver>
清单 6–12 包含一个 XML receiver元素,该元素将MyHelloWidget指定为这个小部件的 Java 类。这个 Android receiver 包含一个 XML intent-filter元素和一个 XML action元素,当需要更新AppWidget MyHelloWidget时,这个 XMLaction元素会导致一个动作发生,如下所示:
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
Android 生命周期
在本章的前面,您看到了HelloWorld.java的内容,它包含了覆盖超类中相同方法的onCreate()方法。事实上,onCreate()是组成 Android 应用生命周期的七种 Android 方法之一。
谷歌 Android 应用包含以下方法,这是 Android 应用生命周期中调用方法的顺序 1 :
onCreate()onRestart()onStart()onResume()onPause()onStop()onDestroy()
创建Activity时会调用onCreate()方法,其作用类似于其他语言中的init()方法。当从内存中移除一个Activity时,就会调用onDestroy()方法,它的作用实质上是 C++ 中的一个析构函数方法。当Activity必须暂停时(比如回收资源),调用onPause()方法。当重新启动Activity时,调用onRestart()方法。当Activity与用户交互时,调用onResume()方法。当Activity在屏幕上可见时,调用onStart()方法。最后,调用onStop()方法来停止Activity。
方法onRestart()、onStart()和onStop()处于可见阶段;方法onResume()和onPause()处于前台阶段。Android 应用在执行期间可以暂停和恢复多次;细节是特定于应用的功能的(也可能是用户交互的类型)。
创建 Android 应用
这一节描述了如何在 Eclipse 中创建 Android 应用,随后的一节向您展示了 Android 应用的目录结构,随后讨论了在每个 Android 应用中创建的主要文件。
启动 Eclipse 并执行以下步骤,以创建一个名为 HelloWorld 的新 Android 应用:
- 导航到文件
新建
Android 项目。
- 输入“HelloWorld”作为项目名称。
- 选择“Android 2.3”左侧的复选框作为构建目标。
- 输入“HelloWorld”作为应用名称。
- 输入“com.apress.hello”作为包名。
- 在创建活动输入字段中输入“HelloWorld”。
- 输入数字“9”表示最小 SDK 版本。
- 单击完成按钮。
Eclipse 将生成一个新的 Android 项目(其结构将在下一节描述)。接下来,通过右键单击项目名 HelloWorld 启动这个应用,然后选择 Run As Android Application。您必须等待 Android 模拟器完成其初始化步骤,这可能需要一分钟左右的时间(但是您的应用的每次后续启动都会明显更快)。
图 6–5 显示了从 Eclipse 启动的 Android 模拟器中 HelloWorld 应用的输出。
**图 6–5。**hello world 安卓应用
Android 应用的结构
导航到您在上一节中创建的 HelloWorld 项目,右键单击项目名称以显示展开的目录结构。接下来的几个小节讨论了每个 Android 应用的目录结构和主文件的内容。
清单 6–13 显示了 Android 项目 HelloWorld 的目录结构。
清单 6–13。 一个安卓项目的架构
`+HelloWorld src/ com/ apress/ hello/ HelloWorld.java
gen/ com/ apress/ hello/ R.java Android 2.3/ android.jar assets/ res/ drawable-hdpi/ icon.png drawable-ldpi/ icon.png drawable-mdpi/ icon.png layout/ main.xml values/ strings.xml AndroidManifest.xml default.properties proguard.cfg`
这个 Android 应用包含两个 Java 文件(HelloWorld.java和R.java)、一个 JAR 文件(android.jar)、一个图像文件(icon.png)、三个 XML 文件(main.xml、strings.xml和AndroidManifest.xml)以及一个文本文件default.properties。
Android 应用中的主要文件
这里列出了我们将在本节中讨论的 HelloWorld Android 应用中的文件(所有文件都是相对于项目根目录列出的):
src/com/apress/hello/HelloWorld.javagen/com/apress/hello/R.javaAndroidManifest.xmlres/layout/main.xmlres/values/strings.xml
清单 6–14 显示了HelloWorld.java的内容,其中包含了这个 Android 应用所需的所有定制 Java 代码。
清单 6–14。*HelloWorld.java*的内容
`package com.apress.hello;
import android.app.Activity; import android.os.Bundle; public class HelloWorld extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }`
在本章的前面,您已经看到了HelloWorld.java的内容。您可以将这段代码视为“样板”代码,它是在项目创建过程中根据用户提供的包名和类名的值自动生成的。
您的自定义代码包含在该语句之后:
setContentView(R.layout.main);
除了从文件main.xml中设置View之外,从另一个 XML 文件或者从 Android 项目中其他地方定义的自定义类中设置View也很常见。
现在让我们看一下清单 6–15,它显示了资源文件R.java的内容,该文件是在您创建 Android 应用时自动生成的。
清单 6–15。*R.java*的内容
`/* AUTO-GENERATED FILE. DO NOT MODIFY. * * This class was automatically generated by the * aapt tool from the resource data it found. It * should not be modified by hand. */
package com.apress.hello;
public final class R { public static final class attr { } public static final class drawable { public static final int icon=0x7f020000; } public static final class layout { public static final int main=0x7f030000; } public static final class string { public static final int app_name=0x7f040001; public static final int hello=0x7f040000; } }`
清单 6–15 中的整数值本质上是对应于 Android 应用素材的引用。例如,变量icon是对位于res目录的子目录中的icon.png文件的引用。变量main是对位于res/layout子目录中的 XML 文件main.xml(将在本节稍后介绍)的引用。变量app_name和hello是对 XML 文件strings.xml(本节前面已经介绍过)中的 XML app_name元素和 XML hello元素的引用,该文件位于res/values子目录中。
既然我们已经研究了基于 Java 的项目文件的内容,让我们把注意力转向 Android 项目中基于 XML 的文件。清单 6–16 显示了AndroidManifest.xml的全部内容。
清单 6–16。*AndroidManifest.xml*的内容
` <manifest xmlns:android="schemas.android.com/apk/res/and…" package="com.apress.hello" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".HelloWorld" android:label="@string/app_name">
`
清单 6–16 以一个 XML 声明开始,后面是一个包含子 XML 元素的 XML manifest元素,它提供了关于您的 Android 应用的信息。注意,XML manifest元素包含一个带有 Android 应用包名的属性。
清单 6–16 中的 XML application元素包含一个值为@drawable/icon的android:icon属性,该属性引用位于res子目录中的一个图像文件icon.png。Android 支持三种类型的图像文件:高密度、中密度和低密度。对应的目录有drawable-hdpi、drawable-mdpi、drawable-ldpi,都是每个安卓应用根目录下res目录的子目录。
清单 6–16 中的 XML application元素还包含一个值为@string/app_name的android:label属性,该属性引用了位于res/values子目录中的文件strings.xml中的一个 XML 元素。
清单 6–16 包含一个 XML intent-filter元素,这在本章前面已经简要讨论过了。清单 6–10 的最后一部分指定了该应用所需的最低 Android 版本号,如下所示:
<uses-sdk android:minSdkVersion="9" />
在我们当前的例子中,最低版本是 9,这也是我们在这个 Android 应用的创建步骤中指定的数字。
现在让我们看看清单 6–17,它显示了 XML 文件strings.xml的内容。
清单 6–17。*strings.xml*的内容
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, HelloWorld!/string> <string name="app_name">HelloWorld</string> </resources>
清单 6–17 很简单:它包含一个 XML resources元素和两个 XML 子元素,用于显示字符串“HelloWorld,Hello World!”当你启动这个安卓应用。注意,XML 文档AndroidManifest.xml中的 XML application元素还引用了第二个 XML string元素,其 name 属性的值为app_name,如下所示:
<application android:icon="@drawable/icon" android:label="@string/app_name">
现在让我们看一下清单 6–18,它显示了 XML 文档main.xml的内容,该文档包含关于这个 Android 应用的View相关信息。
清单 6–18。*main.xml*的内容
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
清单 6–18 包含一个 XML LinearLayout元素,这是 Android 应用的默认布局。Android 支持其他布局类型,包括AbsoluteLayout、FrameLayout、RelativeLayout和TableLayout(本章不讨论)。
XML LinearLayout元素包含一个fill_parent属性,该属性指示当前元素将与父元素一样大(减去填充)。属性layout_width和layout_height指定了View的宽度和高度的基本值。
XML TextView元素包含属性layout_width和layout_height,它们的值分别是fill_parent和wrap_content。wrap_content属性指定View的大小刚好足够包含它的内容(加上填充)。属性text是指在 strings.xml文件(位于res/values子目录中)中指定的 XML hello元素,其定义如下所示:
<string name="hello">Hello World, HelloWorld!</string>
字符串“HelloWorld,Hello World!”是在部署此 Android 应用后,在 Android 模拟器或 Android 设备中启动“Hello World”Android 应用时显示的文本。
在 Android 应用中发送通知
正如你已经知道的,Adobe 不提供通知的内置支持,这在原生 Android 移动应用中是可用的。然而,本节中的示例将向您展示如何创建一个 Adobe AIR mobile 应用和一个原生 Android 应用,它们可以合并成一个单独的.apk文件,该文件将支持与Notification相关的功能。
詹姆斯·沃德(他为这本书写了前言)在这一部分贡献了基于套接字的代码,Elad Elrom 提供了将 Adobe AIR mobile 应用与原生 Android 应用合并的分步说明。
这一节很长,因为有一个初始设置序列(涉及六个步骤),两个 Adobe AIR 源文件,以及这个移动应用的 Android 源文件。第一部分描述了设置序列;本节的第二部分讨论了带有 Adobe AIR 代码的两个源文件;第三部分讨论了包含原生 Android 代码的两个源文件。
- 下载一个包,其中包含为 Android 扩展 AIR 所需的依赖项:
www.jamesward.com/downloads/extending_air_for_android-flex_4_5-air_2_6–v_1.zip - 在 Eclipse 中创建一个常规的 Android 项目(暂时不要创建
Activity):指定 FooAndroid 的“项目名称:”,为“目标名称:”选择 Android 2.2,为“应用名称:”键入“FooAndroid”,为“包名称:”输入“com.proandroidflash”,为“最小 SDK 版本:”键入“8”,然后单击 Finish 按钮。 - 将步骤 1 中下载的 zip 文件中的所有文件复制到新创建的 Android 项目的根目录中。您需要覆盖现有的文件,Eclipse 将提示您更新启动配置。
- 删除
res/layout目录。 - 通过右键单击文件将
airbootstrap.jar文件添加到项目的构建路径,然后选择 build path 和 Add to Build Path。 - 启动项目,确认在 Android 设备上看到“Hello,world”。如果是这样,那么 AIR 应用被正确引导,并且
assets/app.swf中的 Flex 应用被正确运行。
现在我们已经完成了初始设置步骤,让我们使用移动应用模板创建一个名为 Foo 的新 Flex 移动项目,并添加如清单 6–19 所示的代码。
清单 6–19。 接收数据并将数据发送给 Android 上的通知
` <s:View xmlns:fx="ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="HomeView" creationComplete="start()">
<fx:Script source="SQLiteAccess.as"/>
<s:Button x="10" y="10" width="200" height="50" label="Get Statuses" click="invokeMontastic()"> </s:Button>
<s:Button x="250" y="10" width="200" height="50" label="Clear History" click="removeAll()"> </s:Button>
<s:DataGrid id="dg" left="10" right="10" top="70" bottom="100" dataProvider="{dp}"> <s:columns> <s:ArrayList> <s:GridColumn headerText="ID" dataField="id" width="60" /> <s:GridColumn headerText="Status" dataField="status" width="100" /> <s:GridColumn headerText="URL" dataField="url" width="300" /> </s:ArrayList> </s:columns> </s:DataGrid>
<s:Button label="Create Notification" x="10" y="650" width="300" height="50">
<s:click>
>![CDATA[
var s:Socket = new Socket();
s.connect("localhost", 12345); s.addEventListener(Event.CONNECT, function(event:Event):void {
trace('Client successfully connected to server');
(event.currentTarget as Socket).writeInt(1);
(event.currentTarget as Socket).writeUTF(allStatuses);
(event.currentTarget as Socket).flush();
(event.currentTarget as Socket).close();
});
s.addEventListener(IOErrorEvent.IO_ERROR, function(event:IOErrorEvent):void {
trace('error sending allStatuses from client: ' + event.errorID);
});
s.addEventListener(ProgressEvent.SOCKET_DATA,
function(event:ProgressEvent):void {
trace('allStatuses sent successfully');
});
]]>
</s:click>
</s:Button>
</s:View>`
清单 6–19 包含一个 XML 按钮,它调用 Montastic APIs 来检索在 Montastic 中注册的网站的状态。当用户单击此按钮时,网站的状态存储在 SQLite 数据库中,并且数据网格用新的一组行刷新。
第二个 XML 按钮使用户能够删除 SQLite 表中的所有行,这很方便,因为该表的大小可以快速增加。如果您想维护这个表的所有行,您可能应该为 datagrid 提供滚动功能。
当用户单击第三个 XML 按钮元素时,这将在端口 12345 上启动一个基于客户端套接字的连接,以便将网站的最新状态发送到运行在本地 Android 应用中的服务器端套接字。Android 应用读取客户端发送的信息,然后在 Android 通知栏中显示状态。
清单 6–20 中的 ActionScript3 代码类似于清单 6–8,因此您将能够快速阅读其内容,尽管对代码进行了各种特定于应用的更改。
清单 6–20。 接收数据并将数据发送给 Android 上的通知
`Import flash.data.SQLConnection; import flash.data.SQLStatement; import flash.events.Event; import flash.events.IOErrorEvent; import flash.errors.SQLErrorEvent; import flash.events.SQLEvent; import flash.events.TimerEvent; import flash.filesystem.File; import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLRequestHeader; import flash.net.URLRequestMethod; import flash.utils.Timer;
import mx.collections.ArrayCollection; import mx.utils.Base64Encoder;
//Montastic URL private static const montasticURL:String = "www.montastic.com/checkpoints…";
// sqlconn holds the database connection
public var sqlconn:SQLConnection = new SQLConnection(); // sqlstmt is a SQLStatement that holds SQL commands
public var sqlstmt:SQLStatement = new SQLStatement();
// a bindable ArrayCollection and the data provider for the datagrid [Bindable] public var dp:ArrayCollection = new ArrayCollection(); [Bindable] public var allStatuses:String = "1:UP#2:UP#3:UP";
private var urlList:Array = new Array(); private var statusList:Array = new Array();`
清单 6–20 包含各种导入语句,后面是用于打开数据库连接和执行 SQL 语句的变量。Bindable变量提供对数据库表内容的访问,以及在 Montastic 注册的网站的 URL 和状态。
变量checkpointsXMLList包含您已经向 Montastic 注册的网站的“实时”数据。
`// invoked after the application has loaded private function start():void { // set 'montastic.db' as the file for our database (created after it's opened) var db:File = File.applicationStorageDirectory.resolvePath("montastic.db");
// open the database in asynchronous mode sqlconn.openAsync(db);
// event listeners for handling sql errors and 'result' are // invoked whenever data is retrieved from the database sqlconn.addEventListener(SQLEvent.OPEN, db_opened); sqlconn.addEventListener(SQLErrorEvent.ERROR, error); sqlstmt.addEventListener(SQLErrorEvent.ERROR, error); sqlstmt.addEventListener(SQLEvent.RESULT, result); }
private function db_opened(e:SQLEvent):void { // specify the connection for the SQL statement sqlstmt.sqlConnection = sqlconn;
// Table "montastic_table" contains three columns: // 1) id (an autoincrementing integer) // 2) url (the url of each web site) // 3) status (the status of each web site) sqlstmt.text = "CREATE TABLE IF NOT EXISTS montastic_table ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT, status TEXT);";
// execute the sqlstmt to update the database sqlstmt.execute();
// refresh the datagrid to display all data rows refreshDataGrid(); }`
方法start()和db_opened()类似于本章前面的例子,除了数据库名称现在是montastic.db,并且当用户在移动应用中点击相关的Button时,数据库表montastic_table被更新。注意,montastic_table包含列id、url和status,而不是列id、first_name和last_name。
`// function to append new rows to montastic table // use a begin/commit block to insert multiple rows private function addWebsiteInfo():void { allStatuses = ""; sqlconn.begin();
for (var i:uint = 0; i < urlList.length; i++) { var stmt:SQLStatement = new SQLStatement(); stmt.sqlConnection = sqlconn;
stmt.text = "INSERT INTO montastic_table (url, status) VALUES(:url, :status);"; stmt.parameters[":url"] = urlList[i]; stmt.parameters[":status"] = statusList[i];
stmt.execute(); }
// insert the rows into the database table sqlconn.commit();
refreshDataGrid(); }
// refresh the Montastic data in the datagrid private function refreshDataGrid(e:TimerEvent = null):void { // timer object pauses and then attempts to execute again var timer:Timer = new Timer(100,1); timer.addEventListener(TimerEvent.TIMER, refreshDataGrid);
if (!sqlstmt.executing) { sqlstmt.text = "SELECT * FROM montastic_table" sqlstmt.execute(); } else { timer.start(); } }
// invoked when we receive data from a sql command //this method is also called for sql statements to insert items // and to create our table but in this case sqlstmt.getResult().data // is null private function result(e:SQLEvent):void { var data:Array = sqlstmt.getResult().data;
// fill the datagrid with the latest data dp = new ArrayCollection(data); }
// remove all rows from the table private function removeAll():void { sqlstmt.text = "DELETE FROM montastic_table"; sqlstmt.execute();
refreshDataGrid(); }`
方法addWebsiteInfo()是addPerson()方法的“对应物”,在 begin/end 块中执行数据库插入,以便在一个 SQL 语句中执行多行插入。这种技术使我们能够使用与两种方法refreshDataGrid()和result()相同的逻辑,从数据库中检索最新的数据,而不会出现争用错误。
请注意,我们现在有一个从数据库表中删除所有行的方法removeAll(),而不是从数据网格中删除选定行的方法remove()。
`// functions for Montastic public function invokeMontastic():void { var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, completeHandler); loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
var request:URLRequest = new URLRequest( montasticURL ); request.method = URLRequestMethod.GET;
var encoder:Base64Encoder = new Base64Encoder(); encoder.encode("yourname@yahoo.com:insert-your-password-here"); request.requestHeaders.push(new URLRequestHeader("Authorization", "Basic " + encoder.toString())); request.requestHeaders.push(new URLRequestHeader("pragma", "no-cache")); request.requestHeaders.push(new URLRequestHeader("Accept", "application/xml")); request.requestHeaders.push(new URLRequestHeader("Content-Type", "application/xml")); loader.load(request); } private function completeHandler(event:Event):void { var loader:URLLoader = URLLoader(event.target); checkpointsXMLList = new XML(loader.data);
urlList = new Array(); statusList = new Array();
for each (var checkpoint:XML in checkpointsXMLList.checkpoint) { statusList.push(checkpoint.status.toString()); urlList.push(checkpoint.url.toString());
}
allStatuses = "1="+statusList[0]+"#2="+statusList[1];
addWebsiteInfo(); }`
当用户点击相关按钮(其标签为“Get Statuses”)时,方法invokeMontastic()被执行,这又调用 Montastic APIs,这些 API 返回 XML,其中包含关于用户已向 Montastic 注册的网站的状态相关信息。
注意,方法completeHandler()是在对 Montastic 网站的异步请求返回了基于 XML 的数据之后调用的。
变量allStatuses被适当地更新(我们需要将这个字符串发送到服务器套接字),然后方法addWebsiteInfo()被执行,它用我们从 Montastic 接收的数据更新数据库表montastic_table。
private function ioErrorHandler(event:IOErrorEvent):void { trace("IO Error" + event); } private function sqlError(event:SQLErrorEvent):void { trace("SQL Error" + event); }
函数ioErrorHandler()和sqlError()在相关错误发生时被调用,在生产环境中,您可以添加额外的错误消息来提供有用的调试信息。
正如您之前看到的,我们使用了一个硬编码的 XML 字符串,其中包含您在 Montastic 注册的网站的信息样本。目前,您可以通过从命令行调用“curl”程序来检索基于 XML 的网站状态信息,如下所示(作为单行调用):
curl -H 'Accept: application/xml' -H 'Content-type: application/xml' -u yourname@yahoo.com:yourpassword https://www.montastic.com/checkpoints/index
既然我们已经讨论了特定于 AIR 的代码,那么让我们把重点放在处理来自客户端的信息的基于套接字的原生 Android 代码上。套接字代码是我们命名为 FooAndroid 的 Android 应用的一部分。
在我们讨论这个应用的 Java 代码之前,让我们看一下清单 6–21,它包含了我们的 Android 应用的文件AndroidManifest.xml。注意清单 6–21 显示了这个配置文件的最终版本,而不是在 Android 项目 FooAndroid 的创建步骤中生成的内容。
清单 6–21。 AndroidManifest.xml为原生安卓应用
` <manifest xmlns:android="schemas.android.com/apk/res/and…" package="com.proandroidflash" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".MainApp"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar"
android:launchMode="singleTask"
android:screenOrientation="nosensor"
android:configChanges="keyboardHidden|orientation"
android:windowSoftInputMode="stateHidden|adjustResize">
`
清单 6–21 指定MainApp.java作为我们的 Android 应用的 Android Activity。正如你将看到的,Java 类MainApp.java(包含一些定制的 Java 代码)扩展了 Android ActivityAppEntry.java,它是 Android Activity类的子类。请注意,清单 6–21 指定了一个名为TestService.java的 Android Service类,它包含基于套接字的定制代码,用于处理从 Adobe AIR 客户端接收的信息。
现在在 Eclipse 中创建一个名为 FooAndroid 的原生 Android 应用,它带有一个 Java 类MainApp,该类扩展了类AppEntry。Java 类AppEntry.java是一个简单的预建 Java Activity,它是 Android Activity类和我们定制的 Java 类MainApp之间的中间类。清单 6–22 显示了 Java 类MainApp的内容。
**清单 6–22。**主安卓Activity级
`package com.proandroidflash;
import android.app.Activity; import android.content.Intent; import android.os.Bundle;
public class MainApp extends AppEntry { /** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
try { Intent srv = new Intent(this, TestService.class); startService(srv); } catch (Exception e) { // service could not be started } } }`
Java 类MainApp(它是 Android Activity类的间接子类)在我们的 Android 应用启动时执行;MainApp 中的onCreate()方法启动我们的定制 Java 类TestService.java(稍后讨论),该类启动服务器端套接字,以便处理来自 Adobe AIR 客户端的数据请求。
如您所见,onCreate()方法调用了安卓Activity类中的startService()方法,以启动TestService Service。这个功能是可能的,因为MainApp是 Android Activity类的子类。
现在在com.proandroidflash包中创建第二个 Java 类TestServiceApp.java,并将代码插入清单 6–23 中。
清单 6–23。 一个 Android Service类,处理来自 AIR 客户端的数据
`package com.proandroidflash;
import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket;
import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.Looper; import android.util.Log;
public class TestService extends Service { private boolean stopped=false; private Thread serverThread; private ServerSocket ss;
@Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate();
Log.d(getClass().getSimpleName(), "onCreate");
serverThread = new Thread(new Runnable() {
public void run() {
try {
Looper.prepare(); ss = new ServerSocket(12345);
ss.setReuseAddress(true);
ss.setPerformancePreferences(100, 100, 1);
while (!stopped) { Socket accept = ss.accept(); accept.setPerformancePreferences(10, 100, 1); accept.setKeepAlive(true);
DataInputStream _in = null;
try { _in = new DataInputStream(new BufferedInputStream( accept.getInputStream(),1024)); } catch (IOException e2) { e2.printStackTrace(); }
int method = _in.readInt();
switch (method) { // send a notification? case 1: doNotification(_in); break; } } } catch (Throwable e) { e.printStackTrace(); Log.e(getClass().getSimpleName(), "** Error in Listener **",e); }
try { ss.close(); } catch (IOException e) { Log.e(getClass().getSimpleName(), "Could not close serversocket"); } } },"Server thread");
serverThread.start(); }`
FooAndroid 的初始部分包含各种导入语句、私有套接字相关变量和onBind()方法。这个方法可以用于 Android Service类支持的其他功能(这超出了本例的范围),对于我们的目的,这个方法只是返回 null。
清单 6–23 的下一部分包含一个冗长的onCreate()方法,它启动一个服务器端套接字来处理 Adobe AIR 客户端请求。
onCreate()方法启动一个 Java Thread,其run()方法在端口 12345(与客户端套接字是同一个端口)上启动一个服务器端套接字。onCreate()方法包含一个while循环,它等待客户端请求,然后在try/catch块中处理它们。
如果客户端请求中的第一个字符是数字“1”,那么我们知道客户端请求来自我们的 AIR 应用,并且try/catch块中的代码调用方法doNotification()。如果有必要,您可以增强onCreate()(即处理其他数字、文本字符串等的出现),以便服务器端代码可以处理其他客户端请求。
` private void doNotification(DataInputStream in) throws IOException { String id = in.readUTF(); displayNotification(id); } public void displayNotification(String notificationString) { int icon = R.drawable.mp_warning_32x32_n;
CharSequence tickerText = notificationString; long when = System.currentTimeMillis(); Context context = getApplicationContext(); CharSequence contentTitle = notificationString; CharSequence contentText = "Hello World!";
Intent notificationIntent = new Intent(this, MainApp.class); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); Notification notification = new Notification(icon, tickerText, when); notification.vibrate = new long[] {0,100,200,300};
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
mNotificationManager.notify(1, notification); } @Override public void onDestroy() { stopped = true; try { ss.close(); } catch (IOException e) {}
serverThread.interrupt();
try { serverThread.join(); } catch (InterruptedException e) {} } }`
doNotification()方法只是读取输入流中的下一个字符串(由客户端发送),然后调用方法displayNotification()。在我们的例子中,这个字符串是一个串联的字符串,包含每个注册网站的状态(“UP”或“DOWN”)。
您可能已经猜到了,displayNotification()方法包含了在 Android 通知栏中显示通知的 Android 代码。需要注意的关键点是,这个方法用 Java 类MainApp.java创建了一个 Android Intent。新的Intent使我们能够创建一个 Android PendingIntent,这反过来又允许我们创建一个 Android Notification实例。displayNotification()中的最后一行代码启动我们的通知,它在 Android 通知栏中显示注册网站的状态。
代码的最后一部分是onDestroy()方法,它停止在onCreate()方法上启动的服务器端套接字。
现在我们已经完成了这个应用的 Java 代码,我们需要处理这个应用的 XML 相关文件。首先,确保应用中的AndroidManifest.xml内容与清单 6–21 中的内容相同。其次,在 XML 文件strings.xml中包含以下字符串:
<string name="button_yes">Yes</string> <string name="button_no">No</string> <string name="dialog_title"><b>Adobe AIR</b></string> <string name="dialog_text">This application requires that you first install Adobe AIR®.\n\nDownload it free from Android Market now?</string>
现在我们已经完成了所有的 Adobe AIR 代码和原生 Android 代码,我们准备将文件合并到一个移动应用中,这样做的步骤将在本章的下一节中显示。
您可以在 Figure 6–6 中看到本节中调用示例应用的示例,该示例显示了一组由 URL 及其状态组成的记录,这些记录存储在移动设备上的 SQLite 数据库中。
图 6–6。 一组注册网站的状态记录
Adobe AIR 和原生 Android 集成
本节包含将原生 Android 功能(如本章中的示例)集成到 Adobe AIR mobile 应用的过程。请注意,Adobe 不支持完成此操作的过程,实际步骤可能会在本书出版时发生变化。该信息由 Elad Elrom 提供。
清单 6–24 中的命令使用实用程序adt、apktool和adb将 Adobe AIR 应用MyAIRApp的内容与原生 Android 应用AndroidNative.apk的内容合并,以创建 Adobe AIR 移动应用MergedAIRApp.apk。
清单 6–24 显示了您需要调用的实际命令,以便创建一个新的.apk文件,该文件包含来自 Adobe AIR 移动应用和原生 Android 移动应用的代码。确保您更新了变量APP_HOME的值,以便它反映您的环境的正确值。
清单 6–24。 使用 Adobe AIR 和原生 Android 代码创建合并的应用
APP_HOME="/users/ocampesato/AdobeFlashBuilder/MyAIRApp" cd $APP_HOME/bin-debug adt -package -target apk -storetype pkcs12 -keystore certificate.p12 -storepass Nyc1982 out.apk MyAIRApp-app.xml MyAIRApp.swf apktool d -r out.apk air_apk apktool d -r AndroidNative.apk native_apk mkdir native_apk/assets cp -r air_apk/assets/* native_apk/assets cp air_apk/smali/app/AIRApp/AppEntry*.smali native_apk/smali/app/AIRApp apktool b native_apk cd native_apk/dist jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android out.apk androiddebugkey zipalign -v 4 out.apk out-new.apk cd ../../ cp native_apk/dist/out-new.apk MergedAIRApp.apk rm -r native_apk rm -r air_apk rm out.apk adb uninstall app.AIRApp adb install -r MergedAIRApp.apk
如果您熟悉 Linux 或 Unix,清单 6–24 中的命令很简单。如果您更喜欢在 Windows 环境中工作,您可以通过进行以下更改,将清单 6–24 中的命令转换为一组相应的 DOS 命令:
- 定义
APP_HOME变量时使用“set”。 - 使用 DOS 风格的变量(例如:
%abc%而不是$abc)。 - 用
copy替换cp,用erase替换rm。 - 用反斜杠(" \ ")替换正斜杠("/")。
要记住一个重要的细节:您必须获得正确的自签名证书(在清单 6–24 中称为certificate.p12),以使前面的合并过程正确工作。您可以为基于 Flex 的应用生成证书,如下所示:
- 在 FlashBuilder 中选择您的项目。
- 单击导出发布版本。
- 指定“导出到文件夹:”的位置(或单击“下一步”)。
- 点击“创建:”按钮。
- 为必填字段提供值。
- 在“另存为:”输入栏中指定一个值。
- 单击确定按钮。
- 单击“记住此会话的密码”(可选)。
- 单击“完成”按钮。
生成自签名证书后,将该证书复制到您执行清单 6–24 中显示的 shell 脚本命令的目录中,如果您做的一切都正确,您将生成一个可以部署到基于 Android 的移动设备的合并应用。
本节总结了创建 Adobe AIR 应用并将其与原生 Android 应用合并的多步过程。正如您所看到的,这个集成过程不是微不足道的,也可以说是非直观的,因此在这个过程中您一定会遇到困难(当您遇到困难时不要气馁)。需要记住的是,为 Adobe AIR 应用添加原生 Android 支持可能会使您的应用有别于市场上的类似应用。
总结
在本章中,您学习了如何启动本机浏览器、访问数据库以及将 AIR mobile 应用与本机 Android 代码相结合。更具体地说,您了解了以下内容:
- 从包含网站实际 HTML 代码的字符串启动本机 web 浏览器
- 允许用户指定 URL,然后在 AIR 应用中启动该 URL
- 使用 SQLite 数据库创建、更新和删除特定于用户的数据,以及如何自动刷新更新数据的显示
- 在 Eclipse 中创建原生 Android 应用
- 将外部 API、SQLite 数据库和原生 Android 通知的功能集成到一个移动应用中
将 Adobe AIR 应用与原生 Android 应用合并的特定步骤序列。
1**