原文:Pro Android Flash
协议:CC BY-NC-SA 4.0
零、简介
我们写这本书的目的是向每一个开发者打开移动和设备开发的奇妙世界。您不再需要学习定制的移动编程语言或成为移动应用设计专家来编写好看、专业的业务应用。我们相信,在未来,手机和平板电脑将只是应用开发人员的另一个部署目标,借助移动闪存和 Fle×技术,这一未来就在眼前。
对于刚接触 Flash 的人
这本书以对 Flash 工具链和底层技术的温和介绍开始,并通过例子教授编程概念。如果你有另一种基于 C 语言的经验,如 Java、JavaScript 或 Objective-C,这本书的进度将允许你在学习 Flash 和 Fle× mobile 概念和 API 的同时学习 ActionScript 和 MXML。
对于 Android 新手来说
Flash 和 Fle×平台利用了 Android 的所有强大功能,同时使程序员不必处理 Android APIs 和编程模型的复杂性。这意味着,只要终端用户对 Android 有简单的了解,你就可以成为一名应用开发人员,向 Android Market 发布你自己的基于 Flash 的应用。
对于我们所有的摇滚明星开发者来说
让我们面对现实吧——你拿起这本书不仅仅是为了成为另一个移动开发者。你想要拓展平台的极限,挖掘超越平均水平的特性和能力,并构建出酷毙了的应用。
我们与你同在,这就是为什么我们在开发这本书时将技术推向极限。在本书后面的章节中,您将学习如何利用原生 Android 功能,配置和调整您的应用以获得最佳性能,以及部署到除简单手机之外的各种不同设备。
你的团队写的
我们不是那些以写书为生的普通无名作者。我们是应用开发人员和技术极客,就像您一样。我们投资于我们讨论的技术、移动开发的未来,以及最重要的,您作为未来 Flash 平台开发人员的成功。
所有作者都在网上有大量的社区参与,包括领先的 Adobe 用户组和技术宣传。我们对这项技术感到兴奋,并乐于接受问题、询问和对话。我们不仅仅是另一个作者团队,而是您自己的个人 Flash 开发团队。
通过阅读这本书和编写练习,你会学到很多东西,但不要止步于此。开始与其他读者和 Flash 开发者对话。加入专门研究 Flash 和 Fle×技术的技术用户组。带着问题、想法、概念和猜想联系我们,作者。
最重要的是,让技术成为你自己的。
一、移动 Flash 简介
这本书,Pro Android Flash,是使用无处不在的 Flash 平台在移动设备上构建丰富、普及的用户体验的权威指南。我们将向您展示如何利用构成 Flash 平台的强大而成熟的技术、框架和工具来构建高度定制的应用,这些应用充分利用了用户要求其设备具备的所有移动功能。在阅读本书时,您将获得针对移动 Android 设备的基本知识,包括设备密度、硬件输入、本机集成和性能优化。
为什么选择安卓?
有许多不同的移动平台可供选择,也有大量的移动和平板设备可供消费者选择。与桌面市场不同,桌面市场已经有了大量的整合和巩固,移动市场也在不断发展,不断推出新的设备和功能。
显而易见的问题是,你的目标平台是哪个?我们的答案是从安卓开始;然后,通过利用闪存技术,您可以避免受限于任何特定的平台。
这本书着重于在运行 Android 操作系统的设备上创建应用。这是因为 Android 正在迅速成为世界上最受欢迎的移动操作系统,对不同硬件平台和多种外形的支持最好。
根据尼尔森公司的数据,Android 是 2010 年下半年购买智能手机的人的首选。黑莓 RIM 和苹果 iOS 在统计上处于第二位,如图图 1–1 所示。
图 1–1。 美国移动 OS 流量份额 1
这可能是由于许多不同的因素,包括平台是开源的这一事实,这吸引了设备制造商,Android Market、谷歌的设备内应用店面或谷歌体验提供的相对自由,谷歌体验为最终用户提供了 Gmail、谷歌地图、Gtalk、YouTube 和谷歌搜索的无缝集成。不管 Android 流行的原因是什么,很可能你的大部分客户已经拥有 Android 设备,或者正在考虑在不久的将来购买一台。
与此同时,您正在构建一个具有巨大横向增长潜力的平台。Android 只是 Flash 平台的开始,它受益于一个抽象的虚拟机和 API,这些 API 旨在跨多个不同的操作系统和设备工作。您可以利用 Flash 为所有移动应用带来的跨平台透明性。
其他平台上的 Flash
Adobe 启动了 Open Screen Project, 2 ,这是一项全行业的计划,旨在将 Flash 驱动的应用的优势带到您生活中的所有屏幕上。Adobe 已经宣布了支持 iOS、黑莓、Windows 7 和 webOS 的计划,将你从平台锁定中解放出来。
黑莓支持最初针对其平板电脑操作系统,第一个可用的设备是黑莓 PlayBook。预计这种支持将来会扩展到包括它的其他移动设备。
1 资料来源:尼尔森公司,[
nielsen.com/](http://nielsen.com/)
,2010 年
2 Adobe,“开屏项目”,[
www.openscreenproject.org/](http://www.openscreenproject.org/)
苹果仍然限制在浏览器中运行 Flash,但它已经开放了应用商店,允许第三方框架。这意味着对于 iOS 设备,您可以在任何 iOS 设备上部署 Flash 作为 AIR 应用,包括 iPod touch、iPhone 和 iPad。
您还可以在浏览器中支持 Flash 的任何设备上部署 Flash web 应用。这包括谷歌电视、webOS 和 Windows 7。未来,我们有望看到更多支持闪存技术的平台。
探索安卓系统
Android 是一个完整的移动堆栈,包括操作系统、服务和基础设施,以及一组核心应用。虽然您不需要成为 Android 方面的专家来有效地编写 Flash 应用并将其部署到 Android 设备上,但是熟悉 Android 的工作方式确实会有所帮助。
Android 的核心是基于 Linux 操作系统。它使用 Linux 内核的修改版本,该版本具有额外的驱动程序并支持移动硬件设备。
在此之上,有一组库和核心服务组成了基本的 Android 功能。你很少会直接与这些库进行交互,但是每当你播放一个媒体文件,浏览一个网页,甚至在屏幕上绘图,你都在经历一个核心的 Android 库。
原生 Android 应用是使用编译成 Dalvik 字节码的 Java 编程语言编写的。Dalvik 是 Android 特殊虚拟机的名称,它抽象了硬件并支持垃圾收集等高级功能。您运行的所有 Android 应用(包括 Adobe AIR 应用)都在 Dalvik 虚拟机中执行。
完整的 Android 系统架构,按 Linux 内核、库和运行时、应用框架和应用细分,如图 1–2 所示。
图 1–2。 安卓系统架构 3
除了拥有非常坚实的技术基础,Android 还在不断发展,以适应新的硬件进步。Android 平台的一些当前功能包括:
- 移动浏览器(Mobile browser):WebKit,一个现代的框架,支持 HTML5 提出的所有扩展,并支持 Android 的内置浏览器
- Flash player :从 Android 2.2 开始,你可以在网络浏览器中运行 Flash 内容,这是一项标准功能。
- 多点触摸:所有安卓手机都支持触摸屏,大多数都有至少两个触摸点,你可以用它们来进行手势识别。
- 摄像头:安卓手机被要求配备后置摄像头,现在许多手机也配备了前置摄像头。
- GPS,指南针:所有安卓手机都要求有一个三向 GPS 和指南针,可用于导航应用。
- 多任务处理 : Android 是第一个将应用切换和后台操作暴露给已安装应用的移动操作系统。
- GSM 电话技术:作为电话使用的 Android 设备为您提供了 GSM 电话技术的全部功能。
- 蓝牙、Wi-Fi 和 USB :所有 Android 设备都配有蓝牙和 Wi-Fi 连接,以及一个标准 USB 端口,用于数据传输和调试。
- 音频和视频支持 : Android 支持播放网络上最常见的音频和视频格式,包括 MP3、Ogg 和 H.264。
3 转载自 Android 开源项目创建和共享的作品,并根据知识共享 2.5 归属许可中描述的条款使用:谷歌,“什么是 Android?”,[
developer.android.com/guide/basics/what-is-android.html](http://developer.android.com/guide/basics/what-is-android.html)
,2011 年
这些功能使得 Android 平台成为构建移动应用的一个非常强大的基础。此外,Adobe Flash 和 AIR 构建在这些基础功能之上,使 Flash 成为开发 Android 应用的绝佳平台。
Flash 平台
Adobe Flash Platform 是一个完整的系统,集成了运行在不同操作系统、浏览器和设备上的工具、框架、服务器、服务和客户端。许多行业的公司都使用 Flash Platform 来消除设备和平台碎片,并开发出一致且富于表现力的交互式用户体验,不受设备限制。让我们来看看 Flash 平台的运行时和工具。
Flash 运行时
创建 Flash 应用时,您可以选择两个不同的部署目标。第一个是 Adobe Flash Player,这是一个嵌入式浏览器插件,第二个是 Adobe AIR,这是一个独立的客户端运行时。这两个选项都可以在桌面和移动设备上使用,并为您提供了很大的灵活性来定制您的应用部署,以满足最终用户的需求。
Adobe Flash 播放器
据 Adobe 称,Flash Player 安装在 98%的联网电脑和超过 4.5 亿台设备上, 4 为运行在客户端的应用提供了最广泛的应用。2011 年,Adobe 预计将有超过 1.32 亿部智能手机支持 Flash Player,并且已经有超过 2000 万部智能手机预装了 Flash Player。预计 2011 年还会有另外 50 款新的平板设备支持 Flash Player。
Adobe Flash Player 在浏览器中的安全容器中运行。这允许您将 Flash 内容与用 HTML 和 JavaScript 编写的其他 web 内容混合在一起。您还可以获得免安装操作的优势。
4 资料来源:Adobe,“富互联网应用的好处”,www.adobe.com/resources/business/rich_internet_apps/benefits/#
,2009 年
Adobe AIR
目前为 Flash Player 发布内容的设计人员和开发人员也可以重新利用相同的内容来为 Adobe AIR 运行时制作应用。在撰写本文时,有 8400 万部智能手机和平板电脑可以运行 Adobe AIR 应用,Adobe 预计到 2011 年底将有超过 2 亿部智能手机和平板电脑支持 Adobe AIR 应用。
Adobe AIR 将 Flash 扩展到浏览器之外,允许您的内容从 Android Market 下载并作为一流的应用安装。此外,Adobe AIR 应用可以请求用户的许可,以访问受限的硬件,如照相机、麦克风和文件系统。
Table 1–1 总结了在 Flash Player 中部署或作为 Adobe AIR mobile 应用部署的优势。由于 AIR 是 Flash APIs 的适当超集,因此也可以创建部署在两者下的单个应用。
Adobe Flex
Flex 是一个开源软件开发工具包,专门用于在 Flash 平台上构建专业的业务应用。它包括一些额外的库,可以快速方便地构建带有布局、控件和图表的用户界面。此外,大多数 Flex UIs 都是用一种叫做 MXML 的 XML 方言以声明方式编写的,这使得构建嵌套的用户界面布局比直接的 ActionScript 更容易。
Adobe 非常积极地向 Flex 框架添加移动功能,如视图、触摸支持和移动优化皮肤。在本书中,我们将利用 Adobe Flex 技术来演示移动 API。同时,我们将演示纯 ActionScript APIs 的使用,如果您正在构建一个不包含 Flex SDK 的应用,则可以使用这些 API。
闪光工具
自从 Creative Suite 5.5 (CS5.5)发布以来,所有用于 Flash 和 Flex 开发的 Adobe 工具也支持移动开发。
Table 1–2 列出了 Adobe 提供的工具,您可以使用这些工具通过 Flash 和 Flex 开发移动应用。它们之间的互操作非常紧密,这使得利用每种工具的优势变得很容易。这扩展到 Adobe 设计工具,如 InDesign、Photoshop、Illustrator 和 Fireworks,它们可用于为您的应用开发内容,这些内容将直接插入到您的 Flash 和 Flex 应用中。
Adobe Flash 专业版
Adobe Flash Professional 为设计人员和开发人员提供了一套绘图工具、时间线以及添加交互性的能力,从而为多个平台创建丰富的交互式体验。它实际上起源于一个动画工具。当然,这意味着它的核心非常适合处理动画和图形。但是,从它卑微的开始,它已经成长为一个成熟的程序,能够创建丰富的身临其境的体验,并具有用 ActionScript 编写的高级交互性,可以发布到多个平台。
如果您是 Flash 开发的新手,Flash Professional 是一个很好的起点。它提供了一个可用于构建内容的图形电影和时间轴编辑器,以及一个非常实用的 ActionScript 编辑器,该编辑器具有代码模板、API 帮助和代码完成等高级功能。
需单独购买
Adobe Flash Builder 软件旨在帮助开发人员快速开发适用于 Flash 平台的跨平台富互联网应用和游戏。用户可以通过编写 ActionScript 代码来创建游戏,就像使用 Flash Professional 一样。借助 Flash Builder,您还可以使用 Flex 框架编写应用,Flex 框架是一个用于开发和部署富互联网应用(RIA)的免费、高效的开源框架。
如果您正在开发一个具有复杂 UI 和复杂算法或业务逻辑的大型应用,您肯定会希望添加 Flash Builder 4.5。它基于全功能的 Eclipse IDE,提供了您期望从专业开发环境中获得的一切,包括代码导航、键盘加速器和完整的 GUI 生成器。
设备中心
Device Central 是 Flash Professional 附带的补充应用,允许您在桌面上模拟不同的移动设备,包括对倾斜、多点触摸和加速度计的支持。它还让您可以访问一个巨大的信息库,其中列出了 Flash 平台支持的所有可用的移动和嵌入式设备,包括完整的规格和自定义仿真器。
**注意:**截至本文撰写时,Device Central 尚未更新到 AIR 2.6 以支持 Android 设备。
土坯闪光催化剂
Flash Catalyst 是 Adobe 的快速应用开发平台。它允许您将 Photoshop、Illustrator 或 Flash 中制作的艺术资源转化为一流的 UI 控件。Catalyst 的移动工作流是创建或修改包含您的组件和素材的 FXP 文件,然后在 Flash Builder 中打开它,添加业务逻辑并在移动平台上运行。
所有这些应用都可以免费试用;但是,如果您想使用纯开源堆栈进行开发,您可以使用 Flex SDK 直接从命令行进行 Flex 和 ActionScript 开发。作为 Flash Builder 和 Catalyst 基础的所有组件都是 Flex SDK 的一部分,可以通过编程方式访问。如果您正在配置一个自动构建来编译和测试您的 Flex 应用,这也是您想要使用的。
工作流程
除了已经列出的工具,Adobe 还有一个强大的工作流程,允许设计人员使用 Adobe InDesign、Adobe Photoshop、Adobe Illustrator 和 Adobe Fireworks 等程序将图形移动到 Flash Professional 或 Flash Builder 中进行进一步开发,如图 1–3 所示。这意味着在处理图形时很少出现转换问题,也不存在将图形从设计转移到开发的漫长过程。
图 1–3。 从设计到开发再到发布到多个平台/设备的 Flash 工作流程
我们将在第九章中更详细地讨论设计人员/开发人员的工作流程,给出如何在不同工具之间简化工作流程的真实例子。
从 Flash Professional 运行应用
开始编写 Flash 应用的最简单方法是使用 Adobe Flash Professional。它为构建简单的电影提供了一个可视化的环境,也为构建更复杂的逻辑提供了良好的 ActionScript 编辑功能。
创建新的 Android 项目
要为 Android 项目创建一个新的 AIR,从文件 New… 打开新项目对话框,点击模板选项卡。在这里你可以选择一个 AIR for Android 项目,并选择你的设备模板,如图 Figure 1–4 所示。
图 1–4。 Flash Professional 新建模板对话框
这将创建一个新项目,画布的大小完全适合纵向模式下的移动项目,并且允许您在 Flash Professional 中或通过 USB 在设备上测试您的应用。有关设备部署的更多信息,请参阅第五章“应用部署和发布”。
编写 Flash 功能报告
为了演示设备功能,我们将创建一个名为 Flash Capability Reporter 的简单应用。它将会有一个简单的滚动列表,列举出你正在运行的模拟器或设备的所有功能。
对于 ActionScript 代码,我们将使用来自Capabilities
和Multitouch
类的静态常量。其中大多数返回 true 或false
,但有些会返回string
或integer
值。通过使用字符串连接操作符,我们可以很容易地对它们进行显示格式化,如清单 1–1 所示。
清单 1–1。 Flash 能力校验码
`import flash.system.Capabilities;
import flash.ui.Multitouch;
capabilityScroller.capabilities.text =
“AV Hardware Disable: ” + Capabilities.avHardwareDisable + “
” +
“Has Accessibility: ” + Capabilities.hasAccessibility + “
” +
“Has Audio: ” + Capabilities.hasAudio + “
” +
“Has Audio Encoder: ” + Capabilities.hasAudioEncoder + “
” +
“Has Embedded Video: ” + Capabilities.hasEmbeddedVideo + “
” +
“Has MP3: ” + Capabilities.hasMP3 + “
” +
“Has Printing: ” + Capabilities.hasPrinting + “
” +
“Has Screen Broadcast: ” + Capabilities.hasScreenBroadcast + “
” +
“Has Screen Playback: ” + Capabilities.hasScreenPlayback + “
” +
“Has Streaming Audio: ” + Capabilities.hasStreamingAudio + “
” +
“Has Video Encoder: ” + Capabilities.hasVideoEncoder + “
” +
“Is Debugger: ” + Capabilities.isDebugger + “
” +
“Language: ” + Capabilities.language + “
” +
“Local File Read Disable: ” + Capabilities.localFileReadDisable + “
” +
“Manufacturer: ” + Capabilities.manufacturer + “
” +
“OS: ” + Capabilities.os + “
” +
“Pixel Aspect Ratio: ” + Capabilities.pixelAspectRatio + “
” +
“Player Type: ” + Capabilities.playerType + “
” +
“Screen Color: ” + Capabilities.screenColor + “
” +
“Screen DPI: ” + Capabilities.screenDPI + “
” +
“Screen Resolution: ” + Capabilities.screenResolutionX + “x”
+ Capabilities.screenResolutionY + “
” +
“Touch Screen Type: ” + Capabilities.touchscreenType + “
” +
“Version: ” + Capabilities.version + “
” +
“Supports Gesture Events: ” + Multitouch.supportsGestureEvents + “
” +
“Supports Touch Events: ” + Multitouch.supportsTouchEvents + “
” +
“Input Mode: ” + Multitouch.inputMode + “
” +
“Max Touch Points: ” + Multitouch.maxTouchPoints + “
” +
“Supported Gestures: ” + Multitouch.supportedGestures;`
每行末尾的"
字符增加了换行符以提高可读性。然后将结果字符串分配给在
"capabilityScroller
电影中定义的 ID 为capabilities
的 Flash 文本字段。在 Flash 中使用嵌入的电影通过隐藏文本的滚动动画来清理主时间轴。
虽然这在功能上已经完成,但我们在完成的图书样本中添加了一些额外的图形细节,包括以下内容:
- 一个图形剪辑层:为了使文本在滚动时从图形后面出现,在滚动时消失,我们添加了一个带有纯色背景的附加层,并在文本应该可见的地方剪切了一部分。这代替了使用剪辑蒙版,所以我们可以获得使用设备字体的性能优势。
- 闪烁的灯光:一个简单的动画通过使用 Flash Deco 工具在左侧创建,使用砖块图案填充网格。选择了两种不同的颜色,并选中了“随机顺序”选项,以创建三帧动画上闪烁灯光的视觉外观。
** Android 标志和文字:没有一个 Android 应用是不完整的,没有一点吸引眼球的东西。借助 Android 上可用的全彩色高分辨率显示器,您可以对应用的图形外观做很多事情。在这种情况下,选择矢量图形来平滑缩放到任何大小的设备。*
*要运行完整的示例,请转到 AIR Debug Launcher (Mobile) 中的控制测试影片。这将在 AIR Debug Launcher (ADL)运行时运行应用,如图图 1–5 所示。
图 1–5。 桌面上 ADL 中运行的 Flash Capability Reporter 应用
您可以在自己的开发过程中使用该示例来比较桌面和移动设备的功能。您可以随意添加功能列表,并尝试在不同设备上运行。
您会注意到,即使我们在 ADL 的移动模式下运行,返回的值也与您在设备上运行时得到的值不一致。在本章的后面,我们将向你展示如何在 Android 模拟器中或者通过 USB 在设备上运行你的应用。
从 Flash Builder 运行应用
新版 Flash Builder 为移动设备构建 Flash 和 Flex 应用以及直接从 IDE 运行和调试这些应用提供了强大的支持。在本节中,我们将向您展示如何从头开始创建一个新的移动项目,演示 Flex 移动开发的基础知识,包括视图、控件和多点触摸手势识别。
我们将创建的应用称为手势检查。它允许您分析您的设备,以直观地发现支持哪些手势,并测试它们是否被成功识别。在创建此示例的过程中,您将全面了解 Flash Builder 的移动功能,包括如何创建新的 Flex 移动项目、使用 Flash Builder 调试器调试应用以及通过 USB 部署在设备上运行应用。
创建新的 Flex 移动项目
要创建新的 Flex 移动项目,从文件新建 Flex 移动项目打开新建项目对话框。你会得到一个项目创建向导对话框,允许你输入项目名称,如图图 1–6 所示。
图 1–6。 Flex 移动项目创建向导
将项目命名为 GestureCheck,并选择一个文件夹来存储项目。
**提示:**如果您创建的项目名称中没有空格,Flex 将创建与您选择的名称相匹配的项目文件。如果您的名称包含空格、破折号或其他在 ActionScript 标识符中无效的字符,它将使用通用名称“Main”来代替。
完成后,点击下一步进入向导的移动设置页面,如图 Figure 1–7 所示。
图 1–7。??【移动设置】选项卡用于选择应用模板和设置
Flash Builder 附带了几个用于开发移动项目的内置模板,可用于快速启动新项目。其中包括一个简单的空白应用、一个从主页开始的基于视图的应用,以及一个允许您在不同命名视图之间切换的选项卡式应用。您可以在第三章中找到更多关于视图和选项卡导航的信息。
在本练习中,选择默认的基于视图的基本应用模板。您还可以选择重定向、全屏模式和密度缩放。确保禁用自动重定向,使应用停留在纵向模式。我们将在第二章中更深入地讨论纵向/横向切换。
在移动设置页面上完成后,单击完成创建您的移动应用。
首先,Flex 模板为您提供了以下项目结构(标有internal
的文件您永远不要直接修改):
.actionScriptProperties: [internal]
包含库、平台和应用设置的 Flash Builder 设置文件.flexProperties: [internal]
Flex 服务器设置.project: [internal]
Flex Builder 项目设置.settings: [internal]
Eclipse 设置文件夹bin-debug:
这是执行过程中存储 XML 和 SWF 文件的输出文件夹。libs:
资源库文件夹,您可以在其中添加自己的自定义扩展src:
包含您所有应用代码的源文件夹views:
为存储您的应用视图而创建的包[AppName]HomeView.mxml
:应用的主视图(由主视图Application
引用)
[App-Name]-app.xml:
包含移动设置的应用描述符[AppName].mxml:
项目的主要Application
类和执行的切入点
我们将在本教程的剩余部分关注的文件都在src
目录中。这包括您的应用描述符和主Application
类,它们都在根包中,以及您的HomeView
,它是在名为views
的包中创建的。
编写 Flex 移动配置器
创建应用的第一件事是为 UI 构建一个声明性的 XML 布局。为此,我们将使用 Flex 的一些基本布局和 UI 类,包括:
H/VGroup:``HGroup
和VGroup
类让你在一个简单的垂直或水平堆叠布局中安排一组组件。组件按顺序排列,间距由gap
属性设置。- 显示不可编辑的文本字符串的简单组件;这通常用作表单中另一个控件的标签。
Image:``Image
类让你显示一个可以从GIF, JPEG, PNG, SVG
或SWF
文件中加载的图形。在这个例子中,我们将使用透明的PNGs
。CheckBox:
一种表单控件,其值为选中或未选中,带有可视指示器;它还包括文本描述作为显示的一部分。
使用这些布局和控件,我们可以构建一个简单的用户界面,显示设备上是否启用了特定的多点触摸手势,以及用户是否成功测试了手势。第一个“滑动”手势的代码显示在清单 1–2 中。这个代码应该在视图文件中更新,可以在[src/views/GestureCheckHomeView.mxml](http://src/views/GestureCheckHomeView.mxml)
中找到。
清单 1–2。 第一次手势显示的 UI 元素
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Supported Gestures" initialize="init()"> <s:VGroup paddingTop="15" paddingBottom="15" paddingLeft="20" paddingRight="20" gap="10"> <s:HGroup verticalAlign="middle" gap="20"> <s:Label text="Swipe" fontSize="36" width="110"/> <s:Image source="@Embed('/gestures/swipe.png')" width="137"/> <s:VGroup gap="10"> <s:CheckBox content="Enabled" mouseEnabled="false"/> <s:CheckBoxcontent="Tested" mouseEnabled="false"/> </s:VGroup> </s:HGroup> </s:VGroup> </s:View>
要运行此应用,请进入运行作为移动应用运行。这将调出运行配置对话框,如图 1–8 所示。
图 1–8。 Flash 移动运行配置对话框
首先,我们将使用桌面上的 AIR Debug Launcher (ADL)运行应用。为此,选择桌面启动方法,并选择一个合适的设备进行模拟(对于本例,您将需要选择一个具有高密度显示屏的设备,如 Droid X)。
单击 Run 按钮将在 ADL 中执行应用,向您显示您之前添加的 UI 元素,如图 Figure 1–9 所示。
图 1–9。 手势检查用户界面
这构建了基本的 UI 模式,但是没有连接任何应用逻辑来设置CheckBoxes
的状态。为了实现这一点,我们将使用一个initialize
函数,该函数遍历由Multitouch
类报告的所有supportedGestures
。这显示在清单 1–3 中。
清单 1–3。 检测手势支持和用法的附加代码以粗体突出显示
`<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
title=“Supported Gestures” initialize=“init()”>
fx:Script
<![CDATA[
import flash.ui.Multitouch;
private function init():void {
for each(var gesture:String in Multitouch.supportedGestures) {
this[gesture+“Enabled”].selected = true;
addEventListener(gesture, function(e:GestureEvent):void {
e.currentTarget[e.type+“Tested”].selected = true;
});
}
}
]]>
</fx:Script>
<s:VGroup paddingTop=“15” paddingBottom=“15”
paddingLeft=“20” paddingRight=“20” gap=“10”>
<s:HGroup verticalAlign=“middle” gap=“20”>
<s:Label text=“Swipe” fontSize=“36” width=“110”/>
<s:Image source=“@Embed(‘/gestures/swipe.png’)” width=“137”/>
<s:VGroup gap=“10”>
<s:CheckBox id=“gestureSwipeEnabled” content=“Enabled” mouseEnabled=“false”/>
<s:CheckBox id=“gestureSwipeTested” content=“Tested” mouseEnabled=“false”/>
</s:VGroup>
</s:HGroup>
</s:VGroup>
</s:View>`
注意,我们已经向CheckBoxes
添加了一些 id,以便从initialize
函数中引用它们。命名约定是手势名称附加“启用”或“已测试”字样。在设置selected
状态的代码中使用了相同的命名约定。
当创建视图时,init
函数被调用一次,并遍历所有的supportedGestures
。它将启用的CheckBox
的状态设置为 true,并添加一个事件监听器,当在应用中使用该手势时,该监听器会将测试的CheckBox
的状态设置为 true。如果你想了解更多关于手势和事件监听器的功能,我们会在第二章中详细介绍。
如果您运行更新后的示例,您将获得相同的 UI,但也会触发一个错误。ActionScript 错误对话框显示在 Figure 1–10 中,虽然您可能很清楚程序中的问题是什么,但我们将利用这个机会演示 Flash Builder 调试器是如何工作的。
图 1–10。 更新后的应用执行时 ActionScript 出错
**注意:**只有在启用了手势支持的电脑上运行,比如带触摸板的 Macintosh,才会出现前述错误。相反,您可以在带有触摸屏的移动设备上运行,以重现相同的错误。
使用 Flash Builder 调试器
在上一节中,我们在运行应用时遇到了一个错误,但是错误窗口在识别发生了什么或者让我们检查当前状态方面并没有特别大的帮助。事实上,如果我们在移动设备上运行应用,它会继续执行,甚至不会让我们知道发生了错误。虽然这种行为对于生产应用来说是理想的,如果执行可以安全地继续,您不希望小错误困扰您的最终用户,但是这使得调试应用具有挑战性。
幸运的是,Flash Builder 附带了一个内置的调试器,您可以使用它来诊断您的应用。要使用调试器,您必须通过RunDebug AsMobile Application命令启动应用。
当您这样做时,与正常应用运行的唯一显著区别是,您现在将在控制台面板中获得跟踪输出和错误。当试图诊断应用行为时,这本身就非常有用;但是,如果在执行过程中遇到错误,系统会询问您是否要切换到 Flash Debug 透视图,如图 Figure 1–11 所示。
**图 1–11。**Flash 调试透视图突出显示手势检查应用中的错误
Flash Debug 透视图使您能够在应用执行时查看其内部,这是非常强大的。在左上角的调试窗格中,您可以启动和停止您的应用,以及导航堆栈框架,例如我们遇到的错误情况。
当您在“调试”面板中选择一个帧时,它会在右上角的“变量”窗格中显示执行上下文的状态,并在中间的面板中显示相关的源代码。这使得很容易识别出我们在将 enabled CheckBox
设置为true
的调用中失败了,因为没有带有id
“gestureZoom”的CheckBox
。这是因为我们还没有添加 UI 元素来处理额外的手势。
由于我们已经确定了问题,您可以停止应用,并通过使用 Flash Builder 窗口右上角的透视图选择器切换回代码透视图。
正如您将在第二章中了解到的,Android 上的 Flash 支持五种不同的手势事件。这些措施如下:
- 平移:在显示屏上拖动两个手指。
- 旋转:将两个手指放在显示屏上,顺时针或逆时针旋转。
- 缩放:使用两个手指,同时分开或并拢。
- 滑动:用单个手指在显示屏上水平或垂直按压和滑动。
- 双指轻击:同时用两个手指触摸显示屏。
清单 1–4 展示了完整的应用,它将让您尝试这些手势。
清单 1–4。 手势检查示例应用的完整代码清单
`<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
title=“Supported Gestures” initialize=“init()”>
fx:Script
<![CDATA[
import flash.ui.Multitouch;
privatefunction init():void {
for each(var gesture:String in Multitouch.supportedGestures) {
this[gesture+“Enabled”].selected = true;
addEventListener(gesture, function(e:GestureEvent):void {
e.currentTarget[e.type+“Tested”].selected = true;
});
}
}
]]>
</fx:Script>
<s:VGroup paddingTop=“15” paddingBottom=“15”
paddingLeft=“20” paddingRight=“20” gap=“10”>
<s:HGroup verticalAlign=“middle” gap=“20”>
<s:Label text=“Pan” fontSize=“36” width=“110”/>
<s:Image source=“@Embed(‘/gestures/pan.png’)” width=“137”/>
<s:VGroup gap=“10”>
<s:CheckBox id=“gesturePanEnabled” content=“Enabled” mouseEnabled=“false”/>
<s:CheckBox id=“gesturePanTested” content=“Tested” mouseEnabled=“false”/>
</s:VGroup>
</s:HGroup>
<s:HGroup verticalAlign=“middle” gap=“20”>
<s:Label text=“Rotate” fontSize=“36” width=“110”/>
<s:Image source=“@Embed(‘/gestures/rotate.png’)” width=“137”/>
<s:VGroup gap=“10”>
<s:CheckBox id=“gestureRotateEnabled” content=“Enabled” mouseEnabled=“false”/>
<s:CheckBox id=“gestureRotateTested” content=“Tested” mouseEnabled=“false”/>
</s:VGroup>
</s:HGroup>
<s:HGroup verticalAlign=“middle” gap=“20”>
<s:Label text=“Zoom” fontSize=“36” width=“110”/>
<s:Image source=“@Embed(‘/gestures/zoom.png’)” width=“137”/>
<s:VGroup gap=“10”>
<s:CheckBox id=“gestureZoomEnabled” content=“Enabled” mouseEnabled=“false”/>
<s:CheckBox id=“gestureZoomTested” content=“Tested” mouseEnabled=“false”/>
</s:VGroup>
</s:HGroup>
<s:HGroup verticalAlign=“middle” gap=“20”>
<s:Label text=“Swipe” fontSize=“36” width=“110”/>
<s:Image source=“@Embed(‘/gestures/swipe.png’)” width=“137”/>
<s:VGroup gap=“10”>
<s:CheckBox id=“gestureSwipeEnabled” content=“Enabled” mouseEnabled=“false”/>
<s:CheckBox id=“gestureSwipeTested” content=“Tested” mouseEnabled=“false”/>
</s:VGroup>
</s:HGroup>
<s:HGroup verticalAlign=“middle” gap=“20”>
<s:Label text=“Two-Finger Tap” fontSize=“36” width=“110”/>
<s:Image source=“@Embed(‘/gestures/twoFingerTap.png’)” width=“137”/>
<s:VGroup gap=“10”>
<s:CheckBox id=“gestureTwoFingerTapEnabled”
content=“Enabled” mouseEnabled=“false”/>
<s:CheckBox id=“gestureTwoFingerTapTested”
content=“Tested” mouseEnabled=“false”/>
</s:VGroup>
</s:HGroup>
<s:Label text=“Graphics courtesy of GestureWorks.com” fontSize=“12”/>
</s:VGroup>
</s:View>`
如果您从 ADL 桌面模拟器测试此应用,您将会根据您的桌面手势支持获得不同的结果。对于不支持多点触摸的机器,不会启用任何手势;然而,如果你足够幸运,拥有一台带有支持多点触摸的触摸板的台式机或笔记本电脑,你或许可以对该应用进行一些有限的测试,如图 Figure 1–12 所示。
图 1–12。 在配有触控板的 MacBook Pro 上运行时,手势支持有限
虽然它报告五个手势中有四个是启用的,但在我们用来执行这个示例的计算机上,实际上只可能执行平移、旋转和缩放。正如我们将在下一节中看到的,在完全支持所有多点触摸手势的设备上运行它会有趣得多。
在设备上运行闪存
Flash Builder 使在移动设备上执行应用变得非常容易。只需点击一下,它就可以部署应用,在设备上启动它,甚至还可以连接一个远程调试器。
要在一个物理设备上运行你的应用,你需要确保它被正确设置用于 USB 调试。在大多数设备上,你可以通过进入设置应用开发来启用 USB 调试,在那里你会找到图 1–13 所示的选项。
图 1–13。 安卓开发设置屏幕
确保在此页面上启用了 USB 调试。您可能还想同时启用保持清醒的支持,这样您就不必在手机每次进入睡眠状态时都不断地重新登录。
一旦启用了 USB 调试,并且您已经通过 USB 电缆将手机连接到计算机,您的设备应该对 Flash Builder 可见。要切换到在设备上运行,请转到运行运行配置 …,并返回参考图 1–8,您可以选择在设备上启动的选项。一旦选中,每次运行您的应用时,它都会在您连接的 Android 设备上启动,如 Figure 1–14 所示。
如你所见,在一个真实的设备上,锻炼所有的手势事件是可能的。当测试不同的设备以查看它们支持什么手势以及它们如何响应这些手势时,这个应用应该会派上用场。
图 1–14。 完成了在 Android 移动设备上运行的手势检查应用
如果您的 Android 手机无法连接到电脑,以下是您可以遵循的一些故障诊断步骤:
- 确保您的设备通过 USB 连接到电脑。如果成功,您的 Android 设备上的通知区域会显示已通过 USB 连接。
- 您还需要确保通过前面的步骤启用了设备调试。同样,当它正常工作时,会在通知区域列出。
- 如果没有 USB 连接,可能是驱动程序的问题。麦金塔电脑不需要驱动程序;但是,在 Windows 上,您可能需要为您的手机安装一个特殊的 USB 驱动程序。
- 这也可能是与电脑的连接有问题。尝试使用不同的电缆或不同的 USB 端口。
- 如果您有 USB 连接,但设备调试不工作,您可能需要在 Android 设备上更改您的 USB 连接模式。寻找一个写着“充电模式”或“禁用大容量存储”的选项
如果您仍然遇到问题,您应该验证您的手机是否在您正在使用的 Flash Builder 版本的支持设备列表中,并与您的制造商联系以确保您拥有正确的驱动程序和设置。
从命令行运行应用
除了从 Flash Professional 和 Flash Builder 中运行之外,您还可以使用 AIR Debug Launcher (ADL)从命令行启动应用。如果您在没有工具支持的情况下直接使用 Flex,这也是您测试应用的方式。
要使用 ADL,您必须下载免费的 Flex 4.5 SDK,或者导航到 Flash Builder 安装的sdks/4.5.0
文件夹。确保 Flex SDK 的 bin 文件夹在您的路径中,以便您可以调用 ADL 命令行工具。
ADL 工具的语法如下:
adl ( -runtime <runtime-dir> )? ( -pubid <pubid> )? -nodebug? ( -profile PROFILE )? ( - extdir <extensions-dir> )? ( -screensize SCREEN_SIZE )? <app-desc><root-dir>? ( -- … )?
ADL 支持许多可选参数,其中大部分是可选的。以下是对所有论点的简要描述,对移动开发重要的论点以粗体突出显示:
runtime:
指定备用空气运行时间的可选参数;默认情况下,将使用 SDK 中包含的运行时。pubid:
用于指定应用 ID 的已弃用参数;您应该在应用描述符中使用 ID 标记。nodebug:
禁用调试支持,产生轻微的性能提升和更接近生产的应用行为**profile**
:您正在调试的应用的类型;对于移动开发,我们将使用mobileDevice
概要文件。以下是完整的值列表:mobileDevice, extendedMobileDevice, desktop, extendedDesktop, tv, extendedTV
extdir:
action script 扩展的可选目录**screensize**:
应用窗口的大小,可以是 Table 1–3 中列出的关键字之一或以下格式的字符串:<width>×<height>:<fullscreen width>×<fullscreen height>
**app-desc**:
这是 ADL 运行时唯一需要的参数,应该引用您要执行的 AIR 程序的应用描述符。- 默认情况下,ADL 会将应用的根目录设置为存储应用描述符的文件夹,但是您可以通过将其设置为另一个路径来覆盖它。
- (-):最后,您可以通过在两个破折号后添加参数来将参数传入您的应用。
要运行您之前开发的手势检查应用,请导航到根项目文件夹并执行以下命令:
adl -profile mobileDevice -screensize Droid bin-debug/GestureCheck-app.xml
这将使用摩托罗拉 Droid 的移动配置文件和屏幕大小在 AIR Debug Launcher 中执行手势检查应用。由于手势检查应用在其应用描述符中没有将全屏设置为 true,因此 ADL 使用的窗口大小将为 480×816。
执行后,您应该会得到与图 1–12 中的所示相同的结果,与您在 Flash Builder 中执行的早期运行相匹配。
总结
这是一个开始移动开发的激动人心的时刻。智能手机的采用,尤其是基于 Android 的设备,正在呈指数级增长,您最终可以使用具有完整创作工具支持的现代开发框架,如 Flash 和 Flex。
在第一章的短短时间里,你已经学会了如何做以下事情:
- 使用 Flash Professional 和 Flex Builder 创建移动应用
- 在 AIR 调试启动程序中运行应用
- 通过 USB 连接在 Android 设备上部署和测试
- 使用 Flash Builder 调试器诊断您的应用
- 从命令行用不同的屏幕尺寸测试您的应用
这只是 Flash Android 移动开发的冰山一角。在接下来的章节中,我们将向您展示如何构建引人入胜、身临其境的 Flash 应用,充分利用所有移动功能。然后,我们演示如何将您的应用部署和发布到 Android Market。最后,我们将讨论一些高级主题,如原生 Android 集成、性能调优以及将您的应用扩展到平板电脑、电视等。*
二、针对移动配置文件的目标应用
与台式机相比,移动设备的资源非常有限。移动处理器正迅速赶上昨日台式机的速度,但内存和存储仍处于溢价状态。与此同时,用户希望移动应用能够瞬间启动,并对任何时候的硬崩溃或软崩溃具有完全的容错能力。
例如,为了节省内存资源,Android 操作系统可以随时选择关闭后台应用。当用户访问应用时,它依赖于最后已知的活动状态来启动它。如果应用重新启动的时间超过一秒钟,用户会注意到延迟,因为他们认为应用仍在后台运行。
虽然许多相同的概念适用于桌面应用开发,例如使用的工具和编程语言、可用的服务器通信协议以及可用于 UI 开发的控件和外观,但移动设备有一些独特的特征会影响 UI 和应用设计,例如屏幕大小、输入法和部署。
许多相同的空间、占用空间和启动时间的限制在 Web 上已经存在了很长时间。Flash 浏览器应用通常被期望适合一个有限的网页,快速下载,共享有限的计算资源,并即时启动。因此,您现有的 Flash 和 Flex 应用可能是移植到移动设备的良好候选。在本章中,我们将向您展示如何构建充分利用 Android 移动平台的应用。
屏幕尺寸
Android 是操作系统和软件栈,不是硬件平台。Google 提供了一个开源平台,包括一个修改过的 Linux 内核和基于 Java 的应用,可以在各种硬件平台上运行。然而,他们并不能控制运行 Android 的最终设备的确切特征。这意味着设备的确切配置变化很大,屏幕尺寸是分辨率、物理尺寸和像素密度有很大差异的一个方面。Table 2–1 显示了终端用户可能在其上运行您的应用的各种常见 Android 设备的屏幕特征。
在表 2–1 中,分辨率是水平和垂直方向的物理像素数,尺寸是屏幕的对角线尺寸,密度是每英寸的像素数(ppi)。Type 为屏幕分辨率提供了一个通用名称,它是下列之一:
- QVGA(四分之一视频图形阵列) : 240×320 像素或 VGA 显示器(480×640)分辨率的四分之一
- HVGA(半视频图形阵列) : 320×480 或 VGA 显示器分辨率的一半
- WVGA(宽视频图形阵列) : 480×800,与 VGA 高度相同,但宽度为 800(横向观看时)
- FWVGA(全宽视频图形阵列) : 480×854,与 VGA 高度相同,但比例为 16:9,用于显示未剪辑的高清视频
- qHD(四分之一高清) : 540×960 或四分之一 1080p 显示屏,16:9 比例,用于显示未剪辑的高清视频
你的应用的可用区域也会因为 Android 状态栏的高度而减少。中密度显示器(如 HTC Hero)的条形高度为 25 像素,高密度显示器(如 Nexus One)的条形高度为 38 像素,超高密度显示器的条形高度为 50 像素。当显示器从纵向模式切换到横向模式时,这也会发生变化。比如 Nexus One 在人像模式下的可用面积是 480×762,而在风景模式下变成了 442×800。
您可能只有一两个设备需要测试,但这并不意味着您的应用不能支持所有的设备。Flash 可以自动缩放应用以适应屏幕大小,并且很容易获得屏幕分辨率来以编程方式修改界面。清单 2–1 展示了如何从 ActionScript 代码中检索屏幕分辨率和密度。
清单 2–1。 程序化屏幕分辨率和密度捕捉
var resY = Capabilities.screenResolutionX; var resX = Capabilities.screenResolutionY; var dpi = Capabilities.screenDPI; trace("Screen Resolution is " + resX + "x" + resY + " at " + dpi + "ppi");
注意:术语每英寸点数(dpi)和每英寸像素(ppi)是等价的度量。这些在整个 ActionScript APIs 中可以互换使用。
屏幕分辨率与密度
虽然应用开发人员更可能关注屏幕分辨率的差异,但屏幕密度也同样重要。您的应用需要能够扩展到更大或更小的设备,以便文本仍然可读,目标足够大以便操作。图 2–1 比较了几款不同特性手机的物理尺寸和屏幕分辨率。
虽然 Xperia X10 mini 的屏幕分辨率与 Nexus One 相比微不足道,但屏幕的物理尺寸仅小 30%。这意味着用户界面中的所有图形都需要大幅缩小以适合屏幕。另一方面,在为 Xperia X10 mini 构建时,由于像素太大,即使很小的目标也可以被用户轻松操纵。对于 Nexus One,你需要将目标做得更大。
在 2006 年完成的一项研究中,奥卢大学和马里兰大学的研究人员发现,用拇指操纵触摸屏的最小目标尺寸在 9.2 毫米到 9.6 毫米之间。
1 佩卡·帕尔希、艾米·k·卡尔森和本杰明·b·彼得森,“在小型触摸屏设备上单手使用拇指的目标尺寸研究”, http://portal.acm.org/citation.cfm?id=1152260
,2006 年
图 2–1。 几种安卓设备的物理尺寸和分辨率
例如,为了实现触摸交互,你需要在 Xperia X10 mini 上将目标尺寸调整为 57 像素宽,或者在 Nexus One 上调整为 92 像素宽。通过调整用户界面的大小以考虑密度,您可以确保用户界面仍然可用,同时最大化活动设备的屏幕空间。
在 Flash 中模拟与设备无关的像素
Android 有一个与设备无关的像素的概念,可以用来做布局,即使显示器的物理大小不同,也会出现相似的布局。它基于 160 dpi 屏幕的参考平台,相当于每英寸大约一个 13×13 像素的正方形。如果你指定了一个 Android 布局,使用与设备无关的像素,平台会根据你的应用运行的设备自动调整。
Flash 没有设备无关像素的概念,但是用代码模拟非常容易。基本公式是 dips =像素* (160 /密度)。清单 2–2 演示了如何在 ActionScript 中进行计算。
清单 2–2。 ActionScript 函数计算设备无关像素(dips)
function pixelsToDips(pixels:int) { return pixels * (160 / Capabilities.screenDPI); } trace("100 pixels = " + pixelsToDips(100) + " dips");
使用模拟的设备无关像素,您可以在 Flash 应用中重现与原生 Android 应用相似的布局行为。
如果您计划根据当前设备密度缩放应用图形,请确保您的应用没有设置为自动调整大小以填充屏幕或在旋转时居中显示内容。有关如何操作的更多信息,请参见本章后面的“Flash 中的自动方向翻转”一节。
Flex 应用中的密度
Flex 有内置的支持来缩放应用的用户界面,包括图形、字体和控件。它支持三种常见显示密度的离散比例因子,而不是任意缩放。Table 2–2 列出了所有不同的显示密度,以及用于为当前设备选择密度的映射 DPI 范围。
为了利用 Flex density 支持,将您的Application
对象上的applicationDPI
属性设置为应用最初设计的比例。在运行时,您的应用将根据设备屏幕的密度自动缩放。一个 240 dpi 的应用描述符的例子包含在清单 2–3 中。
清单 2–3。 应用描述符设置applicationDPI
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.MainHomeView" **applicationDPI="240"**> </s:ViewNavigatorApplication>
applicationDPI
的唯一有效值是文本字符串“160”、“240”和“320”,对应于三种支持的密度。只能通过 MXML 设置applicationDPI
属性。
根据作者密度与设备密度的比率,使用矢量图形和文本构建的应用部分会根据需要平滑地放大或缩小。就字体而言,调整字体大小,确保文本在任何显示器上都易于阅读。
位图图形也将被缩放,但放大时可能看起来模糊,缩小时可能会丢失细节。为了确保您的位图大小适合不同的密度,您可以通过使用MultiDPIBitmapSource
类提供基于显示密度自动换入的替代图像。
密度浏览器应用
为了更好地了解密度如何影响您的 Flex 应用,我们将指导您创建密度浏览器应用。该应用允许您输入应用dpi
和设备dpi
作为参数,并计算将在不同设备上使用的灵活调整的设备密度和比例因子。
首先,使用移动应用模板创建一个名为“密度浏览器”的新 Flex 移动项目。这将自动生成一个标准项目模板,其中包括一个 Adobe AIR 应用描述符(DensityExplorer-app.xml
)、一个 ViewNavigatorApplication ( DensityExplorer.mxml
)和一个初始视图(DensityExplorerHomeView.mxml
)。
第一步是打开DensityExplorerHomeView.mxml
并添加一些控件,让你设置作者密度和设备 DPI。我们将在第五章中更详细地介绍 Flex 控件,但是对于这个应用来说,几个标签、单选按钮和一个水平滑块就足够了。
清单 2–4 显示了允许使用RadioButton
和HSlider
类输入作者密度和设备 dpi 的基本代码。
清单 2–4。 密度浏览器控件用于applicationDPI
和deviceDPI
条目
<fx:Script> <![CDATA[ [Bindable] protectedvarapplicationDPI:Number; [Bindable] publicvardeviceDPI:Number; ]]> </fx:Script> <s:VGroup paddingTop="20" paddingLeft="15" paddingRight="15" paddingBottom="15" gap="20" width="100%" height="100%"> <s:Label text="Application DPI:"/> <s:HGroup gap="30"> <s:RadioButton content="160" click="applicationDPI = 160"/> <s:RadioButton content="240" click="applicationDPI = 240"/> <s:RadioButton content="320" click="applicationDPI = 320"/> </s:HGroup> <s:Label text="Device DPI: {deviceDPI}"/> <s:HSlider minimum="130" maximum="320" value="@{deviceDPI}" width="100%"/> </s:VGroup>
首先,引入一些可绑定的脚本变量来保存applicationDPI
和deviceDPI
。这些并不是显示基本 UI 所必需的,但是它们将使以后连接输出部分变得更加容易。主控件在VGroup
中垂直组织,而RadioButtons
使用HGroup
水平组织。
使用一个简单的click
处理器将RadioButtons
连接到applicationDPI
。当滑块改变时,双向数据绑定表达式(前缀为@操作符)用于更新dpi
的值。为了完成 UI 的这一部分,设备 dpi 文本包含一个对 DPI 的绑定引用,以便您可以在滑块的值发生变化时看到它。
运行这个程序会给你一个简单的 Flex 应用,如图 2–2 所示。您可以通过移动滑块来验证功能,这将更新deviceDPI
设置。
图 2–2。 密度探索者第一部分:基本控件
此应用的目标是计算 Flex 将使用的调整后的设备密度和比例因子。幸运的是,有一个新的 Flex 4.5 API 可以通过 ActionScript 公开这些信息。我们需要调用的类叫做DensityUtil
,可以在mx.utils
包中找到。DensityUtil
有两个带有以下签名的静态方法:
getRuntimeDPI():Number:
该函数返回applicationDPI
如果置位,否则当前运行时应用的 DPI 分类;它将始终返回下列值之一:160、240 或 320。- 在给定应用 DPI(源)和设备 DPI(目标)的情况下,此函数计算 Flex 将使用的比例。
除了这些函数,我们还需要知道当前的applicationDPI
和设备 dpi 值,这样我们就可以填充 UI 控件的初始值。这些可以通过以下 API 进行查询:
Application.applicationDPI:
对象上的成员变量,可以被查询以获得初始的applicationDPI
值Capabilities.screenDPI:
返回屏幕 dpi 数值的Capabilities
对象的静态方法
利用这些 API,我们可以扩充之前的代码,添加初始化逻辑以及密度和规模的读数。清单 2–5 用粗体显示了更新后的代码。
清单 2–5。 更新了密度探测器代码,并进行初始化和输出
<?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="Density Explorer" **initialize="init()"**> <fx:Script> <![CDATA[ **import mx.binding.utils.BindingUtils;** **import mx.utils.DensityUtil;** [Bindable] protectedvarapplicationDPI:Number; [Bindable] publicvardeviceDPI:Number; **[Bindable]** **protectedvardpiClassification:Number;** **protectedfunction updateDensity(dpi:Number):void {** **dpiClassification = dpi < 200 ? 160 : dpi >= 280 ? 320 : 240;** **}** **protectedfunction init():void {** **applicationDPI= parentApplication.applicationDPI;** **if (applicationDPI!= null) {** **this["ad" + applicationDPI].selected = true;** **}** **BindingUtils.bindSetter(updateDensity, this, "deviceDPI");** **deviceDPI= Capabilities.screenDPI;** **}** ]]> </fx:Script> <s:VGroup paddingTop="20" paddingLeft="15" paddingRight="15" paddingBottom="15" gap="20" width="100%" height="100%"> <s:Label text="ApplicationDPI:"/> <s:HGroup gap="30"> <s:RadioButton content="160" click="applicationDPI= 160"/> <s:RadioButton content="240" click="applicationDPI= 240"/> <s:RadioButton content="320" click="applicationDPI= 320"/> </s:HGroup> <s:Label text="Device DPI: {dpi}"/> <s:HSlider minimum="130" maximum="320" value="@{deviceDPI}" width="100%"/> **<s:Group width="100%" height="100%">** **<s:BorderContainer bottom="0" minHeight="0"width="100%" borderStyle="inset"** **backgroundColor="#d0d0d0"borderColor="#888888" backgroundAlpha=".6">** **<s:layout>** **<s:VerticalLayout gap="10"paddingLeft="10" paddingRight="10"** **paddingTop="10" paddingBottom="10"/>** **</s:layout>** **<s:Label text="Adjusted Device Density: {dpiClassification}"/>** **<s:Label text="Scale Factor: {DensityUtil.getDPIScale(applicationDPI,** **dpiClassification)}"/>** **</s:BorderContainer>**
**</s:Group>** </s:VGroup> </s:View>
在由View.initialize
调用的方法内部执行初始化,以确保所有值都可用。首先从parentApplication
对象更新applicationDPI
,并通过对返回的字符串执行 ID 查找来选择正确的RadioButton
。接下来从Capabilities
对象设置dpi
。为了确保初始值赋值和滑块后续更新对dpi
的所有更新都将重新计算deviceDensity
,bind setter 被配置为触发对dpi
的所有更新。
为了显示deviceDensity
的当前值和计算出的刻度,在View
的末端增加了一个带有几个Labels
的BorderContainer
。通过使用一个BorderContainer
作为环绕组,很容易改变样式,使输出在视觉上不同于输入。
最后一步是添加一个额外的群组,它将随着dpi
设置的更新而淡入设备图片。为了确保图像针对不同密度的显示器进行优化缩放,我们使用了一个MultiDPIBimapSource
,它指的是不同的预缩放伪影。该代码如清单 2–6 所示。
清单 2–6。*【MXML】用于显示代表性设备图像的代码使用了 MultiDPIBitmapSource
*
<s:Group width="100%" height="100%"> <s:Image alpha="{1-Math.abs(deviceDPI-157)/20}" horizontalCenter="0"> <s:source> <s:MultiDPIBitmapSource source160dpi="@Embed('assets/xperia-x10-mini160.jpg')" source240dpi="@Embed('assets/xperia-x10-mini240.jpg')" source320dpi="@Embed('assets/xperia-x10-mini320.jpg')" /> </s:source> </s:Image> <s:Image alpha="{1-Math.abs(deviceDPI-180)/20}" horizontalCenter="0"> <s:source> <s:MultiDPIBitmapSource source160dpi="@Embed('assets/htc-hero160.jpg')" source240dpi="@Embed('assets/htc-hero240.jpg')" source320dpi="@Embed('assets/htc-hero320.jpg')" /> </s:source> </s:Image> <s:Image alpha="{1-Math.abs(deviceDPI-217)/20}" horizontalCenter="0"> <s:source> <s:MultiDPIBitmapSource source160dpi="@Embed('assets/htc-evo-4g160.jpg')" source240dpi="@Embed('assets/htc-evo-4g240.jpg')" source320dpi="@Embed('assets/htc-evo-4g320.jpg')" /> </s:source> </s:Image> <s:Image alpha="{1-Math.abs(deviceDPI-252)/20}" horizontalCenter="0"> <s:source> <s:MultiDPIBitmapSource source160dpi="@Embed('assets/nexus-one160.jpg')" source240dpi="@Embed('assets/nexus-one240.jpg')" source320dpi="@Embed('assets/nexus-one320.jpg')" /> </s:source>
</s:Image> <s:Image alpha="{1-Math.abs(deviceDPI-275)/20}" horizontalCenter="0"> <s:source> <s:MultiDPIBitmapSource source160dpi="@Embed('assets/atrix160.jpg')" source240dpi="@Embed('assets/atrix240.jpg')" source320dpi="@Embed('assets/atrix320.jpg')" /> </s:source> </s:Image> </s:Group>
所有选择的图片都是手机标准新闻图片的缩放版本。为了在接近 dpi 值时慢慢淡入设备,将一个简单的数学公式应用于 alpha:
1-Math.abs(deviceDPI-{physicalDPI})/{threshold}
对于每个电话,实际的dpi
代替该设备的physicalDPI
,并且阈值被设置为足够低的值,使得对于目标 dpi 值不会有两个电话重叠。对于所选择的设备,阈值 20 低于任何电话dpi
值之间的差。
完成的密度浏览器应用如图 2–3 所示。这是试验应用 dpi 和设备 dpi 的不同值的好机会,以查看它们对您部署的应用的影响。
图 2–3。 完成了密度探测器的应用
为了进行对比,Figure 2–4 展示了在物理设备上以 160、240 和 320 dpi 运行的 Density Explorer 的屏幕截图。请注意,尽管屏幕的物理尺寸差异很大,但应用的布局和图形的质量都保持不变。通过将作者密度设置为 240,可以保证您的应用在任何密度的设备上都具有相同的外观和感觉,而无需修改代码。
图 2–4。 密度浏览器在分类为 160 dpi(左)、240 dpi(中)和 320 dpi(右)的设备上运行时的并排比较
CSS 中的密度支持
虽然 Flex 中的applicationDPI
设置为您提供了一种简单的机制来为一种密度编写您的应用,并让 Flex 负责调整大小,但是当您在不同的设备上查看应用时,它并不能让您对应用的精确布局和样式进行细粒度的控制。将applicationDPI
设置为常量对于简单的应用来说很好,但是随着 UI 复杂性的增加,这通常不够好。这就是 CSS 媒体查询的来源。
Flex 媒体查询允许您在 CSS 中对不同设备上的样式进行细粒度控制。它们基于 W3C CSS 媒体查询候选推荐标准, 2 ,但仅包含与 Flex 和移动应用最相关的功能的子集。
Flex 支持两种类型的选择器。第一种类型允许您根据设备类型选择样式。清单 2–7 中的代码演示了如何根据运行设备的类型来改变字体颜色。
2 www.w3.org/TR/css3-mediaqueries/
清单 2–7。 演示设备媒体选择器的代码示例
`@namespace s “library://ns.adobe.com/flex/spark”;
@media (os-platform: “IOS”) {
s|Label
{
color: red;
}
}
@media (os-platform: “Android”) {
s|Label {
color: blue;
}
}`
将该样式表添加到您的应用中会将所有Labels
的颜色变为蓝色或红色,这取决于您运行的移动平台。但是,当作为桌面应用运行时,这不会有任何影响。
第二种选择器允许您根据应用dpi
改变样式。与之匹配的有效值是标准弯曲密度 160、240 和 320。使用dpi
选择器,您可以微调布局和字体,甚至用不同密度的显示器替换图像。
**重要提示:**为了使用 CSS 媒体选择器,您需要确保没有在您的移动应用类上设置applicationDPI
属性。
为了演示dpi
选择器的使用,我们将更新 Density Explorer 示例,使用样式表来替换图像,而不是将其嵌入到带有MultiDPIBitmapSource
的代码中。应用图像的简化应用代码如清单 2–8 所示。
清单 2–8。 更新了 DensityExplorer
代码用于整合 CSS 媒体查询
<s:Group width="100%" height="100%"> <s:Image alpha="{1-Math.abs(deviceDPI-157)/20}" horizontalCenter="0" source="{phones.getStyle('xperiaX10Mini')}"/> <s:Image alpha="{1-Math.abs(deviceDPI-180)/20}" horizontalCenter="0" source="{phones.getStyle('htcHero')}"/> <s:Image alpha="{1-Math.abs(deviceDPI-217)/20}" horizontalCenter="0" source="{phones.getStyle('htcEvo4g')}"/> <s:Image alpha="{1-Math.abs(deviceDPI-252)/20}" horizontalCenter="0" source="{phones.getStyle('nexusOne')}"/> <s:Image alpha="{1-Math.abs(deviceDPI-275)/20}" horizontalCenter="0" source="{phones.getStyle('atrix')}"/> </s:Group>
注意,我们在父对象上使用了getStyle
方法来分配图像源。如果您使用的是图标或按钮状态这样的样式,这通常是不需要的,但是 image 类上的 source 是一个普通的属性。使用这种技术绑定到一个命名的样式使得Image
源可以通过 CSS 访问。
为了完成这个例子,我们还需要创建一个样式表,利用dpi
媒体选择器来替换一个适当缩放的图像。这类似于设备选择器,如清单 2–9 所示。
清单 2–9。 CSS 基于应用 dpi
进行图像切换
`@media (application-dpi: 160) {
#phones {
xperiaX10Mini: Embed(“/assets/xperia-x10-mini160.jpg”);
htcHero: Embed(“/assets/htc-hero160.jpg”);
htcEvo4g: Embed(“/assets/htc-evo-4g160.jpg”);
nexusOne: Embed(“/assets/nexus-one160.jpg”);
atrix: Embed(“/assets/atrix160.jpg”);
}
}
@media (application-dpi: 240) {
#phones {
xperiaX10Mini: Embed(“/assets/xperia-x10-mini240.jpg”);
htcHero: Embed(“/assets/htc-hero240.jpg”);
htcEvo4g: Embed(“/assets/htc-evo-4g240.jpg”);
nexusOne: Embed(“/assets/nexus-one240.jpg”);
atrix: Embed(“/assets/atrix240.jpg”);
}
}
@media (application-dpi: 320) {
#phones {
xperiaX10Mini: Embed(“/assets/xperia-x10-mini320.jpg”);
htcHero: Embed(“/assets/htc-hero320.jpg”);
htcEvo4g: Embed(“/assets/htc-evo-4g320.jpg”);
nexusOne: Embed(“/assets/nexus-one320.jpg”);
atrix: Embed(“/assets/atrix320.jpg”);
}
}`
最后一步是确保我们在ViewNavigatorApplication
中引用了样式表。您还需要移除applicationDPI
设置,否则样式表选择器将总是将dpi
报告为常量值,如清单 2–10 所示。
清单 2–10。 完成 DensityExplorer
应用类对整合媒体查询的支持
<?xml version="1.0" encoding="utf-8"?> <s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" splashScreenImage="@Embed('ProAndroidFlash400.png')" firstView="views.DensityExplorerHomeView"> <fx:Style source="DensityExplorer.css"/> </s:ViewNavigatorApplication>
在不同设备上运行该程序的输出几乎与之前在图 2–4 中的结果相同,只是间距略有不同。原因是 Flex control 团队也在他们的控件中放入了dpi
提示,这样它们就可以根据目标设备自动调整大小,即使没有将applicationDPI
固定为一个常量值。
既然你已经学习了 CSS 媒体选择器,你就有了一个强大的工具来从你的代码中提取样式,甚至是对密度敏感的应用。
屏幕方向
移动设备的一个独特之处是可以在手中旋转。在桌面世界中,这相当于将显示器翻转过来。虽然旋转桌面显示器有一些创造性的用途,如图 2–5 所示,但这肯定不是一个常见的用例。
图 2–5。 独特的利用监视器旋转来制造光弧 3
在移动设备中,旋转是一个重要的 UI 范例,它让您可以充分利用有限的屏幕空间。一个表现良好的移动应用应该在旋转时调整用户界面的大小,让用户停留在他或她喜欢的方向,通常显示一个完全不同的视图,这是为那个方向定制的。
3Tim prit love 拍摄的知识共享许可照片:
www.flickr.com/photos/timpritlove/123865627/
。
Flex 中的纵向/横向切换
要在 Flex 项目中打开自动定向,有两种方法。最方便的方法是在从标准模板创建新的 Flex 移动应用时,只需选中“自动重定向”复选框。图 2–6 显示了选中“自动重定向”选项的项目创建向导的屏幕截图。
图 2–6。 选中了“自动重定向”选项的 Flex builder 项目向导
如果您有一个现有的项目或者想要手动更改自动定向,您需要在应用描述符中设置autoOrients
属性。应用描述符位于根项目目录中名为*-app.xml
的文件中,其中的autoOrients
属性应该被创建为initialWindow
元素的子元素,并被设置为true
,如清单 2–11 所示。
清单 2–11。 应用描述符更改,允许载物台自动定位
<initialWindow> <content>[This value will be overwritten by Flash Builder in the output app.xml]</content> **<autoOrients>true</autoOrients>** </initialWindow>
这会旋转舞台并调整其大小,还会引发事件,您可以通过监听这些事件来更改应用布局。
然而,简单地打开自动定向通常会产生不理想的结果。例如,如果您在 Density Explorer 应用上启用自动定向,用户界面的底部会被裁剪掉,如 Figure 2–7 所示。
图 2–7。
对于 Density Explorer 应用的横向方向,理想的布局是将手机图片放在控件的左侧。在 Flex 应用中有两种方法可以实现这一点。第一个是为动态改变布局的旋转事件添加一个事件处理程序。由于这是一种纯粹的 ActionScript 方法,它在 Flash Professional 中也同样适用。第二个是利用新的肖像和风景状态,这只能从 MXML 访问。在下面的小节中,我们将演示这两种方法。
随着事件进行纵向/横向切换
每次旋转 Flash 移动设备时,都会触发方向事件来通知任何侦听器。方向事件处理程序通过标准的addEventListener
方法添加到Stage
对象上。定向事件的事件类为StageOrientationEvent
,事件类型为StageOrientationEvent.ORIENTATION_CHANGE
。
**注意:**在StageOrientationEvent
类上也有一个ORIENTATION_CHANGING
事件类型,但这不适用于 Android 设备。
StageOrientationEvent
有两个变量对处理方向变化特别有用:
beforeOrientation
:当前旋转事件触发前的手机方向afterOrientation
:当前手机方向
将所有这些放在一起,您可以修改密度浏览器,以根据手机的方向改变布局。
第一步是更新声明性的 MXML UI,以包含一个额外的用于横向的HBox
,用惟一的 id 命名外部的HBox
和内部的VBox
,并连接一个addedToStage
事件监听器。完成这项工作的代码如清单 2–12 所示。
清单 2–12。 MXML 新增支持舞台布局变更
**<s:HGroup width="100%" height="100%" addedToStage="stageInit()">** <s:VGroup paddingTop="20" paddingLeft="15" paddingRight="15" paddingBottom="15" gap="20" width="100%" height="100%"> … <s:Group **id="inner"** width="100%" height="100%"> <s:Group width="100%" height="100%"> … </s:VGroup> **</s:HGroup>**
下一步是实现stageInit
函数来连接一个方向改变事件监听器。除了连接侦听器之外,用当前方向触发一个初始事件通常也很有帮助。这将确保即使您的应用以横向模式启动,它也将遵循相同的代码路径,就像它以纵向模式打开,然后被用户旋转一样。这个动作脚本显示在清单 2–13 中。
清单 2–13。 实现 stageInit
和 orientationChange
功能
**protected function** orientationChange(e:StageOrientationEvent):**void** { **switch** (e.afterOrientation) { **case** StageOrientation.DEFAULT: **case** StageOrientation.UPSIDE_DOWN:
inner.addElementAt(phones, 0); **break**; **case** StageOrientation.ROTATED_RIGHT: **case** StageOrientation.ROTATED_LEFT: outer.addElementAt(phones, 0); **break**; } } **protected function** stageInit():**void** { stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGE, orientationChange); orientationChange(**new** StageOrientationEvent( StageOrientationEvent.ORIENTATION_CHANGE, **false**, **false**, **null**, stage.orientation)); }
在这种情况下,向右和向左旋转的行为是相同的,尽管如果你想变得有创意,你可以根据手机旋转的方式将设备显示屏放在屏幕的不同侧。
运行修改后的密度浏览器应用的结果如图 2–8 所示。正如您所看到的,设备显示屏以可用的大小显示,其余的控件不再在很宽的显示屏上展开。最令人印象深刻的是,当你旋转手机时,布局会动态更新,以优化纵向和横向。
图 2–8。 改进了景观布局的密度浏览器应用
纵向/横向状态切换
在 Flex 中可以用于纵向/横向切换的第二种技术是利用两种内置状态,这两种状态在设备旋转时触发,称为portrait
和landscape
。虽然这只能从 MXML 访问,并且不像事件 API 那样提供那么多关于设备方向的信息,但实现这一点的代码更简单,本质上更具声明性,更易于阅读和维护。
要公开新的纵向和横向状态,您需要在视图代码中添加一个定义了portrait
和landscape
状态的states
声明,如清单 2–14 所示。
清单 2–14。 状态声明为 portrait
和 landscape
模式
<s:states> <s:State name="portrait"/> <s:State name="landscape"/> </s:states>
这些states
将在设备改变方向时自动触发。要修改布局,您可以利用includedIn
属性和Reparent
标签来移动手机图像的位置。你需要做的代码修改如清单 2–15 所示。
清单 2–15。 当状态改变时,用户界面改变以恢复手机图像
**<s:HGroup width="100%" height="100%">** **<fx:Reparent target="phones" includeIn="landscape" />** … <s:Group width="100%" height="100%"> <s:Group width="100%" height="100%" **includeIn="portrait"**> … </s:VGroup> **</s:HGroup>**
最终结果是,用 8 行 MXML 代码就可以完成用事件方法需要 21 行代码才能完成的事情。运行该应用的结果与在图 2–8 中获得的结果相同。
Flash 中的自动方向翻转
Flash 应用也可以配置为在设备旋转时自动翻转舞台的方向。要在 Flash 项目中启用自动方向切换,需要勾选 Android 发布设置对话框的空中自动方向复选框,如图 Figure 2–9 所示。
图 2–9。??【闪光 CS5.5 自动定向设置】带圈
设置此选项将导致应用的纵横比在用户旋转设备时自动从横向翻转到纵向。方向改变时,载物台将旋转,使其垂直定向,旋转后调整大小以填充新尺寸,然后在显示器内居中。
如果您想要更改内容的布局以填充屏幕并完全控制舞台的大小,您需要禁用自动缩放和定位。这可以在 ActionScript 中通过改变Stage
对象的scaleMode
和align
属性来完成,如清单 2–16 所示。
清单 2–16。 从 ActionScript 中禁用舞台缩放和对齐
stage.scaleMode = StageScaleMode.NO_SCALE; // turn off scaling stage.align = StageAlign.TOP_LEFT; // align content to the top-left of the stage
这可以添加到应用启动时执行的任何关键帧中,并保持舞台左上角对齐,不调整内容的大小。然后,您可以在方向改变时添加事件侦听器,以便修改您的应用来适应屏幕大小。
旋转笑脸 Flash 定向示例
为了演示如何在 Flash CS5.5 中快速创建调整方向的内容,您将创建一个小的示例应用,该应用在方向改变时将一张快乐的笑脸图片变形为一张魔鬼图片。
首先,您需要创建一个新的 AIR for Android 项目,大小为 480×480 像素。选择与较小设备尺寸大小相等的正方形画布的原因是为了确保旋转时不会发生额外的缩放。
图 2–10 显示了快乐笑脸图片的开始状态,画出的方框表示横向和纵向模式的范围。两个框的交叉点是 480×480 的画布,附加图形水平和垂直溢出。当方向改变时,这些会被裁剪掉,使笑脸很好地居中。
图 2–10。 快乐笑脸图片起始状态,用方框显示横向和纵向范围
你可以随心所欲地创作自己的图形,但要将元素放在单独的图层中,这样便于以后制作动画和变形。
下一步是创建大约一秒钟的魔鬼笑脸关键帧。这应该包括从快乐的笑脸平滑过渡的运动或形状补间。图 2–11 显示了一些延时帧,这些帧显示了笑脸以及一些背景场景元素的动画。
图 2–11。 从快乐到恶魔般的笑脸的动画
同时,也创建反向动画,在大约两秒钟后回到快乐的笑脸。虽然有一些在 Flash 中反转动画的 ActionScript 技术,但是它们不直观,并且通常会带来性能损失。
现在您已经完成了图形,您需要添加 ActionScript 代码来在设备旋转时制作笑脸动画。这应该添加到时间轴中的第一个关键帧,并且应该包括停止动画自动播放和将方向事件监听器附加到场景,如清单 2–17 所示。
清单 2–17。 响应方向改变事件的 ActionScript 代码
`import flash.events.StageOrientationEvent;
stop();
stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGE, onChanged);
function onChanged(event:StageOrientationEvent):void {
play();
}`
注意,onChanged
事件处理程序只需在每个方向改变事件上播放电影。动画的所有重担都已经被时间线承担了。
所需代码的最后一点是在这个邪恶的笑脸框上添加一个stop()
调用,这样它就会在一个旋转事件之后停止。
另外,如果用户的设备不支持定向事件,例如用户在桌面或电视上运行,您可以向用户添加警告。给出反馈的最简单方法是创建一个隐藏层,在检查方向支持的基础上使其可见,如清单 2–18 所示。
清单 2–18。 定位校验码隐藏/显示错误页面
if (Stage.supportsOrientationChange) { orientationNotSupported.visible = false; }
在设备上运行的完整旋转笑脸应用如图 2–12 所示。虽然这个例子相当简单,但它展示了向应用添加方向支持是多么容易。
图 2–12。 完成纵向(左)和横向(右)旋转笑脸示例
多点触摸和手势
用户界面长期以来一直受到桌面鼠标的限制。第一只鼠标是道格拉斯·恩格尔巴特在 1963 年发明的。它有两个垂直的轮子用于跟踪运动,还有一条长绳子,类似于啮齿动物或老鼠的尾巴。在这之后,出现了内置球、光学跟踪和多个按钮的鼠标,如 80 年代初生产的 Dépraz 鼠标。这两种早期装置都显示在图 2–13 中。
图 2–13。 *恩格尔巴特鼠(右下)和德普拉兹鼠(左上)*的照片
现代鼠标包括诸如滚轮、激光跟踪和无线操作等功能。然而,所有的鼠标都有一个共同的限制,那就是它们一次只能在屏幕上支持一个光标。
移动界面最初也有指针驱动的单点触摸交互的限制。然而,它们已经进化到利用人类的体质。我们有两只手,总共十个手指,每个手指都能够单独与设备上的触摸点进行交互和操作。
大多数 Android 设备支持至少两个同步触摸点,这是处理所有移动手势所需的最低要求。这也是移动设备由手指支撑并用两个拇指操作的最常见的使用场景。然而,支持几乎无限数量的接触点的新设备正在被引入。
您可以通过查询Multitouch
对象来检索您的设备支持的触摸点数量,如清单 2–19 所示。
清单 2–19。 通过 ActionScript 检索触摸点数
trace("Max Touch Points: " + Multitouch.maxTouchPoints);
在本节中,您将学习如何利用多点触摸和用户手势,改善用户体验和 Flash 应用的可用性。
移动手势
使用多点触控最简单的方法是利用 Flash 支持的预定义手势。对于任何至少有两个触摸点的 Android 设备,您将能够使用表 2–3 中的手势。
4 由 Gestureworks ( www.gestureworks.com
)提供的知识共享许可插图
在不支持手势的情况下,在应用中提供一些其他机制来完成相同的行为通常是一个好主意;然而,移动用户已经开始期待手势提供的便利和速度,因此在应用设计中适当地映射它们是很重要的。
要发现运行您的应用的设备是否支持手势,并动态查询手势功能,您可以在Multitouch
类上调用以下静态方法:
Multitouch.supportsGestureEvents
:您运行的设备是否支持发射手势事件Multitouch.supportedGestures
:字符串列表,每个支持的多点触摸事件对应一个字符串
在使用手势事件之前,需要将touchMode
设置为手势输入模式,如清单 2–20 所示。
清单 2–20。 启用手势识别支持的动作脚本代码
Multitouch.inputMode = MultitouchInputMode.GESTURE;
在您期望接收手势事件之前,应该在您的程序中调用它。
Flash 剪贴簿示例
为了演示如何处理手势事件,我们将通过一个示例程序来构建一个响应多点触摸事件的图片剪贴簿。为简单起见,我们将图像作为资源加载(参见第七章了解更多关于从相机胶卷中检索图像的信息)。
以下是我们将支持的一些多点触摸事件:
- 缩放:放大或缩小图片
- 旋转:将图像旋转到任意角度
- 平移:将整页图像作为一个单元移动
- 滑动:滑动标题,在图像页面之间切换
- 双指轻击:用两个手指轻击图像,在自己的视图中打开它
此外,虽然它不是多点触摸手势,但我们将挂接拖动监听器,以便您可以通过用单个手指拖动图像来定位它们。
在 Flash 应用中有两种不同的方法来连接多点触摸监听器。第一种是通过纯 ActionScript,在基于 Flash 或 Flex 的应用中同样适用。第二种是通过使用InteractiveObject
类上的事件挂钩,如果您正在使用 Flex 组件,这是最方便的选择。我们将在这一部分展示两者的例子。
缩放和旋转手势处理
Flash 剪贴簿示例的核心将是一个MultitouchImage
组件,它扩展了 spark Image
类以添加大小调整和旋转功能。对于这个类,我们将使用addEventListener
机制来连接缩放和旋转手势的多点触摸监听器。代码如清单 2–21 所示。
清单 2–21。 MultitouchImage
类添加旋转和调整大小支持
`package com.proandroidflash {
import flash.events.TransformGestureEvent;
import flash.geom.Point;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import mx.events.ResizeEvent;
import spark.components.Image;
publicclassMultitouchImageextends Image {
publicfunctionMultitouchImage() {
addEventListener(ResizeEvent.RESIZE, resizeListener);
addEventListener(TransformGestureEvent.GESTURE_ROTATE, rotateListener);
addEventListener(TransformGestureEvent.GESTURE_ZOOM, zoomListener);
Multitouch.inputMode = MultitouchInputMode.GESTURE;
}
protectedfunction resizeListener(e:ResizeEvent):void {
transformX = width/2;
transformY = height/2;
}
protectedfunction rotateListener(e:TransformGestureEvent):void {
rotation += e.rotation;
}
protectedfunction zoomListener(e:TransformGestureEvent):void {
scaleX *= e.scaleX;
scaleY *= e.scaleY;
}
}
}`
在构造函数中,我们通过调用addEventListener
方法并传入GESTURE_ROTATE
和GESTURE_ZOOM
常量来添加旋转和缩放监听器。旋转回调简单地获取TransformGestureEvent
的旋转参数,并将其添加到节点的当前旋转中,将值保存回来。在这两种情况下,旋转都以度数表示为数值。缩放监听器类似地获取TransformGestureEvent
的scaleX
和scaleY
参数,并将它们乘以节点的scaleX
和scaleY
以获得新值。在这两种情况下,手势事件为您提供了自上次调用侦听器以来的增量,因此您可以简单地增量调整节点值。
为了确保旋转和缩放发生在图像节点的中心,我们将transformX
和transformY
设置在resizeListener
中节点的中点。这也是通过构造函数中的addEventListener
连接的,因此每次节点大小改变时都会触发。
我们做的最后一件事是将inputMode
设置为MultitouchInputMode.GESTURE
,这样事件监听器将被触发。根据我们的需要频繁地设置这个值是安全的,所以我们在每次调用构造函数时都这样做。
对于其余的手势事件,我们将利用InteractiveObject
事件挂钩,通过 MXML 轻松连接;然而,你也可以通过遵循表 2–4 中的类和常量信息,使用addEventListener
机制连接所有其他手势。
按住并拖动鼠标事件
我们将使用的另一个助手类是DraggableGroup
类。这实现了一个标准的指向和拖动隐喻,作为 spark 组的扩展,如清单 2–22 所示。除了有利于封装之外,从手势事件中提取鼠标事件允许您同时处理多个事件。
清单 2–22。 DraggableGroup
实现点和拖动力学的类
`package com.proandroidflash {
import flash.events.MouseEvent;
import mx.core.UIComponent;
import spark.components.Form;
import spark.components.Group;
public class DraggableGroup extends Group {
public function DraggableGroup() {
mouseEnabledWhereTransparent = false;
addEventListener(MouseEvent.MOUSE_DOWN, mouseDownListener);
addEventListener(MouseEvent.MOUSE_UP, mouseUpListener);
}
protected function mouseDownListener(e:MouseEvent):void {
(parent as Group).setElementIndex(this, parent.numChildren-1);
startDrag();
}
protected function mouseUpListener(e:MouseEvent):void {
stopDrag();
// fix for bug in Flex where child elements don’t get invalidated
for (var i:int=0; i<numElements; i++) {
(getElementAt(i) as UIComponent).invalidateProperties();
}
}
}
}`
DraggableGroup
的代码是一个相当简单的 Flex 组件实现,使用与手势代码相同的addEventListener/
回调范例。虽然您可以使用触摸事件实现相同的代码,但是坚持使用鼠标抬起和鼠标放下事件的好处是,即使在没有触摸支持的情况下,UI 的这一部分也可以工作。
代码中的一些微妙之处值得指出。
- 默认情况下,Group 类为其边界区域中的任何单击触发事件。通过将
mouseEnabledWhereTransparent
设置为false
,您可以避免孩子范围之外的误点火。 - 要在单击时引发该对象,需要更改父容器中的顺序。这个实现假设父节点是一个
Group
,并使用setElementIndex
函数将这个节点推到前面。 - Flex 中有一个 bug,在拖动后子元素的布局属性不会失效。在所有子节点上手动调用
invalidateProperties
可以解决这个问题。例如,如果没有此修复,您会注意到旋转/缩放的中心不会随着拖动而平移。
滑动手势处理
为了显示图像,我们将使用一个简单的视图,将各个图像页面的呈现委托给一个ItemRenderer
。首先,我们将看看组成剪贴簿示例主屏幕的View
类。完整的代码如清单 2–23 所示。
清单 2–23。 Flash 剪贴簿主视图代码,滑动事件处理程序以粗体突出显示
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:proandroidflash="com.proandroidflash.*" title="Home" backgroundColor="#333333" destructionPolicy="never"> <fx:Script> <![CDATA[ import mx.core.IVisualElement; [Bindable] protected var page:int = 0; **protected function swipe(e:TransformGestureEvent):void** { page = (page + e.offsetX + pages.numElements) % pages.numElements; updateVisibility(); } protected function updateVisibility():void { for (var i:int=0; i<pages.numElements; i++) { var element:IVisualElement = pages.getElementAt(i); if (element != null) { element.visible = i == page; } } } ]]> </fx:Script> <s:layout> <s:VerticalLayout horizontalAlign="center" paddingTop="10" paddingLeft="10" paddingRight="10" paddingBottom="10"/> </s:layout> <fx:Declarations> <s:ArrayList> <fx:Object image1="@Embed(source='img/cassandra1.jpg')" image2="@Embed(source='img/cassandra2.jpg')" image3="@Embed(source='img/cassandra3.jpg')"/> <fx:Object image1="@Embed(source='img/cassandra4.jpg')" image2="@Embed(source='img/cassandra5.jpg')" image3="@Embed(source='img/cassandra6.jpg')"/> <fx:Object image1="@Embed(source='img/cassandra7.jpg')" image2="@Embed(source='img/cassandra8.jpg')" image3="@Embed(source='img/cassandra9.jpg')"/> <fx:Object image1="@Embed(source='img/cassandra10.jpg')"/> </s:ArrayList> </fx:Declarations> <s:VGroup **gestureSwipe="swipe(event)"**> <s:Label text="Flash Scrapbook" fontSize="32" color="white"/> <s:Label text="Drag, Rotate, and Zoom with your fingers." fontSize="14" color="#aaaaaa"/> </s:VGroup> <s:DataGroup itemRenderer="com.proandroidflash.ScrapbookPage" dataProvider="{images}" width="100%" height="100%" added="updateVisibility()"/> </s:View>
虽然有相当多的代码来创建视图,但是实际的视图定义本身只包含五行来创建标题的VGroup
和显示图像的DataGroup
。剩下的主要是一大块代码,用于简单地将嵌入的图像加载到一个ArrayList
中,以及一些嵌入在脚本标签中的辅助函数。
挂钩 swipe 事件处理程序所需的代码以粗体突出显示。通过利用InteractiveObject
上的gesture*
事件属性,您可以像这样快速地将事件监听器连接到您的应用。每当用户滑动标题 VGroup 时,将调用 protected swipe 方法,其中包含滑动方向的信息。与旋转和缩放事件在手势持续期间不断调用不同,swipe 在手势结束时只被调用一次。您可以通过检查offsetX
和offsetY
属性来解读滑动的方向。
offsetX=0, offsetY=-1: Swipe Up
offsetX=0, offsetY=1: Swipe Down
offsetX=-1, offsetY=0: Swipe Left
offsetX=1, offsetY=0: Swipe Right
值得注意的是,刷卡将总是在水平或垂直方向。不支持对角线滑动,不会被识别为手势。此外,您需要确保您将滑动监听器钩到的组件足够宽或足够高,以提供足够的移动距离来识别手势。一旦用户的手指离开组件,手势识别就会结束。
添加平移和双指轻敲事件监听器
现在我们有了一个带有DataGroup
的主页,我们需要实现引用的ItemRenderer
来构建剪贴簿页面。这也将是MultitouchImage, DraggableGroup
和我们之前定义的主视图之间的链接。
首先创建一个新的 MXML 文件,该文件有一个外部的ItemRenderer
元素,其中声明了页面内容。在这个类中,我们将连接两个新的手势事件监听器。在外部的BorderContainer
上,我们将连接一个平移事件监听器,这样用户就可以用一个手势拖动整个页面和页面上的所有图像。此外,在每个MultitouchImages
上,我们将添加一个双指点击事件监听器,用于切换到全屏视图。
剪贴簿页面实现的完整代码如清单 2–24 所示。
清单 2–24。 Flash ScrapbookPage
项目渲染器代码,平移和双指点击事件处理程序以粗体突出显示
<?xml version="1.0" encoding="utf-8"?> <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:proandroidflash="com.proandroidflash.*" autoDrawBackground="false" width="100%" height="100%"> <s:BorderContainer backgroundColor="#cccccc" borderColor="#555555" **gesturePan="pan(event)"** rotation="5" x="50" width="100%" height="100%"> <fx:Script> <![CDATA[ import spark.components.View; import views.ImageView; **protected function pan(e:TransformGestureEvent):void {** **e.target.x += e.offsetX;** **e.target.y += e.offsetY;** **}** **protected function expand(source:Object):void {** **(parentDocument as View).navigator.pushView(ImageView, source);** **}** ]]> </fx:Script> <proandroidflash:DraggableGroup> <proandroidflash:MultitouchImage source="{data.image1}" y="-70" x="10" width="350" rotation="-3" **gestureTwoFingerTap="expand(data.image1)**"/> </proandroidflash:DraggableGroup> <proandroidflash:DraggableGroup> <proandroidflash:MultitouchImage source="{data.image2}" y="100" x="40" width="350" rotation="13" **gestureTwoFingerTap="expand(data.image2)**"/> </proandroidflash:DraggableGroup> <proandroidflash:DraggableGroup> <proandroidflash:MultitouchImage source="{data.image3}" y="300" x="5" width="350" rotation="-8" **gestureTwoFingerTap="expand(data.image3)**"/> </proandroidflash:DraggableGroup> </s:BorderContainer> </s:ItemRenderer>
平移和双指轻敲事件侦听器的连接方式与我们之前连接滑动事件侦听器的方式类似。平移手势碰巧使用了与滑动手势相同的offsetX
和offsetY
变量,但是具有非常不同的含义。平移事件在用户手势的持续时间内不断被调用,并为offsetX
和offsetY
传递以像素为单位的增量。
对于双指点击手势,我们选择不传入事件,而是替换为包含要显示的图像的上下文相关变量。然后,这作为ViewNavigator's pushView
方法的数据参数传入。
Flash 剪贴簿图片查看
最后一步是实现在pushView
方法调用中引用的ImageView
类。因为 Flex 为我们处理了所有的视图导航逻辑,所以实现非常简单。我们增加的唯一额外功能是另一个双指轻击手势,这样你就可以导航回主视图,而不用点击 Android 的后退按钮。
ImageView
类的代码如清单 2–25 所示。
清单 2–25。 ImageView
代码,用粗体字突出显示的双指点击事件处理程序
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Image Viewer" backgroundColor="#333333"> <s:Image source="{data}" width="100%" height="100%" **gestureTwoFingerTap="navigator.popView**()"/> </s:View>
简单表达式可以内联在事件处理程序中,就像我们在这里所做的那样。这避免了创建Script
标签的需要,使得代码清单非常简洁。
这也完成了 Flash 剪贴簿应用的最后一个文件,所以你现在可以给它一个测试驱动器。启动应用后,您应该会看到类似于图 2–14 所示的屏幕。
在应用的此页面上,尝试执行以下操作:
- 通过用一个手指按住并拖动来在画布上拖动图像—这将练习
DraggableGroup
。 - 用两个手指按下并向相反的方向拖动来放大图像——这可以锻炼
MultitouchImage
上的缩放收听者。 - 用两个手指按下并在圆圈中拖动来旋转图像——这将练习
MultitouchImage
上的旋转监听器。 - 用一个手指在“Flash 剪贴簿”上水平滑动以改变页面,这可以锻炼主视图上的滑动听者。
- 将图像拖到一边,这样你就可以看到背景,并用两个手指拖过背景,平移场景——这练习了
ScrapbookPage
上的平移监听器。 - 用两个手指点击其中一个图像并切换到
ImageView—
这将练习连接到每个MultitouchImage
的两个手指点击监听器。
图 2–14。 首页查看页面完成 Flash 剪贴簿申请
完成最后一步后,您将进入应用的ImageView
页面,如图图 2–15 所示。要返回主视图,你可以用两个手指再次点击图像,或者使用 Android 的后退按钮。
图 2–15。 完成了图片浏览器页面上的 Flash 剪贴簿示例
通过完成这个简单的示例,您已经成功探索了 Flash Android 上所有可用的手势事件。尝试在自己的应用中以创新的方式使用这些手势!
接触点 API
处理多点触摸输入的另一种方法是使用触摸点 API 来直接访问设备上生成的事件。这使您可以根据自己的应用需求进行定制的多点触摸处理。要确定您的设备是否支持触摸事件处理,您可以查询Multitouch
对象,如清单 2–26 所示。
清单 2–26。 打印出是否支持触摸事件的代码片段
trace("Supports touch events: " + Multitouch.supportsTouchEvents);
由于处理触摸事件与手势识别直接冲突,您需要更改应用的输入模式来开始接收触摸点事件。这可以通过将Multitouch.inputMode
变量设置为TOUCH_POINT
来实现,如清单 2–27 所示。
清单 2–27。 启用触摸点事件的代码片段
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
**注意:**将inputMode
设置为TOUCH_POINT
将禁止识别任何手势,如缩放、旋转和平移。
在被调度的事件的数量和类型方面,接触点 API 是相当低级的。您可以注册并监听表 2–5 中列出的任何事件,只要目标对象扩展InteractiveObject
。
大多数触摸事件都是不言自明的,但是touchOver, touchOut, touchRollOver,
和touchRollOut
可能会有点混乱。例如,以三个嵌套的矩形为例,分别标记为 A(外部)、B(中间)和 C(内部)。当手指从 A 滚动到 C 时,您会收到以下滚动事件:
**touchRollOver(A)** -> touchRollOver(B) -> touchRollOver(C)
同时,您还会收到以下溢出/溢出事件:
**touchOver(A) -> touchOut(A) / touchOver(B) -> touchOut(B) / touchOver(C)**
矩形 A 将直接接收的事件以粗体突出显示。滚动事件不会传播,所以您只收到一个touchRollOver
事件。然而,touchOver/Out
事件确实会传播到父节点,所以除了两个额外的touchOut
事件之外,您还会收到三个touchOver
事件。
记住这些如何工作的最简单的方法是将滚动事件与滚动效果的实现相关联。对于翻转效果,如果节点或其任何子节点被触摸,您通常希望显示该效果,这是touchRollOver
的语义。
卡特彼勒发电机示例
作为触摸点 API 的一个简单示例,我们将指导您如何创建一个支持多点触摸的 Flash 应用,当您在屏幕上拖动手指时,该应用会生成毛虫。
首先,我们将创建一些可用于构建示例的艺术素材:
- *背景:*创建一个名为 Background 的图层,作为应用的背景。我们选择使用图案画笔来绘制一个供毛毛虫爬行的虚拟花园,但要尽可能有创意。
- *绿球:*制作一个名为绿球的简单电影剪辑,它将组成毛毛虫的身体。为此,我们在一个椭圆形的图元上做了一个简单的径向渐变。
- Blue Ball: 制作一个名为 Blue Ball 的简单电影剪辑,它将组成毛毛虫的备用身体。为此,我们在椭圆上做了另一个径向渐变。
- *红球:*创建一个名为红球的电影剪辑,其中包含一系列将在毛毛虫上显示的面孔。确保在每一帧上编码一个
stop()
,这样我们就可以一次一个地浏览它们。
应用逻辑的机制非常简单。当用户在屏幕上拖动他或她的手指时,我们将在当前的一个或多个触摸位置为毛虫的身体不断创建新的球对象。一旦用户的手指离开屏幕,我们将绘制毛毛虫的头部。此外,如果用户点击其中一个卡特彼勒头,我们将播放电影来改变所显示的面孔。
为了完成这个,我们需要引入TouchEvent
类,它由每个触摸事件回调函数返回。TouchEvent
上与本例相关的变量包括:
stageX/stageY:
在全局坐标中指定的触摸事件发生的位置;要获得相对于当前Sprite,
的位置,请使用localX/localY
来代替。pressure:
显示屏上使用的压力量(通常与尺寸有关);这是依赖于设备的,所以你不能指望它在所有设备上都可用。- 正在交互的对象
isPrimaryTouchPoint:
这是注册的第一个触摸点还是后来添加的附加触摸点;我们将用这个给毛毛虫涂上不同的颜色。
卡特彼勒发电机应用的完整代码列表如列表 2–28 所示。你可能想把它放在一个叫做 Actions 的单独层的第一帧,以区别于程序中的图形元素。
清单 2–28。 卡特彼勒发电机应用的代码列表
`import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.TouchEvent;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
stage.addEventListener(TouchEvent.TOUCH_BEGIN, beginListener);
stage.addEventListener(TouchEvent.TOUCH_MOVE, moveListener);
stage.addEventListener(TouchEvent.TOUCH_END, endListener);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyListener);
var lastScale:Number;
var startX:Number;
var startY:Number;
function beginListener(event:TouchEvent):void {
lastScale = 0;
}
function moveListener(event:TouchEvent):void {
var ball;
if (event.isPrimaryTouchPoint) {
ball = new GreenBall();
} else {
ball = new BlueBall();
}
ball.x = event.stageX;
ball.y = event.stageY;
lastScale = Math.max(lastScale, event.pressure*7);
ball.scaleX = lastScale;
ball.scaleY = lastScale;
addChild(ball);
}
function endListener(event:TouchEvent):void {
var ball = new RedBall();
ball.x = event.stageX;
ball.y = event.stageY;
ball.scaleX = lastScale;
ball.scaleY = lastScale;
ball.addEventListener(TouchEvent.TOUCH_MOVE, ballMoveListener);
ball.addEventListener(TouchEvent.TOUCH_TAP, changeFace);
addChild(ball);
}
function ballMoveListener(event:TouchEvent):void {
event.stopImmediatePropagation();
}
function changeFace(event:TouchEvent):void {
event.target.play();
}
function keyListener(event:KeyboardEvent):void {
if (event.keyCode = Keyboard.MENU) {
clearAll();
}
}
function clearAll():void {
for (var i:int=numChildren-1; i>=0; i–) {
if (getChildAt(i).name != “background”) {
removeChildAt(i);
}
}
}`
注意,我们将事件监听器添加到了Stage
中,而不是背景中。这样做的原因是,当在手指下添加额外的节点以组成卡特彼勒身体时,它们会遮挡背景,从而防止触发额外的移动事件。但是,stage 会接收所有事件,而不管它们发生在哪个对象中。
向Stage
添加事件侦听器是一把双刃剑,因为这也意味着接收任何点击事件极其困难。为了防止 caterpillar 脸上的移动事件蔓延到舞台,我们称之为event.stopImmediatePropogation().
,这允许在不受舞台事件监听器干扰的情况下处理点击手势。
我们使用的另一个技术是通过使用Math.max
函数来确保每个后续添加的球都比前一个大。这确保了即使当用户将他或她的手指从屏幕上移开时压力减小,卡特彼勒视角也保持不变。
在设备上运行时,最终应用看起来应该类似于图 2–16。
图 2–16。??【毛虫发电机】应用显示杂草中的几条毛虫
这个应用也可以作为一个粗略的性能测试,因为它不断地生成Sprites
并将其添加到 stage 中。您可以通过按下手机上的菜单按钮随时清除场景和重置应用,该按钮已连接到clearAll
功能。
尝试为pressure
使用不同的乘数来调整应用,以在您的设备上获得最佳性能。
总结
在本章中,您学习了如何设计和构建充分利用移动平台的应用。您将能够在未来的移动项目中应用的一些要点包括:
- 不仅要考虑屏幕分辨率,还要考虑密度
- 如何计算设备无关像素并利用
applicationDPI
- 为纵向和横向模式定制您的应用布局
- 通过多点触控手势提高应用的可用性
- 如何在应用中使用和处理原始触摸事件
我们将在整本书中继续使用这些概念来构建更加复杂和强大的应用,但是您应该已经有了一个设计自己的移动用户界面的良好开端。
三、为 Android 构建 Flash 和 Flex 应用
第一章和第二章介绍了如何使用 Flash 和 Flex 作为创建移动应用的平台。现在,您已经了解了选择 Flash 平台的原因,以及为使用触摸手势作为主要用户输入形式的各种屏幕设备编写应用时需要考虑的一些事项。下一步是着手编写自己的应用。在本章结束时,您将知道如何在各种类型的 Flex 应用之间做出选择,如何编写自己的View
以及如何使用 Flex SDK 中的移动就绪控件为这些View
提供丰富的内容。
简而言之,是时候向您展示将您的应用想法变为现实所需的一切了!
用 Flex 构建移动用户界面
由于其便利性和开发人员生产力特性,MXML 是定义 Flex 移动应用主用户界面的首选方式。然而,MXML 的便利性是以性能为代价的。因此,有些任务,比如List
项目渲染器,最好在纯 ActionScript 中完成。我们将在第十章中更深入地讨论这个特殊的话题。
由于屏幕尺寸较小,大多数移动应用被分成多个View
应用。因此,大多数 Android 应用的 AIR 都是用ViewNavigatorApplication
或TabbedViewNavigatorApplication
构建的也就不足为奇了。这些应用容器负责初始化和连接所有与应用View
相关的东西。这包括一个或多个以下组件:
ViewNavigator
:这个类处理将一组View
链接在一起,来回传递数据,并在View
之间转换。ViewNavigator
还拥有应用的ActionBar
,它显示当前View
的标题、可选的动作控件(通常是按钮)和可选的导航控件(通常是 Home 或 back 按钮)。- 这些 Flex 组件提供了应用的大部分实际接口。每个
View
都有一个对其ViewNavigator
和ActionBar
的引用。每个View
可以用自己的内容填充ActionBar
,甚至完全隐藏它。View
s 根据用户交互使用ViewNavigator
触发其他View
s 的显示。
图 3–1 显示了纵向和横向手机的基本构造ViewNavigatorApplication
。源代码可以在位于本书示例代码的chapter-03
目录中的 ViewAppAnatomy 示例项目中找到。
图 3–1。 屈曲移动的基本解剖ViewNavigatorApplication
在图 3–1 中,应用的ActionBar
在屏幕顶部伸展。由三个区域组成:导航区域、标题区域和动作区域。图 3–1 中的ActionBar
在ActionBar
导航区域包含一个标记为 Nav 的按钮,而标题区域显示的是“ActionBar
字符串。ActionBar
的操作区包含两个标记为 A1 和 A2 的按钮。View
的内容区域由ActionBar
下方的屏幕其余部分组成。请记住,尽管View
使用ActionBar
来显示它的标题和控件,但这两者在组件层次结构中是兄弟。除非ViewNavigator
的overlayControls
属性设置为true
,否则View
的width
和height
不包括ActionBar
所占的面积。如果overlayControls
设置为true
,那么ActionBar
以及TabbedViewNavigatorApplication
的标签栏将部分透明,这样它们下面的任何View
内容都是可见的。
作为这种基于View
的应用结构的替代方案,您也可以从一个普通的Application
MXML 文件开始创建一个完全定制的界面,就像您对基于 web 或基于桌面的应用所做的那样。
如果您使用的是 Flash Builder 4.5,您可以单击应用的文件菜单并选择新建 Flex 移动项目。在你命名你的新项目并点击 Next 按钮后,你可以选择从一个普通的旧Application
、一个ViewNavigatorApplication
或者一个TabbedViewNavigatorApplication
开始。我们将在接下来的几节中研究这三种选择之间的区别。
查看导航应用
ViewNavigatorApplication
创建一个单一的ViewNavigator
来管理整个移动应用的View
之间的转换。应用容器还有一个firstView
属性,该属性决定应用启动时将显示哪个View
组件。清单 3–1 显示了一个非常基本的ViewNavigatorApplication
的代码。这段代码来自本书示例代码的examples/chapter-03
目录中的 HelloView 示例项目。
清单 3–1。 一个简单的开始:你的第一个 Flex 移动ViewNavigatorApplication
`
<?xml version=”1.0″ encoding=”utf-8″?>
<s:ViewNavigatorApplication xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
splashScreenImage=“@Embed(‘assets/splash.png’)”
firstView=“views.FirstView”>
</s:ViewNavigatorApplication>
<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
title=“Hello World”>
<s:VGroup width=“100%” horizontalAlign=“center” gap=“20” top=“20” left=“10”
right=“10”>
<s:Label text=“This is a ViewNavigatorApplication.” width=“100%”
textAlign=“center”/>
<s:Button label=“Next View” click=“navigator.pushView(SecondView)”/>
</s:VGroup>
</s:View>
<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
title=“Hello Again”>
<s:Label text=“Press the back button to return to the first view.” top=“20”
left=“10” right=“10”/>
</s:View>`
太神奇了!在大约 20 行代码中,我们有了一个全功能的移动应用,它有多个View
和它们之间的动画转换。这就是为什么 Flex 是移动开发的一个令人信服的选择。Adobe 的团队让您可以轻松快速地开始开发 Android 移动应用。
使用 XML 属性将ViewNavigatorApplication
容器的firstView
属性设置为views.FirstView
。创建项目时,Flash Builder 会在应用的默认包下创建一个视图包。所以views.FirstView
是FirstView.mxml
文件的全限定路径名。此外,我们已经在我们的ViewNavigatorApplication
上指定了一个splashScreenImage
属性。这通常是一个好主意,因为移动应用有时需要一段时间才能启动。@Embed
指令将这个图像嵌入到应用中,这样它就可以在启动时显示。
在清单 3–1 中的应用容器的正下方显示了FirstView
的源代码。文件中的根组件是一个 Spark View
组件。<s:View>
标签的title
属性指定了当View
被激活时将在ActionBar
中显示的字符串。像任何 MXML 文件一样,View
标签的子元素指定了构成View
用户界面的各种 Spark 组件。在这种情况下,我们有一个火花Label
和Button
布置在一个垂直组或VGroup
内。
注意,Button
的click
事件处理程序调用了navigator
对象的pushView
函数,这是View
对应用的ViewNavigator
实例的引用。这个方法的第一个参数是应该显示的View
的类名。在这种情况下,我们告诉navigator
接下来显示SecondView
。而SecondView
则只是简单的指示用户使用 Android 内置的“返回”按钮返回到FirstView
。在SecondView
的代码中没有对navigator
对象进行显式调用。这是可能的,因为ViewNavigator
自动添加一个监听器到 Android 的后退按钮。由于ViewNavigator
还维护一个已经显示的View
的堆栈,它可以从堆栈中弹出最近的View
并返回到前一个View
以响应“后退”按钮的按下,而无需应用开发人员编写任何额外的代码。第一个 Hello World 应用在图 3–2 中运行。
图 3–2。 一个简单的ViewNavigatorApplication
Hello World 节目
视图生命中的重要事件
事件是任何 Flex 和 Flash 应用的生命线。它们不仅允许您对应用中正在发生的事情做出反应,而且知道事件到达的顺序也很重要,这样您就可以选择适当的处理程序来放置程序逻辑。图 3–3 显示了在三个应用阶段接收一些更重要事件的顺序:启动、关闭和从一个View
到另一个View
的转换。图中代表应用容器接收到的事件的方框是深色的,而View
s 接收到的事件是浅色的。
图 3–3。 应用及其View
接收重要事件的顺序
在创建第一个View
之前,应用接收到了initialize
事件。因此,我们知道如果您需要以编程方式而不是作为 XML 属性中的简单字符串来设置ViewNavigatorApplication
的firstView
和firstViewData
属性,那么initialize
处理程序是一个很好的地方。一个方便的例子是,当您在关机期间保存数据,并希望在下次启动时读回数据并恢复应用的View
状态。
在应用接收到initialize
事件后,第一个View
将接收到它的initialize
、creationComplete
和viewActivate
事件。当设置你的View
s 时,记住这个顺序是很重要的。如果你需要在你的控件上编程设置一些初始状态,如果可能的话,最好在initialize
处理程序中完成。如果您一直等到调用creationComplete
处理程序,那么控件实际上被初始化了两次。这可能不会造成明显的延迟,但在为移动平台开发时,意识到性能问题总是值得的。同样,viewActivate
事件将是你在View
启动序列中发表意见的最后机会。
一旦第一个View
完成其初始启动序列,应用将接收其creationComplete
和activate
事件。那么第一个View
也会收到最后一个activate
事件。只有应用的第一个View
会收到activate
事件。只有当应用运行时第一个创建的是View
时,该事件处理程序才是您想要运行的代码的合适位置。
在一个View
转换序列中,新的View
将在旧的View
接收其viewDeactivate
事件之前接收其initialize
和creationComplete
事件。尽管旧的View
仍然有效,但是您应该避免View
之间的相互依赖。任何需要从一个View
传递到下一个View
的数据都可以使用新的View
的data
参数来传递。我们将在本章的后面向您展示如何做到这一点。View
转换序列的最后一步是新的View
接收其viewActivate
事件。关于这个序列需要记住的重要事情是,在ViewNavigator
播放到新View
的动画过渡之前,新View
将接收到initialize
和creationComplete
事件。viewActivate
事件将在过渡播放后接收。如果你想让新的View
控件在转场播放时处于某个特定状态——并且它们对用户可见——你需要使用initialize
或creationComplete
事件。同样,initialize
是首选,这样控件就不会被初始化两次。另一方面,在过渡播放之前做大量的处理将导致用户输入和View
过渡开始之间的明显滞后,这将导致您的界面感觉迟钝。因此,尽可能将处理延迟到viewActivate
事件是一个好主意。
当应用关闭时,应用容器将接收到deactivate
事件,随后是View
堆栈中的每个View
。如果一个View
被实例化多次,它将接收多个deactivate
事件,每个实例一个。在移动环境中,关闭并不总是意味着应用从内存中删除。例如,在 Android 中,当一个应用正在运行时按下“home”按钮将导致该应用接收其停用事件。但是应用还没有退出;它只是在后台运行。如果您真的希望您的应用在停用时退出,您可以从应用容器的deactivate
处理程序中调用NativeApplication
类中的exit
函数,如清单 3–2 所示。
清单 3–2。 使移动应用在停用时完全退出
<?xml version="1.0" encoding="utf-8"?> <s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" deactivate="onDeactivate()" firstView="views.FirstView"> <fx:Script> <![CDATA[ privatefunction onDeactivate():void { NativeApplication.nativeApplication.exit(); } ]]> </fx:Script> </s:ViewNavigatorApplication>
选项卡式视图导航应用
一个TabbedViewNavigatorApplication
允许你划分你的应用,这样每一组与特定应用功能相关的View
就有了自己的标签。例如,股票投资组合应用可能有一个选项卡,允许您查看您的投资组合,其中一个View
显示您拥有的所有股票,另一个详细的 View
允许您详细检查某一特定股票。另一个标签可能会显示市场新闻,其中的View
会列出新闻,而另一个标签会让你查看单个新闻。最后,您可能会有一个帐户选项卡,让您管理您的帐户设置。
在这个场景中,每个选项卡都有自己的ViewNavigator
,负责管理与该选项卡相关联的View
。您在应用容器的 MXML 文件中定义这些ViewNavigator
,如清单 3–3 所示。
清单 3–3。 宣告一个TabbedViewNavigatorApplication
`<?xml version=”1.0″ encoding=”utf-8″?>
<s:TabbedViewNavigatorApplication xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”>
<s:ViewNavigator label=“Hello” width=“100%” height=“100%”
icon=“@Embed(‘assets/smiley.png’)”
firstView=“views.HelloView”/>
<s:ViewNavigator label=“World” width=“100%” height=“100%”
icon=“@Embed(‘assets/globe.png’)”
firstView=“views.WorldView”/>
</s:TabbedViewNavigatorApplication>`
您可以将您的ViewNavigator
声明包含在一个<s:navigators>
标记中,但是这是不必要的,因为navigators
属性是TabbedViewNavigatorApplication
的默认属性。这允许我们将我们的ViewNavigator
声明为TabbedViewNavigatorApplication
的直接子元素。ViewNavigator
s 的width
和height
设置为 100%。如果您想要正确调整View
的大小,这是必需的。否则,它们将只有容纳其内容所需的大小。一个ActionBar
只延伸到屏幕顶部的一部分,看起来有点奇怪!图标的大小也很关键。选项卡组件不会尝试调整图像的大小。如果它太大,你的标签会占据整个屏幕。虽然在这个简单的例子中我们没有这样做,但是在实际的应用中,你会希望使用一个MultiDPIBitmapSource
来定义你的标签图标,这样它们在所有的设备屏幕上都很好看。
与常规ViewNavigatorApplication
的另一个区别是firstView
属性是在ViewNavigator
上定义的,而不是在TabbedViewNavigatorApplication
中定义的。这是有意义的,因为每个ViewNavigator
管理自己的一组View
。在 MXML 文件中声明的第一个ViewNavigator
是应用启动时默认激活的。清单 3–4 显示了组成该应用 Hello 选项卡的两个View
的 MXML,即HelloView
和LanguageView
。
清单 3–4。 我们的TabbedViewNavigatorApplication
中 Hello 页签的两个视图
`
<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
initialize=“onInitialize()”
title=“Hello” >
fx:Script
<![CDATA[
import spark.events.IndexChangeEvent;
privatefunction onChange(event:IndexChangeEvent):void {
data.selectedIndex = event.newIndex;
navigator.pushView(LanguageView, listData.getItemAt(event.newIndex));
}
/**
* Initializes the data object if it does not exist. If it does,
* then restore the selected list index that was persisted.
*/
privatefunction onInitialize():void {
if (data == null) {
data = {selectedIndex: -1};
}
helloList.selectedIndex = data.selectedIndex;
}
]]>
</fx:Script>
<s:List id=“helloList” width=“100%” height=“100%” labelField=“hello”
change=“onChange(event)”>
<s:ArrayCollection id=“listData”>
<fx:Object hello=“Hello” lang=“English”/>
<fx:Object hello=“Hola” lang=“Spanish”/>
<fx:Object hello=“nuqneH” lang=“Klingon”/>
</s:ArrayCollection>
</s:List>
</s:View>
<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
initialize=“onInitialize()”
title=“Language”>
fx:Script
<![CDATA[ privatefunction onInitialize():void { hello.text = data.hello; lang.text = “(“+data.lang+”)”; } ]]>
</fx:Script>
<s:VGroup horizontalAlign=“center” width=“100%” paddingTop=“20”>
<s:Label id=“hello” fontSize=“36”/>
<s:Label id=“lang” fontSize=“36”/>
</s:VGroup>
</s:View>`
HelloView
用两个属性定义了对象的静态ArrayCollection
:一个hello
属性包含用某种特定语言写的单词“Hello ”,另一个lang
属性指定该语言是什么。这个ArrayCollection
然后被用作View
中显示的火花List
的dataProvider
。关于这个View
要注意的第一件事是,当显示其他View
时,它使用它的data
属性为自己保存数据。这在onInitialize
功能中完成。如果View
的data
对象为空,换句话说,如果这是第一次初始化View
,那么使用 ActionScript 的对象文字符号创建一个新的data
对象。否则,现有的data
对象——在其他View
显示时保持不变——用于检索之前在List
中选择的项目的索引,并在View
重新激活时重新选择它。
HelloView
源代码还演示了如何将数据传递给另一个View
,正如在List
的onChange
处理程序中所做的那样。当用户在HelloView
的List
中选择一个项目时,onChange
处理程序会首先将IndexChangeEvent
的newIndex
属性保存在自己的data
对象中,这样在下次激活View
时可以恢复List
选择。然后,处理函数使用同一个newIndex
属性从ArrayCollection
中获取对所选对象的引用。它将对象作为ViewNavigator
的pushView
函数的第二个参数传递给LanguageView
的data
属性。在清单 3–4 的底部,您可以看到LanguageView
的代码用两个Label
组件向用户显示了data
对象的hello
和lang
属性,这两个组件的text
属性被绑定到data
对象的属性。
图 3–4 显示了在 Hello TabbedView 示例应用中运行的 Hello 选项卡的这两个View
。这个项目的源代码可以在本书示例代码的examples/chapter-03
目录中找到。
**图 3–4。***Hello WorldTabbedViewNavigatorApplication
*的 Hello 标签下的View
那关于世界选项卡呢?世界标签包含一个View
,它的名字很有创意,叫做WorldView
。不直观地说,它不包含地球的图片。相反,它展示了基于 GUI 的 Hello World 程序的另一个主要部分:问候消息。图 3–5 展示了这种View
的作用。
**图 3–5。***Hello WorldTabbedViewNavigatorApplication
*的 World 标签下的View
这个特定实现的独特之处在于,它演示了ActionBar
可以包含任何种类的 Spark 控件,而不仅仅是按钮。清单 3–5 展示了这是如何实现的。
清单 3–5。 一个问候消息的简单实现
`<?xml version=”1.0″ encoding=”utf-8″?>
<s:View xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”>
fx:Script
<![CDATA[
import spark.events.TextOperationEvent;
privatefunction onChange(event:TextOperationEvent):void {
viewLabel.text = “Hello, “+textInput.text;
}
]]>
</fx:Script>
<s:titleContent>
<s:TextInput id=“textInput” prompt=“Enter your name…” width=“100%”
change=“onChange(event)”/>
</s:titleContent>
<s:VGroup horizontalAlign=“center” width=“100%” paddingTop=“20”>
<s:Label id=“viewLabel” text=“Hello, World” fontSize=“44”/>
</s:VGroup>
</s:View>`
通常显示View
标题字符串的View
的titleContent
,已经被一个 Spark TextInput
控件取代。TextInput
的change
处理程序只是复制已经输入到Label
的text
属性中的字符。这是一个很好的例子,说明了定制ActionBar
的灵活性。
只是一个应用
创建新的 Flex mobile 项目的第三个选项是从空白应用开始。如果您正在处理一个独特的移动应用,它的界面不使用多“视图”的典型模式,那么您可以选择这个选项。如果您正在处理一个只有一个View
的应用,因此您不需要ViewNavigator
和它带来的所有东西,那么您也可以利用这个选项。当您选择从一个空白的应用开始时,您将得到的正是这个,如清单 3–6 所示。
清单 3–6。 一个空白的手机应用
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"> <fx:Declarations> <!-- Place non-visual elements (e.g., services, value objects) here --> </fx:Declarations> </s:Application>
然而,您从一个空白应用开始并不意味着您不能利用 Flex 4.5 中包含的特定于移动设备的控件。举个例子,你正在开发一个只有一个屏幕的应用。为一个屏幕设置一个完整的基于View
的系统是不值得的。但是这并不意味着你不能使用一个ActionBar
来让你的应用看起来更像一个传统的移动应用!清单 3–7 展示了一个应用,它最初是一个空白的应用,现在看起来像任何其他 Flex 移动应用。
清单 3–7。 一款没有ViewNavigator
的 Flex 移动应用
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"> <fx:Declarations> <s:SkinnablePopUpContainer x="{(width-myAlert.width)/2}" y="{(height-myAlert.height)/2}"> <s:Panel title="Nice Button Click!"> <s:VGroup horizontalAlign="center" paddingTop="20" paddingBottom="20" paddingLeft="20" paddingRight="20" gap="20" width="100%"> <s:Label text="You clicked on an ActionBar button."/> <s:Button label="OK" click="myAlert.close()"/> </s:VGroup> </s:Panel> </s:SkinnablePopUpContainer> </fx:Declarations>
` <s:ActionBar id=“ab” left=“0” right=“0” top=“0” title=“Just an App”
titleAlign=“center”>
<s:navigationContent>
<s:Button label=“Nav” click=“myAlert.open(this, true)”/>
</s:navigationContent>
<s:actionContent>
<s:Button label=“Act” click=“myAlert.open(this, true)”/>
</s:actionContent>
</s:ActionBar>
<s:VGroup top=“100” horizontalCenter=“0” horizontalAlign=“contentJustify”>
<s:Label text=“ActionBars are just another component.
They can even be placed on the:”
width=“400” fontSize=“32” textAlign=“center”/>
<s:Button label=“Top” click=“ab.top=0;ab.bottom=null;ab.left=0;ab.right=0”/>
<s:Label text=“or” fontSize=“32” textAlign=“center”/>
<s:Button label=“Bottom” click=“ab.top=null;ab.bottom=0;ab.left=0;ab.right=0”/>
</s:VGroup>
</s:Application>`
这个应用声明了它自己的ActionBar
,就像它声明任何其他组件一样。事实上,该应用允许您将ActionBar
放在屏幕的顶部或底部。在这个清单中定义的ActionBar
在其navigationContent
和actionContent
中也包含显示弹出容器的按钮。SkinnablePopupContainer
是可用于移动应用的新型火花控制器之一。Android 界面的一个常见设计模式是,长时间点击组件可以显示一个弹出容器,允许用户选择更多选项。SkinnablePopupContainer
是如何在 Flex 移动应用中实现这种模式。我们将在本章后面更详细地介绍 Android 设计模式和 Flex 移动组件。Figure 3–6 显示了该应用运行时,底部的ActionBar
和弹出容器是可见的。
不过,要明确的是,ActionBar
s 属于屏幕的顶部;不要在家里尝试,我们是专业人士!在前面的例子中,我们已经多次提到了ViewNavigator
、View
和ActionBar
。在下一节中,我们将更深入地探讨移动 Flex 应用的这些主要部分。
图 3–6。 一个带有ActionBar
组件和一个可见弹出容器的应用
视图导航器和视图
ViewNavigator
是一个可设置皮肤的容器,它保存了一堆View
对象,其中在任何给定的时间,只有栈顶的View
是可见的和活动的。将新的View
压入堆栈会自动播放动画过渡并显示新的View
。要返回到前一个View
,应用只需将顶部的View
弹出堆栈。
ViewNavigator
还显示一个ActionBar
,它显示由激活的View
定义的上下文信息。每当显示新的View
时,ViewNavigator
自动更新ActionBar
。ViewNavigator
类中主要感兴趣的方法如下:
pushView
:推一个新的View
到堆栈上,自动使其在屏幕上可见;这个函数的第一个参数是要显示的View
的类。该方法还有另外三个可选参数:一个将被传递给新的View
的data
对象,一个由ViewNavigator
存储并可由新的View
读取的context
对象,以及一个在View
之间播放的transition
。我们将在本章的后面详细介绍这三个可选参数。popView
:从堆栈中删除当前View
,显示前一个View
;该函数有一个可选参数:在View
之间播放的transition
popToFirstView
:从堆栈中移除所有的View
,除了第一个,然后第一个变成可见的View
;该函数也接受一个transition
参数。popAll
:从堆栈中删除所有View
并显示一个空白屏幕;transition
参数也是可选的。hideActionBar
/showActionBar
:隐藏或显示ActionBar
;一些移动应用可以通过点击ActionBar
中的控件来选择全屏显示。在全屏模式下,轻击屏幕会再次显示ActionBar
。这些方法可用于在您的 Flex 应用中实现这样的系统。默认情况下,ActionBar
的隐藏和显示是动态的,但是您可以向这些函数传递一个Boolean
参数来关闭它。
ViewNavigator
将自动处理 Android 后退按钮的按压,并代表应用调用popView
。ActionBar
、View
和ViewNavigator
类都合作为移动开发者提供了许多这样的特性。本节的其余部分将探讨这些类如何协同工作,为您提供一个高效、灵活、健壮的框架来开发您的移动应用。
动作栏
在移动应用中,ActionBar
是View
标题和控件的传统位置。ActionBar
有三个不同的区域:导航区域、标题区域和动作区域。回头参考图 3–1 中显示这些区域的示例。这三个区域都可以包含任意控件,但是默认情况下,标题区域将显示一个标题字符串。尽管标题区域也可以显示任意的控件,但是如果给了它可显示的替代内容,它就不会显示标题字符串。
每个ViewNavigator
都有一个由导航器实例化的View
共享的ActionBar
控件。因此,一个ViewNavigatorApplication
对于整个应用将只有一个ActionBar
,而一个TabbedViewNavigatorApplication
对于应用中的每个ViewNavigator
将有一个单独的ActionBar
。ActionBar
有七个属性决定了它的内容和布局。
actionContent
:决定在ActionBar
的动作区域(标题的右边)显示什么的控件数组actionLayout
:Spark 布局,允许由actionContent
数组指定的控件的自定义布局navigationContent
:一个控件数组,决定在ActionBar
的导航区域(标题的左边)显示什么navigationLayout
:Spark 布局,允许由navigationContent
数组指定的控件的自定义布局title
:如果titleContent
为空,将在标题区显示的字符串titleContent
:决定在ActionBar
的标题区域(ActionBar
的中心)显示什么的控件数组titleLayout
:Spark 布局,允许由actionContent
数组指定的控件的自定义布局
这七个属性在ViewNavigatorApplication
、ViewNavigator
和View
类中被复制。如果您在ViewNavigatorApplication
中为这些属性赋值,您实际上是为整个应用定义了ActionBar
的默认外观。在ViewNavigator
中定义这些属性作为所有View
的默认属性,这些属性将由ViewNavigator
显示。在TabbedViewNavigatorApplication
s 中,这是指定默认ActionBar
设置的唯一方式——每个ViewNavigator
一次。当显示一个新的View
时,它的ViewNavigator
将用那个View
的ActionBar
相关属性更新ActionBar
,从而覆盖应用或导航器指定的任何默认值。此外,View
s 还有一个额外的属性,actionBarVisible
,它决定了在显示View
时,是否应该显示ActionBar
。
我们已经展示了在导航和操作区域显示控件的示例应用,以及用TextField
替换标题内容的应用,所以在本节中我们不再重复这些示例。您可能会发现有用的另外一条信息是影响ActionBar
外观的两种特殊样式的使用。titleAlign
风格允许您将标题字符串的对齐方式设置为left
、right
或center
。defaultButtonAppearance
风格可以设置为normal
或beveled
。在 Android 上,这些默认为左对齐的标题和正常的按钮外观。您可以根据应用的需要更改它们,或者如果您计划将应用移植到 iOS 平台,也可能需要更改它们。在这种情况下,iOS 上的通常会有倾斜的按钮和居中的标题。图 3–7 显示了这种情况。
图 3–7。 一个机器人ActionBar
穿着 iPhone 去约会
将斜面样式应用到defaultButtonAppearance
甚至为导航内容中的按钮添加了传统的 iOS 箭头形状。微小的触动会让一切变得不同。清单 3–8 显示了创建图 3–7 的ActionBar
外观的代码。
清单 3–8。 一个 iOS 风格的ActionBar
,有格调!
`<?xml version=”1.0″ encoding=”utf-8″?>
<s:ViewNavigatorApplication xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
firstView=“views.MainHomeView”>
fx:Style
@namespace s “library://ns.adobe.com/flex/spark”;
s|ActionBar {
titleAlign: “center”;
defaultButtonAppearance: “beveled”;
chromeColor: “0x7893AB”
}
</fx:Style>
<s:navigationContent>
<s:Button label=“Back”/>
</s:navigationContent>
</s:ViewNavigatorApplication>`
我们已经利用了在 MXML 文件中直接为ViewNavigatorApplication
定义navigationContent
的能力。由于这种布局,后退按钮会出现在整个应用的每个View
上。除了titleAlign
和defaultButtonAppearance
样式,我们还为ActionBar
定义了自定义颜色。ActionBar
将使用chromeColor
样式作为填充ActionBar
背景的渐变的基础。为ActionBar
定义一个定制chromeColor
是定制一个移动应用以实现品牌或独特性的常见方式。
动画视图过渡
View
过渡控制一个View
替换另一个View
时播放的动画。当按下一个新的View
时,默认的过渡是向左一个SlideViewTransition
,而弹出一个View
会向右一个SlideViewTransition
。这两种默认转换都使用其push
模式。然而,您可以用无数不同的方式定制View
过渡动画。Table 3–1 显示了转换及其模式和方向。
当你在组合中加入不同的 easers 时,你真的有大量的组合可以玩。例如,uncover
模式中的SlideViewTransition
和向下方向看起来像当前的View
正在向下滑动离开屏幕,以显示位于其下方的新的View
。再放一个Bounce
画架,顶部的View
会滑下来,碰到屏幕底部会反弹。您甚至可以通过扩展ViewTransitionBase
类来编写自己的自定义过渡,通过实现IEaser
接口来编写自己的 easers。你真的只是被你的想象力所限制!
您可以通过将您的转换作为第四个参数传递给pushView
或replaceView
函数来指定ViewNavigator
将使用的转换。ViewNavigator
的popView
、popAll
和popToFirstView
也采用单个可选参数,指定在更改View
s 时播放哪个过渡。啊,但是如果用户按下 Android 的后退按钮呢?在这种情况下,我们不能显式地调用pop
函数,所以相反,您必须将ViewNavigator
的defaultPopTransition
属性设置为您希望它默认播放的过渡。如果您没有为pop
函数指定过渡参数或设置defaultPopTransition
属性,则ViewNavigator
将在弹出View
时播放其默认的幻灯片过渡。即使您使用自定义过渡来推动View
,当View
弹出时,ViewNavigator
也不会尝试反向播放推动过渡。还需要注意的是ViewNavigator
有对应的defaultPushTransition
。您可以使用这两个属性为特定的ViewNavigator
播放的所有过渡设置默认值。
现在唯一合理且有趣的事情是编写一个应用来尝试这些转换组合,对吗?没错。清单 3–9 显示了 ViewTransitions 示例程序的TransitionListView
的代码。这个View
显示了所有内置View
过渡的List
,每个过渡都显示了一些不同的模式、方向和画师的组合。ViewTransitions 项目可以在本书第三章的示例代码中找到。
清单 3–9。 展示了几个不同品种的每一个View
跃迁
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Home"> <fx:Declarations> <s:Bounce/> <s:SlideViewTransition/> <s:SlideViewTransition easer="{bounce}" duration="1000"/> <s:SlideViewTransition mode="cover"/> <s:SlideViewTransition mode="uncover"/> <s:SlideViewTransition mode="uncover" easer="{bounce}" direction="down" duration="1000"/> <s:FlipViewTransition/> <s:FlipViewTransition easer="{bounce}" duration="1000"/> <s:FlipViewTransition mode="cube"/> <s:CrossFadeViewTransition/> <s:ZoomViewTransition/> <s:ZoomViewTransition easer="{bounce}" duration="1000"/> <s:ZoomViewTransition mode="in"/>
` <s:ZoomViewTransition id=“zoomInBounce” mode=“in” easer=“{bounce}”
duration=“1000”/>
</fx:Declarations>
fx:Script
<![CDATA[
import spark.events.IndexChangeEvent;
import spark.transitions.ViewTransitionBase;
privatefunction onChange(event:IndexChangeEvent):void {
var selectedItem:Object = transitionList.selectedItem;
var transition:ViewTransitionBase = selectedItem.transition;
var data:Object = {name: selectedItem.name};
navigator.defaultPopTransition = transition;
navigator.pushView(TransitionedView, data, null, transition);
}
]]>
</fx:Script>
<s:navigationContent/>
<s:List id=“transitionList” width=“100%” height=“100%” labelField=“name”
change=“onChange(event)”>
<s:ArrayCollection>
<fx:Object name=“Default – Push Slide” transition=“{slide}”/>
<fx:Object name=“Push Slide with Bounce” transition=“{slideBounce}”/>
<fx:Object name=“Cover Slide” transition=“{slideCover}”/>
<fx:Object name=“Uncover Slide” transition=“{slideUncover}”/>
<fx:Object name=“Uncover Slide with Bounce” transition=“{uncoverBounce}”/>
<fx:Object name=“Flip” transition=“{flip}”/>
<fx:Object name=“Flip with Bounce” transition=“{flipBounce}”/>
<fx:Object name=“Cube Flip” transition=“{flipCube}”/>
<fx:Object name=“Fade” transition=“{fade}”/>
<fx:Object name=“Zoom Out” transition=“{zoom}”/>
<fx:Object name=“Zoom Out with Bounce” transition=“{zoomBounce}”/>
<fx:Object name=“Zoom In” transition=“{zoomIn}”/>
<fx:Object name=“Zoom In with Bounce” transition=“{zoomInBounce}”/>
</s:ArrayCollection>
</s:List>
</s:View>`
<fx:Declarations>
部分用于声明一个Bounce
更容易和各种不同的过渡。一些过渡使用它们的默认设置,而另一些则指定特殊的模式或方向。几个转换也使用duration
属性来指定转换需要的总毫秒数。内置转换的默认持续时间范围是 300 到 400 毫秒。这对于一个有效的弹跳动画来说有点太快了,所以那些使用Bounce
easer 的过渡有更长的持续时间。
当从List
中选择一个过渡时,onChange
处理函数检索所选对象,并将过渡的名称传递给下一个View
的data.name
属性。包含在List
中的对象也保留了对所需过渡的引用。所以这个转换属性作为第四个参数传递给导航器的 pushView
方法。但是请注意,这个转换也用于在调用pushView
之前设置导航器的defaultPopTransition
属性。这将确保如果在推送过程中播放了一个FlipViewTransition
,当返回到TransitionListView
时将播放相同的过渡。这有一点欺骗,因为当你通常想要在特定的一对View
的推和弹出上播放相同类型的过渡时,你通常会颠倒弹出过渡的方向。这在常规应用中很容易实现,但在这种情况下,不值得在示例代码中为每个当前转换对象定义一个相反方向的转换。图 3–8 显示了在立方体模式下FlipViewTransition
期间捕获的 ViewTransitions 示例程序。
图 3–8。 使用立方体翻转过渡从一个View
到下一个
如果您在 ViewTransitions 示例程序运行时仔细观察ActionBar
,或者如果您检查图 3–8 中的中间图像,您将会注意到ActionBar
没有随着View
进行转换。这是因为ActionBar
被认为属于整个应用——或者整个选项卡——而不仅仅是一个View
。但是有些时候,如果ActionBar
参与进来,这种转变看起来会更好。在这些情况下,您可以将View
转换类的transitionControlsWithContent
属性设置为true
。因此,如果我们如下更改示例应用中的立方体翻转声明,我们就可以获得如图 Figure 3–9 所示的效果。
<s:FlipViewTransition mode="cube" transitionControlsWithContent="true"/>
图 3-9。 在立方体翻转过渡上用View
过渡ActionBar
查看菜单
所有 Android 设备都有一个内置的菜单按钮,可以显示屏幕菜单。AIR 通过ViewMenu
和ViewMenuItem
类支持这一功能。ViewMenu
充当一个ViewMenuItem
s 单级的容器;不支持子菜单。应用中的每个View
都可以定义自己的ViewMenu
。清单 3–10 展示了如何为View
声明一个ViewMenu
。
清单 3–10。 宣告一个ViewMenu
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="MenuItem Example">
` fx:Script
<![CDATA[ privatefunction onClick(event:MouseEvent):void { message.text = “You selected ” + (event.target as ViewMenuItem).label } ]]>
</fx:Script>
<s:viewMenuItems>
<s:ViewMenuItem label=“Pro” click=“onClick(event)”
icon=“@Embed(‘assets/ProAndroidFlashIcon36.png’)”/>
<s:ViewMenuItem label=“Android” click=“onClick(event)”
icon=“@Embed(‘assets/android.png’)” />
<s:ViewMenuItem label=“Flash” click=“onClick(event)”
icon=“@Embed(‘assets/flash.png’)” />
<s:ViewMenuItem label=“Book” click=“onClick(event)”
icon=“@Embed(‘assets/book.png’)” />
</s:viewMenuItems>
<s:Label id=“message” width=“100%” top=“20” textAlign=“center”/>
</s:View>`
一个ViewMenuItem
实际上只是另一种按钮。它甚至扩展了 Spark ButtonBase
类。因此,就像任何其他按钮一样,您可以定义标签和图标属性以及一个click
事件处理程序。在这个例子中,每个ViewMenuItem
都有相同的onClick
处理程序,它使用ViewMenuItem
的标签向用户显示选择。使ViewMenu
容器和ViewMenuItem
按钮与普通 Spark 不同的是,它们的布局设计模仿了原生 Android 菜单。
**提示:**记住 Windows 上的 Ctrl+N 和 Mac 上的 Cmd+Non 会在桌面模拟器中显示 Viewmenu。
图 3–10 显示了在 Android 设备上运行时产生的ViewMenu
的样子。
图 3-10。 一个ViewMenu
带图标
在视图之间传递数据
为了节省资源,ViewNavigator
将确保在任何给定时间内存中只有一个View
。当一个View
被推到栈顶时,旧的View
的data
对象被自动保存,如果一个新的data
对象作为pushView
函数的第二个参数被提供,它将被传递给新的View
。如果没有提供data
对象,那么新的View
的data
属性将为空。由于当一个新的View
被压入堆栈时,一个View
的data
对象被持久化,所以当由于其他View
被弹出堆栈而导致View
被重新激活时,那个data
对象将被恢复。
那么,通过data
对象的通信似乎是单向的:一个View
可以将一个data
对象传递给它正在推入堆栈的View
,但是当它弹出堆栈时,View
无法将数据返回给原始的View
。那么,如果需要的话,新的View
如何将数据返回给原来的View
?答案是新的View
将简单地覆盖View
的createReturnObject
功能。该函数返回一个保存在ViewNavigator
的poppedViewReturnedObject
属性中的对象,其类型为ViewReturnObject
。所以为了访问新的View
返回的对象,原来的View
将访问navigator.poppedViewReturnedObject.object
。
您也可以通过使用context
对象将数据传递给新的View
。你可以传递一个context
对象作为ViewNavigator
的pushView
函数的第三个参数。context
的行为很像data
对象;通过访问navigator.context
属性,新的View
可以随时访问它。当顶部的View
从ViewNavigatorView
栈中弹出时,先前的View
的context
也被恢复。弹出的View
的context
对象也存在于navigator
的poppedViewReturnedObject.context
属性中。
data
和context
对象的使用在某种程度上是可以互换的,在这种情况下,你应该更喜欢使用data
对象。context
对象对于那些有一个View
的情况很有用,根据用户导航到View
的方式,这个【】的显示会略有不同。例如,您可能有一个显示一个人的联系信息的细节View
。有时,根据用户如何导航到“View
”——无论是通过点击“查看”按钮还是“编辑”按钮——View
应该相应地调整其显示。这是一个使用context
对象来区分包含在data
对象中的联系信息是应该简单地呈现给用户还是应该可编辑的好地方。
保存视图和会话数据
我们已经在清单 3–4 中看到,并且在上一节中简要讨论过,当View
被ViewNavigator
弹出函数之一重新激活时,View
的data
对象被恢复。因此,View
数据的一个持久性策略是在调用pushView
之前将值存储在它的data
对象中,或者存储在viewDeactivate
事件的处理程序中。如果新的View
调用其中一个弹出函数,那么先前由原来的View
存储的数据将可以通过它的data
对象再次访问。这种策略只对正在运行的应用中的View
之间的持久化数据有效。如果用户触发调用NativeApplication
的exit
功能的动作或 Android 操作系统关闭应用,那么所有Viewdata
对象都将丢失。
PersistenceManager
类用于在应用运行之间保存数据。ViewNavigatorApplication
和TabbedViewNavigatorApplication
容器引用了一个persistenceManager
实例,该实例可用于在应用启动或关闭时保存和加载持久化数据。清单 3–11 展示了一个使用persistenceManager
保存应用启动次数的简单例子。这段代码是名为简单持久性的第三章示例项目的一部分。
清单 3–11。 在应用运行之间保存数据
<?xml version="1.0" encoding="utf-8"?> <s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" splashScreenImage="@Embed('assets/splash.png')" initialize="onInitialize()" deactivate="onDeactivate()" applicationDPI="160">
` fx:Script
<![CDATA[
import views.PersistenceCountView;
privatestaticconst RUN_COUNT:String = “runCount”;
privatefunction onInitialize():void {
var rc:Number = Number(persistenceManager.getProperty(RUN_COUNT));
navigator.pushView(views.PersistenceCountView, rc);
}
privatefunction onDeactivate():void {
var rc:Number = Number(persistenceManager.getProperty(RUN_COUNT));
persistenceManager.setProperty(RUN_COUNT, ++rc);
NativeApplication.nativeApplication.exit(0);
}
]]>
</fx:Script>
</s:ViewNavigatorApplication>`
当应用被放到后台或被 Android 操作系统关闭时,应用的onDeactivate
处理程序被调用。所以在这个处理程序中,我们增加了运行计数,并调用persistenceManager
的setProperty
函数来保存它。然后我们调用NativeApplication
的exit
函数来确保应用在两次运行之间被关闭。这确保了我们的数据真正被持久化和恢复。
当应用的onInitialize
处理程序被触发时,使用getProperty
函数从persistenceManager
中检索应用的运行计数。getProperty
函数接受一个参数,该参数是要检索的属性的键String
。该运行计数被转换为一个Number
并传递给应用的第一个View
,在此显示如图 3–11 所示。
图 3–11。 显示应用的持续运行次数
基于View
导航器的应用也有一个名为persistNavigatorState
的属性,当设置为true
时,将自动保存ViewNavigator
的状态和View
堆栈。持久化的数据会在下次运行程序时自动加载回来。除了ViewNavigator
数据,应用的版本和数据保存的时间也被保存。使用PersistenceManager
的getProperty
函数和“应用版本”和“时间戳”键可以访问这两个数据。
既然我们已经探索了移动应用容器,是时候关注移动 Flex 应用可用的控件了。
视觉控制
任何 Flex 应用的核心都是一组丰富的 UI 控件,让您可以在 UI 中表达常见元素。我们已经在本书前面的代码和应用中使用了其中的许多;然而,在本节中,我们将特别关注可用于 mobile Flex 应用的不同类型的控件。
对于移动应用,性能和外观对于确保用户界面的可用性都非常重要。出于这个原因,强烈建议远离 Flex 中的旧 MX 包,并专注于具有移动皮肤的 Spark 控件。
Spark 控件的完整列表显示在表 3–2 中,以及它们对移动应用的适用性。
许多控件目前没有移动优化外观,因此目前不应在移动设备上使用。例如,ComboBox
、NumericStepper
和DropDownList
如果在移动设备上使用它们的桌面皮肤,就不会有一致的外观和交互。如果您需要一个具有这些功能之一的控件,您可以创建自己的自定义外观,以匹配您的移动应用的样式。
一些可用的组件也没有针对移动设备上的性能进行优化。我们在第十章的中更详细地讨论了这个话题,但是如果你遵循前面的指导方针,使用TextArea
和TextInput
而不是RichText
和RichEditableText
,你应该没问题。对于Image
类也是如此,重复使用时可以是重量级的,比如在ListItemRenderers
中。
该列表是截至 Flex 4.5 的最新版本,但是 Adobe 正在为其余控件添加额外的移动外观,因此请参考 API 文档以了解有关移动控件兼容性的最新信息。
在本章的其余部分,我们将详细介绍如何使用每个支持移动设备的控件的全部功能。
文本控件
三个为移动设备优化并能给你带来最佳性能应用的控件是Label
、TextArea
和TextInput
。每个控件都可以通过 CSS 样式高度定制。
Label
让您能够以统一的格式显示单行或多行文本。它在幕后使用 Flash 文本引擎(FTE),这使它快速而轻量,但不如使用全文布局框架(TLF)的RichText
控件灵活。Labels
应该用在任何你想在屏幕上显示不可修改的文本的地方,比如控件标签或者章节标题。
TextInput
和TextArea
分别提供单行和多行使用的文本输入。当在移动设备上使用时,它们在幕后使用StyleableTextField
类,这使它们具有极高的性能,但功能有限。在桌面上,这些控件由 TLF 支持,为您提供国际语言支持、改进的排版和嵌入的 CFF 字体。如果你在移动设备上需要这些功能,你将不得不使用RichEditableText
控件,这会带来很大的性能损失。
三个推荐文本组件的移动样式如表 3–3 所示。虽然在桌面配置文件上运行时还有其他样式属性可用,如kerning
、lineBreak
和renderingMode
,但由于移动主题中使用的轻量级文本引擎,这些属性在移动设备上不受支持。
正如您所看到的,这些文本组件支持的样式几乎是相同的,只是为内容区域、边框以及TextInput
和TextArea
的焦点添加了一些样式。
使用不同文本组件的最大区别在于使用不同的属性进行文本编辑。这些属性中有许多在TextInput
和TextArea
上可用,但对于Label
并不需要,因为它仅用于文本渲染。Table 3–4 列出了三个文本组件的所有可用属性。
为了展示文本组件的不同风格和功能,我们构建了一个小的示例应用,它呈现了美国独立宣言的前几段以及一个可编辑的签名列表。该应用的代码如清单 3–12 所示。
清单 3–12。显示独立宣言的文本组件示例代码
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Text"> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; .title { fontFamily: Times; fontSize: 30; fontWeight: bold; } .body { color: #222222; fontFamily: Times; fontSize: 12; fontStyle: italic; textAlign: justify; } .main-signature { fontFamily: Helvetica; fontSize: 18; } .state-signatures { fontFamily: Helvetica; fontSize: 12; } </fx:Style> <s:VGroup left="15" top="15" right="15" width="100%" gap="12"> <s:Label styleName="title" text="Declaration of Independence"/> <s:Label styleName="body" width="100%" text="When in the Course of human events, it becomes necessary for one people to …" /> <s:Label styleName="body" width="100%" maxDisplayedLines="12" text="We hold these truths to be self-evident, that all men are created equal, that they are …" /> <s:HGroup verticalAlign="baseline" width="100%"> <s:Label styleName="main-signature" text="President of Congress:"/> <s:TextInput styleName="main-signature" text="John Hancock" editable="false" width="100%"/> </s:HGroup> <s:Label styleName="main-signature" text="State Representatives:"/> <s:TextArea styleName="state-signatures" width="100%" text="Josiah Bartlett, William Whipple, Matthew Thornton, Samuel Adams, John Adams, …" /> </s:VGroup> </s:View>
注意使用内联 CSS 从代码中抽象出样式。也可以将文本组件上的样式直接声明为 XML 属性,尽管为了模块化,您可能更愿意反其道而行之,将 CSS 完全提取到一个单独的文件中。
运行这个例子会得到如图 Figure 3–12 所示的输出。
图 3–12。 独立宣言测试样本
作为测试文本组件的一个练习,尝试对应用进行以下更改:
- 在
TextArea
组件上使用密码保护。 - 更改
TextInput
的大小,使其与初始文本大小完全匹配。 - 将
TextInput
默认文本改为用户开始输入时消失的提示。 - 更改
TextInput
的样式和交互性,以匹配Label
的样式和交互性。
**提示:**使用一个禁用了可编辑性且样式类似于Label
的TextInput
比直接使用一个Label
组件性能更高,这是因为使用了幕后的StyleableTextField
实现。
软键盘支持
当使用文本组件时,Android 软键盘会像你所期望的那样在焦点上自动触发。然而,有时您需要更精细地控制软键盘何时被触发,以及当它被激活时会发生什么。
Flex 中的软键盘由应用焦点控制。当一个将needsSoftKeyboard
属性设置为true
的组件获得焦点时,软键盘将出现在前面,舞台将滚动,以便选定的组件可见。当该组件失去焦点时,软键盘将消失,舞台将返回其正常位置。
有了对焦点的理解,你可以通过做以下事情来控制软键盘:
- 以声明方式显示软键盘:为你的组件设置
needsSoftKeyboard
到true
。 - 以编程方式显示软键盘:在已经设置了
needsSoftKeyboard
的组件上调用requestSoftKeyboard()
。 - 隐藏软键盘:在没有设置
needsSoftKeyboard
的组件上调用setFocus()
。
这对于通常不触发软键盘的组件来说工作良好;但是,对于自动升高键盘的组件,将needsSoftKeyboard
设置为false
没有任何作用。防止键盘在这些组件上弹出的一个解决方法是侦听激活事件,并用如下代码抑制它:
<fx:Script> <![CDATA[ private function preventActivate(event:SoftKeyboardEvent):void { event.preventDefault(); } ]]> </fx:Script> <s:TextArea text="I am a text component, but have no keyboard?" softKeyboardActivating="preventActivate(event)"/>
这段代码捕获了TextArea
组件上的softKeyboardActivating
事件,并取消了提升软键盘的默认动作。
除了在激活时获取事件,您还可以捕捉softKeyboardActivate
和softKeyboardDeactivate
事件,以便根据软键盘状态执行操作。
清单 3–13 展示了一个软键盘示例应用,它展示了所有这些技术一起使用来完全控制软键盘。
清单 3–13。 软键盘交互示例代码
`<?xml version=”1.0″ encoding=”utf-8″?>
<s:Application xmlns:fx=“http://ns.adobe.com/mxml/2009”
xmlns:s=“library://ns.adobe.com/flex/spark”
splashScreenImage=“@Embed(‘ProAndroidFlash400.png’)”>
fx:Script
<![CDATA[
[Bindable]
privatevar state:String;
[Bindable]
privatevar type:String;
privatefunction handleActivating(event:SoftKeyboardEvent):void {
state = “Activating…”;
type = event.triggerType;
}
privatefunction handleActivate(event:SoftKeyboardEvent):void {
state = “Active”;
type = event.triggerType;
}
privatefunction handleDeactivate(event:SoftKeyboardEvent):void {
state = “Deactive”;
type = event.triggerType;
}
privatefunction preventActivate(event:SoftKeyboardEvent):void {
event.preventDefault();
}
]]>
</fx:Script>
<s:VGroup left=“20” top=“20” right=“20” gap=“15”
softKeyboardActivating=“handleActivating(event)”
softKeyboardActivate=“handleActivate(event)”
softKeyboardDeactivate=“handleDeactivate(event)”>
<s:HGroup>
<s:Label text=“Keyboard State: ” fontWeight=“bold”/>
<s:Label text=”{state}“/>
</s:HGroup>
<s:HGroup>
<s:Label text=“Trigger Type: ” fontWeight=“bold”/>
<s:Label text=”{type}”/>
</s:HGroup>
<s:Button id=“needy” label=“I Need the Keyboard” needsSoftKeyboard=“true” emphasized=“true”/>
<s:TextArea text=“I am a text component, but have no keyboard?”
softKeyboardActivating=“preventActivate(event)”/>
<s:HGroup width=“100%” gap=“15”>
<s:Button label=“Hide Keyboard” click=“{setFocus()}” width=“50%”/>
<s:Button label=“Show Keyboard” click=“{needy.requestSoftKeyboard()}” width=“50%”/>
</s:HGroup>
</s:VGroup>
</s:Application>`
这段代码创建了几个控件,并为它们附加了动作,这样你就可以随意隐藏和显示软键盘,还可以看到当前软键盘的状态,就像涓流事件所报告的那样。运行该应用后,您将会看到如图图 3–13 所示的 UI。
图 3–13。 演示如何控制软键盘的示例应用
请注意,通常触发软键盘的TextArea
控件不再弹出软键盘,而高亮按钮在获得焦点时会立即弹出软键盘。底部显示和隐藏键盘的两个按钮仅仅是玩焦点把戏,让 Flash 随意显示和隐藏键盘。
你可以在你的应用中使用同样的技术来完全控制软键盘。
按钮控件
也许任何用户界面最基本的元素之一就是按钮。自从 1973 年施乐 Alto 上出现第一个图形用户界面以来,它就一直存在。Figure 3–14 展示了一张桌子大小的 Alto 的图片,以及其文件管理器中使用的按钮样式。自那时以来,我们已经走过了漫长的道路,但基本概念并没有多大变化。
图 3–14。 施乐 Alto 的图像(左)和其 GUI 中使用的按钮样式的复制品(右)
Flex 有几个内置的按钮控件,具有移动优化的样式,可以在设备上以可用的大小呈现它们。其中包括以下按钮类型:
Button
CheckBox
RadioButton
ButtonBar
标准的Button
控件是高度可定制的,包括嵌入图像图标的能力。CheckBox
控件提供了一个带有标签的定制按钮和一个可视开关,可以通过单击来启用或禁用。RadioButton
类似于CheckBox
,但是使用一个圆形指示器,并与一组相关的RadioButton
一起工作,一次只能选择其中一个。最后,ButtonBar
将一组切换按钮合并成一行,在给定时间只能选择其中一个,类似于RadioButton
组。
所有这些控件都有相似的样式,可以用来自定义它们。由于手机皮肤的不同,并不是所有的桌面风格都可用,比如cffHinting``direction``renderingMode
。Table 3–5 列出了按钮类上支持移动的样式。
所有按钮类型都支持其中的大多数样式,包括嵌入在类型为ButtonBarButton
的ButtonBar
中的按钮。有一些例外,样式被明确地从子类中排除,比如textAlign
和icon
,它们在CheckBoxes
和Radiobuttons
中都不被支持。对于在ButtonBarButtons
上设置样式,通常只需在ButtonBar
上设置样式,并让 CSS 继承将它应用到创建的子按钮上。
由于功能的不同,操作按钮的可用属性也略有不同。Table 3–6 列出了可用的公共属性,包括它们适用于哪个按钮类。
除了这些属性之外,button 类上还有一些事件和方法有助于交互性。其中最重要的是clickHandler
函数,每当用户在按钮上按下并释放鼠标时,该函数就会被调用。此外,您可以监听一个buttonDown
事件,并覆盖buttonReleased
和mouseEventHandler
函数来进行更高级的交互。可切换按钮子类(CheckBox
、RadioButton
和ButtonBarButton
)上的另一个可用事件是change
事件,每当selected
属性改变时就会触发该事件。
为了演示不同按钮控件的使用,我们制作了一个小按钮示例,模仿了现代微波炉上的复杂控件集。本例的代码如清单 3–14 所示。
清单 3–14。 现代微波的代号举例
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="Buttons"> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; .number { touchDelay: 500; } .header { color: #660000; } </fx:Style> <s:VGroup left="60" right="60" top="20" width="100%"> <s:ButtonBar styleName="header" requireSelection="true" width="100%"> <s:dataProvider> <s:ArrayCollection source="['Defrost', 'Cook', 'Irradiate']" /> </s:dataProvider> </s:ButtonBar> <s:RadioButton label="Meat" color="#404040" symbolColor="green"/> <s:RadioButton label="Poultry" color="#404040" symbolColor="yellow"/> <s:RadioButton label="Alien Fish" color="#d02525" symbolColor="#d02525"/> <s:CheckBox label="Trigger Meltdown" symbolColor="red"/> <s:HGroup width="100%"> <s:Button styleName="number" label="9" width="100%"/> <s:Button styleName="number" label="8" width="100%"/> <s:Button styleName="number" label="7" width="100%"/> </s:HGroup> <s:HGroup width="100%"> <s:Button styleName="number" label="6" width="100%"/> <s:Button styleName="number" label="5" width="100%"/> <s:Button styleName="number" label="4" width="100%"/> </s:HGroup> <s:HGroup width="100%"> <s:Button styleName="number" label="3" width="100%"/> <s:Button styleName="number" label="2" width="100%"/> <s:Button styleName="number" label="1" width="100%"/> </s:HGroup> <s:HGroup width="100%"> <s:Button styleName="number" label="?" width="100%"/> <s:Button styleName="number" label="0" width="100%"/> <s:Button styleName="number" icon="@Embed('alien.gif')" width="100%"/> </s:HGroup> <s:HGroup width="100%"> <s:Button label="End" width="100%"/> <s:Button label="Start" width="100%" emphasized="true"/> </s:HGroup> </s:VGroup> </s:View>
当在移动设备上运行时,该应用将类似于图 3–15 中所示。
图 3–15。 运转现代微波输出的例子
要使用您所学的一些新样式和属性进行练习,请尝试以下方法:
- 更改数字按钮中标签字体的大小和颜色。
- 增加一个
clickHandler
,微波炉启动时会播放声音。 - 添加一个
Label
,重复地将按钮上的数字追加到文本中。
弹性列表
s 可能是移动应用中最重要的控件之一。由于有限的屏幕空间,它们取代了数据网格,通常用于通过菜单或层次结构进行向下导航。
FlexList
控件已经针对移动应用进行了彻底的改进,其行为类似于你对移动设备的期望。这包括带有图标和装饰的大图形,以及当你通过一个List
的开始或结束时的滚动“反弹”。
最简单的方法是,你可以创建并显示一个 Flex List
,只需给它一个要渲染的对象集合,如清单 3–15 所示。
清单 3–15。 代码从一个ArrayCollection
创建一个List
<s:List width="100%" height="100%"> <s:ArrayCollection source="['Alabama', 'Alaska', 'Arizona']" /> </s:List>
上述代码将默认的dataProvider
属性设置为字符串的ArrayCollection
。默认情况下,它将使用LabelItemRenderer
,它只是在一个StyleableTextField
中显示List
的每个条目。执行该程序将产生一个基本的List
,如图 3–16 中的所示。
图 3–16。 基本List
例题使用LabelItemRenderer
要创建一个更复杂的List
,你可以改变itemRenderer
来使用一个更复杂的渲染器。Flex 4.5 附带了第二个名为IconItemRenderer
的内置渲染器,它具有显示以下项目组件的附加功能:
- 图标:显示在文本左侧的图形图标,通过设置
iconField
或分配iconFunction
来选择 - 标签:以大字体显示的单行文本,通过设置
labelField
或分配labelFunction
来选择 - 消息:以较小字体显示的多行描述,通过设置
messageField
或分配messageFunction
来选择 - Decorator:显示在图像右侧的图标,设置在
decorator
属性上
为了演示IconItemRenderers
的用法,我们制作了一个示例,让您浏览所有 50 个州的格言和纪念币列表。该示例的代码如清单 3–16 所示。
清单 3–16。 *IconItemRenderer*
样本应用代码
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="List"> <fx:Declarations> <s:ArrayCollection> <fx:Object state='Alabama' coin="@Embed('coins/Alabama.jpg')" motto="Audemus jura nostra defendere -- We dare defend our rights"/> <fx:Object state='Alaska' coin="@Embed('coins/Alaska.jpg')" motto="Futurum aquilonem -- North to the future"/> <fx:Object state='Arizona' coin="@Embed('coins/Arizona.jpg')" motto="Ditat Deus -- God enriches"/> <fx:Object state='Arkansas' coin="@Embed('coins/Arkansas.jpg')" motto="Regnat populus -- The people rule"/> <fx:Object state='California' coin="@Embed('coins/California.jpg')" motto="Eureka (??????) -- I have found it"/> … </s:ArrayCollection> </fx:Declarations> <s:Group width="100%" height="100%"> <s:List dataProvider="{stateInfo}" width="100%" height="100%"> <s:itemRenderer> <fx:Component> <s:IconItemRenderer labelField="state" messageField="motto" iconField="coin" decorator="@Embed('usflag.png')"/> </fx:Component> </s:itemRenderer> </s:List> <s:Label text="United States Mint images." fontSize="10" left="2" bottom="2"/> </s:Group> </s:View>
注意,我们没有嵌套dataProvider
,而是将其抽象为一个声明,并由id
引用。通常,您的数据将由 web 服务或数据库查询提供,在这种情况下,您只需用您的dataProvider
替换示例代码中使用的那个。
对于IconItemRenderer
,我们使用Component
标签创建了一个内联实例,并将其直接分配给itemRenderer
属性。还要注意,我们选择使用*Field
版本来选择标签、消息和图标。出于性能原因,这是更可取的,因为这意味着IconItemRenderer
知道值是静态的,可以进行更多的缓存来提高性能。
Figure 3–17 显示了在移动设备上运行的状态信息示例。
图 3–17。 国家信息应用展示纪念币和格言
通过利用IconItemRenderer
类的样式属性,您可以进一步定制列表。这里有一些关于你可以尝试的改变的建议:
-
Change the font family, size, and color to further distinguish the motto from the state name.
提示:
IconItemRenderer
上设置的样式将被标签继承,而messageStyleName
类可以用来改变消息的样式。 -
通过以编程方式更改硬币图形的宽度和缩放模式来增加硬币图形的大小。
-
使用
allowMultipleSelection
属性在List
组件上启用多选。
滑块、滚动条和忙碌指示器控件
在创建移动应用时,您会发现其他几个控件也很有用。这些控件包括HSlider
、Scroller
和BusyIndicator
控件。
元首
HSlider
是一个标准的水平滑块控件,允许您指定用户可以选择的值范围。HSlider
的一些特性包括:
- 移动大小的滑动条
- 显示精确值的数据提示
- 支持可配置的值范围和步进
滚动条本身的样式仅限于几个简单的属性,包括focusAlpha
、focusColor
、liveDragging
和slideDuration
。前两种样式与其他控件上的样式相同。此外,您可以通过将liveDragging
设置为false
来禁用它,这将强制该值仅在用户放开鼠标按钮时更新。slideDuration
样式控制滑块背景被按下时拇指移动的时间,默认为 300 毫秒。
除此之外,您可以使用几个属性来控制数据提示的显示,包括将Numeric
值转换为字符串的dataTipFormatFunction
、dataTipPrecision
来选择小数位数,以及让您完全禁用数据提示的showDataTip
。数据提示文本使用与上一节中提到的按钮组件相同的文本样式属性。对于移动使用,支持以下文本样式:fontFamily
、fontStyle
、leading
、letterSpacing
、locale
、textDecoration
。
为了控制滑块的范围,有几个属性可用,包括minimum
、maximum
、stepSize
和snapInterval
。这些都是Numeric
值,让您控制滑块的范围以及步进和捕捉行为。您可以使用value
属性设置滑块的初始值。当用户与滑块交互时,该属性也会动态更新。
最后,在使用滑块时,您可以跟踪几个事件来进行交互行为。这包括change
、changing
、thumbDrag
、thumbPress
和thumbRelease
的事件。
滚动条
Scroller
是一个支持移动的控件,允许用户在大于可视区域的内容周围翻页。移动皮肤是一个完整的重新设计,它使用触摸事件而不是静态滚动条来平移视口。这使得在触摸屏显示器上操作更加容易,同时提供了等效的功能。
Scroller
的子节点必须实现IViewport
接口,该接口包括 Spark 库中的Group
、DataGroup
和RicheditableText
组件。下面的示例代码展示了如何创建一个新的Scroller
实例来浏览静态图像:
<s:Scroller width="100%" height="100%"> <s:VGroup> <s:BitmapImage source="@Embed('/ProAndroidFlash.png')"/> </s:VGroup> </s:Scroller>
在这个例子中,viewport
的默认属性被设置为一个VGroup
,它包含一个大的BitmapImage
。Group
只是用来包装BitmapImage
,确保外部组件是IViewport
类型,否则用户看不到。在运行这个示例时,用户将能够在屏幕上拖动鼠标来浏览图像。
除此之外,由于简化的移动用户界面,真的没有什么可定制的。文本或滚动条样式都不适用,包括字体、颜色以及隐藏或显示滚动条。
忙碌指示器
最后一个组件显示一个简单的繁忙指示器小部件,带有一组圆形的旋转叶片。当图形显示在屏幕上时,它会不断地使图形产生动画效果,表明诸如加载之类的活动正在后台进行。
BusyIndicator
的直径计算为高度和宽度的最小值,四舍五入为 2 的倍数。另外两个专用于BusyIndicator
组件的刻度盘是一个用于控制动画速度(以毫秒为单位)的rotationInterval
和一个用于改变等待图形颜色的symbolColor
。
合并样本
为了演示这三个控件的用法,我们制作了一个快速演示,它使用所有控件来提供图像的缩放和平移。HSlider
控件用于改变图像的缩放级别,Scroller
组件在图像被放大时提供平移,而BusyIndicator
在这些动作发生时显示活动。
这个例子的完整代码显示在清单 3–17 中。
清单 3–17。 示例代码演示了HSlider
、Scroller
和BusyIndicator
控件的使用
<?xml version="1.0" encoding="utf-8"?> <s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="Misc"> <fx:Script> <![CDATA[ [Bindable] private var scale:Number = .5; [Bindable] private var busy:Boolean = false; ]]> </fx:Script> <s:VGroup top="15" left="15" right="15" bottom="15" gap="10" width="100%" height="100%"> <s:HGroup width="100%" verticalAlign="middle"> <s:Label text="Scale:"/> <s:HSlider width="100%" value="@{scale}" minimum=".01" maximum="1" stepSize="0" changeStart="{busy=true}" changeEnd="{busy=false}"/> <s:BusyIndicator visible="{busy}"/> </s:HGroup> <s:Scroller width="100%" height="100%" mouseDown="{busy=true}" mouseUp="{busy=false}" mouseOut="{busy=false}"> <s:VGroup> <s:BitmapImage source="@Embed('/ProAndroidFlash.png')" scaleX="{scale}" scaleY="{scale}"/> </s:VGroup> </s:Scroller> </s:VGroup> </s:View>
在运行这个示例时,您将看到类似于 Figure 3–18 的输出,这是在Scroller
拖动操作的中途捕获的。
图 3-18。 *Scroller*
平移操作期间捕获的示例
尝试操作控件来缩放和平移图像,并随意用您选择的图像替换此示例中的图像。
在阅读了前面几节关于所有可用控件的内容并对示例进行了实验之后,您现在已经对 Flex toolkit 的 UI 功能有了很好的理解。
总结
本章详细分析了 mobile Flex 应用。有了这些知识,您现在应该能够开始编写自己的移动应用了。以下主题现在是您开发工具的一部分:
TabbedViewNavigatorApplication
和ViewNavigatorApplication
应用容器以及何时使用其中之一ViewNavigator
和View
的关系- 如何指定
View
之间的动画过渡 - 如何在
View
之间来回传递数据 - 在运行之间保持应用状态时使用的正确方法
- 哪些控件针对移动应用进行了优化
- 如何使用文本控件显示用户的输出和集合输入
- 如何控制 Android 的软键盘
- 使用和设计移动
Button
控件的一些技巧和提示 - 如何使用
IconItemRenderer
在你的List
控件中显示丰富的内容 - 如何使用滑块输入范围内的值
- 使用
Scroller
在比移动设备的受限屏幕更大的内容中平移 - 如何使用
BusyIndicator
通知用户正在进行的操作
您现在知道如何创建移动应用。在下一章中,我们将向你展示如何用图形、动画和图表来增加一些活力!