安卓应用开发学习手册-全-
安卓应用开发学习手册(全)
零、简介
Android 操作系统是当今世界上最受欢迎的操作系统,可以在从手表到高清智能手机、触摸屏平板电脑、电子书阅读器到交互式电视机的所有设备上运行。
由于世界各地的人们拥有数十亿台 Android 消费电子设备,因此,如果你有正确的概念和设计,为这些人开发应用可能是一项非常有利可图的事业。这本书将有助于你学习如何创建一个有吸引力的 Android 应用,它跨越多种类型的 Android 设备并支持多种 Android 操作系统版本。
我写了学习 Android 应用开发作为我的绝对初学者 Android 应用标题的下一个层次,目标读者是那些更精通技术,并且熟悉计算机编程概念和技术的读者。话虽如此,这将是一个很好的后续标题绝对初学者的 Android 应用标题,都涵盖了最新的 Android 4.2.2 果冻豆和 Android 操作系统版本。
我设计这本书是为了比大多数 Android 应用开发书籍更全面地概述 Android 应用开发工作流程。出于这个原因,本书涵盖了各种其他开源软件包的使用,例如 GIMP 和 Audacity,以及它们的使用如何适应整个 Android 应用开发工作流程。这种方法使这本书与目前市场上的其他 Android 应用开发书籍截然不同。
这本书从安装最新的 Java 和 Android SDKs 以及带有 Android ADT 捆绑包的 Eclipse IDE 开始,然后通过创建一个基本的 Android 应用,并在每章中添加到该应用中。我们继续这个过程,直到所有的主要概念都包含在一个全面的 Android 应用中。这种方法与现实世界的应用开发更加相似,在现实世界中,随着时间的推移,应用会不断地被添加,随着时间的推移,应用会变得越来越健壮,功能也越来越丰富,同时确保每个新功能不会导致应用崩溃。
我们将探讨 Java 对象和构造函数、使用 XML 标记的用户界面设计、数字图像和图形设计、数字视频和动画、音频采样和音频排序,以及其他先进的新媒体概念和多媒体应用功能,因为这是当今 Android 应用开发中流行的内容。我们关注 Android 操作系统的核心领域,包括内容供应器(SQLite 数据库)、广播接收器、服务以及使用事件、意图和活动,所有这些都非常详细。
我们涵盖了在 Android 操作系统包含的更高级领域工作所需的基础知识。其中一些包括数字图像合成、数字视频优化、程序动画、数据库设计、多屏分辨率支持、3D 渲染和类似的高级主题,Android 开发人员需要了解这些主题的基本知识,以便在他们的应用设计和开发工作流程中智能地工作。
如果您想要全面了解 Android、Eclipse、Java、XML 和 Android 开发人员工具环境,以及如何在领先的开源新媒体内容设计和开发工具中优化使用这些技术的知识,那么这本书将会引起您的极大兴趣。
一、为 4.2 版本构建 Android IDE:获取、安装和配置 Android 开发环境
在学习 Android 应用开发之前,我们需要做的第一件事是在我们的开发工作站上构建一个可运行的 Android 应用开发环境。希望你有一台入门级的四核 AMD 或 Intel 电脑,配 4GB 或以上 DDR 内存,Windows 7 或 Windows 8;我将为这本书使用的电脑是 398 美元的宏基 64 位四核 AMD,运行频率为 3.1GHz,配有 4GB 的 DDR3 内存和 1TB 的硬盘驱动器,以及我在沃尔玛买的 Windows 7。
幸运的是,对于我们这些 Android 应用开发者来说,只需几百美元就能买到非常强大的 64 位计算机!如果你有一台 32 位的计算机,这也同样适用于 Android 应用开发,因为 Android 4.2 开发环境有 32 位和 64 位两种版本。此外,我们将在本书中用于应用开发的所有软件都是免费的商业用途,也称为开源软件,因此,现在启动自己的 Android 应用软件开发业务的成本确实很低。
如果出于某种原因,你想使用早于 Android 4.2.2 的开发环境(我强烈建议不要使用),本书末尾有一个附录,介绍了安装 Android 4.1.2 开发环境的更复杂的工作过程。
我们的攻击计划
在本章中,我们将确保我们的系统拥有最新版本的 Oracle Java 6 软件开发包 (Java SDK,也称为 JDK,或 Java 开发包)编程环境,以及 Android 软件开发环境,其中包括 Google Android 软件开发包(SDK)、Eclipse 的 Android 开发工具(ADT)插件和 Eclipse 4.2 集成开发环境(IDE)。
所有这些一次性安装在一起的软件被称为 Android ADT 捆绑包 ,从 Android 4.2 开始,你现在可以在http://developer.android.com
的 Android 开发者网站一次性下载所有这些软件,下载容量不超过 400MB。
在 Android 4.2 Jelly Bean+(Android API 17 级)之前,开发者必须单独下载并安装这些组件中的每一个,这相当繁琐。如果你想这样做,或者想看看必须这样做是什么样子(并更好地理解 Eclipse 和 Android SDK 和 Android ADT 之间的关系),你可以在本书的附录 A 中看到安装的详细版本。
一旦下载并安装了我们的 JDK,我们将下载并安装一个名为 Android ADT 捆绑包的 Android 集成开发环境(IDE) ,它的基础是 Eclipse 4.2 Juno for Java EE IDE。Eclipse 为我们提供了一个流畅的图形用户界面(GUI ),我们可以用它来编写、运行、测试和调试我们的 Android 应用代码,从而使开发 Android 应用变得更加容易。
Eclipse 运行在 Java Runtime Environment (JRE) 之上,因为 Eclipse 是用 Java 编写的,因此它使用 Java 平台来运行自己的代码库,这些代码库构成了 Eclipse IDE 用户界面和特性集,您将会看到(随着您在本书中的深入)这些特性确实非常广泛。
这是我们首先下载并安装 Java 6 JDK 的主要原因,这样 Java SDK 和 JRE 就在我们的工作站上了。这样,一旦我们开始安装基于 Eclipse 的 ADT 包,Eclipse 就可以很容易地找到 Java 运行时环境(以便 Eclipse 可以启动和运行)。一旦 Eclipse 能够找到 Java,它可以使用 Java SDK 为我们的 Android 开发环境构建 Java 编程代码基础,因为 Android API(SDK)是基于 Java 6 API(SDK)的。
一旦我们下载并安装了 ADT 包,并在 Java 6 上顺利运行,我们实际上已经在一个包中安装了 Google Android 软件开发工具包(SDK)、Eclipse 4.2.2 IDE 和为 Android 4.2.2 API Level 17 开发所需的所有 ADT 插件。
从鸟瞰的角度来看,如果这个过程被公式化为一个方程,它看起来会是这样的:
JDK (Java 6 SDK) + ADT 捆绑包(Eclipse + Android SDK + ADT Eclipse 插件)=自定义 Android IDE
作为 Android Bundle 安装和开发使用配置工作流程的一部分(第二个主要部分),我们将安装一些 Android 虚拟设备 (AVD) 模拟器,这些模拟器将存在于 Eclipse 4.2 中,允许我们在各种 Android 虚拟设备上测试我们的应用,例如虚拟 Nexus 7 平板电脑,虚拟 Nexus S 智能手机,甚至虚拟 GoogleTV 电视机。因此,让我们现在就开始这个过程,这样我们就可以完成它,并在下一章开始开发应用!
Android 编程基础:Java 6
从编程和集成开发环境(IDE)的角度来看,Android 应用开发的基础是 Java 6。Android 应用是使用 Java 6 编程语言编写的(也使用 XML,我们将在第二章中更详细地讨论),Android 应用是在 Eclipse 4.2.2 IDE 中开发的,它也是用 Java 6 编程语言编写的,并且运行在 Java 6 运行时环境(也称为 JRE)之上。委婉地说,您设置构成 Android 开发环境的各种软件组件的确切顺序非常重要,这也是第一章的原因。
因此,我们既有 Java 编程语言(我们可以通过 JDK 或 Java 开发工具包获得)又有 Java 运行时环境(JRE )(它是 JDK 的一部分),请访问 Oracle TechNetwork,下载最新的 JDK 6 安装软件并将其安装在您的计算机上。我们这样做首先是因为 Eclipse 需要 Java 来运行,也就是说,Eclipse 可以说是在 Java 平台和语言的“顶部”运行。Android 的 Android 开发者工具(ADT)插件也需要 Java 和 Eclipse,所以我们首先安装 Java 平台和 Java 环境,然后安装 Android ADT 包。
我们开始吧。
安装 JDK
我们必须做的第一件事是访问 Java SDK 下载页面,有两种方法可以做到这一点;一个是通用的,一个是精确的。即使 Oracle 更改了其 Java SDK 下载页面的位置(它很可能不会更改),也始终有效的通用方法是使用 Google 搜索,关键字短语为“Java SDK Download”和,这将显示 Oracle TechNetwork Java 下载 URL。第二种方法是直接在浏览器中键入页面的 URL。下面是网址:
`http://www.oracle.com/technetwork/java/javase/downloads/index.html`
这指向的是 Internet (HTTP)和 Oracle 网站在它们的 TechNetwork 区域(文件夹)在 Java 区域(子文件夹)在 Java SE 或 Standard Edition 区域(子子文件夹)在下载区域(子子子文件夹)。Java 有三个主要版本:面向个人用户的 SE 或标准版,面向大量用户的 ee 或企业版,以及面向老式移动翻盖手机的 ME 或 Micro 版。大多数现代智能手机使用 Android 和 Java SE,而不是 Java ME。Android 非常酷的一点是,它像 PC 一样使用完整的 Java 标准版(即 Java SE)。这是因为 Android 运行在完整版本的 Linux 操作系统内核之上,所以对于所有实际用途来说,Android 消费电子设备本质上是一台成熟的 Linux 计算机。
一旦你输入了这个 URL,你就到达了 Java 6 JDK 下载页面,你需要找到这个页面的 Java 6 JDK 下载部分,它看起来就像图 1-1 中所示的(部分)页面部分。
图 1-1。Oracle tech network Java SE 网页的 Java SE 6 JDK 下载部分
向下滚动页面大约一半,点击 JDK 下的蓝色下载按钮(记住,JDK 既包含又包含JDK 和 JRE,所以根本不要下载 JRE),如图图 1-1 所示。
这将带您进入图 1-2 所示的 Java 6 JDK 下载页面,您首先接受软件许可协议,然后下载 Java 6 的 Windows 32 位版本或 Windows 64 位版本。
图 1-2。接受 Java 6 许可协议并下载 Windows x86 32 位或 Windows x64 64 位。exe 文件
如果您使用的是 Linux 操作系统或 Solaris 操作系统,也可以在这里找到这些操作系统的 32 位和 64 位版本。请注意,在下载 Windows x86 32 位之前。exe 文件或 64 位 Windows x64。exe 文件,必须点击网页部分顶部“接受许可协议”选项旁边的单选按钮选择器,如图图 1-2 所示。一旦你这样做了,右边的下载链接就会变成粗体,你可以点击它们来下载安装文件。
如果你有 32 位 Windows 操作系统,如 Windows XP,点击jdk-6u43-windows-i586.exe链接(或任何最新的更新版本),或者如果你有 64 位 Windows 操作系统,如 Windows 7 或 Windows 8,点击jdk-6u43-windows-x64.exe链接(或任何最新的更新版本),然后将 JDK 6 安装程序文件下载到你工作站上的下载文件夹中。请注意,你不而需要下载这两个文件。
删除任何过期的 JDK
在您安装当前的 Java 6 JDK 之前,您应该检查您的系统,以确保您的工作站上没有预先存在的(旧的)Java 版本。这是在 Windows 中通过控制面板完成的,控制面板可通过 Windows 开始菜单访问。现在启动控制面板,找到标有“程序和功能”的图标,启动如图图 1-3 所示的程序和功能对话框。
图 1-3。右键单击现有的 Java 安装,选择卸载将其从系统中移除
注意,在我的工作站上,我已经安装了 32 位 Java 6 Update 31。因为我想在这个安装过程中安装最新的 64 位开发软件,所以我将使用完全相同的工作过程卸载以前的 Java 6 Update 31 安装,以及任何旧版本的 Eclipse 和 Android(如果它们存在,但它们不在这个特定的工作站上)。
为此,你需要选择旧版本的 Java,然后右击它,选择卸载选项,如图图 1-3 所示。然后,Windows 继续从您的系统中删除旧版本的 Java(以及 Eclipse 和 Android,如果需要的话)。
接下来,进入你的下载文件夹(或者你的桌面,或者任何你保存文件下载的地方),双击 jdk-6u43-windows-i586.exe 或者 jdk-6u43-windows-x64.exe的文件开始 Java 6 的安装。接受安装的默认设置,如果您有一个配有高速硬盘驱动器和至少 3GB 内存的现代工作站,安装过程应该会很快。
一旦 Java 6 JDK 安装完成,您就可以下载并安装 Android ADT 包了。接下来让我们这样做,这样我们就有了一个 Android IDE 设置,可以在本书的剩余部分中用于我们未来的 Android 应用开发。
Android 开发者工具(ADT)包
从 Android 4.2 Jelly Bean 开始,Android 软件开发工具包(或 Android SDK)与 Eclipse IDE 和 Android 开发工具(ADT) 捆绑在一个称为 ADT Bundle 的 400MB 大下载包中。
下载 Android ADT IDE
我们必须做的第一件事是访问 Android 开发者网页,同样有两种方法可以做到这一点;一个普通,另一个精确。即使谷歌改变其 Android 开发者网页的位置(它很可能不会改变),通用的方法也总是有效的,那就是使用带有关键词短语“Android Developer Website”的谷歌搜索,这应该会调出Developer.Android.com
网站 URL。第二种方式是直接在浏览器中输入 Android 开发者网页的网址(建议大家熟记这个网址):http://developer.android.com/index.html
.
一旦你输入网址,你就到达了 Android 开发者网站主页,你需要找到页面底部的Get SDK按钮,它看起来就像图 1-4 所示的菜单。
图 1-4。选择获取 SDK 按钮转到Developer.Android.com
站点上的 Android SDK 下载页面
一旦你点击获取 SDK 按钮,你就会被带到获取 Android SDK 页面,在这里你会看到一个蓝色的大按钮,上面写着:下载 Windows 的 SDK - ADT 捆绑包(如果你目前使用的是 Linux 或 Macintosh 操作系统,也可能是 Linux 或 Macintosh 网站会自动检测您的操作系统,并为您提供正确的下载链接)。点击这个蓝色按钮,如图 1-5 所示,进入下载页面获取 Android SDK ADT 捆绑安装程序。zip 文件,现在有 32 位和 64 位版本。
图 1-5。点击Developer.Android.com
获取 Android SDK 页面上的下载 Windows ADT 捆绑包 SDK 按钮
一旦你点击下载 SDK ADT 捆绑包按钮,你将被带到如图图 1-6 所示的页面,在这里你可以阅读软件包的许可协议(Android 4.2.2 SDK,Eclipse 4.2.2,Android 开发者工具等)。)下载到捆绑安装中。
图 1-6。同意条款和条件,并在Developer.Android.com
页面下载面向 Windows 的 SDK ADT 捆绑包
在您单击“我已经阅读并同意上述条款和条件”复选框,然后选择您正在运行的操作系统类型(32 位或 64 位)的单选按钮后,您可以单击下载 Windows SDK ADT 捆绑包按钮,并下载适合您的操作系统类型的 zip 文件。
因为我有一个 Windows 7 64 位四核 AMD 系统,所以我选择了 64 位 SDK 版本,这是截至 Android 4.2 API Level 17 最近才推出的版本,也称为 Jelly Bean Plus。在 2012 年 11 月下旬 Android 4.2 问世之前(在感恩节假期,信不信由你),Android 应用开发人员别无选择,只能使用 32 位开发软件环境,即使他们在 64 位工作站上开发。因此,如果您喜欢在 64 位工作站上保持 64 位的整洁,那么您很幸运!现在,让我们开始下载,将那个 400MB 的文件下载到我们工作站的硬盘上。
安装 Android ADT IDE
400 MB 下载完成后,打开 Windows 资源管理器文件导航和文件管理实用程序,找到。系统下载文件夹中的 zip 文件。如果找不到,请在右上角的搜索框中使用通配符输入 zip 文件的部分名称,并在左侧选择您的计算机或 C:\ drive,Windows 资源管理器将为您找到该文件。
下面是我使用的搜索字符串:adt-bundle*.zip
(星号通配符可以扩展以适合文件名中的任何内容)。
一旦您找到了 adt-bundle-windows zip 存档文件,右键单击它以获得一个上下文相关的菜单,其中包含可以对该文件执行的所有操作,并选择 Extract All。。。选择将 zip 存档中的所有文件和文件夹提取到您的硬盘上。这显示在图 1-7 (用红色圈出)。
图 1-7。右键单击 Downloads 文件夹中的 adt-bundle zip 文件,并选择 Extract All。。。选择权
接下来,您将看到一个提取压缩文件夹对话框,要求您选择要将 ADT 包文件和文件夹层次结构提取到的目标目录。如图 1-8 中的顶部对话框所示,存档提取实用程序将文件的当前位置放入您的系统下载文件夹作为其目标路径,这并不奇怪,因为它确实不知道您想要将这些文件放在哪里,所以它做了一个假设,将自己放在 zip 文件当前“站立”的地方,可以这么说。让我们改变位置,将文件放在工作站上更合理的位置。
图 1-8。将解压缩文件的目标文件夹从 C:\Users\Username\Downloads 更改为 C:\adt-bundle-windows
因为我们并不真正希望我们的 adt 包安装到我们的 downloads 文件夹中(我们主要使用它来保存我们的文件下载),所以最简单的工作过程是将它移动到我们的 C:\ primary 硬盘驱动器的顶部或“根目录”,所以让我们在文件名中单击 ADT 包前面的光标,然后在自动生成的路径名的 Downloads、( Your User Name Here )和 Users 部分上退格。
这样,您就在刚刚下载的作为文件夹名称的adt-bundle-windows-x86 .zip
文件名前面加上了 C:\ hard drive 说明符。这将把 ADT 包安装在主硬盘分区上 C:\驱动器的根目录(顶部),您可以在需要的任何时候方便地看到和找到它。产生的路径显示在图 1-8 中屏幕截图的下部。
注意,如果你愿意,你也可以删除文件夹名称的windows
和x86
部分,并把文件夹命名为C:\ADT
或C:\ADT-Bundle
,或者C:\Android_Development_Environment
或类似的名字。因为您将找到并设置此应用可执行文件的快速启动图标,所以无论您将此顶级文件夹称为什么都不重要,只要您可以轻松找到它。
一旦你点击提取按钮,你会得到一个小的进度对话框,如图图 1-9 所示,它向你显示了所有的 8740 个项目(584 兆字节)正在被膨胀并复制到你的硬盘上。这个过程完成后,打开您的 Windows Explorer 文件查看实用程序,找到 eclipse.exe,,这是 Eclipse 4.2.2 应用的可执行文件。
图 1-9。从 400MB 的 ADT 捆绑包 zip 文件中提取 8,740 个文件,共 584MB
为此,在 Windows Explorer 实用程序中打开左侧导航面板上的 C: drive,然后查找您下载、安装并命名的 adt-bundle-windows-x86 文件名,在该文件名下,您将找到 eclipse 文件夹及其子文件夹,如图图 1-10 所示。如果您使用了自己的自定义名称,比如ADT_Bundle
,那么就寻找您决定用于 ADT 包文件夹的名称。
图 1-10。在 Windows 资源管理器中找到 Eclipse 应用文件,点击右键调用“锁定到任务栏”特性
在任务栏上设置快速启动图标
在单词 eclipse 旁边寻找一个紫色球体,其文件类型为“application ”,这就是您的 Eclipse IDE 软件可执行文件。接下来,右键单击这个 Eclipse 应用文件以获得另一个上下文相关的命令菜单,然后选择该菜单第三部分中的固定到任务栏命令。这将 Eclipse 作为一个快速启动图标安装在任务栏程序启动区域。这个工作过程也如图 1-10 中所示。
一旦从右键菜单中选择了 Pin to Taskbar 命令,Eclipse 4.2 ADT Bundle 紫色球体启动图标就会出现在 Windows 任务栏上。这通常位于操作系统屏幕的底部,但可以停靠(通过拖动)到屏幕的顶部,甚至是左侧或右侧。图 1-11 显示了安装在我的 Win7 任务栏上的 Eclipse 4.2 ADT 捆绑包启动图标以及其他专业开源软件包(GIMP、Blender 3D、Audacity、Lightworks、OpenOffice 和 Chrome,按此顺序显示),其中许多我们将在本章稍后安装。
图 1-11。Eclipse 4.2 ADT 捆绑包启动图标显示在 Windows 任务栏的最左侧
单击 Eclipse 快速启动图标,确保 Eclipse for ADT 启动;如果是的话,这意味着您已经正确地安装了 Java 6,并且 Eclipse ADT Bundle 已经成功地找到了 Java,并且正在使用它来运行它的集成开发环境。启动屏幕如图 1-12 所示。
图 1-12。通过新安装的快速启动图标启动 Eclipse 4.2.2 的 Android 开发者工具版本
值得注意的是,从 Android 4.2 Jelly Bean+开始,Eclipse Juno 4.2 启动屏幕现在已经消失,新的 ADT Bundle 启动屏幕显示 Eclipse 现在是我们刚刚安装的 64 位 Android Developer Tools 软件包的一个集成部分。
启动和配置 Android 开发人员工具 IDE
在您启动 Eclipse 4.2 for Android Developer Tools 软件后,您看到弹出的第一个屏幕是Contribute Usage Statistics对话框。您只在第一次启动 Eclipse ADT 包时看到这个屏幕,所以现在选择您想要的共享使用统计信息的选项。如果你愿意,你可以随时通过 Android 的使用统计菜单序列改变你的想法(选择的选项)。
我选择了将使用统计数据发送给 Google (是)选项,这样 Google 就可以看到我是如何使用 Eclipse 和 ADT 的,这样他们就可以对产品进行改进。您也可以选择没有负面影响的“否”选项。该对话框如图图 1-13 所示。
图 1-13。首次启动 Eclipse ADT 软件时遇到的 Contribute 使用统计对话框
做出选择后,单击 Finish 按钮,Eclipse ADT 包继续启动 Android 集成开发环境(IDE)。
当你第一次启动 Eclipse 4.2 的 Android 开发者工具版本时,你会看到一个 Android IDE 欢迎界面。我们将在本书的前几章中介绍屏幕上的所有内容,所以如果你想读的话,请全部阅读,然后使用顶部 Android IDE 标签右侧的 X 关闭它。一旦你这样做了,一个空版本的 Eclipse IDE 就会出现,如图 1-14 所示。
图 1-14。 Eclipse 4.2 for ADT 首次启动时显示空的包资源管理器、编辑、控制台和大纲窗格
我们要做的第一件事是确保我们刚刚安装的 ADT 包是最新的。我们通过帮助菜单和位于底部的检查更新命令来完成。一旦你选择了这个选项,你会看到如图图 1-15 所示的对话框。如果在 ADT 软件包安装过程中安装了 8,740 个文件的更新版本,您将看到检查更新。。。函数“获取”它们,并更新当前系统上的旧版本。
图 1-15。使用 Eclipse 和 Google 上的软件库在线检查 Android 软件更新
现在让我们看看如何在任何时候访问 Eclipse ADT 捆绑包的 Android SDK 部分,以了解 Eclipse 中当前安装的 Android API 支持级别。
Android SDK 管理器工具
因为我们下载了 Android 4.2 ADT 捆绑包,所以我们知道这将至少是 Android 4.2 API Level 17,但现在让我们使用 Android SDK Manager 工具来深入了解一下,看看 Android SDK 实际上为我们提供了哪些支持。我们可以通过 Eclipse 4.2 的窗口菜单,以及位于该菜单底部的 Android SDK 管理器选项,随时访问 Android IDE 的 Android SDK 部分。点击这个选项会打开一个 Android SDK 管理器窗口,显示我们当前在 Android IDE 中安装了哪些 API 级别(Android OS 版本)。让我们现在就开始,这样您就可以看到 ADT 捆绑包实际安装在您的 PC 上。
正如您在图 1-16 中看到的,这包括 Android SDK 工具、Android SDK 平台工具、Android SDK 4.2 平台、ARM 处理器系统映像(用于在我们即将设置的仿真器中进行测试)以及 Android 支持库。 Android 支持库允许支持以前版本的 Android,因此我们不需要下载所有其他 API 级别来支持所有的硬件设备。值得注意的是,这是我们在本书的示例中只使用在此过程中安装的 Android API Level 17,而不下载 Android SDK Manager 中显示的任何其他 Android API 的主要原因。
图 1-16。 Android SDK 管理器对话框,显示 ADT 捆绑包中安装的库和包
将来,如果你绝对需要开发其他版本的 Android,比如亚马逊 Kindle Fire 的 2.3.7,或者亚马逊 Kindle Fire HD 的 4.0.4,只需选择这些 Android 版本,然后点击安装包。。。窗口右下角的按钮。我强烈建议你一次只开发一个 API 级别,并且只针对那些你完全确定需要开发的 Android API 级别,因为每个级别都需要下载数百兆字节的文件和文档。
因为我们现在不会对 Android SDK 管理器做任何事情,所以单击窗口右上角的红色 X 关闭它,让我们准备好为我们正在开发的 Android 设备设置模拟器。在下一节中,我们将学习如何为一些更流行的智能手机、平板电脑和 GoogleTV 消费电子设备设置 AVD 仿真器。
配置 Android 虚拟设备(avd)
为本书的其余部分准备好您的 Android 开发环境的过程的最后一步是设置您的 Android 虚拟设备 (AVDs)。一个 AVD 是一个模拟器,它模仿安卓智能手机、平板电脑、电子阅读器、独立电视(GoogleTV)或机顶盒消费电子产品,允许你在你的工作站上测试你的安卓应用。
拥有一个仿真器是很有必要的,因为不断地将应用上传到实际的物理 Android 设备(硬件)上的工作过程比右键单击项目并选择 Run As Android Application 要繁琐得多。这并不是说你不应该偶尔在真正的 Android 硬件设备上测试你的应用,而是 AVD 允许你在开发时测试,并且速度更快,频率更高。在本节中,我们将创建三个这样的设备:一个 Nexus 7 平板电脑、一个 Nexus S 智能手机和一个 Android iTV 电视机。
因为你已经在 Eclipse 中,进入窗口菜单,选择底部附近的 Android 虚拟设备管理器选项。这将打开 Android 虚拟设备管理器对话框,其中列出了你的 avd。您当前没有 AVDs 设置,它显示:在对话框的中心区域没有可用的 AVD,如图图 1-17 所示。
图 1-17。 Android 虚拟设备管理器对话框——点击新建。。。添加 AVD 的按钮
要向您的 Eclipse 环境添加一个新的 AVD,单击 New。。。对话框右上方的按钮,打开创建新的 Android 虚拟设备(AVD) 对话框。
创建您的 Nexus 7 平板电脑模拟器
我们要做的第一件事是命名我们的仿真器,所以在名称:字段输入:Android _ 4.2 _ Emulator _ Nexus _ 7,如图图 1-18 所示。在设备:字段中,使用下拉菜单(通过向下箭头)选择 Nexus 7 平板电脑选项。然后选择目标:对于仿真器,是仿真器的 API 级别,是 Android 4.2,或者 API 级别 17,是 Android OS 目前最新的修订版。
图 1-18。使用 AVD 管理器为 Nexus 7 平板电脑和 Nexus S 智能手机创建新的 Android 设备(AVD)
因为 Nexus 7 使用的是 ARM (armeabi-v7a)处理器,所以这个字段是自动为你设置的,不能更改。接下来,检查硬件键盘存在和显示带有硬件控件的皮肤,因为这些选项适合拥有无线键盘的用户使用 Nexus 7,大多数严肃的用户都会这样做。
将前置摄像头选项下拉菜单设置为:网络摄像头如果您的工作站上安装了网络摄像头,否则选择无。这是从 Android 4.2 开始的新的 Android AVD 模拟器功能。注意,在 Android API Level 17 之前,Android Emulator (AVD)不支持任何种类的摄像头测试。
Nexus 7 AVD 配置文件根据 Nexus 7 制造商的硬件规格,为您设置内存选项和内部存储设置。接下来,将 SD 卡设置为普通 2048MB SD 卡配置,勾选快照选项。
最后,点击 OK 按钮,创建三个 Android 4.2 AVD 仿真器中的第一个,这一个用于 Nexus 7 平板电脑。如果你打算开发其他一些流行的 API 级别的平板电脑,利用完全相同的工作流程,并添加其他名为 Kindle Fire (使用 Android 2.3.7 API 级别 10 和 HDPI)或 Kindle Fire HD (使用 Android 4.0.4 API 级别 15 和 XXHDPI)的仿真器,例如,如果你需要测试你的应用在那些其他特定硬件平台上的交付。
接下来,让我们为 Nexus S 创建一个智能手机仿真器,如图 1-18 右侧所示,这样我们就有了一个智能手机仿真器和一个平板电脑仿真器。
创建您的 Nexus 智能手机模拟器
我们做的第一件事是命名模拟器,因此在名称字段中输入Android _ 4.2 _ Emulator _ Nexus _ S。在设备区域,使用下拉菜单(通过向下箭头)选择 Nexus S 选项。然后为模拟器选择目标,同样是 Android 4.2 API Level 17。
接下来检查硬件键盘存在和显示带有硬件控件的皮肤,因为这些选项适合 Nexus 的高级用户,并使应用测试更容易。如果您的工作站上有网络摄像头,请将前置摄像头选项下拉菜单设置为网络摄像头,否则选择无。
Nexus S AVD 配置文件根据 Nexus S 的硬件规格为您设置了内存选项和内部存储设置。接下来,将 SD 卡设置为普通 1024MB SD 卡配置,勾选快照选项。最后,单击 OK 按钮创建第二个 AVD 仿真器。我们现在在 Android 虚拟设备管理器窗口中创建了两个模拟器,如图 1-19 所示。
图 1-19。 Android 虚拟设备管理器展示了两款适用于 Nexus 7 平板电脑和 Nexus S 智能手机的新模拟器
我们真正应该创建来测试我们在本书中的内容的唯一其他类型的 Android 虚拟设备是上市的最新类型的 Android 设备 iTV,或交互式电视机。
为 GoogleTV 支持创建您的 Android iTV 模拟器
Android iTV 电视机运行一个名为 GoogleTV 的品牌版本的 Android 操作系统,可以作为 iTV 电视机、机顶盒(STB)或家庭媒体中心(HMC)使用,甚至可以作为飞利浦产品的 USB 记忆棒使用。
幸运的是,我刚刚得到了新的飞利浦谷歌电视 u 盘产品,用于我的一个客户的 Android 应用开发项目。因此,我有了产品的规格,我需要创建自己的自定义 AVD 设备定义规格来创建用于测试的 AVD。
我需要做的第一件事是查看它是否在标有设备定义的右侧选项卡上的预配置 avd 列表中,如图 1-20 所示。但是,Philips GoogleTVu 盘不在其中,因此我们需要进入另一系列对话框,将 Philips Google TV 产品添加到当前 AVD 管理器设备定义对话框中的产品列表中。我在我们的平板电脑和智能手机模拟器中添加 iTV 模拟器的原因之一是专门向您展示这个工作过程,因为它在 AVD 管理器中比 AVD 创建过程隐藏得更深。
图 1-20。AVD 管理器设备定义选项卡,您可以从中选择预定义的 AVD 设备或创建自己的设备
解开这个新设备定义工作流程的关键是,你猜对了,新设备。。。按钮,位于 Android 虚拟设备管理器的设备定义标签面板的右上角。点击新建设备。。。按钮,得到创建新设备对话框,如图图 1-21 所示。
图 1-21。AVD 管理器窗口的设备定义选项卡中的创建新设备对话框
“创建新设备”对话框允许我们为 Android 虚拟设备管理器窗口中的“设备定义”选项卡创建自定义产品设备定义。现在让我们为飞利浦谷歌电视 u 盘创建一个。在名称:栏,放置飞利浦的 GoogleTV 盘的产品名称,同时输入最常见的屏幕尺寸: 42 英寸的,其物理像素分辨率为 HDTV 1920 x 1080 像素。因为高清电视没有传感器,不要勾选这四个选项,因为谷歌电视 u 盘没有任何摄像头,也不要勾选这两个选项。飞利浦 GoogleTV 盘产品使用鼠标,因此选择轨迹球的输入部分选项。
该产品具有 1GB 的 RAM,因此在 RAM: 字段中输入一个 1 ,并选择 GiB 下拉菜单选项。该产品具有超大尺寸,因此为尺寸:选项选择 xlarge 值,为屏幕比例:选项选择 long ,因为高清电视是宽屏设备。像素密度为 1920 x 1080 像素,与屏幕尺寸无关,因此选择 XXHDPI 作为密度:设置。由于本产品是 u 盘,选择按钮的软件:选项,然后启用纵向和横向模式复选框,如图图 1-21 所示,因为本产品在这两种模式下都能正常工作。
正确输入所有这些新产品规格后,可以点击对话框底部的创建设备按钮,将新的产品定义添加到 AVD 管理器窗口的设备定义选项卡中,如图图 1-22 所示。
图 1-22。飞利浦谷歌电视 u 盘添加到 AVD 管理器设备定义列表
现在我们在设备定义选项卡中有了 Philips GoogleTV USB stick 产品,我们可以选择它,如图 1-22 所示,并通过单击对话框中间的创建 AVD…按钮,使用该产品定义创建一个新的 Android 虚拟设备仿真器。
这将打开创建新的 Android 虚拟设备(AVD) 对话框,如图图 1-23 所示,在对话框的相关区域有适当的设备设置,很像我们为谷歌 Nexus 产品创建 AVD 仿真器时观察到的情况,如图图 1-18 所示。
图 1-23。为飞利浦谷歌电视 u 盘创建新的 Android 虚拟设备
请注意,ADT 为我们将设备命名为AVD _ for _ Philips _ Google TV _ USB _ Stick,并且它还在第二个设备:下拉菜单中为我们选择了该产品。目标:默认 Android 4.2 API Level 17 也是自动为我们设置的,所以我们所要做的就是填写内部存储和 SD 卡值,它们都是 4GB 。
选择快照选项,确保键盘和皮肤选项未选中,因为产品没有键盘或硬件控制功能。注意,我们的内存(RAM)设置是正确的,为 1024 兆字节(1GB),就像我们之前在创建新设备对话框中设置的一样。
填写完所有相关字段后,点击 OK ,为飞利浦谷歌电视 u 盘创建 AVD。请注意,飞利浦谷歌电视 u 盘 AVD 已被添加到图 1-24 中的 AVD 管理器主窗口,我们现在有了用于智能手机、平板电脑和 iTV 的 AVD。
图 1-24。飞利浦谷歌电视 u 盘被添加为 Android 虚拟设备
现在我们已经为 Android 应用开发做好了准备,让我们去寻找一些我们将在本书中使用的新媒体工具,为我们的应用添加元素,使它们与众不同。我们将获得主要的新媒体开发软件类型,包括数字图像(GIMP)、数字音频(Audacity)、数字视频(Lightworks)和 3D (Blender)。所有这些专业级开源软件包都可以在所有 Windows (XP、Vista、7 和 8)和 Linux 平台上使用。
Android 开发中使用的新媒体工具
因为我们正在下载和安装第一章中的软件包,以完成开发所必需的所有必要的繁重工作,但这并没有教会我们任何实际的 Android 应用开发,所以让我们也抓住所有其他强大的开源软件,我们将需要与 Android 开发环境结合使用,以创建真正令人难忘的、丰富的媒体加载用户界面(ui)和用户体验(UXs)。
我们将在第一章中解决所有这些软件下载和安装的问题,并为我们自己构建一个内容开发工作站,为所有类型的新媒体内容开发提供良好的服务。
数字成像软件:GIMP 2.8.4
首先,让我们安装领先的开源数字成像软件包 GIMP 2.8.4 for Windows。如果你有 Photoshop CS6 那很好,但是为了确保所有的读者都有一个专业的图像应用,你需要下载并安装 GIMP,因为它是免费的!
你可以找到瘸子 GIMP.org。在页面的右上方,你会看到一个下载按钮以及一个下载链接(见图 1-25 ),点击后会将你带到 GIMP 2.8 下载页面。接下来,单击与您的系统当前运行的操作系统相匹配的 GIMP 程序版本的下载链接。对于我的 Windows 7 工作站,我下载了。GIMP 2.8.4 的 Windows XP、Vista、Windows 7 和 Windows 8 版本的 EXE 安装程序。
图 1-25。GIMP 2.8 下载按钮和下载链接位于 www.gimp.org网站
找到。EXE 安装程序文件,双击它,使用建议的默认设置安装软件。然后,在你的 Program Files 文件夹中找到这个文件,右击应用 EXE 文件,然后固定到任务栏,这样它就位于任务栏的启动图标区域,如前面的图 1-10 所示。您现在离创建一个极其强大的新媒体 Android 应用开发工作站又近了一步。接下来让我们得到一个领先的数字音频编辑软件包。
数字音频软件:Audacity 2.0.3
接下来,让我们安装领先的开源数字音频软件包 Audacity 2.0.3 for Windows。如果你有 Reason、SoundForge 或 ProTools,那很好,但为了确保我们都有一个专业的音频工程应用,你需要下载并安装 Audacity 2.0.3,因为它也是免费的!你可以在 SourceForge.net 开源网站找到 Audacity。
在首页的搜索栏中输入 Audacity ,就会找到【Audacity.SourceForge.Net】的软件下载页面,如图 1-26 所示。在页面的左上角,你会看到一个下载按钮,带你去下载软件,在我的例子中,是 Windows 操作系统版本。
图 1-26。SourceForge 的 Audacity 2.0.3 项目。net—单击 Windows 2000/XP/Vista/Windows 7/Windows 8 的下载链接
接下来,单击与您的系统操作系统相匹配的程序版本的下载链接。Windows 版本的 EXE 安装程序。找到。EXE 安装程序文件,双击它并使用默认设置安装该软件。
接下来,在它的 Program Files 文件夹中找到该文件,右键单击该应用 EXE 文件并将固定到任务栏,使其位于任务栏的启动图标区域,如前面的图 1-10 所示。现在,您离创建超级强大的新媒体 Android 应用开发工作站又近了一步。
数字视频软件:EditShare Lightworks 11
接下来,让我们安装领先的开源数字视频编辑软件包EditShare light works 11for Windows。如果你有 FinalCut 或 AfterEffects CS6,那很好,但为了确保我们所有人都有一个专业的视频编辑应用,你应该下载并安装 Lightworks,因为它是免费的!你会在 www.LWKS.com
找到 Lightworks 11,在页面右上方会看到一个下载按钮,带你进入图 1-27 所示的下载页面。
图 1-27。用于 Windows installer 的 EditShare Lightworks 11 下载页面
要下载此免费软件,您必须登录或注册,然后您可以单击与您系统的操作系统和位级别相匹配的 Lightworks 程序版本的下载链接。
就我而言,我下载了。Windows 7 64 位版本的 EXE 安装程序,大概 100MB 左右。请务必下载发行说明和自述文件,并彻底阅读它们,因为这是一个高端软件包,过去要花费数千美元。
下载完成后,找到。EXE 安装程序文件,然后双击它,并使用其默认设置安装该软件。如果您只安装一个 light works“seat”(一台机器上的一个副本),请将其设置为数字 1(在安装期间出现的第一个对话框中输入 1 或 001)。请注意,您可能需要在第一个对话框中退格并实际键入一个 1 来激活下一步按钮(使其不变暗并可点击)。
然后,在它的 Program Files 文件夹中找到该文件,右键单击应用 EXE 文件并固定到任务栏,这样它就位于任务栏的启动图标区域,如前面的图 1-10 所示。您现在离创建您一直梦想的强大的新媒体 Android 应用开发工作站又近了一步。
3D 建模和动画软件:Blender 3D
最后,让我们安装领先的开源 3D 建模、渲染和动画软件包 Blender 3D 2.66 for Windows。如果你有 Lightwave 或 Maya,那很好,但为了确保我们所有人都有一个专业的 3D 应用,你应该下载并安装 Blender 3D,因为你又猜对了——它是免费的!
你可以在 www.Blender.org
找到 Blender 3D,在 Blender 主页的左上方,你会看到一个下载菜单,或者,如果你喜欢,在右上方有一个下载 Blender 链接,会带你到下载页面(见图 1-28 )。
图 1-28。位于 Blender.Org 的 Blender 3D 网站显示了下载菜单(顶部)和下载 Blender 链接(右侧)
首先,点击下载 Blender 链接,在下载页面寻找与你的系统的操作系统和位级相匹配的程序版本。就我而言,我下载了。Windows 64 位版本的。一旦安装文件下载完毕,找到搅拌机。EXE 安装程序文件,双击它,并使用默认设置安装该软件。
接下来,继续通常的工作过程,在程序文件文件夹中找到该文件,右键单击应用 EXE 文件并固定到任务栏,这样它就位于任务栏的启动图标区域,如之前的图 1-10 所示。
现在,您已经创建了一个全面的新媒体 Android 应用开发工作站,可以在本书以及未来的软件开发项目中使用。这个新的媒体开发工作站将允许您开发具有高级 3D 图像、数字音频、数字视频和数字图像的高度专业的 Android 应用。恭喜你!你刚刚安装了开源软件,如果你付费的话,它的价格将是你刚刚购买这本书的一百倍。不错的投资回报!
摘要
在第一章中,我们准备好了所有的东西(或者更好的是,我们准备好了所有的软糖),为我们将在本书剩余章节中进行的 Android 应用开发做准备。
我们收购并下载了一系列非常有价值的内容制作软件,从 Java 到 Eclipse 到 Android,以及所有主要的四种新媒体类型的领先新媒体应用:3D 建模、数字视频编辑、数字音频编辑和数字图像编辑。
我们学习了 Eclipse 如何在 Java 之上运行,以及 Android SDK 如何通过 ADT 工具插件集成到 Eclipse IDE 中,以创建无缝的 Android 开发环境。我们看到,从 Android 4.2 开始,ADT 捆绑包的安装比过去要容易得多,附录 A 证实了这一点,并在更深层次上展示了 Eclipse 4.2、Android 4.1 SDK 和所有 ADT 插件是如何无缝集成在一起的。
在第二章中,我们将进入正题,创建我们的第一个 Hello World 应用,看看 Android 如何使用 Java 和 XML 以及我们的新媒体资源设置一切。我们将学习 Android 用来描述 Android 应用的不同领域和功能的术语,甚至在本章稍后构建的 Hello World 应用中使用其中的一个。
二、探索 Android 应用开发:Android 的行话并构建您的第一个 Hello World 应用!
因为本书面向已经熟悉计算机编程原理和工作流程的读者,所以每个人都熟悉 Hello World 编程示例的通用概念,该示例用于向我们展示关于任何给定编程语言和平台如何建立其基本代码结构并运行的所有基础知识。
因为我想让我们的读者在本书中尽可能快地达到中级水平,所以在本章的第二部分,我将在这里安装并运行 Hello World 应用。首先,我将讨论 Android 应用开发中使用的术语。然后,我们将使用 Hello World 应用示例向您展示 Android 如何构建其编程环境,并向您展示基本的 Android 应用开发工作流程。
我们将了解如何在 Eclipse 中创建新的 Android 应用项目,如何在 Eclipse Package Explorer 中构建和组织该项目,Android 如何使用 XML 标记快速轻松地定义数据结构、项目参数、操作系统权限和用户界面,这样我们就不必用 Java 来完成这些工作,我们的新媒体素材如何命名并存储在预定义的逻辑资源文件夹层次结构中,当然,Java 编程语言如何通过 Android 包、类、方法和常量来引导和整合这些内容。Android 开发绝对是独一无二的。
然而,首先,我们需要花一点时间预先了解 Android 应用的各种组件。我们将了解 Android 环境中各种 Android 组件的术语。Android 使用一些非常独特的术语或行话来描述各种 Android 应用组件,我们将学习这些行话以及这些组件如何无缝地一起工作。
然后,在本章的下一部分,我们可以开始工作,使用一些新的术语、XML 标记和 Java 编程语言来构建我们的第一个 Android 应用。
Android 应用开发行话
Android 使用非常独特的术语来描述其应用开发的各种组件或领域。在这一节中,我定义了这些领域是什么,以及它们与其他常见编程语言(如 C++ 或 Java)的最接近之处。此外,我将向您提供更多关于除了 Java 和 XML 之外还有哪些开源技术驱动 Android 的见解,以及 Android 如何将其 Java 优化为二进制文件,以便在嵌入式(便携式消费电子)设备上使用。
Android 的基础:Linux 2.6 内核
Android OS 的基础是 Linux 2.6 内核;就像 Eclipse IDE 运行在 Java 之上一样,Android 运行在最新 Linux 操作系统的完整版本之上。所以,是的,任何和所有的 Android 设备本质上都是功能齐全的 Linux 计算机,这就是为什么你现在看到的 Android 设备都有双核、四核和八核处理器,就像“真正的”计算机一样。内存也是如此——大多数 Android 设备都至少有 1GB 的 RAM(随机存取内存),许多设备都有 2GB,很快你就会看到 3GB 或 4GB RAM 的 Android 设备。
Android 操作系统使用 Linux 操作系统的核心库来完成所有使用任何计算机操作系统完成的文件、内存和处理的底层工作,由于 Linux 高度优化,它可以比其他操作系统使用更少的内存(更高效地)来完成这些工作。为什么新的 Windows 8 操作系统的内存效率突然变得如此之高?因为它要和 Linux (Android)竞争,这就是原因。
据传,谷歌在更新的 Linux 3.8 内核上运行 Android 4.2.2,而 Android 5.0 将在 2013 年晚些时候在更现代的 Linux 内核上运行。
Android 优化:Dalvik 虚拟机(DVM)
Android 应用开发者使用 Android SDK 及其 Java 编程语言来访问较低级的 OS 功能,以及 Android 为使用各种新媒体素材、SQLite 数据库和 Android 设备的硬件特征(例如相机、陀螺仪、GPS 等)而提供的其他较高级的功能。
当您编译 Java 代码和其他素材(XML、音频、图像等)时。)Android 使用 DVM 或 Dalvik 虚拟机技术将这些文件优化为高度优化的二进制格式,就像可执行文件一样,只是为了在更小、更便携的设备上运行而进行了更多优化。如果你查看你的 Android APK 文件,你会在你的项目根目录下看到一个. DEX 文件。这是一个 Dalvik 可执行文件。
DVM 不是 Android 开发人员在日常开发中需要关心的事情,我在这里只是提到它,这样如果有一天你碰巧在 Android 相关的 Google 搜索中遇到它,你就会知道它是什么以及 DVM 在整个开发过程中做什么。接下来,让我们进入 Android 的行话,了解 Android 应用的四个主要组件,它们是什么,以及它们如何一起工作以形成一个统一的 Android 应用。
Android 活动:表示层
Android 使用术语活动来描述用户看到的应用的“前端”屏幕。
活动是设计元素的集合,它们一起工作,组成应用的每个屏幕视图。这些元素包括用户界面元素、文本、背景图形、3D、前景内容、数字视频、弹出菜单、动画元素和其他视觉设计组件,这些元素用于在应用的功能(其活动)和使用应用的最终用户之间提供界面。我们在本书的前两部分详细了解了活动。
在 Android 术语中,活动通常由一个布局容器组成,它将一系列被称为小部件的用户界面元素,连同应用的内容一起,组织和排列到预定义的屏幕区域中。如果你想包含图形元素,让你的设计更引人注目,那么你可以使用 Drawables ,这是 Android 对图像或动画等图形元素的术语。
Android 中的动画同时使用了 Widgets 和 Drawablesdrawable 用于基于帧的动画(光栅动画),Widgets 用于基于程序或补间动画(矢量动画),drawable 也是如此。令人惊讶的是,Android 将动画准确地称为:动画。Android 中还有一个术语与其他编程语言中的术语相同: Events 。事件允许使用事件处理代码来处理组成我们活动的许多元素,就像在许多其他编程语言中一样。因为本书的读者熟悉编程,并且因为需要事件处理来使本书前半部分的用户界面设计具有功能性,所以我将在本书的早期介绍事件,这样我们可以更快地创建更健壮的应用。
我们将在第四章的中学习更多关于 Android Activity 类,以及通过 Android Layouts 设计屏幕布局,这利用了 Android ViewGroup 类。在第五章中,我们将通过 Android 小部件学习更多关于用户界面设计的知识,这些小部件利用了 Android 视图类。最后,当我们讲述在 Android 中使用数字图像、数字视频和动画的高级用户界面设计和图形设计时,我们将在第六章的中了解更多关于事件的信息,并在第七章到第十章的中了解更多关于的信息。
Android 服务:处理层
活动代表你的 Android 应用的前端或前台,而 Android 服务代表后端或后台处理,如果你愿意的话,是繁重的工作。
Android 服务类用于创建执行重复处理任务的 Java 类,通常通过称为循环的编程结构或通过数据获取过程(如流)在后台执行,同时用户通过其前端用户界面活动使用您的应用。一个很好的例子就是用户在使用你的应用时播放 MP3 音频文件。
Android 中的服务类有许多用途,明显和常见的用途是播放音频 MP3 和数字视频 MP4 文件以供娱乐,从远程服务器流式传输新媒体文件,将文本转换为语音(语音合成),在玩游戏时计算游戏逻辑,处理 Android Live 壁纸或 Android 白日梦,使用蓝牙或 NFC 在最终用户之间实时传输大型新媒体文件,运行拼写检查过程,任何过于复杂的数学计算,3D 渲染, 以及任何需要大量处理器开销(功率)的东西,这使得活动用户界面或内容变得生硬和不专业。
对 Android 开发人员来说幸运的是,大多数现代 Android 设备,如智能手机、平板电脑、电子阅读器和 iTV 电视机,都配备了双核(两个中央处理器,或称 CPU)处理器的最低硬件配置,现在更多的设备都配备了四核(四个 CPU)处理器,现在市场上也出现了八核(八个 CPU)处理器的 Android 设备。这意味着您的 Android 服务类有足够的处理能力来利用!
Android 服务类中更强大的选项之一是将您的后台处理服务放入它自己的线程(我喜欢称之为产卵,但在编程术语中它被称为进程),由于 Android 设备中存在如此多的 CPU,这实际上可以转化为您的服务类被分配了它们自己的 CPU 核心。
Android 还有许多专门的服务类,这些服务类已经是主要 Android 服务类的子类(我们将在第三章中学习这个 Java 术语)。例如,Android 开发人员也可以使用 SpellCheckerService、WallpaperService、TextToSpeechService、DreamService、IntentService、AccessibilityService、VpnService 和 AbstractInputMethodService 类。我们将在本书第十七章的中学习所有关于服务的知识。
Android 广播接收器:通信层
Android 通过广播接收器在其应用基础设施内部进行通信。
这些通常用于应用间的通信,以及为您的用户提供警告,很多时候来自 Android 操作系统或 Android 设备本身。例如,如果电话在响,或者如果平板电脑的电池电量即将耗尽,Android 操作系统会发送一个广播接收器,您的应用编程逻辑可以通过某种自定义操作做出响应。其他常见消息涉及用于拍照的相机、时区改变、数据下载完成、语言偏好改变、视频缓存完成并准备好回放等等。
与 Android 服务类似,广播接收器在后台运行,您可以配置您的应用代码来“捕获”您认为您的应用及其最终用户需要关注的任何类型的广播接收器。一旦您的代码检测到需要响应的广播接收器,它就可以在适当的活动中调用适当的用户界面元素,并提醒最终用户操作系统或设备状态的变化。我们将在第十八章中仔细研究广播接收机。
Android 内容供应器:数据存储层
Android 对一个常见的概念有一个独特的术语:数据存储。无论是存储在内存中、SD 卡上的文件中还是 SQLite 数据库中,Android 中存储的数据都可以通过内容供应器进行访问。
跨 Android 应用存储和共享数据的主要(也是最好的)方式是使用开源的 SQL 技术,这是 Android 操作系统的一部分。这是 SQLite 数据库管理工具包。Android 有一个专门用于 sqlite 数据库管理范例的完整包,名为 android.database.sqlite,,它包含了创建和访问 SQLite 数据库所需的一切。
Android 内容供应器 SQLite 数据库被 Android 操作系统广泛用于 Android 设备用户定义的数据存储。在本书的后面部分,我们将会了解一些对 Android 设备用户更重要的数据库,比如联系人数据库和日历数据库。还有新的媒体相关数据库,可以跟踪用户的图像、音频文件、视频素材和播放列表。
在第十九章中,我们将了解更多关于 Android 内容供应器的信息,并了解如何创建、填充、修改和删除 Android SQLite 数据库。
Android 意图:应用间通信
一个 Android 应用通过一个意图在它的主要组件之间通信。
例如,您可以使用意图在您的活动和服务之间进行通信,您可以做任何事情,从启动新的任务或活动,到向现有的任务发出新的任务指令。
Intent 对象保存关于哪个应用组件需要执行所需任务的信息。这包括需要采取什么动作来完成该任务,任务用来执行该动作的数据的定义,以及可选地,该数据的 MIME 类型和任何标志(设置)以及可能需要的其他可选数据或信息,以通过从一个 Android 应用模块发送到另一个 Android 应用模块的意图对象来完全传达确切需要完成的内容。是的,就实现而言,意图是 Android 操作系统中较为复杂的领域之一。
每种类型的 Android 组件、活动、服务或广播接收器都有自己的意图对象类型。这保证了意图不会混淆,并保持一切井井有条。因此,要通过意向启动服务,需要传递一个 context . start Service();Java 方法调用与您的意图。对于活动,使用 context . start Activity();Java 方法调用,对于广播接收器,使用 context . send Broadcast();Java 方法调用。我们将在第三章 (Java)和第十六章(意图)中了解这一切。
Android 清单:应用权限定义
最后,Android 利用术语 Manifest 来描述 XML 文件,该文件“引导”或定义并启动任何给定的 Android 应用。Android 使用 XML 来定义许多东西,就像用于启动网站的 index.html 文件一样,Android 应用通过他们的项目 APK 的 AndroidManifest.xml 文件启动。
在任何 Android 应用的根级别中。 APK (Android 包)文件,你会发现一个名为: AndroidManifest.xml 的文件,其中包含 xml 标记标签,这些标签定义了 Android 操作系统需要了解的关于你的应用的所有信息。这包括但不限于,应用支持哪些 Android OS 版本,应用包含哪些活动、广播接收器和服务,应用需要哪些权限来访问安全数据库和互联网资源,以及需要建立的意图过滤器。
我们将在需要定义特殊活动、服务、广播接收器或为应用组件分配权限的章节中讨论 Android Manifest。当我们读到这本书的结尾时,这将会是相当多的。
创建您的第一个 Android 应用
我们不要再浪费时间了,因为这一章我们有很多内容要讲,所以让我们直接进入正题,通过你在第一章中设置的快速启动图标启动 Eclipse。我们将在本节创建一个 Hello World 应用框架,我们将使用它来构建一个 world generation toolkit,您可以使用它作为创建自己的星际游戏的起点。
接受 Eclipse 在用户文件夹中为您设置的工作区文件夹的默认路径,并在屏幕上启动 IDE。进入文件菜单,找到顶部的新子菜单,然后选择安卓应用项目 子菜单。
或者,你也可以使用文件新建
项目。。。菜单顺序,这是 Android 4.2 之前的访问方式。这将打开一个新项目对话框,其中列出了您可以在 Eclipse 中创建的不同类型的项目。找到 Android 项目类型,点击左边的三角形 UI 元素打开 Android 应用类型的子菜单,然后选择 Android 应用项目,最后点击对话框底部的下一个
按钮。
这两个工作流程都会打开新建 Android 应用对话框,在这里我们可以设置 Hello World Android 应用的所有顶级属性,如图图 2-1 所示。
图 2-1。在 Eclipse 中新的 Android 应用对话框中命名我们的应用、项目和包
首先在对话框的第一个字段中输入应用名称,Hello_World。请注意,当您在 Hello_World 中键入时,第二个字段项目名称也是通过双重键入特性填写的。这意味着 Android 希望我们的应用名称和项目名称相同,正因为如此,我将在我们的 Hello World 应用名称中使用下划线而不是空格,因为这是一种相当常见的编程实践,也因为我不喜欢在我的文件名或文件夹名中使用空格。
第三个字段允许我们为我们的新 Android 应用项目创建我们的包名,为了对本书使用一致的包命名约定,让我们使用: chapter.two.hello_world 作为我们的包名,它应该使用所有小写字母和下划线。接下来,我们需要为我们的 Android 应用选择我们的最低要求 API 支持级别,我们将通过 Android 2.2 API 级别 8 设置将其设置为 9 个版本的向后兼容性,因为市场上仍有许多 Android 2.2 和 2.3.7 设备需要我们提供支持。我们还需要设置我们的目标 SDK 级别,这是我们正在开发的目标 Android API 级别,在这种情况下,它是 Android 4.1 API 级别 16,因为这是我们在第一章中安装的仿真器。我们将使用我们刚刚在第一章中安装的当前 Android 4.2 API Level 17 进行编译,因为这是我们拥有的最新(也是最无 bug 的)软件,并且您总是希望使用最新的开发软件代码进行编译。现在点击下一个 按钮,进入下一个对话框,如图图 2-2 所示。
图 2-2。新的 Android 应用–配置项目对话框,我们在其中选择创建新项目的选项
在配置项目对话框中,勾选对话框顶部的创建自定义启动器图标复选框和创建活动复选框,以及对话框底部的在工作区创建项目复选框,然后点击 旁边的进入配置启动器图标对话框,如图图 2-3 所示。
图 2-3。在“配置启动器图标”对话框中,我们可以选择预定义的 Android 应用启动图标
此对话框允许您从图像、剪贴画或文本资源中选择预定义的 Android 应用启动图标,其中许多已经为您提供。现在让我们选择缺省值(正如我们在所有这些对话框中所做的那样),看看缺省的新 Android 应用项目创建过程是如何进行的,这样我们就有了一个最基本的理解。在你选择了你的应用启动器图标(Android 将它命名为 ic_launcher.png,我们将在本章后面看到)之后,点击下一个 进入创建活动对话框,在这里我们将为我们的应用创建一个空白活动。我们选择了一个 BlankActivity ,这样我们就可以看到 Android 将通过新的 Android 应用项目系列对话框生成的最少代码。对话框如图 2-4 所示。
图 2-4。新 Android 应用项目系列对话框中的创建活动对话框
你可能想知道 Android 中的 Activity 是什么,它的功能到底是什么。一个活动是一个包含内容和用户界面(UI)设计的屏幕区域,为您的应用提供一个前端(显示区域)来与您的最终用户进行可视化交互。正如您将在本章中看到的,主活动是通过 XML 标记定义的,然后通过 Java 代码在主活动类的应用 onCreate()方法中进行膨胀。该活动包含由 Android ViewGroup 类指定的布局容器(逻辑上布局是视图组),这些布局容器包含 Android View 类小部件,这些小部件是 UI 元素和内容容器。
点击下一步 按钮,进入新建空白活动对话框,如图图 2-5 所示,我们将为我们的 Android 应用的活动设置参数,就像我们在第一个新建 Android 应用对话框中所做的一样。让我们接受默认的(建议的)名称,只是为了熟悉 Android 希望我们如何称呼应用的各种主要组件,并将我们的活动命名为 MainActivity ,将我们的布局 XML 文件命名为 activity_main ,导航类型为None——同样,这样我们可以看到新的 Android 应用项目系列对话框将为我们提供的最少的引导代码。现在点击完成并让 ADT 继续新项目的创建过程。
图 2-5。新的空白活动对话框,我们在其中命名活动并选择其导航类型
单击最后一个对话框中的最后一个 Next 按钮后,Android 开发工具(ADT) 将检查您当前的安装(SDK 和插件设置),以确保您拥有创建您刚才指定的 Android 应用项目所需的一切。例如,如果您指定了 Android 2.2 到 4.1 支持,但只安装了 API 10 级(2.3.7)到 API 16 级(4.1.2),那么会出现一个安装依赖项对话框,要求您安装 API 8 级(2.2)。一旦你点击了 Install/Upgrade 按钮,这些额外的 API 将会从 Google 仓库中取出并安装在你的系统上,用于你的 Eclipse Android 开发配置。现在,我们准备在 Eclipse IDE 中开发新的 Hello World 应用,一旦安装依赖项下载完成(如果需要)并且其安装过程已经完成,它将出现,并填充有新的 Android 应用项目。接下来让我们看看 ADT 如此好心地为我们创建的 virgin Android 项目!
剖析一个 Android 应用项目
让我们看看 ADT 新的 Android 应用项目 helper 在我们的 Eclipse IDE 中为我们创建了什么,从 IDE 左侧的 Package Explorer 项目导航窗格开始。Package Explorer 实用程序允许我们在开发过程中的任何时候浏览 Android 应用的项目素材层次结构,就像 Windows Explorer 实用程序允许我们浏览硬盘驱动器和操作系统一样。图 2-6 显示了新项目在 IDE 的屏幕上弹出后 Eclipse 的样子。
图 2-6。Eclipse 中显示了新的 Android 应用项目,打开了关键文件夹和子文件夹以显示项目素材
让我们通过点击指向右边的每个文件夹旁边的小三角形 UI 元素,打开您的 Hello_World 顶层项目文件夹中的一些关键子文件夹。现在,让我们把重点放在名为 src (源代码)和 res (资源)的文件夹上,因为它们是任何 Android 项目中最重要的文件夹。打开 src 文件夹,其中包含一个名为 chapter.two.hello_world 的子文件夹,您可能记得我们在新的 Android 应用对话框中将其指定为我们的包名。
再深入挖掘一下,在 chapter.two.hello_world 文件夹下,你会发现我们的MainActivity.java文件,其中包含新的 Android 应用创建过程为我们编写的 Java 代码,它“引导”或启动我们的活动(主屏幕)用户界面布局和菜单代码,这些代码恰好是通过布局和菜单文件夹中的 XML 文件定义的。我们将在本章的后面部分打开这个 Java 文件,看看这个 Java 代码和它到底做了什么。您开发的任何 Android 应用的所有 Java 代码文件都将保存在这个 package-name 文件夹中。
还要注意图 2-6 中的,我点击了 MainActivity.java文件旁边的向右箭头三角形,包浏览器也显示了这个 Java 代码中的 Java 方法,所以看起来 Eclipse 包浏览器不仅导航文件,还导航代码结构。对于 IDE 来说,这是一个非常前卫的特性。
我们需要仔细查看的另一个主要应用素材文件夹是 resources(资源)或 res 文件夹,正如您可能已经猜到的那样,Android 应用不可避免地要利用它来创建用户体验(UX)的所有 XML 和新媒体资源都存放在这个文件夹中。点击 res 文件夹旁边的三角形 UI 图标,如图图 2-6 所示,打开它,你可以看到它的很多子文件夹都是新的 Android 应用项目助手为我们创建的。
您在顶部看到的前四个文件夹都以单词 drawable 开头,正如您可能猜对的那样,这是一个 Android 应用素材,可以通过我们之前了解的活动绘制到屏幕上。drawable 可以是图像、形状、帧动画、图像过渡、图标或任何类似的图形相关内容或应用所需的用户界面元素。还要注意,有四个可绘制的文件夹——每个文件夹对应一个屏幕密度级别:低、中、高、超高。值得注意的是,从 Android 4.2.2 开始,还有一个额外的超高(XXHDPI)分类。
单击任何一个(或者所有四个,如果你喜欢的话)可绘制文件夹旁边的三角形 UI 元素,你会看到 ic_launcher.png 启动器图标,这是我们在新的 Android 应用项目系列对话框的第三个配置启动器图标对话框中创建的。请注意,您只需将应用图标正确命名(ic_launcher.png)并放置在正确的 drawable 文件夹中,就可以为您的应用显示图标。没错:零编码。我们将在本书的第二部分讨论如何为这些不同的可绘制文件夹优化图标和图形。
这四个可绘制的文件夹允许 Android 开发者(比如你自己)在所有类型的 Android 设备上提供定制的像素级完美图形元素,从智能手机到平板电脑到电子阅读器到 iTV 电视机。低密度像素图像(LDPI) 针对最小的 Android 屏幕进行了优化,例如 120 DPI(也代表点每英寸)的翻盖手机或 MP3 播放器,它们通常具有 320 x240 像素的小屏幕。中等密度像素图像(MDPI)适用于使用 160 DPI 像素屏幕密度的入门级智能手机(480 x320 和 640 x480 分辨率)。
高密度像素图像(HDPI) 适用于主流智能手机和使用 240 DPI 像素密度屏幕的 5 英寸至 7 英寸迷你平板电脑(800 x480、854 x480 和 960 x540 分辨率)。超高密度像素图像(XHDPI) 使用 320 DPI 像素密度屏幕,适合高清智能手机和更大的 8 英寸至 11 英寸平板电脑(1024 x600、1280 x720 和 1280 x800 分辨率)。互动电视密度像素成像(TVDPI)也使用 320 DPI(分辨率为 1920×1080 和 1920×1200 ),适合新的谷歌电视 iTV 设置。我们将在本书的第二部分更详细地讨论这个主题,其中包括活动用户界面和内容开发的图形设计和动画。
下一个文件夹是布局文件夹;单击三角形 UI 元素将其打开,您将看到它当前包含我们活动的布局 XML 文件,命名为 activity_main.xml ,正如我们之前在新的 Android 应用项目系列对话框的第五个对话框中指定的。该文件包含所有定义活动屏幕布局及其用户界面元素的 XML 标记。我们将在本章的下一节在 Eclipse IDE 中打开它,并最终定制 XML 标记来创建一个更酷的 Hello World 用户体验。
您为应用定义的每个活动(用户界面屏幕)在这个布局文件夹中都有自己的 XML 文件,每个文件都包含定义每个应用屏幕外观的唯一标签。例如,如果你的应用有一个登录界面,那么这个文件夹中也会有一个 activity_login.xml 文件,它包含的 xml 标记与 activity_main.xml 文件完全不同。
下一个文件夹是 菜单文件夹,其中保存了定义每个活动用户界面屏幕的 Android 应用菜单结构的 XML 文件。单击菜单文件夹旁边的三角形 UI 元素将其打开,您将看到它当前包含我们活动的菜单 XML 文件,Android 已将其命名为 activity_main.xml ,如之前在我们新的 Android 应用项目系列对话框的第五个对话框中所指定的。
布局和菜单 XML 文件具有相同名称的原因是因为通常每个应用(活动)屏幕都有 UI 元素和菜单,所以对它们进行分组的最合理的方式是根据它们的文件名。因为这些文件在不同的文件夹中,它们被分开保存,因此可以有相同的文件名,Android 将通过使用文件名的第一部分以及它所在的文件夹来查找。我们将在本章的稍后部分在 Eclipse IDE 中打开这两个文件,稍后我们甚至将定制它们的 XML 标记来创建一个更高级的 Hello World 布局和菜单系统,这样您就可以更好地体验 Android 中的活动是如何工作的。
resource 文件夹中的最后三个子文件夹是 values 文件夹,其中保存了我们的应用的常量或“硬编码”值,例如字符串、主题、尺寸、颜色等,这样以后就可以在项目层次结构中的一个集中位置对它们进行访问(并且非常容易地进行更改)。Android 希望将所有值外化到这些文件夹中,我们将在下一节看到 strings.xml 文件。正如您将在整本书中看到的,如果您没有正确使用值,Eclipse 会在 IDE 中用三角形黄色警告图标标记您的代码。安卓操作系统有点严格!
接下来,让我们仔细看看 Android 如何利用 XML 标记来允许团队成员,主要是设计角色的非程序员,为应用开发过程做出贡献。XML 用于 Android 开发中的许多事情,例如用户界面设计、AndroidManifest.xml 文件中的应用配置等等。
Android 应用项目:XML 标记
您的应用在MainActivity.java中的 Java 代码引用了我们在布局和菜单文件夹中看到的 activity_main.xml 文件,事实上,当 Eclipse 在新的 Android 应用项目创建过程之后打开时,它会自动从其中央主编辑区域的布局文件夹中打开活动布局 xml 文件 activity_main.xml 。这可以在图 2-6 中看到,它展示了 Eclipse 第一次打开您的新应用项目时的样子。请注意,在图形布局编辑器(底部标签中的注释名称)的中心区域,activity_main.xml 已打开(顶部标签中的注释名称)。
使用 XML 在 Android 中创建用户界面屏幕布局
让我们点击 Eclipse 中央编辑窗格底部的 activity_main.xml 选项卡,如图图 2-7 所示,并查看这个 xml 标记在您的 Android 应用中是如何工作的。下面是定义相对布局容器并放置 Hello world 的 XML 标记!里面的文字:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world" />
</RelativeLayout>
图 2-7。使用主编辑区右下方的 XML 编辑选项卡查看 activity_main 布局 XML
第一个 XML 标签显示在 < RelativeLayout > 标签中,它提供了一个布局容器,用于保存我们的用户界面元素,在本例中是文本,最后是图像和按钮。在 RelativeLayout 标记中,您会发现几个参数,每个参数都遵循以下格式:
`platform:feature="setting value"`
例如,第一个参数是 xmlns:android 并且引用了 XML 平台(xmlns 表示扩展标记语言命名模式)并且定义了互联网上的一个位置,在这个位置,在这个 XML 文档中使用的用于 android 的 XML 标签被公开定义并且在一个中心位置被引用。第二个参数 xmlns:tools 也来自 XML 平台,它定义了在互联网上公开定义这个 XML 文档中使用的 XML 工具的位置。 tools:context 标签告诉我们这些工具将在哪里使用,在这个例子中是在我们的 MainActivity Java 类中,通过引用。MainActivity 该参数内引号中的设置值。
布局 XML 文件中的其他参数都来自 Android 平台,并定义了与 Android OS 相关的参数,例如,android:layout_width 和 android:layout_height 都被设置为 match_parent 值,该值告诉相对布局容器扩展以填充整个显示屏。
现在让我们看看 < TextView > 标签中的参数,它在我们的相对布局容器中设置我们的文本。我们再次看到 android:layout_width 和 android:layout_height 参数,但是这一次它们引用了 wrap_content 设置,这实质上与 match_parent 设置完全相反。不是扩展标签来填充它的容器,而是“收缩包装”容器,或者在这个例子中是用户界面元素,围绕它的内容!接下来让我们看看 android:text 参数引用的布局居中参数和 string.xml 文件值。
Android:layout _ center horizontal和Android:layout _ center vertical参数都被设置为 true,这表示我们希望将 TextView UI 元素水平和垂直居中,以便 TextView UI 元素位于活动屏幕的中心,而不管用户设备当时使用的屏幕密度、方向或分辨率如何。
TextView UI 元素的最后也是最重要的参数是该 UI 元素要包含的实际文本内容,这可以通过参数 android:text 来访问。引号中包含的设置值被设置为 @string/hello_world ,这是 Android OS 特定的符号表示法,用于:
C:\Users\Username\workspace\Hello_World\res\values\strings.xml <string> tag named: hello_world
使用 XML 在 Android 中创建选项菜单
我们将在本章后面的值文件夹中查看 strings.xml 文件,看看这个文本值是如何设置的。首先,我们来看看菜单文件夹中包含的另一个 activity_main.xml 文件,分析一下 ADT 为我们编写的菜单 xml 代码。在你的包资源管理器中右键点击菜单文件夹中的 activity_main.xml 文件(如图图 2-6 所示),选择打开或者直接选择文件,用键盘上的 F3 键打开。第二个标签在 Eclipse 的中央编辑区打开,标签为 activity_main.xml ,你会看到 xml 标记定义了你的菜单设置条目,如图 2-8 所示。
图 2-8。在 Eclipse 中央代码编辑区的第二个(菜单)activity_main.xml 选项卡中显示的菜单 XML 文件
Android 中的菜单 XML 文件也使用名为标签的标记结构,通过 <菜单> 标签容器和其中每个菜单项的 <项目> 标签来定义菜单项。在这种情况下,只有一个名为 Settings 的菜单项,但是,稍后我们会添加更多的菜单项并激活它们,这样你就可以完全理解如何在你的应用中实现菜单项。
值得注意的是,当你运行你的应用时,当你在 Android 模拟器中使用菜单键(按钮)时,菜单会弹出,即使菜单项被点击时不会做任何事情。这允许您定义活动菜单用户界面,并在编写代码实现它实际做什么之前测试它。现在让我们一行一行地看看 Android 为我们创建的菜单 XML 代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/menu_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/menu_settings"/>
</menu>
正如您所看到的,任何 XML 文件中的开始标记,在本例中是“parent”<菜单> 标记,包含对 Android XML 命名模式的 HTTP 引用,就像我们前面看到的 activity_main.xml 文件的 < RelativeLayout > 文件夹版本中的布局标记一样。在<菜单>标签中,我们“嵌套”了定义每个菜单项的<项目>标签,而每个<项目>标签又有自己的参数,这些参数定义每个菜单项的名称、它在菜单上显示的内容、它出现的顺序,以及它是否在 Android 3.x、4.x 或 5.x 操作系统中显示为动作图标。让我们检查一下每个参数。
第一个 android:id 参数将这个菜单项命名为 menu_settings ,以便我们可以在 Java 代码中引用它。第二个 android:orderInCategory 参数设置菜单项在包含多个菜单项的菜单中的顺序,大多数菜单都是这样。当我们稍后添加菜单项时,我们将把这个参数设置为 200(以此类推),以确定我们添加的每个菜单项功能的重要性。
第三个 android:showAsAction 参数决定了你的菜单是否显示在 Android OS 版本 3.x 和 4.x(以及即将推出的 5.x)设备的动作图标栏上。在这种情况下,我们使用 none 设置,因为我们希望向后兼容 Android 1.6 和 2.x 设备,如亚马逊 Kindle Fire (2.3.7)。一个无设置会在屏幕底部弹出菜单,当你稍后在 4.2 模拟器中运行 Hello_World 应用时就会看到。如果你是单独为 Android 3.x 和更高版本开发应用,你可以使用 ifRoom 参数作为设置,你的菜单项将出现在 Android 操作栏中,如果有空间的话。
最后一个 android:title 参数是弹出菜单中菜单项本身的标题或标签。因为这个菜单标题是一个字符串常量,所以它被设置在 strings.xml 文件的值文件夹中。事实上,现在让我们通过右键单击 values 文件夹中的 strings.xml 文件并选择 Open 命令来查看一下!
使用 XML 为 Android 应用设置常量值
如图图 2-9 所示, strings.xml 文件在 Eclipse 的中央区域打开它自己的编辑标签,如果你想直观地看到编辑过的字符串,点击左边底部的资源标签或者使用右边的 xml 视图标签,标签为文件名 strings.xml 。
图 2-9。XML 编码模式下 Eclipse central 编辑窗格中显示的 strings.xml 文件(位于 values 文件夹中)
因为我们在本章的这一节学习 XML 代码,所以我选择在 Eclipse ADT 编辑器中显示 XML 标记视图窗格,如图 2-9 所示。
现在让我们看看 XML 标记代码,看看我们是如何定义常量的:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hello_World</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
</resources>
第一个 XML 标记声明我们使用的是 XML 1.0 版,并且我们使用的是文本(字体)编码范式 UTF-8,它支持跨越许多常见语言的相当大的字符集。第二个“parent”标签是保存嵌套在资源容器中的
既然我们已经介绍了可以通过 Android 应用中的 XML 标记设置的大量选项和值,那么让我们看看 Java 如何通过各种 onCreate()方法调用、启动和膨胀所有这些 XML 素材。
Android 应用项目:Java 编码
现在让我们来看看 Android 应用 Java 代码素材本身,它们位于我们在本章前面详细讨论过的项目浏览器层次结构中。我们将再次从项目文件夹层次结构的最顶层开始,查看 src 文件夹,在我们的包文件夹下,我们将找到包含 MainActivity 类和 onCreate()方法的 MainActivity.java Java 源代码文件。
假设您的 src 和 package-name 文件夹已经打开,您可以看到 MainActivity.java 文件,单击MainActivity.java文件将其选中,并按下键盘上的 F3 键,或者您可以右键单击文件名,并从出现的上下文菜单中选择打开菜单项。第四个选项卡在 Eclipse central 编辑窗格的顶部打开,您应该会看到类似于图 2-10 中所示的内容。
图 2-10。MainActivity.java 文件显示在 Eclipse central 编辑窗格中,准备进行进一步编辑
请注意,在 Eclipse 的中间部分打开了一个标签,在顶部写着MainActivity.java,并包含该文件中的 Java 代码。Eclipse 的核心部分是代码编辑窗格,可以同时打开多个选项卡,正如我们在打开布局和菜单 XML 文件进行查看和编辑时看到的那样。所以,现在让我们来看看我们的 Java 代码。
通过导入语句定义 Android 类
顶部有三个 import 语句,它们引用了我们将在 Java 类及其下面的方法中使用的其他 Java 代码。注意我们将在下一章更详细地讨论所有这些 Java 术语。要查看所有的导入语句,点击第一个导入语句旁边的 + UI 符号,就可以看到所有三个导入语句。
第一个 onCreate( ) 方法使用第一个 Bundle 类,第二个 Activity 类扩展我们的 MainActivity 类,第三个 Menu 类通过 onCreateOptionsMenu( ) 方法创建一个菜单。所有这些 Android 类在本书的前两部分都有更详细的讨论。下面是为 Hello_World 应用创建活动(用户界面)和菜单的 Java 代码,正如在 ADT New Android Application Project helper 中为我们创建的一样:
package chapter.two.hello_world;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
使用 onCreate()方法创建我们的应用基础结构
接下来,让我们看看 Java 类的 MainActivity 和位于其中的 onCreate( ) 方法。在 Java 中,在类名前使用 class 关键字来声明一个类。在这种情况下,我们的类被称为 MainActivity,它可以被任何其他代码访问,因此它也被声明为 public,在 class 关键字之前使用 public 关键字。这个 MainActivity 类扩展了(或从其借用方法)Android 应用活动“超级类”,我们之前在代码顶部使用 import android.app.Activity 导入了该类;行代码。我们完整的 MainActivity 类声明如下所示:
public class MainActivity extends Activity { ... }
我们类中的第一个方法也是 public ,也声明为 void,,这意味着它不返回任何值给调用函数(在本例中是 Android OS),它只是做一些事情,在本例中是通过 onCreate( ) 方法创建我们的初始活动或用户界面屏幕。Android 操作系统向 onCreate()方法传递了一个名为 saveInstanceState 的 Bundle 对象,它包含了我们活动中用户界面元素的状态(设置),以值和设置捆绑的形式。
onCreate()方法内部有两个方法调用。第一个方法调用使用 super 关键字将 saveInstanceState Bundle 对象向上传递给 android.app.Activity 超类及其 onCreate()方法。第二个方法调用通过 setContentView( ) 方法为应用设置内容视图(活动屏幕),同时引用本章前面提到的 layout 文件夹中的 activity_main.xml 文件。那看起来是这样的:setContentView(r . layout . activity _ main);其中 R 等同于 Hello_World 项目路径,向下到 layout 文件夹,或者 C:\ Users \ Username \ workspace \ Hello _ World \ RES \因此您可以从这些信息中进行推断, R.layout.activity_main 实际上转化为(意味着 Android):
C:\Users\Username\workspace\Hello_World\res\layout\activity_main.xml
这用于通过活动子类中的 Java setContentView( ) 方法加载您的 XML 布局定义(记住我们的类声明的扩展活动部分)。接下来,让我们看看第二个方法 onCreateOptionsMenu( ) ,它同样用于从 activity_main.xml 菜单 xml 文件创建您的活动的选项菜单,使用的 getMenuInflater( ) 方法通过我们前面看到的Android . view . Menuclassimport语句访问。
getMenuInflater()方法调用一个。inflate( ) 方法有两个参数,一个是通过 onCreateOptionsMenu(菜单菜单)传入该方法的名为菜单的菜单对象,另一个是通过我们现在熟悉的 R.menu.activity_main 传入菜单文件夹中 activity_main.xml 文件的路径,相当于:
??C:\Users\Username\workspace\Hello_World\res\menu\activity_main.xml
所以基本上。inflate()方法获取名为菜单的菜单对象,并用 R.menu.activity_main 中引用的 XML 菜单定义对其进行膨胀。注意,Android 使用文件名的第一部分(不是。xml 部分,它不使用文件扩展名)来引用 XML 和图形(可绘制)文件。一旦菜单对象 Menu 膨胀并准备好使用,它就向操作系统返回一个真值。
在 Android 4.2 模拟器中运行你的 Hello World 应用
现在让我们编译并运行所有这些 XML 和 Java 代码,看看它在我们在第一章为 Nexus S 智能手机创建的 Android 4.2 模拟器中做了什么。要启动模拟器,只需在 Eclipse Package Explorer 中右键单击 Hello_World 项目顶层文件夹,并选择Run AsAndroid Application菜单项。当 Nexus S 智能手机的 Android 4.2 模拟器出现时,您将看到 Hello_World 应用及其 Hello world!仿真器屏幕上的感叹号如图图 2-11 所示。
图 2-11。在 Android 4.2 Nexus S 智能手机模拟器中运行 Hello_World Android 应用
我们可以在新的 Android 应用项目系列对话框中看到我们指定的应用名称,以及我们选择的启动图标和我们在 strings.xml 文件中设置的文本消息。我们的 XML 布局标记完美地将文本居中,现在我们准备单击右上角的 X 并退出模拟器。
在第一次 Android 4.2 仿真后设置 Logcat
一旦您第一次退出 Android 4.2 Nexus S Smartphone 模拟器,您会注意到出现一个错误对话框,通知您没有为您的 Eclipse Android 开发环境设置 Logcat,并提供为您设置它。该自动监控 Logcat 对话框如图图 2-12 所示。
图 2-12。在 ADT 中首次启动任何仿真器后出现的自动监视 Logcat 对话框
Android 的 Logcat 代表 Log Catalog,是一个自动日志记录系统,为您提供了一种方便的方法来收集和查看所有 Android 系统调试器输出。来自各种应用和操作系统各部分的详细日志被收集在一系列循环缓冲区中,然后可以通过 Eclipse IDE 中的 Logcat 窗格查看甚至过滤这些日志。
一旦您成为更高级的 Android 开发人员,您也可以从 ADB shell 使用 Logcat 来查看日志消息。更多信息可在以下网址找到:
[`developer.android.com/tools/help/logcat.html`](http://developer.android.com/tools/help/logcat.html)
您会发现 Logcat 实用程序有助于研究应用开发过程中可能出现的错误。如果您将 Eclipse Logcat 窗格中的错误消息剪切并粘贴到 Google 搜索栏中,然后按 Return 或单击 search,您可以很快找到其他开发人员,他们的应用中也出现了同样的错误消息,并查看他们是如何解决的。
摘要
在这一章中,你创建了你的第一个 Android Hello World 应用,在 Android Developer Tools (ADT)新的 Android 应用项目“helper”系列对话框的帮助下。在接下来的章节中,我们将修改这个基本代码来构建一个更令人印象深刻的 Hello World 应用,这样您就可以将您对 Eclipse、XML 标记和 Android 中的 Java 编码的了解提升到一个新的水平。
我们首先关注的是通过一些对话框创建新的 Android 应用项目的工作流程,这些对话框允许开发人员设置无数选项来创建“引导”应用外壳,然后他们可以修改并变形为他们想要开发的应用。这包括命名应用、Java 类和 XML 文件、确定 Android OS 版本支持、选择应用启动图标、命名和选择活动类型以及选择导航模式。
接下来,我们查看了 Eclipse IDE 及其非常有用的 Package Explorer 窗格,并使用该实用程序检查了新的 Android 应用项目助手为我们创建的 Android Hello_World 应用结构的剖析。我们查看了源代码和资源文件夹层次结构,以及图形素材、XML 定义和 Java 代码的文件保存位置。
接下来,我们打开了用于 Hello_World 应用项目的布局、菜单和字符串常量定义的关键 XML 文件,并研究了它们的功能:标记和参数。我们了解了 Eclipse center 代码编辑窗格以及顶部和底部选项卡区域,这些区域允许我们选择正在工作的文件和编辑模式。
接下来,我们打开了我们的主 Java 类文件,看了看 Java 代码如何将我们的用户界面、菜单和内容的 XML 定义组合在一起,并了解了一些核心 Android 类,如 Activity、Bundle 和 Menu 类。最后,我们第一次在我们在第一章中设置的 Android 4.2 Nexus S 模拟器中运行了我们的 Hello_World 应用,并允许 ADT 在我们退出模拟器时为我们设置 Logcat。
在下一章中,我们将通过添加和更改 ADT 为我们创建的初始代码,并添加我们自己的内容、用户界面元素、图标和菜单选项,将我们的 Hello_World 应用提升到一个新的水平,以了解如何使用 ADT 为我们创建的引导应用,并将其转化为定制的东西,以实现我们自己的 Prime 指令。与此同时,我们将了解更多关于 Java、XML、图标和其他在前两章中没有揭示的 Android 秘密。
三、面向 Android 入门的 Java:增强我们的 Hello World 应用
在这一章中,我们将使用新的 Android 应用项目助手,使用我们在上一章中创建的 Hello World 应用,并添加我们自己的 Java 代码,使它更有趣。同时,我们也将利用这一章,通过 Java 编程语言及其对象、变量、方法、类、包、修饰符等等,向那些不是 Java 程序员的读者提供本书中使用的各种代码结构的高层次概述。
我将尝试无缝地融合这两个目标,增强我们的 Hello World 应用,并在一个高层次上总结 Java 概念,形成一个有凝聚力的章节,在我们的 Hello World 应用的上下文中定义 Java 的方法和功能。使用 Java 代码,我们将为我们的 Hello World 应用添加功能,以便它生成新的世界,并开拓和保护它们。
Java 的最高层次:应用编程接口
与任何编程语言一样,该语言的最高级别被称为 API ,或应用编程接口 ,这是整个编程语言本身的总和,在其最新修订版中,都在一个集合或位置中。众所周知,如果您想使用任何给定的编程语言进行开发,您必须去获取(并最终学习)该编程语言的 API,以便使用其开发范式在该编程语言下开发应用。
在这方面,Java 和其他编程语言没有什么不同。在本章的剩余部分,我们将讨论 Java 的行话,以及它的各种编程结构,就像我们在前一章讨论 Android 中各种模块使用的行话一样。通过这种方式,你将在概念上了解我们在本书剩余部分所讨论的内容。
组织 Java API:包
Java 提供了一种将编程项目中的代码组织成逻辑模块或代码集合的方法,称为包。在 Java 编程语言中,一个包是 Java 类的集合,我们将在本章的下一节更详细地了解它。
我们已经在 Eclipse 中为我们的 Hello_World 项目创建了一个包,在我们在第二章中使用的新的 Android 应用项目系列助手对话框中。这五个对话框用于帮助创建 Hello World 应用的基本 Java 和 XML 代码基础。
因为我们在本书的第二章中创建了 Hello_World 应用,所以我们在逻辑上将我们的 Java 包命名为: chapter.two.hello_world ,然后在我们的MainActivity.java文件的顶部声明它,Java 代码如下:
package chapter.two.hello_world;
包是在每个 Java 代码模块的顶部被声明的,这些 Java 代码模块利用了包含在这些包中的类和方法(稍后会有更多的介绍)。包总是使用 Java 的包关键字来声明。
项目包名称通常使用小写字符,并告诉用户包中的代码是做什么的。由于这个原因,我们需要使用逻辑包命名约定来告诉我们包的其他用户这个包是什么,如果可能的话,它是做什么的。
注意,包含功能类的包,尤其是在 Android 中,有时会在包中的姓氏上使用大写字母。你会在 Android 操作系统中经常看到这种情况,因为类是用大写字母命名的,比如 String、View、Activity、Object、Bundle、等等。
我们之前在第二章中创建的 chapter.two.hello_world 包目前包含一个名为 MainActivity 的类,其 Java 源代码位于我们项目的源代码文件夹(名为 src)下的一个名为MainActivity.java的文件中。在这一章中,我们将从头开始创建另一个全新的类,名为 WorldGen ,它将允许我们为 Hello World 应用创建新的世界(行星),同时也教授您基本的 Java 概念,以防您不是 Java 程序员。
随着您使用越来越多的 Android 操作系统特性,您会发现 Android 中的包名总是逻辑地反映出该包是什么,它做什么,以及该包在 Java 和 Android 操作系统层次结构中的位置。
让我们更仔细地看看这个,例如,使用 Android 包,我们已经用 Java 导入语句将这些包带入到我们的第二章 Java 代码中。这些导入语句位于我们的 MainActivity Java 代码文件中,就在我们的包声明语句下面:
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
Java 中的导入命令用来—
你猜对了—
导入其他 Java 包。导入的包是逻辑上捆绑在一起的功能类的集合,这些功能类已经为您编写和测试过,并且当前可以在您的应用中用于商业用途。
在 Android OS 应用开发的情况下,import 语句允许我们使用预先存在的 Java 代码(保存在包中),这些代码是专门为支持 Android OS 功能而编写的,这些代码是为在我们的 Android 应用中使用而提供的,随着我们越来越多地使用这些包,我们将在本书中更详细地了解这些功能。
Java 最强大的一点是,我们可以导入已经编写和测试过的代码的整个库或包,然后在我们自己的 Android 应用中为我们自己的利益(甚至是利润)使用它们,而不必为该软件的(代码)开发支付一分钱。如果你仔细想想,这是一个相当惊人的开源软件开发商业提议。
让我们仔细看看这三个引用核心 Android 包及其类的导入语句,它们是在 Eclipse ADTNew Android Application Project 系列对话框期间在我们的 MainActivity.java 文件中为我们创建的,我们在第二章中使用了这些对话框。
查看 Java 导入语句
第一个, android.os.Bundle 引用了 Android OS。中间的引用告诉我们代码与操作系统相关,包名最后部分的 Bundle 告诉我们这个包是与 Android 中的 Bundle 特性相关的 Java 类和方法的集合。
第二个导入语句, android.app.Activity 也是 android 相关的。第二部分告诉我们它与 app 的应用功能有关,包名的最后一部分告诉我们这个包包含了一个类和方法的集合,用于提供 Android 中的活动功能。这个包是一个重要的包,在所有的 Android 应用中都有使用。接下来的几章将特别关注活动。
最后的导入语句, android.view.Menu 是一个与视图(屏幕相关)android 功能相关的 Android 包。它支持大量的视图类(以及叫做小部件的子类,我们将在接下来的几章中学习)。Android View 类与 Android View (屏幕布局和用户界面小部件)特性相关,我们将利用其中的许多特性来增强我们的 Hello_World 用户界面设计和用户体验功能。但是首先我们需要学习一点关于 Java 编程语言和 Java 对象的知识。在这种情况下,我们从 android.view 包中导入 Menu 类和方法,这样我们就可以实现我们的菜单系统。
Java 的基础:对象
Java 是一种面向对象编程 语言。像 C++ 一样,Java 使用被称为对象的编程结构来建模其编程逻辑中使用的数据和函数。Java 对象就像是你在日常生活中看到的现实生活中的对象的描述。
就像您每天遇到的现实生活中的对象一样,如汽车、电器、计算机、建筑物、船只、飞机等等,Java 对象也具有独特的属性和用途(即,可以增强您日常生活的功能),与您每天使用的那些现实生活中的对象非常相似。
在 Java 中,为了“建模”或创建一个对象,您将创建一个 Java 类,它保存该对象的状态和行为。我们将在本章的下一节学习更多关于类的知识,但是要理解类,我们必须首先理解对象,所以让我们在这里更详细地讨论对象。
一个对象的功能可以通过它的行为来描述;也就是对象可以做或执行的那些事情。对象通过被称为方法、的编程结构获得这种功能,我们将很快在本章后面的 Hello World 应用中了解和使用这些方法。
对象的每个特征可以通过状态来定义;即描述任何给定时刻的对象属性的数据。对象通过被称为实例变量、的编程结构获得这些属性,我们将在本章后面的应用中学习和使用它们。
让我们以 Hello World 应用为例,定义一些世界或星球,每个世界或星球都将被表示为一个对象,这样您就可以看到这些概念在起作用。
首先,让我们定义一些我们的世界对象的属性或状态:
- 行星名称
—
一个字符串数据类型或文本值,例如,“地球” - 行星质量
—
一个代表 Yottagrams 的整数值;地球是 5973 YG - 行星重力
—
一个真实的或浮动的重力值;地球=9.78 米/秒的平方 - 行星殖民地
—
一个整数数据值,代表一个殖民地的数量 - 行星人口
—
一个 64 位的双值,代表一个人的数量 - 行星基地??整数值,代表军事基地的数量
- 星球军事
—
一个整数值,代表军事人员的数量 - 行星保护
—
一个布尔值,告诉我们行星力场是否开启
接下来,让我们定义至少十几个可以归因于这些新世界对象的对象行为或功能:
- WorldGen
—
构造新的世界,即生成一个新的世界对象 - 设定行星殖民地
—
在行星表面增加任意数量的新殖民地 - 获取行星殖民地
—
返回行星表面的殖民地数量 - 设定星球军事
—
在星球表面增加任意数量的军事基地 - 获取星球军事
—
返回星球表面的多个军事基地 - 开启力场
—
开启力场保护星球大气) - 关闭力场
—
关闭力场保护行星大气层 - 获取力场状态
—
返回力场状态开/真或关/假 - 设定殖民地移民
—
将人添加到星球表面的每个殖民地) - 获得殖民地移民
—
返回当前星球表面的人口 - 设置基地保护
—
增加军事来保护星球上的军事基地 - 获得基地保护
—
返回一个星球上的军事人员数量
正如你所看到的,这些是每个新的世界对象可以做的,或者可以改变的关于它的存在和状态或特征的行为或事情。
正如你将在本书后面看到的,在对象层次结构中,对象也可以包含其他对象。例如,这类似于系统硬盘上的文件夹层次结构。这允许更复杂的对象以更有组织和逻辑的方式模块化地构建。
在我们的 Hello World 应用示例中,这个概念的一个例子是 WorldGen 对象(我们在本章中定义和创建它)与 java.lang.Object 顶级对象一起使用,并增加了它的复杂性,我们将在本章的下一节中了解更多。
面向对象编程(OOP)的核心概念之一是数据封装、的概念,其中对象的状态或其实例变量只能通过其自身的函数或方法来改变。
数据封装的原因是,对象是自包含的,可以在真空中创建和测试,而不受正在创建的整个软件包中任何其他内容的影响。
这允许实现模块化代码开发过程,通常被称为代码模块化。在自包含代码模块中编写代码使得 bug 和其他问题更容易被隔离,在逻辑上有组织的功能中,每个功能在整个软件开发项目中单独开发。
这种代码模块化非常适合于最大化代码重用,因此允许软件开发团队在整个软件包开发过程中专注于开发他们自己的模块和功能。这样,一旦代码模块被编写、测试、调试,并最终发布供所有其他团队成员使用,每个人的编码工作就可以被其他团队成员使用。
Java 对象的蓝图:类
Java 中为 Java 对象编写蓝图的主要工具被称为类,一旦 Java 类或 Java 包形式的类集合被发布使用,甚至更复杂的类或包也可以通过称为子类化的过程使用这些代码来创建。
在 Java 中,这是被称为继承的 OOP 概念的一个例子,其中一个现有的 Java 类可以在以后被用来创建那个原始类的一个更详细或更复杂的版本。子类的其他常用术语有:子类、派生类和扩展类。
从原始类的派生(或从原始类的派生)的新类被称为子类,原始的“父”类被称为超类。超类可以通过 super 关键字在它们的子类中被引用,这一点我们将在本书中看到。超类的其他常用术语有:父类或基类。
这个类层次结构可以在 Android 开发者网站上的任何给定 Android 类描述网页的顶部看到,其中显示了每个 Android 类起源的层次结构,从顶级 java.lang.Object 超类开始,向下通过子类的层次结构,每个子类都是为了向其上的超类添加一些附加特性和功能而创建的。
java.lang.Object > Object Subclass > Subclass Subclass > etcetera
因此,如果您想查看每个类级别的特性添加到了哪里,从 java.lang.Object(对象特性的来源)开始,沿着树向下查看每个级别的子类。
让我们看一个 Java String 数据类型类的 Android 类层次的例子。这个类起源于 java.lang.Object 类,因此 Android java.lang.String 类继承了 Object 类,为 java 和 Android 增加了字符串数据类型功能。我们将使用 String 数据类型为我们将要创建的 WorldGen 对象定义 planetName 实例变量。
String 对象允许通过 char (character) 原始数据类型创建文本的数组(集合)。因此,字符串数据类型对象实际上是字符的集合(通过数组)(通过 char 数据类型),而 Java 中所有其他常见的数据类型被称为原语或原语数据类型。代码中的小写字符串不会代表 Java 的字符串数据类型,而会被认为是一个对象或变量名,所以如果您使用大量文本,这是需要注意的。
总而言之,只要记住顶级的 java.lang.Object 或 java 语言对象类是所有 Java 类的母体,因为它为 Java 中的所有对象提供了基础,无论它们是大是小。现在,让我们继续学习如何编写这些 Java 类,这样我们就可以用它们来产生或创建或实例化一些新的 Java 对象!
正如蓝图定义了任何给定的结构将如何构造一样,Java 类也定义了一个对象将如何构造。一个 Java 类由保存对象状态值的实例变量、构造每个新对象的构造器以及操作其变量的方法组成,这样对象就可以拥有一些功能。同样重要的是要注意,在方法内部定义的变量被称为局部变量,而在方法外部但在类结构内部定义的变量被称为实例变量。
你可能会想,如何用一个类创建一个对象呢?在定义了对象的状态(实例变量)和它的功能(方法)的类被编码之后,我们可以利用这个类定义来创建一个对象的实例。一个实例是该对象的单个出现,并且该 Java 对象的每个实例化或出现可以是该特定对象的状态或属性的唯一且不同的集合。
使用上一章的 Hello World 应用示例,让我们创建一个新的类,为现有的 Hello World 应用生成新的 World 对象。为了声明这个我们称之为 WorldGen 的类,我们将使用下面的 Java 语法:
public class WorldGen { instance variables and methods go between curly brackets }
就像在任何其他编程语言中一样,我们首先要在 Java 类的顶部声明的是实例变量,我们将用它来保存我们的 WorldGen 对象的属性或状态。在 Java 中,这是通过使用以下通用格式来完成的:
<data type> then <variable name> then = then <set variable value> then;
因此,对于我们在上一节中描述的对象状态实例变量,我们将编写如下的变量描述代码行:
String planetName = "Earth"; (shown only to demonstrate how to set a default value)
int planetMass;
double planetGravity;
int planetColonies;
long planetPopulation;
int planetBases;
int planetMilitary;
boolean planetProtection;
我们在上面为 planetName 字符串变量使用了一个默认值,该值将应用于 planet Earth 向您展示如何在您的类变量声明中包含一个默认初始值。当你包含一个缺省值时,就像任何编程语言一样,它被称为初始化那个变量。通常,你可以声明变量,然后通过你的构造函数方法来设置它们,我们将在下一节看到。
如果您是一名程序员(我们假设您是),您已经熟悉了字符串(文本)整数(非十进制整数)双精度(最多 64 位值实数,或十进制数) long (最多 64 位值高精度数) char 和 boolean (真/假值)数据类型。
现在我们已经定义了对象的状态或实例变量,接下来让我们继续定义对象的行为,使用一些 Java 方法。
定义 Java 对象的函数:方法
为了在你的 Java 类中定义一个函数或者方法,你可以使用下面的格式在你的 Java 代码中命名并且声明那个方法:
<Modifiers> <Return Data Types> <Method Name> (parameters) {Java code}
方法通常在类中的实例变量声明之后(虽然它们不是绝对必须的,但对于代码的其他查看者来说,这只是为了更好的代码组织和可读性的一个约定)。
对于我们之前描述的行为或函数,一个 WorldGen 构造函数和 11 个方法将声明如下(接下来我们将编写它们的功能):
public WorldGen (String name, int mass, double gravity) {...}
void setPlanetColonies (int numColonies) {...}
int getPlanetColonies () {...}
void setPlanetMilitary (int numBases) {...}
int getPlanetMilitary () {...}
void turnForceFieldOn () {...}
void turnForceFieldOff () {...}
boolean getForceFieldState () {...}
void setColonyImmigration (int numColonists) {...}
long getColonyImmigration () {...}
void setBaseProtection (int numForces) {...}
int getBaseProtection () {...}
在 Java 中,直接放在方法名前面的关键字叫做修饰符。Java 中的修饰符可以和类、方法或变量一起使用。如果需要,可以有多个修饰符来定义类、方法或变量的特征。有两种类型的修饰符,访问控制修饰符,以及不定义任何访问控制的修饰符。
有四个级别的访问控制修改: no modifier (见所有前面的方法,除了构造函数方法)其中方法或变量对包含它的整个包是可见的; public 修饰符,这意味着任何 Java 类,甚至是包外的类,都可以使用该方法或变量;私有修饰符,这意味着只有定义了方法或变量的类才能使用该方法或变量;和 protected 修饰符,这意味着该方法或变量对包以及所有可能从该类创建的子类都是可见的。
还有其他几种不影响访问控制的修饰符,比如:静态、最终、摘要、和同步修饰符。
除了修饰符之外,还有在 Java 方法之前声明的返回数据类型。我们在许多方法中使用的 void 数据返回类型表示,当调用 void 方法时,所有这些方法都不会向调用实体返回任何数据值。我们正在使用的其他返回数据类型, int , long,和 boolean ,分别将数据返回给精确原始数据类型的调用实体。
现在让我们看看如何通过花括号中的代码为这些方法添加一些功能,花括号定义了每个方法的开始和结束。第一个 WorldGen( ) 方法是一种特殊类型的方法,称为构造函数,这个方法代码编写如下:
public WorldGen (String name, int mass, double gravity) {
planetName = name;
planetMass = mass;
planetGravity = gravity;
planetColonies = 0;
planetPopulation = 0;
planetBases = 0;
planetMilitary = 0;
planetProtection = false;
}
Java 构造函数与方法有许多不同之处。首先,它不使用任何数据返回类型,比如 void 和 int ,因为它是用来创建一个对象,而不是执行一个函数。事实上,这就是为什么它首先被称为构造函数;因为它的功能仅仅是构造或创建新的对象。
注意,每个创建 Java 对象的类都有一个与类同名的构造函数,所以构造函数是一种方法类型,它的名字可以(并且总是)以大写字母开头。
构造函数和方法的另一个区别是构造函数不能有非访问控制修饰符,所以一定不要将构造函数声明为:静态、最终、抽象或同步。
在这个 WorldGen()构造函数中,我们为新行星的名称、质量和重力获取三个重要的参数并在构造函数方法中设置它们。我们还将初始化其他对象实例变量为零,以创建一个干净,处女,无保护的世界。
接下来,让我们编写另外六个方法,它们执行那些修改我们世界状态的功能。这为 Hello_World 应用的用户提供了许多有用的构建世界的功能。
第一个 setPlanetColonies( ) 方法比我们的 WorldGen()构造函数方法简单得多,它允许我们向我们的世界对象添加新的殖民地。这个方法的 Java 代码编写如下:
void setPlanetColonies (int numColonies) {
planetColonies += numColonies;
}
在我们的 setPlanetColonies()方法名之前声明的 void 数据返回类型声明了该方法将返回什么类型的值。在这种情况下,这个方法根本不返回任何数据值,所以我们将它声明为 void(或者没有任何数据返回类型或数据值)。
还要注意,我们的方法名以小写字母开头,对于方法名内部的单词使用大写字母。在计算机编程行业,这种命名约定被称为 CamelCase 。
在我们的 setPlanetColonies( ) 方法主体中,我们将利用一个赋值操作符,它将 numColonies 整数参数添加到该对象的 PlanetColonies 实例变量中。作为程序员,我们知道这是将传入方法的菌落数参数添加到 Planet Colonies 实例变量中保存的行星菌落总数的一种快捷方式,我们在 WorldGen 类的开头声明了该变量。
我们将使用 setPlanetMilitary( ) 方法做一些非常类似的事情,一旦我们用 Eclipse 对其进行编码,它将看起来像这样,:
void setPlanetMilitary (int numBases) {
planetBases += numBases;
}
接下来,让, s 写 turnForceFieldOn( ) 和 turnForceFieldOff( ) 方法,设置布尔参数,表示一个星球的保护力场是开还是关。默认情况下,正如我们从 WorldGen( ) 构造函数方法,的主体中可以看到的,我们在行星最初被创建(实例化),时将它的 planetProtection 变量初始化为 false,这样行星最初不会受到任何力场的保护。
void turnForceFieldOn () {
planetProtection = true;
}
void turnForceFieldOff () {
planetProtection = false;
}
现在让我们编写我们的setcolonimigration()方法,它允许我们将殖民者添加到我们世界的殖民地中。该方法使用加法赋值运算符将传递给该方法的殖民者数量参数添加到 planetPopulation count 实例变量中。
void setColonyImmigration (int numColonists) {
planetPopulation += numColonists;
}
最后,让我们编写我们的 setBaseProtection( ) 方法,它允许我们将军事力量添加到我们的世界军事基地中。这个方法使用加法赋值操作符将传递给它的部队数量参数添加到 planetMilitary count 变量中。
void setBaseProtection (int numForces) {
planetMilitary += numForces;
}
现在我们准备在 Eclipse ADT 中打开我们的项目,并创建我们新的 WorldGen Java 类。我们将把之前开发的所有代码都写入 Eclipse central 文本编辑器窗格,这样我们以后就可以在我们的应用中使用它来创建、填充和保护新世界。
在 Eclipse 中编写 Java 类:创建 WorldGen 类
首先,我们需要通过单击我们在第一章中设置的快速启动图标来启动 Eclipse ADT,并接受我们项目的默认工作区文件夹位置,它应该是 C:/Users/YourName/workspace/或类似的位置,当然,除非您已经将其设置为不同的位置。
一旦 Eclipse ADT 打开其主 IDE 窗口,您将看到我们在第二章中创建的 Hello World 项目。对于我们在第二章中使用的 Java 和 XML 编辑窗格,标签页应该仍然是打开的。
因为我们创建的 MainActivity 类包含用于启动应用主活动窗口的 Java 代码(以及它的名字),所以我们将利用 Java 的模块化特性,创建我们自己单独的 WorldGen 类来生成(衍生)世界对象。
创建我们新的 WorldGen Java 类
Java 是模块化的一个主要原因是允许我们通过其逻辑功能来组织代码,这样我们就可以编写类来创建我们的新世界对象,并拥有启动和定义我们的用户界面和内容活动屏幕的其他类。
要在 Eclipse ADT 中创建一个新的 Java 类,使用文件新建
类菜单序列,这将打开新建 Java 类对话框,如图 3-1 中的所示。
这个新的 Java 类对话框包含大约十几个字段、复选框和单选按钮,允许我们以类似于新的 Android 应用项目对话框允许我们在配置新的 Android 应用项目设置时所做的方式来配置新的 Java 类。
图 3-1。使用新的 Java 类对话框创建我们的 WorldGen 类
当对话框打开时,您会看到它已经自动填充了几个字段;前两个设置是正确的,包括您的包名和 Hello_World 源代码文件夹位置。超类字段被设置为您的 MainActivity 类(该对话框将假设一个现有的 Java 类将被用作超类),因此我们希望将其设置为 java.lang.Object 类,因为我们的 WorldGen()类将生成世界对象,而不是活动。
首先,使用名称:字段将您的类命名为 WorldGen ,然后单击浏览。。。按钮,位于超类字段的右侧,这样我们可以浏览 Android 中所有可用的类,找到我们想要从中继承 WorldGen 类的 Java Object 类。
这将打开超类选择对话框,在这里我们开始在对话框顶部的选择类型:字段中输入单词对象,如图图 3-2 所示。在我们键入“o”字符后,对话框中间的匹配项目:部分填充,然后我们可以选择(单击)对象项目,如图所示。最后点击底部的 OK ,我们的 java.lang.Object 超类就定义好了。
图 3-2。使用超类选择对话框浏览 java.lang.Object 类
在你点击 OK 之后,你将返回到 New Java Class 对话框,在这里你可以点击 Finish 来创建你的新的 WorldGen( ) Java 类。Eclipse 将新的 Java 类文件、代码和信息添加到它的三个主要窗格中:左边的 Package Explorer 窗格、中间的代码编辑窗格和右边的代码大纲窗格,如图 3-3 所示。
图 3-3。 Eclipse ADT IDE 具有通过“新建 Java 类”对话框创建的空 WorldGen 类代码结构
注意在代码编辑窗格的顶部,新的 Java 类对话框也为您编写了您的包 Java 语句,以及您的 WorldGen 公共类声明,这意味着我们已经准备好添加我们在本章的上一节中编写的 Java 代码。
请注意,我们将我们的 WorldGen 类公开,这样任何 Java 类,甚至是没有包含在我们的包中的 Java 类,都可以调用或调用我们的 WorldGen 对象构造函数,以及我们的 WorldGen 方法,这允许其他 Java 包也为它们的应用创建新的世界。
我们也可以将 public 修饰符完全从这个类声明中去掉,这将向 Java 编译器表明只有我们的 chapter.two.hello_world 包中的类才能生成新的世界。在这种特殊的情况下,这对于我们的应用来说同样适用,因为 MainActivity 类也在我们的包中。事实上,你可能会有兴趣在以后的某个时候尝试一下(去掉 public 修饰符)来获得更多的经验。
现在,让我们在 IDE 中央的 Eclipse ADT 编辑窗格中输入我们在本章前两节中创建的所有 Java 代码。或者,作为一种快捷方式,您可以剪切并粘贴它。确保所有内容都在定义 WorldGen 类边界的两个花括号(也称为大括号)内。
结果显示在图 3-4 中,正如您所见,Eclipse ADT 在我们的代码中没有发现任何错误,并且在右边的 outline 窗格中方便地总结了我们所有的实例变量、构造函数和方法。
图 3-4。进入 Eclipse ADT 集成开发环境的 WorldGen 类的 Java 代码
现在我们已经编写了我们的 WorldGen 类,我们可以让其他类中的代码利用该类来创建 WorldGen (World)对象。
使用 WorldGen 类创建 WorldGen 对象
我们将(暂时)在 MainActivity 类中做这件事,只是为了向您展示它是如何完成的,然后在后面的章节中,我们将通过其他 Java 方法和类调用这段代码,可能会从一些新的 XML 用户界面元素中调用,比如菜单、按钮、文本字段等等。
单击 Eclipse central 编辑窗格中的 MainActivity.java 顶部选项卡,这样我们就可以切换到查看主要活动的 Java 代码。我们将使用 Java new( ) 方法添加一个对象声明,这允许我们创建新的 Java 对象。
我们将在我们的 onCreate( ) 方法中这样做,紧接在通过 super.onCreate( ) 方法调用创建我们的主活动的语句之后,以及在 setContentView( ) 方法调用之后,因为在我们可以做任何其他事情之前,我们需要已经创建了我们的活动,并且已经设置了它的内容视图。
使用构造函数方法调用创建 Java 对象的格式如下
<Declare Constructor Method> <Object Name> = new <Constructor Method Call>
所以,要创建一个 WorldGen 对象,我们声明它(WorldGen),然后命名它(earth),最后通过 new 关键字调用我们的构造函数方法,如图图 3-5 所示,像这样:
WorldGen earth = new WorldGen("Earth", 5973, 9.78);
图 3-5。向我们的 MainActivity 类添加 Java 代码,通过我们的 WorldGen 类生成一个新的 World 对象
基本上,这一行代码应该是这样的:我想声明一个名为地球的 WorldGen 对象,它将是一个新对象,它需要使用名为 WorldGen 的构造函数方法,传递给它三个参数("地球",5973,9.78) ,这些参数是在它的方法中定义的。
注意在图 3-5 中,当我们输入代码来创建新的地球 WorldGen 对象后,单词地球下面会加一条黄色波浪线。在 Eclipse 中,这是一个关于代码中那个单词的警告,为了让 Eclipse 告诉您它警告您什么,您只需将鼠标放在带下划线的单词上,就会弹出一个黄色对话框,告诉您 Eclipse 认为问题出在哪里。这也适用于该行代码左边的黄色警告图标。
在这种情况下,警告显示:没有使用局部变量 earth 的值,这是因为我们刚刚声明了它,甚至还没有时间使用我们的新 earth 对象。因此,在这种情况下,我们忽略警告,键入使用地球对象的第二行代码。
我们这样做是为了向您展示一个对象如何调用或调用它的方法,而且(只是一点点)我们这样做是为了去掉 Eclipse 中讨厌的黄色警告图标和波浪下划线。在我们创建了新的 earth WorldGen 对象之后,让我们使用这个 WorldGen 对象来调用一个 WorldGen 方法,以便将殖民地添加到我们的世界中。
为了从一个对象或在一个对象上调用一个方法,我们使用了一种叫做点符号的东西。最简单的形式是,这种符号采用以下格式:
<Object Name>.<Method Name>(Method Parameter List)
在这种情况下,我们将通过一行相当简单的 Java 代码向新的 WorldGen 对象 earth 添加一个新的 colony,如下所示:
earth.setPlanetColonies(1);
正如你在图 3-6 中看到的,这消除了地球对象声明中的 Eclipse 警告,因为该对象现在正在使用中,通过 setPlanetColonies()方法创建新的殖民地。咻!现在,我们的 IDE 中显示了干净的代码。真是松了一口气!
图 3-6。在 Eclipse ADT 中对我们新创建的 earth WorldGen 对象调用 setPlanetColonies()方法
现在,让我们利用其他与设置属性相关的方法来添加其他对象配置设置,这些设置将为我们的新世界设置殖民地、军事基地、居民和士兵,并打开力场来保护地球免受外部攻击。
在设置行星殖民地的行后添加一行,并再次输入对象名称地球。请注意,Eclipse 用红色给该行加了下划线(因为它还没有被使用),并且一旦您按下句点字符来添加方法调用的点标记附件,Eclipse 将打开一个对话框,其中包含所有方法、变量以及您可以通过点标记从地球对象进行的其他调用。
这在图 3-7 中显示,是一个非常有用的功能,因为 Eclipse 现在在自己的用户界面助手对话框中引用自己的代码。
图 3-7。使用 Eclipse 对象方法帮助器弹出来设置我们的其他新对象特征
在列表中找到 setPlanetMilitary( ) 方法,然后双击将其添加为下一个对象方法调用,然后在参数列表括号内输入一个 1 为新世界对象创建(添加)一个新的军事基地。
接下来,在那一行之后添加另一行,再次键入地球对象名称和句点字符,然后从出现的弹出列表中选择setcolonymigration()方法,并将移民设置为 1000 移民。
注意你也可以简单的输入earth . setcolonymigration(1000);您可以完全忽略 Eclipse 提供的弹出对话框,因为在您开始键入您想要通过 object dot 符号引用的方法名之后,它就消失了。
接下来,让我们引入 100 名士兵来管理我们之前添加到我们的世界对象中的军事基地。在下一行,输入:earth . setbase protection(100);或通过键入接地然后一个句号,并从对话框中选择 setBaseProtection 方法。
最后,在那之后再加一行,打开行星的力场,使用earth . setforcefieldon(); object 方法调用,这样我们新形成的星球现在就被保护在它的大气层外面了。
注意,最后一个方法调用不需要传递任何参数,所以括号是空的,不包含任何值。这是因为这种方法是一种开关,简单地通过调用它,我们就打开了地球保护力场的开关。
图 3-8 显示了 MainActivity 类中所有新的 Java 代码,用于创建新的 WorldGen 对象,以及在 Eclipse central editing 窗格中设置它的方法(无错误)。
图 3-8。用 MainActivity.java 的 Java 代码创建一个新的名为 earth 的 WorldGen 对象并调用它的 set 方法
ADT Eclipse 内部的注释、警告和错误
注意在图 3-8 的中,我们已经注释了我们的 Java 代码,使用了双正斜杠字符序列来创建我们新的 WorldGen 对象。
这就是我们如何在 Java 中的每行代码后添加单行注释,正如你在我们最终的对象实例化代码的图 3-8 中看到的屏幕截图。
让你的注释文本尽可能的易读是很重要的,所以试着把你的注释排成一行,并且清楚的把它们和你的代码分开。让你的注释尽可能的简洁明了,这样他们会让其他程序员受益,他们以后会试着阅读你的代码,破译你在用它做什么。
同样重要的是要注意,Eclipse 将您的注释涂成绿色,以使它们在查看者面前更加突出。还要注意,为了可读性,Eclipse 还将其他关键的 Java 代码涂上了颜色,比如将 Java 关键字涂上紫色,变量名涂上蓝色,所有其他代码涂上黑色。
警告用黄色下划线标出,潜在的编译器错误用红色下划线标出。要查看 Eclipse 认为可能出错的地方,请将鼠标放在带下划线的文本上,或者在 Eclipse 代码编辑中心窗口主窗格中单击该行代码两侧的警告或错误图标。
现在我们已经了解了 Java 对象、类、方法和变量,让我们来看看最后一个 Java 构造,叫做接口,它允许我们定义供公共使用的 Java 类并公开定义它的方法。
为公众创建一个 Java 接口:IntWorldGen 接口
我想在本章介绍的最后一个 Java 概念是 Java 公共接口,尽管我们不会在 Hello World Android 应用中具体实现它。
Java 接口有很多规则,并且引入了一些我们在本章中还没有涉及到的新概念,比如抽象和静态修饰符,所以最好最后讨论接口。此外,在我们为任何给定的类开发公共接口之前,我们将需要对我们所有的方法进行编码,因为我们现在已经完成了这一点,所以它一直工作得很好。你可能会想,接口是用来做什么的。
Java 接口为我们的 WorldGen 类做了什么
如果我们要将我们在本章中创建的 WorldGen Java 类发布到它自己的包中,供全世界的人用作他们自己的 World Generation Toolkit,并创建他们自己定制的 Hello World Planet Creation 应用,那么我们就要为他们定义 Java 公共接口。
这个 Java 接口将精确地定义如何与我们的 WorldGen 类接口(使用什么方法以及如何利用它们)。我们将在本章的这一部分继续实际操作,向您展示这是如何完成的,但我们不会对我们在本章前面编写的代码实际执行(添加)这些更改。这是因为我们的 WorldGen 类是专门为我们自己的 Hello World Android 应用编写的,不会向公众发布。
如果您想对代码进行这些更改(将所有方法都变成公共的,就像构造函数一样),并实现这个接口,代码仍然可以工作,所以如果您愿意,您可以将此作为一个额外的练习。
在 Java 中,一个类的公共接口最常用于给你的类的用户一个关于它的所有方法的快照,以及这些方法操作(或使用)什么类型的数据值。这样做是为了让用户知道你的类方法做什么,以及通过什么样的变量数据类型来完成这些事情。Java 接口编码的一般格式如下:
<Access Control Modifier List> interface <Interface Name> { ... }
接口内声明的所有方法根据定义都是抽象,也就是说,它们不能直接使用,而必须被子类化才能使用,这就是抽象修饰符所表示的。
简而言之,抽象类是被编写(意味着)为子类的类,但不能用于直接创建对象。为了能够从一个抽象类中创建一个对象,你将该抽象类子类化,这使它成为一个具体的类(只要子类类在子类的类声明中没有在单词 class 之前用 abstract 修饰符声明)。如果我们把 WorldGen 抽象成这样:
public abstract class WorldGen { ... }
然后,为了创建 WorldGen 对象,我们必须从现在抽象的 WorldGen 类中继承一个具体的类,现在被设计为 WorldGen 模板:
public class WorldGenPlanets extends WorldGen { ... }
注意关键字扩展了,用于从 WorldGen 子类化 WorldGenPlanets。因为在 WorldGenPlanets 中没有抽象的修饰符,它是一个具体的类,只要它实现了所有的 WorldGen 超类方法,那么它就可以用来创建 WorldGenPlanet 对象。
因为在一个接口中声明的所有方法本质上都是抽象的,所以它们不需要使用 Java 抽象修饰符来声明。类似地,因为一个接口旨在(再一次,本质上)让向公众公开这些方法,所以 public 访问控制修饰符也是隐式假定的,因此也不必显式声明。
最后(没有双关的意思),所有在 Java 接口中声明的方法都被固有地声明为 final ,所以 Java final 修饰符也不必被显式包含。 final 修改器使某个东西,嗯,成为最终的,这样以后就不能修改了,有点像锁定功能。
定义为 final 的变量本质上是一个常数,并且只被初始化一次,然后从那时起就被固定了。任何修改代码中最终变量的尝试都会触发编译器错误(或者编译前 Eclipse 内部的错误通知)。最终变量与常量的不同之处在于,常量值在编译时是已知的,而最终变量的值在编译时可能是未知的。
为我们的 WorldGen 类创建一个 Java 接口
下面是一个简单的例子。假设我们已经用 final 修改器为 WorldGen 类集创建了 planetName、planetMass 和 planetGravity 实例变量。
如果我们以这种方式设置它,那么一旦这些变量被设置,通过我们的 WorldGen()构造函数方法的任何给定实例,这三个实例变量将被设置为 final 然而,它们不会在编译时被设置为常量值,而是在运行时通过我们的公共 WorldGen()构造函数方法由唯一的 WorldGen 对象的每次创建来设置。
声明为 final 的类不能被子类化;声明为 final 的 Java 类的例子是 java.lang.String 和 java.lang.System 。因此,如果我们将 WorldGen 类声明为:
public final class WorldGen { ... }
那么我们在本页前面所做的代码语句将不被允许:
public class WorldGenPlanets extends WorldGen { ... } // This will generate an error!
声明为 final 的方法不能在任何子类中被覆盖(也就是说,它的代码不能被更改、替换或增强),本质上锁定了该方法及其功能,因此以后不能被更改。
用 final 修饰符声明一个方法的原因是,它可能包含一些关键的功能,出于多种原因,包括代码稳定性、函数保留或一致性维护,这些功能不应该更改。不用说,final 修饰符在 Java 语言中确实是一个有用的工具,可以确保代码得到正确使用。在任何情况下,只要记住在 Java 接口内部声明的所有方法本质上都是公共的、抽象的和最终的。
当前存在的 WorldGen 的接口将被编码如下:
public interface IntWorldGen {
void setPlanetColonies(int numColonies);
int getPlanetColonies();
void setPlanetMilitary(int numBases);
int getPlanetMilitary();
void turnForceFieldOn();
void turnForceFieldOff();
boolean getForceFieldState();
void setColonyImmigration(int numColonists);
long getColonyImmigration();
void setBaseProtection(int numForces);
int getBaseProtection();
}
注意,我们在接口定义中不需要构造函数方法,只包括方法。您使用以下语法实现一个 Java 接口:
public class WorldGen implements IntWorldGen { ... }
implements 关键字将类的构造函数方法传递给新的接口实现,因此,在这种情况下,类名或接口名都可以用来创建新的对象,如下所示:
IntWorldGen earth = new IntWorldGen("Earth", 5793, 9.78);
和这样做是一样的:
WorldGen earth = new WorldGen("Earth", 5793, 9.78);
换句话说,一旦 WorldGen 实现了 IntWorldGen 接口,这两个对象实例化都将调用 WorldGen 构造函数方法。
摘要
在本章中,我们仔细研究了 Java 编程语言中一些更重要的面向对象编程(OOP)概念。同时,为了练习在 Hello_World 应用代码中实现这些概念,我们应用这些概念创建了 WorldGen World Generation 类。
我们可以使用这个 Java 类将在第二章中创建的 Hello_World Android 应用提升到一个新的水平,只要我们编写一些 XML 标记,在接下来的几章中添加一些很酷的屏幕布局、菜单、文本、按钮、用户界面设计、图形图像和动画。
我们首先查看了最高级别的 Java 构造,即 API 或应用编程接口,然后我们查看了 Java 包以及它如何组织我们的项目代码。然后,我们看了 Android 中的包如何包含有用的 Android 类,我们可能希望将这些类导入并在我们自己的 Hello_World Android 应用以及其他应用中加以利用。
接下来,我们看了 Java 对象的概念,它是 OOP 的基础,也是 Java 编程语言中的核心编码结构。我们了解到 Java 对象对现实世界的对象进行建模,包括属性(实例变量)和功能(方法)。
下一个逻辑步骤是让我们看看如何使用 Java 类创建 Java 对象,Java 类是允许我们定义实例变量和包含局部变量的方法的代码构造。
深入到类结构的下一层,我们看了 Java 方法,以及这些方法是如何创建的。我们写了十几个方法,所以我们现在有了 Hello_World Android 应用的世界生成工具包。我希望你不要以为我们只是要做普通的印刷“你好,世界!”在本书中的 Hello World 应用屏幕上,因为我们实际上要做的正好相反,创建有史以来最棒的 Hello World 示例应用实现!
在我们学习了一些 Java OOP 理论和行话之后,是时候启动 Eclipse ADT 并真正编写我们的 WorldGen 类了。我们学习了 Eclipse 如何在其 IDE 中为我们标记警告和错误,以及如何鼠标悬停和鼠标点击这些来找出 Eclipse 认为我们的代码有什么问题。我们还学习了当我们键入对象名并按下句点键时,如何让 Eclipse 收集我们的方法和变量,以及如何注释我们的代码以增强可读性。
最后,我们看了一下 Java 公共接口,以及如果我们决定为我们的任何类实现一个接口,它们是如何定义我们应该使用什么方法的,在本书的后面我们将对一些 Android 类这样做。
在下一章中,我们将开始为 Hello_World Android 应用构建屏幕布局、菜单和用户界面,但这一次我们将使用 XML 标记代码(大部分)来定义这些元素。
四、布局和活动:使用ViewGroup
类
既然我们已经在第三章中创建了 Java 代码,为我们的 Hello_World 应用生成了健壮的新世界(WorldGen),我们需要开始为我们的 Android 应用构建前端,我喜欢称之为用户界面设计。
用户界面 (UI)设计、用户体验(UX)以及应用内容都是通过 Android 消费电子设备的主屏幕交付的,您的目标最终用户或客户正在使用该设备查看您的 Android 应用。用户体验(UX)是用户界面设计、可用性和内容感知影响的总和。
在这一章中,我们将仔细看看最底层,实际上是 Android 中任何屏幕设计的基础。这被称为 Android 布局容器,它是使用视图组类在 Android 中实现的。Android ViewGroup 类是 Android View 类的子类,后者本身是我们在第三章中了解到的 Java Object 类的子类。幸运的是,我们已经了解了 Java 类的层次结构。
我将再次尝试在这一章中无缝地融合两个目标;通过添加布局容器和菜单来再次增强我们的 Hello World 应用,并教你如何同时使用 XML 和所有关于 Android 视图和视图组类概念来编写菜单和屏幕布局。
我们将在我们的 Hello_World 应用中定义一些不同类型的屏幕布局容器,并为它们提供基本的用户界面元素,稍后,我们将在本书的第二部分中用更高级的用户界面元素、图形、视频和动画来替换(升级)。我们还将学习如何创建 Android 活动,我们将使用它来保存我们的布局容器,并在 Hello_World 应用中定义逻辑区域。
Android 屏幕布局容器:ViewGroup 类
在 Android 中,屏幕布局是通过 Android ViewGroup 类创建、定义和组织的,该类是从更一般化的 View 类继承而来的。View 类必须通过大家熟悉的 import android.view.View 导入到每一个 Android 应用中;声明。
因为 Android 视图类以各种方式管理我们的屏幕界面,所以它最终有大量的子类,而大多数视图子类本身有更专门的子类,正如你将在本章中看到的,以及在下一章涵盖用户界面设计中看到的,一般来说,贯穿本书的其余部分。
不用说,视图在 Android 中很重要,因为它们提供了你的应用和最终用户之间的接口。Android View 包用于在新的 Android 应用项目对话框中创建的 bootstrap (basic) Android 应用,因为要使用应用的屏幕,您必须导入 View 类才能做到这一点。
Android View 类有大量专门的子类,其中许多我们会在第五章中详细讨论。本章致力于一个非常特殊的视图子类,叫做视图组,它允许以小部件子类(用户界面元素)的形式无缝地组织视图子类。
ViewGroup 是View
的子类,因为 Android View
类提供了那些顶级的屏幕管理特征(大小、尺寸、方向和类似的屏幕属性)以及功能(事件处理、焦点等)。)我们将在第五章中详细介绍。
ViewGroup
子类增加了全局屏幕布局属性和方法,允许开发人员定义和微调屏幕布局组织特征和顶级功能。
就像 Android View 类为其ViewGroup
子类提供更高级别的属性和功能一样,ViewGroup
base 类为其自身的子类提供更高级别的屏幕布局参数属性和屏幕布局创建功能。
我在这里提到这个的原因是因为在 Android 中没有直接使用ViewGroup
类来实现任何特定的布局容器;因此,它可以被定义或归类为它的许多子类中的一个基本类。
所以,请记住,Android 开发人员目前可以使用的所有不同类型的屏幕布局容器类来创建用户界面设计,实际上是 Android ViewGroup
基类的子类。
Android 视图组子类:布局容器类型
Android 提供了几种主流的布局容器类型作为 ViewGroup 类的子类。这些类是你将在大多数项目中使用的,也是我们将在本书中涉及的。
这些 ViewGroup 子类被命名为: RelativeLayout (默认布局类型,在新的 Android 应用项目对话框中创建)、 FrameLayout (用于单项布局)和 LinearLayout (用于需要符合行或列格式的用户界面元素,如内嵌按钮条和自顶向下列表)。
还有一些其他的布局容器类型被弃用了,我们不会讨论这些,因为它们已经停止使用了。弃用意味着停止使用,但仍支持(但不推荐)使用,以便使用此弃用代码的旧代码仍能运行。Android 中被弃用的布局容器和被弃用时的 API 等级包括 AbsoluteLayout (等级 3)和 SlidingDrawer (等级 17)。
还有一些布局容器类型本质上与 deprecated 正好相反,也就是说,它们太新了(API 级别 13 和更高),以至于 Google 还没有完成它们的代码实现,如果使用它们,当所有的功能最终就绪时,您可能需要在以后更改代码。此外,它们只能在 API 级别 13 到 17 下工作,不支持 API 级别 3 到 12,而传统的消费电子产品仍然支持 API 级别 3 到 12。在本书中,我们想要关注的是适用于所有 Android 设备的布局容器。
API 级(蜂巢)及以后的布局容器有网格布局、 ViewPager、和 PagerTitleStrip ,这些我们就不赘述了。
注意,我们也不讨论 FragmentBreadCrumbs 布局容器,因为它的复杂性超出了学习 Android 应用开发的入门书籍的范围。
我们看到的第一个布局容器是最常用的容器之一,也是 Android 操作系统的默认容器: RelativeLayout 。因为RelativeLayout
已经在您的引导 Hello_World activity_main.xml 文件中设置好了,我们将继续向您展示如何正确使用它。
相对布局定位:Android RelativeLayout 类
我们将从 RelativeLayout 类开始,因为这是 Eclipse ADT 在我们新的 Android 应用项目系列对话框中为我们设置的默认布局容器。我们将使用它,并首先了解 RelativeLayouts,因为它们是最常用的。
RelativeLayout
容器被恰当地命名,因为它使用相对定位来排列用户界面元素(UI 窗口小部件,使用 Android View
类对其进行子类化,我们将在下一章中学习)。
在本章的下一节中,当我们开始使用 Android XML 标记语言编写 Hello_World 应用启动屏幕用户界面元素时,您将看到如何创建相对布局,用参数填充,然后进行微调。
我们将使用当前的 主活动屏幕活动作为我们的启动屏幕。因为我们已经定义了一个“默认”或初始地球世界,我们将在我们的应用主屏幕上使用它来向我们的最终用户显示他们当前所在的位置—
地球。这样,我们就不用重写任何代码了!
我们还将向您展示如何定义菜单,以便您可以调用其他屏幕(活动)定义。我们在这些活动中使用不同类型的屏幕布局容器,以便您可以了解每种容器是如何实现的。我们在本章中介绍菜单,因为菜单代码已经在第二章中写好了,所以尽可能早地实现我们的菜单是合理的,这样你就可以学习如何为你的活动屏幕实现菜单。另外,菜单也是 UI 设计的一部分,对吧?
创建启动屏幕:通过 XML 定义我们的 relative layout
我们定义的第一个屏幕(活动)布局容器是我们的 Hello World 应用启动屏幕,它是在第二章中定义的。Java 代码位于 MainActivity.java 的中,XML 标记位于的 activity_main.xml** 中,目前它是一个带有 Hello World 的 RelativeLayout 容器!里面有短信。**
我们希望将这个当前的启动屏幕转化为一个信息图表,显示我们的应用最终用户当前所处世界的当前属性。我们使用一些 Java 代码和 XML 标记更改(相当小的更改)。当应用首次启动时,这是我们在第三章中定义的地球物体。我们将让我们的用户在世界之间旅行,所以我们必须从一开始就让这个屏幕访问变量内容(而不是固定的文本值),这让它变得有点困难。让我们设计我们的屏幕输出如下:
Planet Name: Earth <planetName instance variable>
Planet Mass: 5,973 YG <planetMass instance variable>
Planet Gravity: 9.78 m/s <planetGravity instance variable>
Planet Colonies: 1 <planetColonies instance variable>
Planet Population: 1,000 <planetPopulation instance variable>
Planet Military: 100 <planetMilitary instance variable>
Planet Bases: 1 <planetBases instance variable>
Planet Forcefield: On <getForceFieldState() method call>
这在我们的相对布局容器中转化为八个文本字段(在左边),包含八个字符串常量和八个右边的文本字段,它们访问我们的 WorldGen 对象方法和变量,当我们的用户旅行到另一个世界时,可以通过 Java 代码设置。
尽管我们将在下一章详细介绍用户界面元素,但我们至少要介绍一个视图子类(TextView)来创建这个当前世界信息启动屏幕。所以,让我们点击 Eclipse 快速启动图标,开始吧!
Eclipse 打开我们的 Hello_World 应用,正如我们在第二章和第三章中对它的配置一样。顶部应该有已经为 activity_main.xml (res/layout 文件夹)、activity_main.xml (res/menu 文件夹)、strings.xml (res/values 文件夹)、MainActivity.java(src/chapter . two . hello _ world 文件夹)和 WorldGen.java(src/chapter . two . hello _ world 文件夹)打开的编辑选项卡。让我们从单击 strings.xml edit 选项卡开始。
为启动屏幕创建字符串常量
让我们先添加我们的启动屏幕信息文本标签常量,因为我们的 RelativeLayout 容器中的 TextView 小部件将访问它们。正如您所看到的,在这个 XML 文件中已经为我们的应用名称(Hello_World)和 Hello_World 问候消息定义了字符串常量,还为一个菜单标签(设置)定义了字符串常量,我们稍后将对其进行一些更改。
单击 strings.xml 的编辑选项卡,并为我们在本节前面定义的八个世界信息字段标签添加 < string > XML 标签。XML 标签应该看起来像这样,也显示在图 4-1 : 中
<string name="planet_name_label">Planet Name: </string>
<string name="planet_mass_label">Planet Mass: </string>
<string name="planet_gravity_label">Planet Gravity: </string>
<string name="planet_colonies_label">Planet Colonies: </string>
<string name="planet_population_label">Planet Population: </string>
<string name="planet_military_label">Planet Military: </string>
<string name="planet_bases_label">Planet Bases: </string>
<string name="planet_forcefield_label">Planet Forcefield: </string>
图 4-1。在 Eclipse 的 strings.xml 文件中添加我们的八个启动屏幕文本常量
请注意,我们在每个分号后添加了一个空格,这样我们稍后添加的变量数据就不会接触到我们的标签文本常量。作为程序员,我们必须总是提前考虑像这样的 UX 问题。
通过
现在点击包含了 xml 容器的 activity_main.xml 标签,图 4-1 中最左边的编辑标签。我们不需要(或不想)将这些新的 TextView 元素垂直或水平居中,所以我们想做的第一件事是删除这两个 XML 参数。
接下来,让我们向第一个 TextView UI 元素添加一个 android:id 参数,就在开始的 < TextView > 标签之后。这样,一旦我们复制并粘贴了所有的 TextView 标签,我们就可以根据我们如何配置第一个标签,通过名称来识别我们的 RelativeLayout 中的每个 TextView 标签。
这个 ID 还被用来从 Java 以及我们的 XML 标记中引用每个 UI 元素 ,这是使用我们现在熟悉的 android:id XML 参数来完成的。参数是这样写的:
android:id="@+id/textView1"
最后,更改文本视图中的最后一个参数, android:text 指向我们之前在 strings.xml 文件中创建的@ string/planet _ name _ label。完成的 TextView UI 元素 XML 标记如图图 4-2 所示。
图 4-2。在 Eclipse 中添加 TextView XML 标签,并通过弹出对话框配置相关布局参数
因为我们的活动屏幕上只有一个 UI 元素,这里仍然没有相对的布局定位,所以让我们复制并粘贴第一个 TextView UI 元素到它本身的下面,这样我们就有了两个,然后可以探索它们相对于彼此的定位。
通过编辑 android:id 参数,将第二个标签命名为 textView2 ,如图图 4-2 所示,然后记得同时更改标签的 android:text 参数,以指向我们之前在 values 文件夹的 strings.xml 文件中定义的 planet_mass_label 字符串常量。
接下来,我们通过在 android:id 参数下添加一行并键入单词 android 来添加我们的相对定位参数。注意,在你按下参数名的 android 部分之后的冒号字符之后,在参数名的布局部分之前,你触发了一个 Eclipse 参数助手对话框,如图图 4-2 所示。
XML 参数选择器助手对话框总是弹出参数建议,就像我们在定义 Java 对象时按下句点键后在第二章中观察到的一样。
在你按下冒号键之后,继续输入单词布局,注意,当你输入的时候,Eclipse 会根据它认为你正在寻找的内容,提炼弹出帮助器的内容,再次显示在图 4-2 中。
在输入完 android:layout 之后,你就列出了所有可以用于 relative layout 容器的相关布局参数。
正如你在图 4-2 中看到的,大约有三打(显示了一打,因为滚动条是高度跨度的三分之一,我估计是三倍),你需要选择列表中的第五个。
双击 android:layout_below 参数以选择它的用法(因为我们希望我们的 TextView UI 元素一个在另一个下面)。一旦这个相对布局参数被添加到 Eclipse XML 编辑窗格中,在 helper 实用程序提供给您的两个引号字符内键入 @+id/textView1 引用,这是对直接位于它上面的第一个 TextView UI 元素的引用。
注意,例如,如果你在一个 LinearLayout 容器中,这个参数弹出帮助对话框将包含与安卓布局容器的 LinearLayout 类型兼容的参数。
接下来,我们将使用第一个 TextView XML 标记创建的第二个 TextView XML 构造复制并粘贴六次,这样我们就不必再键入六次所有的 TextView 标记及其参数。
对于六个复制的 TextView 标签中的每一个,让我们编辑每个复制的 TextView 标签的' android:text 参数中的 @string 变量,以便它们与最后六个< string >标签常量名称相匹配,这些名称是我们之前在 values 文件夹中的 strings.xml 文件中定义的。
最后,让我们为六个复制的 TextView 标签中的每一个编辑 android:layout_below 参数,这样每一个都指向,或者说引用,TextView UI 元素的 android:id 参数,它直接位于每一个标签之上。
Hello_World 应用启动屏幕的文本标签常量的八个 XML 标签现在定义在我们的activity _ mainrelative layout XML 容器中,应该看起来像图 4-3 中所示的屏幕。
图 4-3。我们配置了相对布局(layout_below)参数的八个基本 TextView XML 标签
注意,有两种不同的方法来关闭 XML 标签。一种方法是在标签参数后用一个 / > 符号来结束每个 TextView 标签。这是关闭没有任何其他标签嵌套在其中的标签的方法。
另一种在 XML 标记内嵌套有其他标记的情况下关闭 XML 标记的方法是,只使用一个 > 符号来关闭开始标记,然后在该标记内的任何嵌套项后让标记名以/ >符号结尾。我们这里的 < RelativeLayout > 标签就是这种情况。
在图 4-3 中,我们可以看到这两种不同的标签关闭方法,我们当前的相对布局标签和嵌套在其中的文本视图标签。
现在让我们编译并运行我们的 Hello_World Android 应用,看看我们的启动屏幕到目前为止是什么样子。确保你写的所有代码都保存好了(你可以在任何标签页使用 CTRL-S 随时保存,然后你可以在左边的包浏览器窗格中右键点击项目文件夹,然后选择运行为 Android 应用菜单选项。
正如你在图 4-4 中看到的,我们的启动屏幕的文本标签彼此完美地排列在一起,所以我们的相对布局参数工作良好。为了完善我们的用户体验,我们唯一需要做的就是将我们的八个文本标签向右移动一点,这样它们就不会碰到用户屏幕的左边缘。
图 4-4。在 Nexus S 模拟器中运行我们的 RelativeLayout,看看相对定位是如何工作的
我们将通过使用 RelativeLayout 容器左边距参数Android:layout _ margin left来实现这一点。这要求我们为八个 TextView 标记中的每一个添加一行 XML 标记,如下所示:
android:layout_marginLeft="5dip"
这将我们的文本常量标签向右移动五个设备独立像素 ( 倾斜或 DP ),为我们的新启动屏幕创建一个直的左边距。将这一行 XML 标记添加到每个 TextView,就在 android:id 参数之后,然后使用 Run As Android App 命令检查结果。
通过
现在我们已经准备好添加 TextView UI 元素,它将保存我们的 WorldGen 对象的实际数据值。我们将使用一些新的相对布局参数,类似于我们刚刚用来完成这个屏幕的用户界面设计的左半部分。
让我们再次采用另一种快捷方式,复制第一个 textView 标记,将其 ID 从 textView1 重命名为 dataView1 ,并进行一些其他更改,以创建我们的对象数据变量 TextView 用户界面元素标记,如下所示:
<TextView
android:id="@+id/dataView1"
android:layout_toRightOf="@+id/textView1"
android:layout_marginLeft="36dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/planet_name_label" />
我们添加的第一个东西是一个 android:layout_toRightOf 参数,它告诉我们这个将保存我们的星球名称变量数据值的第一个 TextView UI 元素被放置在 toRightOf (在我们的相对布局容器中的第一个星球名称标签常量的右边)。
我们需要修改的第二件事是Android:layout _ margin left参数,它用于将变量数据推出,远离标签常量。现在,我保留了 android:text 参数(我们稍后将在工作过程中删除它),这样我们可以在屏幕中间将变量数据排成一条直线,这样看起来更专业。
让我们通过将marginLeft
设置为 36dip 来推出可变数据文本(现在使用行星名称“虚拟”文本),以便它比屏幕左侧的最长标签常数(行星数量)稍微远一点。这为我们排列其他可变数据文本 UI 元素提供了一个基线。XML 代码如图 4-5 所示。
图 4-5。使用 android:layout_toRightOf 参数添加变量数据 TextView
现在,我将向您展示我是如何精确地获得这个 36°倾角设置值的,而不是在我优化该参数时连续多次使用作为 Android 应用运行的(仿真器),因为这个工作过程可能非常耗时!
Eclipse XML 标记编辑窗格中有一个新特性叫做图形布局编辑器(我经常称之为 GLE ,简称,可以通过一个小标签在 XML 编辑窗格的左下方访问,如图 4-6 中用红色圈出的所示。
图 4-6。使用 Eclipse 图形化布局编辑器来预览我们的相对布局参数设置
单击这个 GLE 选项卡“呈现”您在模拟器中编写的 XML 代码,当您通过 MainActivity Java 代码运行该 UI 设计时,模拟器将向您显示这些代码。注意,这个 GLE 也可以用来为您编写 XML 代码,使用拖放功能,您可以在它的周围看到。因为我们都是程序员,所以我将严格从代码编写的角度来看这本书,并主要使用这个 GLE 特性作为渲染设计预览的捷径,以节省我们 UI 设计过程中及时的仿真器启动周期。
同样,让我们复制第二个 textView 标签,将其 ID 从 textView2 重命名为 dataView2 ,并做一些其他更改来创建我们的下一个对象数据变量 TextView 用户界面元素标签,如下:
<TextView
android:id="@+id/dataView2"
android:layout_toRightOf="@+id/textView2"
android:layout_alignStart="@+id/dataView1"
android:layout_alignBelow="@+id/dataView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/planet_mass_label" />
我们将 ID 重命名为 dataView2,确保它是 textView2 的to right,将 alignBelow 参数设置为低于 dataView1,还将 marginLeft 参数替换为Android:layout _ align start参数。
我们使用的这个新的相对布局参数对齐 UI 元素的起始(像素)位置,我们将它设置为引用我们刚刚创建的 dataView1,以便 dataView1 和 dataView2 完美地对齐。
现在,我们将 android:text 引用设置为我们在 textView2 标签中使用的相同的 planet mass 字符串,尽管稍后我们可以删除它,因为最终我们的 Java 代码将设置这个值,并且我们将不需要它来检查对齐(这就是为什么我们现在将它留在这里)。
一旦我们做了这些改变,如图 4-7 所示,我们可以使用 dataView2 作为模板,复制并稍作修改,创建 dataViews 3 到 8。
图 4-7。使用 android:layout_alignStart 参数将 dataView2 与 dataView1 的左侧对齐
在我们复制并粘贴了 dataView3 到 dataView8 的 TextView 标签后,我们可以点击屏幕底部的图形布局编辑器选项卡,并看到我们的虚拟文本变量完全对齐,如图 4-8 中的所示。
图 4-8。使用 layout_alignStart 参数在图形布局视图中排列的虚拟数据文本变量
注意,当我们复制它时,唯一可以灵活改变其值的相对布局参数是Android:layout _ align start参数。它可以被设置为引用 dataView1 或它上面的数据视图。我在它上面使用了 dataView,因为从我复制的前一个 dataView 标记的每个副本中为每个数字加 1 更容易。这就是我复制这些数据视图标签的工作流程。
使用 Java 将数据写入我们的启动屏幕 UI
现在我们需要让我们的 Java 代码用实际的对象变量数据值替换这些虚拟实例变量(这将在运行时完成)。这要求我们在我们的MainActivity
类中编写一些方法。
我们编写第一个方法将用于巩固我们的onCreate()
方法,通过把我们放在那里的所有对象创建代码,回到第二章的,并创建一个 setStartUpWorldValues( ) 方法来以有序的方式设置我们的初始(默认)世界对象和位置的值。
因为我们也在编写一个 setStartUpScreenText( ) 方法来访问我们的earth WorldGen
对象,我们需要将创建我们的WorldGen
对象的代码行移到我们的MainActivity
类的顶部,如图图 4-9 所示,这样该类中的所有方法都可以访问它。这一行 Java 代码如下所示:
WorldGen earth = new WorldGen("Earth", 5973, 9.78);
图 4-9。将 WorldGen 对象创建和配置代码重写为 setStartUpWorldValues()方法
用实例变量数据设置填充我们的earth WorldGen
对象的其余 WorldGen
方法调用现在放在我们的保护的 void setStartUpWorldValues( ) 方法的花括号内,如图 4-9 所示。这个方法被保护,这样这个类(和子类)中的其他方法,比如onCreate()
就可以访问(调用)它。它也被声明为 void ,因为它执行一个不返回值的任务。还要注意,我们已经将注释与代码一起复制并对齐了。
现在是时候编写我们的第二个 setStartUpScreenText( ) 方法了,该方法将使用来自名为 earth 的WorldGen
对象的数据填充 dataView TextView UI 元素。对于每个 dataView TextView 对象,这需要两行 Java 代码;一个用于创建、命名和引用 TextView UI 元素,另一个用于将其 android:text 参数值设置为每个 earth WorldGen 对象实例变量数据字段的值。
为了声明和命名我们的第一个 TextView 对象,我们使用这行代码:
TextView planetNameValue = (TextView)findViewById(R.id.dataView1);
这将对象声明为 TextView 类型,将其命名,并引用我们在 activity_main.xml 文件中设计的 dataView1 标记。
请注意图 4-10 中的,当我们键入文本视图对象类型时,Eclipse 使用红色波浪下划线,当我们将鼠标悬停在下划线对象类型上以了解原因时,Eclipse 通知我们文本视图无法解析为类型,并给我们一些潜在的解决方案,其中最明显的是第一个导入文本视图,因为我们从第二章中知道,在我们自己的代码中使用对象类之前,我们必须导入它。
图 4-10。编码 setStartUpScreenText()方法,添加 dataView TextView 及其 import 语句
所以点击Import TextView(Android . widget package)选项,让 Eclipse 为您编写我们的 Import 语句,正如您将在图 4-11 中看到的,它现在已经就绪,可以使用了。
图 4-11。使用 Java。setText()方法调用来为 dataView1 设置我们的 planetNameValue TextView 对象
我们需要编写的第二行代码获取我们刚刚声明的名为 planetNameValue 的 TextView 对象,并使用它来调用 TextView 类的一个用于设置文本值的方法。毫无疑问,这个方法被称为【setText()】方法。您会注意到,在您键入 planetNameValue 对象名称,并键入句点字符后,Eclipse 将弹出其对象方法选择帮助器对话框,如图 4-11 所示。向下滚动到列出的setText(char sequence text)方法,双击它将对该方法的点调用添加到您的对象中。
在圆括号内输入对 earth.planetName 对象和实例变量数据值的引用,用于方法调用,如图图 4-12 所示。现在让我们为行星质量数据编写第二两行代码。
图 4-12。【setStartUpScreenText()的 Java 代码,用地球对象数据填充 dataView UI 元素
复制 TextView planetNameValue 行代码,将 planetNameValue 改为 planetMassValue ,然后在 findViewById( ) 方法调用中引用 dataView2 而不是 dataView1。然后复制 planetNameValue.setText()代码行,并更改 planetNameValue 对象名,以匹配在前面一行 Java 代码中创建的 planetMassValue 对象名。
因为 planetMass 是一个整数数据值,我们将不得不在我们的中嵌套一个方法。setText( ) 将整数值转换成字符串值的方法。这是通过完成的。字符串类的 valueOf( ) 方法,由嵌套调用****String . value of()方法调用如下:
planetMassValue.setText(String.valueOf(earth.planetMass));
这一行 Java 代码嵌套了两个方法调用,将String . value of(earth . planet mass)方法调用的返回值(将整数值转换为字符串值)传递给planet mass value . settext()方法调用(需要一个字符串值作为输入),所有这些都在一个非常紧凑(嵌套)的代码语句中。
接下来的六行与这两行非常相似,所以让我们走编码者的捷径,复制最后两行代码六次,并相应地替换我们的 dataView 和 planetVariableValue 命名约定。所有这些艰巨的 Java 编码工作的结果如图 4-12 所示。
现在让我们在 Nexus S 模拟器中运行我们的 Hello_World Android 应用,看看这个启动屏幕结出了我们的劳动果实。右键单击项目文件夹,并选择 Run As Android Application ,您将看到我们的 XML 和 Java 编码工作已经生成了一个完美的应用 UI 屏幕,它为我们之前创建的 earth WorldGen 对象创建了一个数据信息屏幕,并将作为我们 Hello_World 应用的初始主世界。如图 4-13 中的所示。
图 4-13。使用填充了对象数据的 dataView TextView UI 元素运行 Hello_World 应用
接下来,让我们在主页(启动)屏幕的底部添加一个菜单系统!
向我们的活动添加菜单:菜单展开器和菜单 XML
我们需要做的第一件事是定义我们的启动屏幕菜单。Android 中的菜单通常只有五个或更少的条目,所以让我们挑选五个主要功能,我们需要在本章的后面定义布局屏幕(活动)。毕竟这是版面设计章节。
我们的 Hello_World 行星生成器需要有屏幕,允许其用户选择一个新世界,设置其属性,访问该行星(和平时期),攻击该行星(战争时期),并返回到主屏幕(启动屏幕),我们称之为显示主行星,以防用户离开地球,并前往他们创建的其他行星之一。
因此,逻辑启动(主页)屏幕菜单选择,以及当菜单项被选择时它们将调用的 Java 活动、XML 标记文件名和布局容器类型如下:
Add a New Planet Call NewActivity.java, activity_new.xml RelativeLayout
Configure Planet Call ConfigActivity.java, activity_config.xml LinearLayout
Travel to Planet Call TravelActivity.java, activity_travel.xml FrameLayout
Attack on Planet Call AttackActivity.java, activity_attack.xml LinearLayout
Show Home Planet Stay in MainActivity.java, activity_main.xml RelativeLayout
请注意,我们已经将菜单标签设计为均匀间隔(文本排列整齐)。这是为了让我们的菜单看起来尽可能吸引我们的终端用户,增强我们应用的用户体验或 UX。
为了向我们的主活动屏幕添加菜单项,我们将使用 XML 标记在名为 activity_main.xml 的文件中定义我们的菜单项,该文件位于我们的 res (resources)文件夹中的菜单文件夹中。但是,我们已经在布局文件夹中有一个 activity_main.xml 文件,你正在聪明地思考。这是正确的;然而,该文件在布局文件夹中,因此是与同样在菜单文件夹中的 activity_main.xml 文件不同的文件,尽管它具有完全相同的文件名。
每个布局容器 XML 定义和每个活动屏幕的菜单 XML 定义应该具有相同的文件名,如果您稍微考虑一下,这确实非常符合逻辑,因为您的应用(如果是高级应用)中的每个屏幕(活动)都将定义复杂的用户界面或内容元素(布局 XML),以及为每个活动(功能应用区域和屏幕)定义的菜单选择(菜单 XML)。
请注意,我正在交换这些屏幕/活动术语,至少是一小会儿,这样你就会习惯 Android 的行话。在我们向您展示如何为我们的菜单将要调用的这些屏幕编写单独的活动类之前,让我们修改新的 Android 应用项目系列对话框为我们创建的当前 activity_main.xml 文件,以便它包含我们的五个菜单标签,而不仅仅是一个设置标签。
从我们在第二章中的工作来看,您仍然应该在 Eclipse IDE 中打开 activity_main.xml 的菜单文件夹版本,如果它没有在 IDE 中打开,只需打开菜单文件夹并右键单击 activity_main.xml 文件,然后从上下文相关菜单中选择打开选项。然后点击 XML 编辑选项卡,再复制四次<条目>标签,创建我们五个条目的菜单。在我们复制现有的<项目>标签之前,让我们先编辑它,使它与我们之前设计的第一个菜单项匹配。
将 android:id 从 menu_settings 更改为 menu_add ,将 android:title 更改为 menu_add_planet ,然后在第一个标签下复制< item >标签四次。将 menu_add 的 android:id 参数分别更改为:menu_config、menu_travel、menu_attack 和 menu_home。
然后将Android:orderin category值分别改为:200、300、400、500,将 android:title 参数分别改为:menu_config_planet、menu_travel_planet、menu_attack_planet、menu_home_planet。
因此,每个
<item android:id="@+id/menu_function"
android:orderInCategory="Number Value for Order in Menu"
android:showAsAction="never"
android:title="@string/menu_string_constant" />
该工作过程的结果显示在图 4-14 中的最终 XML 菜单标记中。
图 4-14。将我们的五个菜单项添加到菜单文件夹中现有的 activity_main.xml 菜单定义文件中
现在我们需要将菜单标签文本常量添加到我们的 strings.xml 文件中,所以接下来单击 Eclipse 顶部的 strings.xml 选项卡,并编辑已经存在的用于名为 menu_settings 的
一旦你完成了这些,复制
<string name="menu_add_planet">Add a New Planet</string>
图 4-15。将我们的五个菜单标签字符串常量添加到 values 文件夹中的 strings.xml 文件中
我们不需要编写 Menu Inflater 代码来创建菜单,因为新的 Android 应用项目助手已经为我们完成了这项工作,但是,因为我们在这里使用它来创建我们的菜单,所以现在让我们更仔细地看看它,并把它处理好。以下是菜单增压泵代码:
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
onCreateOptionsMenu( ) 方法是 Android Activity 类的一部分,它用于为任何给定的活动创建选项菜单。在这个特例中,它是我们的 android.app.Activity 的 MainActivity 子类,它被导入到我们的 MainActivity.java 文件的顶部。
该方法将一个名为 Menu 的 Menu 对象传递给该方法,在该方法中,它通过一个 inflate( ) 方法填充了您的 menu XML 参数,该方法通过 R.menu.activity_main 方法中的第一个(inflate from)参数引用您的\ resource \ Menu \ activity _ main . XML文件,然后将该数据膨胀到第二个(inflate to object)参数中,该参数是声明并传递给 onCreateOptionsMenu()方法的 menu 对象。
getMenuInflater( ) 方法调用 inflate()方法,并使用它来填充活动的 MenuInflater 对象。 MenuInflater 类是 android.view 包(Android . view . MenuInflater)的一个成员,它使用那些 <菜单> 和<项目> 标签及其参数,获取您的菜单 XML 定义和实例化膨胀的菜单对象。
一旦 getMenuInflater()。inflate( ) 过程已经完成,没有任何失败, onCreateOptionsMenu( ) 方法退出,并返回一个 true 值,表示菜单资源已经被正确实现。
我们现在可以右键单击我们的项目文件夹,并选择 Run As Android Application 命令来编译和运行我们的 Java 和 XML 代码。让我们来看看我们的菜单在模拟器中的效果,如图 4-16 所示。
图 4-16。带有功能菜单的主活动启动屏幕
在我们可以在 Java 代码中实际实现这些菜单选项之前,我们需要为每个菜单选项创建布局容器(通过 XML 标记)和活动(通过 Java 代码)。这是因为选择这些菜单项中的每一个都会将应用切换到这些活动中的每一个。
因此,让我们为前四个菜单选项创建四个新的 Activity Java 类,每个类都有自己的自定义布局容器 XML 定义,如图 4-16 所示,包含了我们的 Hello_World Android 应用的主要功能区域。
这些新的活动及其布局容器为我们提供了大量的应用功能屏幕,在本书的接下来几章中,我们将学习 Android 中的事件、意图、UI 小部件、图形设计和动画。
定义添加新星球屏幕:创建 Android 活动
我们需要做的第一件事是为我们的 Add a New Planet 菜单项创建一个 NewPlanet 类,所以右击你的 Hello_World 项目文件夹并选择 New Class 菜单项。我们将使用与我们在第二章中创建 WorldGen 类时相同的工作流程,除了这一次,我们将子类化 android.app.Activity 类,因为我们正在创建一个活动,而不是一个世界对象生成器。
这两种类型的类的主要区别在于,Activity 类没有构造函数方法,而生成对象的类有。这是因为活动不是在运行时生成的,需要在 AndroidManifest.xml 文件中定义。
在 新建 Java 类对话框中点击浏览。。。按钮位于包区旁边。选择 chapter.two.hello_world 包,然后在 Name: 字段中输入 NewPlanet 来命名我们的新活动类。这个活动类是以它将允许我们的最终用户在一个新的星球上添加什么来命名的。
接下来点击浏览。。。旁边的按钮超类字段,输入活动(只输入行为会得到相同的结果)将所有与活动相关的类带入选择区域。
选择 Activity - android.app 包选项,然后点击 OK 按钮。保留对话框中的其他默认设置,点击完成按钮。瞧,您的NewPlanet.java类被创建并在 Eclipse 中央编辑窗格区域内打开进行编辑。
接下来,我们添加一个 onCreate( ) 方法,最简单的方法是单击新类中的空白行(在两个花括号之间)来选择它(它变成浅蓝色)并按住 Control (Ctrl)键,同时按下空格键、,这是调出 Eclipse ADT 方法选择器助手对话框所需的击键序列。
在对话框中向下滚动一半,找到onCreate(Bundle saveInstanceState):void方法,如图图 4-17 所示,双击该方法,将需要的 onCreate()方法添加到我们的 NewPlanet (class) Activity 子类中。您创建的每个活动都需要实现一个 onCreate()方法,以便它可以在系统内存中创建活动。完成后,您可以定义一个区域来查看您的应用内容,这是我们接下来要做的。
图 4-17。ctrl-空格键击键序列调用方法选择器助手对话框
在 super.onCreate( ) 方法调用之后,我们需要添加一行调用 setContentView( ) 方法的代码,类似于我们的 MainActivity.java 文件中的代码。事实上,您可以采用程序员的快捷方式,剪切并粘贴那行代码,然后修改它以引用我们接下来要创建的 activity_add.xml 文件。代码应如下所示:
setContentView(R.layout.activity_add);
请注意,当您这样做时,Eclipse 使用红色波浪下划线来标记 activity_add 引用,如图图 4-18 所示,我们已经知道这是因为引用的文件还不存在,所以我们现在忽略这个错误,因为我们将在这个活动创建工作流程的下一步创建这个布局 XML 文件。
图 4-18。将 setContentView()方法调用和引用添加到尚未创建的 activity_add.xml 文件
在包浏览器中再次右键单击 Hello_World 项目文件夹,然后选择 New Android XML File 菜单选项。从下拉列表中选择布局 XML 类型(如果还没有这样设置的话)并指定 Hello_World 项目。接下来添加 activity_add 文件名(仅第一部分,对话框添加。xml extension for you ),从对话框中间的选择区域选择 RelativeLayout 布局类型,如图图 4-19 所示。然后点击完成按钮,在布局文件夹中创建 XML 文件,并在 Eclipse 的中央编辑窗格中打开。
图 4-19。通过新的 Android XML 文件对话框创建一个 RelativeLayout 布局 XML 文件
现在,让我们将一些 ImageView 用户界面元素添加到相对布局容器中,这样我们可以稍后(在图形设计的章节中)添加一些行星图片作为视觉选择用户界面设计。
这次我们将使用图形布局编辑器(GLE)添加第一个 < ImageView > 标签,这样您就可以看到它的拖放功能是如何工作的。因为这是一本面向程序员的书,所以我们不会像在 Apress Absolute 初学者系列中关于如何创建 Android 应用的书中那样充分利用 GLE,但是您应该看看它是如何工作的,以防万一您想在自己的工作过程中使用它(而不是作为 XML 代码预览工具或快捷方式)。
单击中央编辑窗格左下方的选项卡,并选择图形布局编辑器模式。找到并点击左边的 Images & Media 抽屉,这样我们就可以访问 ImageView 用户界面元素,或者 widgets ,我们将在以后的 UI 设计第六章中更详细地了解它们。抓取 ImageView,如图图 4-20 所示,并将其拖动到模拟应用屏幕上,放在中央附近。
图 4-20。使用图形布局编辑器窗格创建初始 ImageView 标签和参数
请注意,在拖放操作过程中,当您在屏幕上拖动 UI 元素时,工具提示会实时提供参数和位置信息。一定要尝试这个特性,因为它是一个很好的学习工具,可以帮助你了解哪些参数是可用的。放下 ImageView UI 元素后,单击屏幕底部的 xml 编辑选项卡(标记为 activity_add.xml ),查看为您生成的 XML 代码。它应该是这样的:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="146dp"
android:layout_marginTop="164dp"
android:src="@drawable/ic_launcher" />
</RelativeLayout>
注意,ImageView 标签下面有一条黄色波浪线,如果你将鼠标放在上面,它会告诉你缺少一个内容描述标签,Android 希望看到包含这个标签以帮助残疾人。添加新的一行,输入 android,然后输入冒号,在弹出的对话框中找到Android:content description参数。XML 标记应该是这样的,引用了一个字符串变量:
android:contentDescription="@string/content_desc_earth"
接下来,在我们复制它来创建我们的六世界选择器 UI 之前,让我们对这个 ImageView 标记做一些小的修改。将 android:id 更改为 imageEarth,并将 marginLeft 和 marginTop 更改为 5dp(或 5dip)值。事实上,因为两个边距值都是 5,所以让我们节省一行标记,简单地使用Android:layout _ margin = " 5dp "在我们的行星图像周围给我们均匀的间距。注意,现在,我们使用 drawable 文件夹中的启动器图标作为占位符图形,直到稍后我们在图形设计第六章中创建不同的世界图像。
因为我们在 ImageView 标签中引用了针对视障人士的内容描述字符串,所以接下来让我们添加六个星球内容描述字符串,然后结束。每个<字符串>标签的格式是
<string name="content_desc_planet">Planet Name</string>
让我们使用六个最常见的行星名称作为我们的字符串常量:地球、火星、金星、木星、土星和海王星。参见图 4-21 。
图 4-21。将六个星球内容描述字符串标签添加到 values 文件夹中的 strings.xml 文件中
现在复制第一个 ImageView 标签并粘贴到下面,并将 ID 更改为 imageMars。让我们也删除 alignParentLeft 和 alignParentTop,因为它们仅用于左上角的 ImageView,以确保此 RelativeLayout 从布局(父容器)的左上角开始对齐。接下来,让我们添加一个引用 imageEarth ID 的 android:align_toRightOf 参数,因为我们希望的第二个 ImageView 与第一个 ImageView 对齐。还要更改 contentDescription 以引用@ string/content _ desc _ 火星。
复制第二个 ImageView 标记并再次粘贴到下面。接下来将 ID 更改为 imageVenus 并将 layout_toRightOf 参数更改为 layout_below 并引用 imageEarth ID,因为我们希望第三个 ImageView 在第一个 ImageView 的下方对齐。另外,记得修改 contentDescription 标签来引用@ string/content _ desc _ 维纳斯。
复制第三个 ImageView 标记并再次粘贴到下面。将 ID 更改为 imageJupiter 并将 layout_below 参数更改为引用 imageMars,并添加一个引用 imageVenus 的 android:layout_toRightOf 参数,以便 imageJupiter 位于 imageMars 的下方和 imageVenus 的右侧。编辑内容描述并指向@ string/content _ desc _ 朱庇特。
第一个
<ImageView android:id="@+id/imageEarth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_margin="5dp"
android:contentDescription="@string/content_desc_earth"
android:src="@drawable/ic_launcher" />
请注意,一旦您复制了这个 XML 标记来创建其他所需的五个
图 4-22。 RelativeLayout XML 标记定义添加一个新的带有占位符图形的 Planet screen 用户界面
最后,复制 imageVenus 和 imageJupiter 的最后两个 ImageView 标记,并将它们粘贴到 imageJupiter 下面,并将它们的 id 分别更改为 imageSaturn 和 imageNeptune。将它们的 contentDescription 引用更改为content _ desc _ 土星和content _ desc _ 海王星,并将它们的 layout_below 参数分别更改为引用 imageVenus 和 imageJupiter。确保 imageNeptune ImageView 也有一个 android:layout_toRightOf 参数,该参数强制它与 imageSaturn 的右边对齐。六个完整的 ImageView 标签如图 4-22 所示。
定义配置行星活动:LinearLayout 容器
接下来,我们将创建我们的 Configure Planet UI 屏幕,这涉及到创建另一个名为ConfigActivity.java的活动子类,它带有一个在 activity_config.xml 文件中定义的 LinearLayout 容器。因为你已经这样做过一次,我将放弃截图和数字,因为如果需要的话,你可以参考前面的部分。
首先,右键单击 Hello_World 项目文件夹,选择 New Class ,为 chapter.two.hello_world 包创建一个 Java 类,该类子类化 android.app.Activity,命名为 ConfigPlanet 。保留其他默认选项设置,点击完成。
接下来,添加一个 onCreate( ) 方法到中,在 Android 中创建活动,使其存在。当您引用(访问)活动时,会调用此方法,并在系统内存中创建活动(在运行时为其分配内存)。这就是为什么每个活动都需要一个 onCreate()方法实现。
接下来,添加一个 setContentView( ) 方法,该方法引用我们接下来创建的 activity_config.xml 来保存我们的 LinearLayout 定义,如下所示:
setContentView(R.layout.activity_config);
接下来,再次右键单击 Hello_World 项目文件夹,选择新的 Android XML 文件,并创建一个 LinearLayout XML 文件来定义 LinearLayout 用户界面设计。在此对话框中选择资源类型:布局,项目: Hello_World ,文件名: activity_config ,根元素: LinearLayout (参考图 4-19 ,点击完成。
接下来,在我们的 LinearLayout 容器中添加一些按钮标签,这样您就可以看到 LinearLayout 做了什么。在后面的第五章中,我们将把它们连接起来,以便它们执行它们应该执行的任务,但是现在,我们只关心 XML 布局文件定义。单击 XML 编辑窗格底部的图形布局编辑器选项卡(窗格顶部的 activity_config.xml 选项卡应该是活动的),并将一个按钮 UI 元素从表单小部件抽屉中拖到模拟的应用屏幕上。然后切换到 XML 编辑窗格,将按钮标签的 android:id 值更改为 coloniesButton ,然后将 android:text 参数更改为引用我们接下来将创建的@ strings/Button _ name _ colonies值。这可以在图 4-23 中看到。
图 4-23。用按钮 UI 元素填充 LinearLayout,以用于世界配置 UI 屏幕
每个
<Button android:id="@+id/coloniesButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_name_colonies" />
接下来,在第一个标记下再复制五次按钮标记,并编辑 ID 和文本参数值,以匹配每个按钮 UI 元素将要做的事情。因此,我们将殖民地分别改为殖民者、基地、军事、ffon(力场开启)和 ffoff(力场关闭)。
接下来,添加六个字符串标签,它们定义了每个按钮的文本标签,并通过 android:text 按钮标签参数引用,如下所示:
<string name="button_name_colonies">Add Colonies</string>
<string name="button_name_colonists">Add Colonists</string>
<string name="button_name_bases">Add Bases</string>
<string name="button_name_military">Add Military</string>
<string name="button_name_ffon">Forcefield On</string>
<string name="button_name_ffoff">Forcefield Off</string>
这里我将省略额外的截图,但是这些
接下来,让我们继续创建我们的 TravelPlanet 类 Activity 子类,以及一个 FrameLayout 容器,以便稍后我们可以在我们的应用中添加星系间旅行的数字视频。
定义去行星旅行活动:框架布局容器
接下来,我们创建我们的 Travel to Planet UI 屏幕,这涉及到创建另一个名为TravelActivity.java的活动子类,它具有一个在 activity_travel.xml 文件中定义的 FrameLayout 容器。因为你以前做过这个,我将跳过大部分数字,因为你可以参考 NewPlanet.java 部分,如果你需要,它回到定义一个新的行星活动屏幕部分。
首先,右键单击 Hello_World 项目文件夹并选择 New Class 并为 chapter.two.hello_world 包创建一个 Java 类,该类子类化 android.app.Activity 并命名为 TravelPlanet 。保留其他默认选项设置,然后点击完成。
接下来,添加一个 onCreate( ) 方法来在 Android 中创建活动,以便它存在。当您引用(访问)活动时,会调用此方法,并在系统内存中创建活动(在运行时为其分配内存)。这就是为什么每个活动都需要一个 onCreate()方法实现。
接下来,添加一个引用 activity_travel.xml、的 setContentView( ) 方法,我们接下来创建它来保存我们的 LinearLayout 定义,如下所示:
setContentView(R.layout.activity_travel);
接下来,再次右键单击 Hello_World 项目文件夹,选择 New Android XML 文件,创建 FrameLayout XML 文件,用于定义 FrameLayout 用户界面设计。在此对话框中选择资源类型:布局,项目: Hello_World ,文件名: activity_travel ,根元素:框架布局(参考图 4-19 ,点击完成。
接下来,在 FrameLayout 容器中添加一个 VideoView 标签。FrameLayout 是用于全屏数字视频的完美布局,因为 FrameLayout 只包含一个 UI 元素。在后面的章节中,我们将添加数字视频素材,但是现在,我们只关心我们的 XML 布局文件定义。
基本的 <视频视图> 标签 XML 标记实现如下:
<VideoView android:id="@+id/travelVideoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
这个
图 4-24。向 Eclipse 中的 activity_travel.xml 文件添加 FrameLayout 容器和 VideoView UI 元素
单击 XML 编辑窗格底部的图形布局编辑器选项卡( activity_travel.xml 选项卡现在应该在编辑窗格的顶部处于活动状态),并将一个 VideoView UI 元素从 Images & Media 抽屉中拖到模拟的应用屏幕上。
接下来,切换到 XML 编辑窗格,将 VideoView 标签的 android:id 值更改为 travelVideoView 。这可以在图 4.24 中看到。
最后,我们添加了 AttackPlanet.java 类活动子类,以及另一个包含 ImageButton 标签的 LinearLayout。现在,我们已经为 Hello_World 应用建立了一个五屏幕框架,供本书剩余部分使用。
定义对行星活动的攻击:我们的第二个线性布局
接下来,我们在 Planet UI 屏幕上创建我们的 Attack,这涉及到创建另一个名为AttackActivity.java的活动子类,它具有一个在 activity_attack.xml 文件中定义的 LinearLayout 容器。因为你已经经历过了,我将放弃截图和数字,因为你可以参考定义添加一个新的行星屏幕部分,如果需要的话。
首先,右键单击 Hello_World 项目文件夹,选择 New Class ,为 chapter.two.hello_world 包创建一个 Java 类,该类子类化 android.app.Activity,名为 AttackPlanet 。保留其他默认选项,点击完成。
接下来,添加一个 onCreate( ) 方法来在 Android 中创建活动,以便它存在。当您引用(访问)活动时,会调用此方法,并在系统内存中创建活动(在运行时为其分配内存)。这就是为什么每个活动都需要一个 onCreate()方法实现。
接下来,添加一个 setContentView( ) 方法,该方法引用: activity_attack.xml,,我们创建它来保存我们的 LinearLayout 定义,如下所示:
setContentView(R.layout.activity_attack);
接下来,再次右键单击 Hello_World 项目文件夹,选择 New Android XML 文件,创建一个 LinearLayout XML 文件,用于定义 LinearLayout 用户界面设计。在此对话框中选择资源类型:布局,项目: Hello_World ,文件名: activity_attack ,根元素: LinearLayout (参考图 4-19 ,点击完成。
接下来,在 LinearLayout 容器中添加一些 ImageButton 标签,这样我们就可以在我们的 Hello_World(或者我们应该命名为 Goodbye_World) Android 应用中添加一些攻击图标和功能。
请注意,在后面的章节中,我们将把它们连接起来,以便它们执行它们应该执行的任务,但是现在,我们只关心我们的 XML 布局文件定义。
在添加 ImageButton 标签之前,让我们首先定义它们将引用的四个
图 4-25。为我们的 activity_config.xml 和 activity_attack.xml 布局字符串引用添加字符串标签
有四个图形(图像或图标)按钮可以让我们对一个星球发起四种不同类型的攻击:轰炸、入侵、感染和激光。
因为 Android 中的图形(ImageView 和 ImageButton UI 元素)需要一个内容描述标记来帮助视觉障碍者,所以让我们在通过 android:contentDescription 引用的 strings.xml 文件(values 文件夹)中添加四个反映这些攻击模式的字符串常量,如下所示:
<string name="content_desc_bomb">Bomb the Planet</string>
<string name="content_desc_invade">Invade the Planet</string>
<string name="content_desc_virus">Infect Planet with Virus</string>
<string name="content_desc_laser">Fire Laser at Planet</string>
接下来,单击顶部的 activity_attack.xml 选项卡和底部的图形布局选项卡,并拖出一个 ImageButton UI 元素(参见 Images & Media)并选择应用启动器图标作为占位符图形以供使用。然后进入 XML 编辑模式,将其 ID 更改为 bombButton ,并添加字符串引用@ string/content _ desc _ bomb,指向刚刚添加的字符串标签。标签的<图像按钮>标记应如下所示:
<ImageButton android:id="@+id/bombButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/content_desc_bomb"
android:src="@drawable/ic_launcher" />
将它复制并粘贴到标签下方三次,并将炸弹 ID 参考更改为入侵、感染和激光,如图 4-26 所示,并将内容描述参考分别更改为入侵、病毒和激光。
图 4-26。AttackPlanet.java 活动类的 activity_attack.xml 文件的 LinearLayout XML 定义
既然我们所有的活动和它们的布局容器都已经定义好了,我们要做的最后一件事就是在 AndroidManifest.xml 文件中使用
将我们的应用活动添加到我们的 XML: AndroidManifest.xml
我们需要做的最后一件事是 100%确定我们在这一章中所做的一切都可以干净地编译和运行,那就是将我们对这些活动的使用声明添加到 AndroidManifest.xml 文件中。这是通过添加 <活动> 标签来实现的,每个标签定义一个活动的使用,并通过特定的命名约定为 Android OS 提供活动的名称。
<activity android:name="chapter.two.hello_world.NewPlanet" />
<activity android:name="chapter.two.hello_world.ConfigPlanet" />
<activity android:name="chapter.two.hello_world.TravelPlanet" />
<activity android:name="chapter.two.hello_world.AttackPlanet" />
这里是所有四个
正如您在图 4-27 中看到的,我们新的<活动>标签在 Eclipse 中没有“抛出”任何错误或警告。我的 android:targetSDKVersion 上有一个警告,说它不是最新版本,所以我将新的 android 应用项目助手放入此 < uses-sdk > 标签的 16 API 级别 Android 4.1.2 JellyBean 版本的值更改为当前的 17 API 级别 Android 4 . 2 . 2Jelly Bean+版本。正如我前面提到的,Eclipse ADT 可能会犯一些错误,您可能需要在以后修复这些错误!
图 4-27。在 Hello_World AndroidManifest.xml 文件中添加四个< activity >标签来定义每个 activity 子类
为了确保我们在这一章中创建的一切都处于完美的工作状态(我们添加了许多令人印象深刻的 Java 代码和 XML 标记),我现在将在 Eclipse Package Explorer 中右键单击 Hello_World 项目文件夹,并选择 Run As Android Application 菜单序列来启动我们可信赖的 Nexus S 模拟器,并查看我们的应用是否可以运行。
在加载模拟器及其用户界面几分钟后,然后替换之前的应用屏幕,这个应用运行完美,我看到了与我们之前在图 4-4 中看到的完全相同的屏幕。
有时,如果新的应用版本没有出现,你可以在模拟器中单击运行上一个箭头(一个向左的、上下翻飞的箭头)(在右上角的按钮排上,第二行,左起第三个按钮),它会为你重新启动 Hello_World 应用(或者至少会加快一点速度)。咻!在这一章中,我们真的得到了一个吨的关键应用基础设施(活动屏幕,功能类,布局 XML)。拍拍自己的背!
摘要
在这一章中,我们学习了所有关于 Android 布局容器的知识,包括那些已经废弃(不再使用)的布局类型,以及那些最近为 API 14 到 17 (Android 4.x)开发使用而添加的布局类型。
然后,我们利用了三个最常用的屏幕布局容器,它们是当前可用的,并推荐用于所有 Android API 级别(API 3 到 17)的应用开发:容器 RelativeLayout 、容器 LinearLayout 和容器 FrameLayout 。
为了在我们不断发展的 Hello_World Android 应用中使用 XML 标记实现这些主流 Android 屏幕布局容器,我们为我们的应用设计并创建了四个新的 Activity 屏幕。然后,我们在这四个新的 Android Activity 子类中实现了三种不同类型的布局容器。
为了创建所有这些新的应用活动类,我们再次使用我们可信赖的新的类菜单序列来创建四个 Java 类,我们使用它们来通过 Android 活动支持我们的布局容器,Android 活动是 Android 应用的功能屏幕区域。
我们将这些新的 Java 类命名为:NewPlanet.java、ConfigPlanet.java、TravelPlanet.java 和 AttackPlanet.java,它们是 android.app.Activity 类的子类。我们现在已经从头开始创建了少数(五个)我们自己的定制 Java 类,我们只在第四章中!
我们学习了如何创建一个 Android Activity 子类,以及如何实现必要的 onCreate( ) 和 setContentView( ) 方法,这是为我们的每个 Hello_World 活动定义内存和屏幕区域所需要的。
当我们进行所有这些 Java 编码和 XML 标记时,我们还学习了 Eclipse IDE,以及它如何标记我们的错误,警告我们它希望看到实现的约定,并为我们提供帮助对话框,使我们的类、方法和变量选择更加容易。
我们学习了如何使用图形布局编辑器(GLE) ,以及 Eclipse central 编辑窗格底部的选项卡如何为我们提供不同的数据视图,而编辑窗格顶部的编辑窗格选项卡允许我们选择正在处理的代码或标记。
最后,我们将几个 <活动> 标签添加到我们的 AndroidManifest.xml 文件中,这样我们的四个新活动将被声明供 Android 操作系统使用(并且可见)。我们运行了新的应用代码,它成功了!
在下一章中,我们将开始把我们在本章中学到并创建的新屏幕布局、UI 元素和活动“连接”到我们在第三章中创建的 WorldGen 类和对象。我们将使用 Android 意图和事件,两者都允许 Android 的活动和用户界面元素相互对话。
五、Android 意图和事件:增加交互性
在这一章中, 我们将使用我们在前三章中创建的 Hello World Android 应用,并添加更多的 Java 代码来开始使它具有交互性和可用性的过程。
现在我们已经创建了 Android 应用项目(在第二章),定义了我们的WorldGen
对象结构(在第三章),创建了我们的五个用户界面布局容器、四个活动屏幕和一个应用菜单结构(第四章),是时候将所有这些连接在一起,以便用户可以使用主菜单在应用的功能屏幕中导航,以体验当他们单击一些(占位符)用户界面元素时的实际结果。
同时,我们也将利用这一章来教授 Android 编程中两个非常重要的概念:意图和事件。这些甚至以一种奇怪的方式押韵!意图和事件都允许应用内部的通信,这就是它们允许实现交互性的原因。
Intents 允许在 Android 应用内进行更全局层次的通信,允许 Android 应用的组件,例如我们在上一章中创建的 functional Activity 子类,在最终用户需要时被调用和访问(通过 Java 代码)。
事件允许在 Android 应用中进行更本地化的通信,因为它们使应用的最底层组件,通常是用户界面元素,或小部件,能够与您的 Java 和 XML 编程逻辑对话,或更准确地说是回调。
因为你已经是一个程序员了,所以你可能很熟悉事件处理的概念。在本章的后半部分,我们将详细介绍 Java 和 Android 是如何做到这一点的,以及 Android 支持哪些事件,以及它们是如何被处理的。我们开始吧!
Android 高层通信:Android 意图对象
Android Intent
实际上是一个对象,所以包 android.content 中的 Android Intent
类是 java.lang.Object 类的子类。它在 Android Content ProviderAndroid . Content包中,因为 Intents 可以用来快速访问数据库记录,这一点我们将在本书后面了解。一个Intent
对象可以(通常是)非常简单,然而,它也可以很复杂,因为一个Intent
对象可以有多达七个不同的部分或组件。我们在本书的结尾(在第十六章中)更详细地介绍了意图,但现阶段需要在我们的应用中使用它们,所以我们将在本章中以先驱的水平介绍它们。
Intent
被恰当地命名,因为它实际上是一个描述需要完成的事情的对象,并从应用的一个组件发送到另一个组件。这类似于现实生活中传达个人执行或完成某事的意图。
Android 应用可以处理Intent
对象的主要领域包括活动,我们已经创建了这些活动;服务,用于后台处理,我们将在后面介绍;和广播接收器,用于向 Android 终端用户发送消息。
Android 的这三个不同的功能领域都有自己的Intent
对象,该对象针对这些领域的独特属性进行了优化。毕竟,活动被优化为显示屏幕界面、用于执行繁重后台处理的服务以及用于广播通信消息的广播接收器,因此这些应用组件中的每一个都必须具有不同的意图对象处理结构,以便彼此最佳且无缝地工作。
那么这些Intent
对象在应用组件之间传递什么类型的信息呢?意图对象的功能区域包括:
- 需要处理意图的组件(类)的组件名
- 需要执行的动作
- 动作需要操作的数据
- 正在处理的数据的类型(MIME 类型)
- 该处理所属的类别
- 进一步定义需要执行的处理所需的任何标志和附加标志
组件名称
Intent
对象中最重要的字段是组件名,每个Intent
对象的实例变量包含一个引用到完整的路径到意图对象是目标的类。包含组件名的意图被称为显式意图,而不而指定组件名的意图被称为隐式意图。
类名的完整路径包括包名和类名,如果这个类在另一个包中的话。所以如果你要从另一个包中启动我们的NewPlanet
Activity 类,你可以使用 chapter.two.hello_world。NewPlanet.class 作为类的完整路径名。如果您在 chapter.two.hello_world 包中(在本章后面的 Intent 示例中,我们就是这样),您将使用 NewPlanet.class 作为对我们的NewPlanet.java
类代码的运行时引用。
动作
意图对象中下一个最重要的变量是动作,Android OS 中有许多预定义的动作常量,可以通过意图对象访问。如果你想全部复习,去 http://developer.android.com/reference/android/content/Intent.html
。
Android 操作系统的 Activity 类常用动作的一些例子包括:
ACTION_DIAL
(显示要拨打的电话号码)ACTION_CALL
(打电话)ACTION_MAIN
(启动应用的主活动)ACTION_EDIT
(编辑数据库)
使用广播接收器的一些常用动作包括:
ACTION_TIMEZONE_CHANGED
(用户进入了一个新的时区)ACTION_POWER_CONNECTED
(用户已插入设备)ACTION_SHUTDOWN
(用户关闭了 Android 设备)
数据参考
Intent 中下一个最重要的引用是数据引用,它指向动作要操作的数据。每一个动作都需要一些操作(一些使用或改变的数据值),每一个数据值都需要一些指定的动作(对数据做什么)。因此,意图动作和数据是携手并进的,数据通常使用一种叫做 URI 或通用资源标识符的东西来指定。
类型参考
Intent 中下一个最重要的引用是类型引用,它引用了一个 MIME 类型,这是一个在互联网 1.0 (HTTP)和 2.0(移动或消费电子)平台中使用的标准数据分类。
MIME 最初代表多用途互联网邮件扩展,最初创建它是为了定义电子邮件服务器支持的电子邮件(附件)数据类型,但 MIME 已经被许多不同类型的数据服务器采用,现在,甚至在客户端,也被 Android 操作系统本身采用。
这里需要注意的是,如果意向对象参数分类的七个区域中的任何一个被遗漏,Android 将推断什么应该在那个参数槽中。对于一个被省略的 MIME 类型来说,这可能是最容易的,因为这比 MP3 音频数据文件的 MIME 类型应该是:内容类型:音频/mp3 或者 MP4 视频数据文件的正确 MIME 类型应该是:内容类型:视频/mp4 更容易推断。
Android 类别常量
意图对象中下一个最重要的引用是 Android 类别常量,用于定义意图指向的 Android OS 的特定区域。一些更受欢迎的类别常量包括:
- 类别 _ 默认
- 类别 _ 可浏览
- 类别 _ 标签页
- 类别 _ 启动器
- 类别信息
- 分类 _ 首页
- 类别 _ 偏好
- 类别 _ 汽车 _ 码头
- 类别 _ 桌面 _ 码头
- 类别 _ 汽车 _ 模式
- 类别 _ 应用 _ 市场
类别常量都在 http://developer.android.com/reference/android/content/Intent.html
列出并描述(通过链接)。
举例来说,如何将类别与意图动作一起使用:为了启动用户的 Android 设备主屏幕,您将利用预先加载了 ACTION_MAIN 动作常量以及 CATEGORY_HOME 类别常量的意图对象。
旗帜和额外物品
可以添加到更复杂的意图对象结构中的最后两种引用类型是 标志和附加。正如您从编程经验中了解到的,标志是布尔值,就像选项开关一样,可以通过 Intent 对象传递。 Extras 是需要的其他类型的数据,如文本或其他对象结构。
因为我们不会在本书中讨论高级意图,所以我们不会在我们的意图对象中使用标志和额外功能,但我想让您知道,它们包含在意图对象层次中,如果您的高级意图对象设计需要它们,它们是可用的。
隐式意图:定义隐式意图处理的意图过滤器
正如我前面提到的,隐式意图是Intent
对象,它不指定意图对象本身内的任何组件说明符,也就是说,意图对象不指定(不引导意图)它需要去哪里得到处理或处理到它的分辨率。
在这种情况下,Android 操作系统必须通过使用其他Intent
对象参数来推断它需要将Intent
对象传递到操作系统中的什么区域(或者更准确地说,当前正在系统内存中执行什么代码),以便成功处理该意图或解析。
Android 基于对所有各种动作、数据、MIME 类型以及实际上在Intent
对象中定义的类别的仔细比较来执行这个推理过程,使用系统内存中当前可用的代码组件来处理这个意图。
您还可以使用意图过滤器创建自己的推理引擎,这可以使用应用 AndroidManifest.xml 文件中的 XML 标签来定义。您可以在 developer.android.com/reference/android/content/IntentFilter.html
找到更多关于意图过滤器的详细信息。
设计处理隐式意图和利用意图过滤器 XML 的 Android 应用超出了学习 Android 应用编程的入门书籍的范围;然而,我们将在这里讨论这个概念,给你一个大概的概念,用它们可以完成什么,在什么情况下你会想要使用隐式意图和意图过滤器。
通过使用 <意图过滤器> 标签,意图过滤器结构在AndroidManifest.xml
文件中声明。它们基于意图对象的七个属性中的三个来过滤隐含的意图;意图动作,其数据,可能还有其类别,如果包括且适用的话。
意图过滤器标签可以包含(嵌套)在 < activity > 和
如果您想查看关于使用 <意图过滤器> 标签实现意图过滤器结构定义的更多详细信息,请访问意图过滤器元素 Android 开发者页面,位于 developer.android.com/guide/topics/manifest/intent-filter-element.html
。
意图过滤器标签结构用于定义需要匹配的Intent
对象配置的描述。如果遇到不止一个匹配,它们还允许实现一个优先级属性。
首先测试意图过滤器的动作匹配,然后测试数据匹配、,最后测试类别匹配。如果没有指定动作意图过滤器,则根本不会测试意图的动作参数,将测试过程向下移动以测试意图的数据参数。同样,如果没有指定数据意图过滤器,那么只匹配不包含数据的意图。
对于包含数据特征的意图过滤器,数据参数分为四类,包括数据类型 (MIME 类型)数据方案(例如 http://)数据授权(服务器主机和服务器端口,指定为 host:port) 、以及最后的数据路径。
到您的 strings.xml 文件、的数据路径例如、将被指定为C:/Users/UserName/workspace/Hello _ World/RES/values/strings . XML .
比如下面的意图过滤数据参数规范content://com . a press . projects:500/project _ files/Android/file _ number _ 2,t 数据方案为 content:// ,数据权限(主机先指定)为 com.apress.projects:500 (端口后指定),,数据路径分别为/project _ files/Android/file _ number _ 2,。
下面是一个意图过滤数据定义的简单示例,它(从应用AndroidManifest.xml
文件内部)指定视频 ( MPEG4 H.264 格式数据)和音频 ( MPEG3 格式数据)将使用 HTTP 数据方案从互联网、远程访问:
<intent-filter>
<data android:mimeType="video/mp4" android:scheme="http" />
<data android:mimeType="audio/mp3" android:scheme="http" />
</intent-filter>
隐式意图和意图过滤器通常用在更高级的应用开发场景中,当应用被设计为由其他开发人员使用或者与其他开发人员事先不知道使用方法的应用一起使用时。在这种情况下,需要构建意图过滤器结构来处理外部开发人员对应用功能集的不可预见的访问,这些开发人员没有接受过如何访问应用数据结构和功能的培训。
在本书中,我们将关注应用开发的明确意图结构,因为我们知道我们将针对哪些应用组件。
使用明确的意图:让我们的 Hello_World 菜单起作用
现在是时候使用明确的意图,通过使我们的应用菜单功能化,将我们的Hello_World
Android 应用提升到一个新的水平。
在过去的三章中,我们已经将定制的 Java 程序逻辑编码成六个不同的类,其中五个是我们 100%从头开始创建的。到目前为止,我们已经创建了 Java 代码,允许我们的用户创建新的世界,殖民这些世界,到每个世界旅行,军事化一个世界,使用力场保护一个世界,甚至对另一个世界发动攻击。
我们已经编写了 Java 代码来创建WorldGen
对象,并编写了 XML 标签标记来定义屏幕布局,这些布局包含用户界面元素、菜单和按钮,它们可以访问我们 Android 应用的不同功能。我们在前一章中编写的四个新类利用活动将所有这些 UI 元素保存在它们自己的功能屏幕中。
然而,如果没有明确的意图,这些都只是孤立的组件,不能一起使用来做任何有意义的事情。当然,每个人都会自己做一些事情,比如创造新的星球(NewPlanet.java
),给一个给定的星球配置殖民地、殖民者、基地和军队(ConfigPlanet.java
),旅行到一个星球(TravelPlanet.java
,或者攻击一个星球(AttackPlanet.java
)。但是为了使应用成为其组成部分的有用总和,我们需要通过我们的主屏幕选项菜单系统访问所有这些功能屏幕模块(活动),这要求我们使用意图对象来调用和启动这些活动。
因此,我们需要做的下一件事是利用显式的Intent
对象来使我们的四个主菜单项调用我们已经创建的用于保存应用的这些不同功能区域的Activity
子类。现在让我们设置菜单功能,这样我们就可以使用明确的意图来调用我们创建的这些活动。
膨胀我们的菜单
菜单创建涉及到 onCreateOptionsMenu( ) 方法,该方法包含我们的应用选项菜单展开例程,我们已经利用该例程通过展开我们的菜单 XML 定义来创建我们的主应用菜单。
要使这个选项菜单起作用,我们需要向我们的MainActivity.java
类添加第二个方法。 onOptionsItemSelected( ) 方法与onCreateOptionsMenu()
方法一起使用,它包含了所有允许根据具体情况选择和执行各种菜单选项的代码。这是通过 Java switch 代码构造完成的,你们中的一些程序员可能也称之为 case 语句。
让我们现在编写这段代码,这样我们就可以看到它们是如何协同工作的。首先,使用我们的MainActivity.java
类中每个方法左边的小减号来关闭所有这些 Java 方法的代码视图,在编辑窗格中给我们更多的代码编辑空间。
接下来让我们在onCreateOptionsMenu()
方法后添加一行空格(通过按回车键),然后按下Ctrl-空格键击键组合,这是我们在前一章中学到的。这将打开方法选择器帮助器对话框,在这里我们可以找到 onOptionsItemSelected( ) 方法,双击它在当前的MainActivity.java
类(Activity 子类)内部实现它。
请注意,它有公共访问控制,因此所有代码都可以访问菜单选项,还有一个布尔返回类型,因此可以将处理状态(true 表示已处理,false 表示未处理)返回给调用实体。该方法将一个名为 item 的 MenuItem 对象作为其参数。
在onOptionsItemSelected()
方法中,我们将使用一个 switch 语句在五个菜单选项或案例之间进行选择,在switch
语句中的每个 case 分支内都有代码,这些代码将在案例出现时执行。switch
语句本身需要 MenuItem 对象的 ID 作为它的参数,因为名为 item 的MenuItem
对象作为它的参数被传入了onOptionsItemSelected()
方法,我们也可以使用。switch 语句参数区域内名为 item 的 MenuItem 对象上的 getItemId( ) 方法,我们用一行代码对其进行了编码,如下所示:
switch(item.getItemId()) { the case statements will go inside of these brackets }
因此,让我们将它输入到我们的方法中,然后添加我们的第一个 case 分支,使用我们在前一章中为我们的 Add a Planet 菜单项创建的 menu_add ID 参数,如下所示:
case R.id.menu_add:
这些都显示在图 5-1 中,还有下一步,我们添加显式意图对象,该对象调用我们的NewPlanet
活动类。
图 5-1。添加一个 onOptionsItemSelected()方法来处理我们的菜单选项并导入意图类
添加新行星意向
在下一行代码中,我们创建了第一个显式 Intent 对象,我们用它来调用我们在上一章中编写的 NewPlanet Activity 子类。这与我们在第三章中声明我们的 WorldGen 对象的方式类似,使用 new 关键字,就像这样:
Intent intent = new Intent(this, NewPlanet.class);
注意图 5-1 中的【Eclipse 用红色错误波浪线给我们的 Intent 对象声明加了下划线,我们现在意识到这是因为 Intent 类没有通过使用 import 语句在我们的代码中正确声明。让我们将鼠标悬停在红色下划线的 Intent 关键字上,当助手对话框弹出时,选择选项从包含 Intent 类的 android.content 包中导入 Intent 。
现在您已经熟悉了这个对象声明过程,您意识到上面的代码行声明了一个名为 intent 的 Intent 对象,并使用 this 的当前上下文用一个 new Intent 对象加载它,然后通过一个 NewPlanet.class 运行时类引用将它指向NewPlanet
Activity 类。由于我们稍后将向这个switch
结构添加更多的意图对象,我们将把这个意图对象命名为 intent_add ,以便更清楚地反映它是哪个意图对象。
Intent intent_add = new Intent(this, NewPlanet.class);
接下来添加一行调用意图对象的的代码。startActivity( ) 方法,它使用我们刚刚创建的 Intent 对象来启动我们刚刚在前面的代码行中定义的活动子类 NewPlanet.class,并使用代码从当前上下文对象(this)中调用它:
this.startActivity(intent_add);
最后,我们添加一个break;语句,这样我们就可以在需要执行的代码处理完之后退出switch case
语句的这一部分。
这显示在图 5-2 中,以及我们将在接下来的几段中添加的其余case
语句。Java switch 容器中的每个单独的 case 语句都需要像这样构造:
case R.id.menu_add:
Intent intent_add = new Intent(this, NewPlanet.class);
this.startActivity(intent_add);
break;
图 5-2。添加五个发送意图对象的 case 语句,调用我们的 Hello_World 功能活动屏幕
在本章的下一节,我们将在 Java switch 容器中添加菜单选项的 case 语句。
添加其余的意图
接下来,让我们采用程序员的快捷方式,复制并粘贴第一个case
语句到它的下面,并进行必要的修改,使它调用我们的ConfigPlanet.java
活动子类。将R.id
引用更改为 R.id.menu_config ,将意图对象的名称更改为 intent_config ,并将活动类设置为 ConfigPlanet.class ,以便在选择配置行星菜单选项时打开正确的屏幕。
我们一半的 Activity 子类现在都是通过选项菜单中的 Intent 对象来调用的;让我们使用刚才使用的相同的复制和粘贴方法添加另外两个。在第二个 case 语句下复制我们的 switch 代码结构中的第一个或第二个 case 语句,以添加第三个 case 语句,然后将 R.id 引用更改为 R.id.menu_travel ,将第三个 Intent 对象的名称更改为 intent_travel ,并将第三个 Activity 类 call 设置为 TravelPlanet.class ,以便在选择 Travel to Planet 菜单选项时打开 Travel to a Planet 屏幕。
让我们在下一个 case 语句中调用第四个 AttackPlanet 活动子类,通过再次粘贴 case 语句,在第三个 case 语句下创建第四个 case 语句,然后将 R.id 引用更改为指向 R.id.menu_attack ,并将第四个也是最后一个意图对象的名称更改为 intent_attack ,同时将第四个活动类 call 设置为 AttackPlanet.class ,这样,当选择 Attack on Planet 菜单选项时,我们将打开我们的应用攻击行星功能屏幕。
最后,在每个switch
或case
语句的底部,我们可以有一个偶发事件语句,它处理一些代码,这些代码只有在 switch 构造中没有 case 语句与执行这些 case 语句的任何参数相匹配时才被执行。
在 Android switch 语句中,这被称为默认 case 语句,在这种情况下,它通过一行代码将控制权返回给超类,该代码如下所示:
default: return super.onOptionsItemSelected(item);
这本质上与为整个switch
语句返回一个 false 值是一样的,并将控制权传递回超类的onOptionsItemSelected()
方法。让我们右键单击我们的项目文件夹,然后作为 Android 应用运行,并运行我们的代码,看看它是否工作。菜单项现在调用我们在前一章中设计的每个活动类和布局容器(查看图 4-16)。
启用我们的新星球活动的用户界面:事件处理
既然我们可以从我们的MainActivity
类选项菜单中启动我们的 Activity 子类,我们需要能够使用这些 Activity 子类来执行它们各自的功能,然后将用户返回到我们的MainActivity
主屏幕。这是通过 android.app.Activity 类(或我们的子类) finish( ) 方法完成的,该方法在我们使用完一个活动后关闭它。
让我们编写部分NewPlanet.java类(Activity 子类),向您展示如何实现 finish( ) 方法,以及如何使用 onClick( ) 事件处理方法来处理对我们的一个新行星图像(或者现在,对我们的一个占位符图像)的点击。
首先,我们将在创建类时创建的onCreate()
和setContentView()
方法之后添加一行代码,并创建一个 ImageView 对象,用于引用我们之前在 activity_add.xml 文件中定义的 imageMars UI 元素。这是通过声明一个名为 marsImage 的 ImageView 对象,并使用引用 XML android:id 参数的 findViewById( ) 方法将其引用到 XML UI 元素定义来实现的,如下所示:
ImageView marsImage = (ImageView)findViewById(R.id.imageMars);
请注意,当我们将这一行代码输入 Eclipse 时,它会在这个 ImageView 类引用下面加红色下划线,因为我们还没有导入它以供使用。让我们将鼠标悬停在这个突出显示的代码元素上,选择Import ImageView(android.widget
package)初始选项,并让 Eclipse ADT 为我们编写导入代码语句,如图图 5-3 所示。
图 5-3。在 NewPlanet.java 创建一个名为 marsImage 的 ImageView 对象,它引用了 XML UI 定义
既然我们已经定义了在 Java 中使用的 marsImage UI 对象,我们想通过使用点符号为它附加一个方法,允许它处理用户的点击(或者触摸,如果使用了触摸屏设备的话)。这是使用完成的。来自View
类的 setOnClickListener( ) 方法。
让我们使用新的关键字和setOnClickListener()
方法为我们的marsImage
ImageView UI 对象设置一个OnClickListener()
方法,如下所示:
marsImage.setOnClickListener(new View.OnClickListener() { code goes in here } );
这一行简洁的代码所做的是在视图类中声明一个新的 OnClickListener( ) 方法,该方法位于一个 setOnClickListener( ) 方法内,该方法附加到一个 marsImage ImageView 对象,我们在前面的一行代码中创建了该对象。
注意,Eclipse 再次给我们的 Java 代码加了红色下划线,因为我们使用的方法来自一个视图类,该视图类还没有导入到我们的NewPlanet.java
代码中。现在让我们将鼠标悬停在代码中的视图类引用上,以获得如图图 5-4 所示的帮助器对话框,该对话框将为我们导入视图 ( android.view
包),这样我们就不必自己编写导入代码了。
图 5-4。将 setOnClickListener 方法添加到 marsImage ImageView 对象并创建新的 OnClickListener( )
一旦我们的导入 Android . view . view;语句已经为我们准备好了,Eclipse 会重新评估我们的代码,并且只在 OnClickListener( ) 方法下面加上红色下划线。
这个新的错误高亮显示出现是因为,至少现在,这个监听器方法目前是一个空的或者未实现的方法,因为我们还没有在两个花括号内添加任何事件处理代码。
让我们借此机会让 Eclipse 为我们多写一些代码,再次将鼠标悬停在红色下划线的代码上,并选择添加未实现的方法选项,如图图 5-5 所示。瞧啊。Eclipse 编写了这个方法:
@Override
public void onClick(View v) {
//Add Code To Process Here
}
图 5-5。实现继承的抽象方法视图。OnClickListener.onClick(View)通过 Eclipse 助手
接下来,我们在这个 onClick( ) 方法中添加代码来处理我们想要发生的事情(当用户单击一个marsImage
ImageView UI 元素时),这个方法被传递了一个需要处理的视图对象(名为v
)。
因为这是NewPlanet
类,我们希望在火星图像上单击(或触摸)来创建一个新的 WorldGen 对象,该对象配置有火星的属性。这是通过调用我们的 WorldGen( ) 构造函数方法来完成的,就像我们之前对 MainActivity.java 类中的 earth 对象所做的那样。
我们在 onClick( ) 事件处理程序中的第一行代码创建了一个名为火星的新WorldGen
行星对象,然后通过传递给WorldGen()
构造函数的参数配置其名称、质量和重力:
WorldGen mars = new WorldGen("Mars", 642, 3.7);
注意,一旦我们输入这个新的对象创建代码行,Eclipse 黄色会在 mars 对象下面加下划线,因为它还没有被使用。我们可以忽略这个警告,如图 5-6 中的所示,但是为了清晰的代码编辑视图,我们将添加一个殖民地到火星,以满足 Eclipse。
图 5-6。添加 WorldGen()构造函数方法调用 onClick(View)事件处理程序方法创建 mars 对象
为了给火星添加一个殖民地,我们将调用来自火星对象的.setPlanetColonies(1)
方法,我们只是使用如下的点符号实例化:
mars.setPlanetColonists(1);
接下来,我们需要告诉NewPlanet
Activity 我们已经使用完了,并希望返回到我们应用的主 Activity 主屏幕。这是使用 finish( ) 方法完成的,这是我们在onClick()
事件处理方法中的下一行代码,如这里和图 5-7 所示:
finish();
图 5-7。通话。对 mars 对象的 setPlanetColonies()方法使用本地 mars 变量;调用 finish()意向方法
现在,让我们右键单击Hello_World
项目文件夹,并选择作为 Android 应用,它会保存我们所有的代码更改(如果我们没有通过 CTRL-S 保存它们的话),并启动 Nexus S 模拟器,使用我们新的意图和事件处理代码运行我们的Hello_World
应用。
当你点击模拟器菜单按钮时,屏幕再次看起来如图 4-16 所示。接下来,单击添加新行星菜单项,Intent 将引用并打开新行星活动屏幕及其添加行星布局,这是我们在布局文件夹的 activity_add.xml 文件中创建的。
现在,让我们看看我们的事件处理代码是否工作正常。单击右上角的(火星)图像占位符图标,onClick()
事件处理程序创建一个新的火星星球和一个殖民地,然后通过我们的onClick()
方法代码语句末尾的finish()
方法调用,将您返回到应用的主屏幕。
接下来,让我们将事件处理添加到我们的其他应用功能活动屏幕,以便我们的应用选项菜单结构可用于在应用的各个区域之间导航。这样,当我们完成本书的第一部分(第一部分)时,我们已经有了应用的基本结构。
启用我们的 ConfigPlanet 活动用户界面:事件处理
接下来,让我们准备我们的 ConfigPlanet 活动和 UI 布局,以便在用户完成当前星球的配置后将他们返回到主屏幕。我们通过向 activity_config.xml 添加一个Done configuration按钮和向 strings.xml 文件添加一个匹配的 button_name_done 字符串常量来实现这一点。然后,我们将使用 Java 代码连接这个 XML Done 按钮,允许用户在单击 Done 按钮时返回到 Hello_World 主屏幕。我不会在这一部分使用很多屏幕截图,因为你之前已经看到了工作流程,所以不需要它们。
在你的 Eclipse 编辑标签的右上方的中央窗格中有一个数字(在图 5-7 中是一个 10);单击此处可下拉包含所有打开的编辑选项卡的菜单。选择 activity_config.xml 编辑选项卡,这样我们可以添加完成配置按钮 xml 标记。
将这个 XML 布局定义中的最后一个按钮复制并粘贴到它自己的下面,并将 android:id 标记更改为 doneButton 并将 android:text 标记字符串常量引用更改为 button_name_done 并按 Ctrl-S 保存 XML 文件。然后再次点击“选项卡未显示数字”下拉菜单,选择 strings.xml 编辑选项卡,通过复制粘贴 button _ name _ ffoff string 标签,添加新的button _ name _ Donestring 标签以及Done configuration的值,然后更改其名称和数据值。
既然我们的 XML 标记编辑已经完成,我们可以转向在ConfigActivity.java类中编辑 Java 代码了。这里我们需要添加一个按钮对象,在其上附加我们对完成按钮的onClick()
事件处理,就像我们在上一节中对 ImageView UI 元素所做的那样。
让我们经历一个与我们在NewPlanet
类中所做的类似的工作过程,并添加一个名为 doneButton 的按钮对象,它引用我们刚刚编辑完的 XML 文件中的 doneButton ID。我们通过使用 findViewById( ) 方法,使用一行代码来实现这一点,如下所示:
Button doneButton = (Button)findViewById(R.id.doneButton);
接下来,我们将使用这个刚刚创建的 doneButton 按钮 UI 对象,并通过熟悉的将一个 OnClickListener( ) 方法附加到这个 UI 对象。setOnClickListener( ) 方法创建一个新的OnClickListener
如下:
doneButton.setOnClickListener(new OnClickListener() { our code goes in here } );
我们用于将用户返回到主屏幕(MainActivity
)的 Done Configuring 按钮的 Java 事件处理代码再次涉及到在 onClick( ) 事件处理程序中使用 Activity 类的 finish( ) 方法,如图 5-8 中我们修改过的ConfigPlanet
类所示。
@Override
public void onClick(View v) {
finish();
}
图 5-8。添加一个。OnClickListener()方法添加到 ConfigPlanet.java 类中的 doneButton 按钮对象
接下来,我们将修改我们的TravelPlanet.java
类,这样就可以从主选项菜单中调用它,并在稍后将用户返回到主屏幕。
启用我们的 TravelPlanet 活动用户界面:事件处理
正如我们为 ConfigPlanet 类所做的那样,现在让我们准备我们的 TravelPlanet 活动和 UI 布局,以便当用户完成当前活动星球的旅行时,将他们返回到他们的主屏幕。
我们通过向我们的 activity_travel.xml 添加一个 Return Home 按钮,并向我们的 strings.xml 文件添加一个匹配的 button_name_return 字符串常量来实现这一点。然后,我们将使用一些 Java 事件处理代码连接 XML 返回按钮 UI 元素,每当用户单击返回按钮时,这些代码允许用户返回到他们的 Hello_World 主屏幕。
在你的 Eclipse 编辑标签的右上方的中央窗格是一个数字(在图 5-8 中,是 11);再次单击此按钮,下拉包含所有打开的代码模块的选择菜单。选择 activity_travel.xml 编辑选项卡,这样我们可以添加 Java 事件处理程序代码将要引用的返回主页按钮 xml 标记。
让我们采取一个快捷方式,复制并粘贴我们在您的activity_config.xml
布局定义中使用的最后一个按钮,并在我们的 < VideoView > UI 元素标签下使用,这样我们就有一个按钮可以在这个布局容器中使用,如图图 5-9 所示。<按钮> XML 标记应该是这样的:
<Button android:id="@+id/returnButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_name_return" />
图 5-9。在 Eclipse 中向 activity_travel.xml 布局容器 xml 定义添加一个返回主页按钮标签
接下来,让我们将 android:id 标签更改为 returnButton ,将 android:text 标签字符串常量引用更改为 button_name_return ,然后按 Ctrl-S 保存我们修改后的 XML 布局容器定义。
再次单击 tabs-not-displayed-number 下拉菜单,这一次,选择 strings.xml 编辑选项卡,这样我们可以将字符串常量添加到我们的新按钮 UI 对象引用中。添加新的 button_name_return 字符串标签,以及 return Home 的值,方法是再次复制并粘贴button_name_done
字符串标签,然后将值从 done 更改为 Return,并从 Done 配置更改为 Return Home。
现在我们的 XML 标记编辑已经完成,我们可以转向在TravelActivity.java类中的 Java 代码编辑,在这里我们需要添加另一个按钮对象,将我们下一个 onClick( ) 返回主页按钮的事件处理方法附加到该对象,就像我们在上一节中在ConfigActivity.java
类中所做的一样。
让我们完成一个与 ConfigPlanet 类类似的工作过程,并添加一个名为 returnButton 的按钮对象,它将引用我们刚刚编辑完的 XML 文件中的 returnButton ID。我们通过使用一个 findViewById( ) 方法来实现这一点,使用一行代码,如下所示:
Button returnButton = (Button)findViewById(R.id.returnButton);
接下来使用我们刚刚创建的 returnButton 按钮 UI 对象,并通过熟悉的将一个 OnClickListener( ) 方法附加到这个 UI 对象。setOnClickListener( ) 方法创建一个新的 OnClickListener,如下所示:
returnButton.setOnClickListener(new OnClickListener() { your code goes in here } );
我们用于将用户返回到主屏幕 (MainActivity)的 Done Configuring 按钮的 Java 事件处理代码再次涉及在 onClick( ) 事件处理程序内使用 Activity 类的 finish( ) 方法,如下面的 Java 代码所示:
@Override
public void onClick(View v) {
finish();
}
图 5-10 中的显示了我们修改后的TravelPlanet
类。
图 5-10。将 returnButton 按钮 UI 对象和事件处理代码添加到我们的 TravelPlanet.java 类
我们越来越近了!一旦我们修改了我们的AttackPlanet.java类以返回到我们的应用的主屏幕,我们的整个 Hello_World 用户界面导航基础设施将拥有它的高级导航,允许我们的最终用户通过我们的主选项菜单在我们的应用的功能活动屏幕之间移动(没有双关语),然后从每个应用屏幕返回到主应用主屏幕。
启用我们的攻击星球活动用户界面:事件处理
就像我们为 TravelPlanet 类所做的一样,现在让我们准备我们的 AttackPlanet 活动和 UI 布局,这样我们就可以在用户完成了对其他星球的有趣攻击后将他们返回到他们的应用主屏幕。
我们通过向我们的 activity_attack.xml 布局规范添加一个退出攻击模式 ImageButton UI 小部件来实现这一点。请注意,因为我们使用的是 ImageButton UI 元素,所以我们而不是需要向我们的strings.xml
文件添加一个 button_name_exit 字符串常量。稍后,我们将使用事件处理程序代码连接 Exit ImageButton 元素,该代码允许用户在单击 Exit ImageButton 时返回 Hello_World 主屏幕。
在你的 Eclipse 编辑标签的右上方的中央窗格中有一个数字(在图 5-10 中,是 10);再次单击此按钮,下拉包含所有打开的代码模块的选择菜单。选择 activity_attack.xml 编辑选项卡,这样我们可以添加 Java 事件处理程序代码将要引用的退出攻击模式按钮 xml 标记。
让我们走捷径,复制并粘贴我们最初在activity_attack.xml
布局定义中定义的最后一个ImageButton
标签,并将其用作我们的第五个ImageButton
UI 元素,这样我们就有了另一个匹配的图像按钮用于这个布局容器,如图图 5-11 所示。第五个< ImageButton >标签及其参数应该类似于以下 XML 标记:
<ImageButton
android:id="@+id/exitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/content_desc_exit"
android:src="@drawable/ic_launcher" />
Make sure that you add a <string> tag to your strings.xml file for your Exit button content description parameter that says Exit to Home Planet using the following XML mark-up:
<string name="content_desc_exit">Exit to Home Planet</string>
图 5-11。向我们的 activity_attack.xml 布局标记添加第五个退出攻击模式 ImageButton UI 元素标签
还要注意在图 5-11 的中,我们将 LinearLayout 容器标签的 android:orientation 参数从水平改为垂直。这是为了让我们的五个图像按钮(目前是图标占位符图像,直到我们到达第七章)更容易地在屏幕上上下移动,而不是沿着顶部移动,现在已经有五个了。
现在我们的 XML 标记编辑已经完成,我们可以在AttackActivity.java类中编辑 Java 代码了。这里我们需要添加一个 ImageButton 对象,在其上附加下一个 onClick( ) 事件处理方法,使我们的退出攻击模式按钮具有交互性,就像我们在上一节中在TravelActivity.java
类内部所做的一样。
让我们进行一个与我们在ConfigPlanet
类中所做的类似的工作过程,并添加一个名为 exitButton 的ImageButton
对象,该对象引用我们刚刚编辑完的 XML 文件中的 exitButton ID。我们将使用一个 findViewById( ) 方法,通过一行代码来实现,如下所示:
ImageButton exitButton = (ImageButton)findViewById(R.id.exitButton);
接下来,我们将使用刚刚创建的exitButton ImageButton
对象,并通过熟悉的将一个 OnClickListener( ) 方法附加到这个 UI 对象。setOnClickListener( ) 方法创建一个新的 OnClickListener
,如下所示:
exitButton.setOnClickListener(new OnClickListener() { the code will go in here } );
我们将用于让用户返回主屏幕(MainActivity)的 Exit Attack Mode 按钮的 Java 事件处理代码涉及到再次使用 Activity 类的 finish( ) 方法,在 onClick( ) 事件处理程序内部,如我们在图 5-12 中修改的AttackPlanet
类所示。
图 5-12。将名为 exitButton 的 ImageButton UI 对象添加到 AttackPlanet.java 类来处理事件& finish( )
现在右键单击Hello_World
项目文件夹,并选择运行为 Android 应用,这将再次启动我们的 Nexus S 模拟器,这样我们就可以使用新的意图和事件处理代码来测试我们的应用。
当你点击模拟器菜单按钮时,屏幕再次看起来如图 4-16 所示。接下来,点击配置行星菜单项和意向参考,打开我们在activity_config.xml
文件中创建的配置行星活动屏幕及其配置行星布局。现在让我们看看我们的事件处理程序代码是否在工作。点击完成配置按钮,onClick()
事件处理程序调用finish()
方法并返回主屏幕。
接下来,点击 Travel to Planet 菜单项,查看 Intent 是否引用并打开我们在 activity_travel.xml 文件中创建的 TravelPlanet 活动屏幕及其 Travel to Planet 布局。要查看我们的事件处理程序代码是否正常工作,请单击返回主页按钮。那个onClick()
事件处理程序也应该调用我们的finish()
方法并返回我们的家。
最后,点击攻击行星菜单项,意图引用并打开我们在activity_attack.xml
文件中创建的攻击行星活动画面及其攻击行星布局。让我们也测试一下 ImageButton UI 元素的事件处理程序代码是否工作正常。点击左下方的图标退出攻击模式 ImageButton,查看我们的onClick()
事件处理程序是否调用了finish()
方法。
现在,在 Hello_World 应用的功能屏幕上导航一切正常,让我们来看看事件处理—
键中更重要的一个方面。因为现在很大比例的 Android 设备都有这样或那样的键,我们接下来将看看 onKey( ) 事件处理方法,这样我们就涵盖了本章中的所有“主要”内容!
小键盘或键盘的事件处理:OnKey 事件处理器
到目前为止,我们已经使用了onClick()
事件处理程序来捕获用户触摸我们的活动屏幕 UI 元素,或者使用许多 Android 设备上的导航箭头板和中心选择按钮进行导航。
到目前为止,我们的大部分报道都集中在onClick()
处理程序方法上,因为onClick()
事件处理程序是 Android 开发中最常用的。这是因为onClick()
事件处理程序涵盖了触摸屏的使用,以及任何导航硬件(导航键、轨迹球等)。)使用一个单一的事件处理方法。
随着 GoogleTV 或 Android iTVs 的出现,我们还需要考虑更多的键盘(或小键盘)将可供我们的最终用户使用。市场上也有带滑出式或可连接迷你键盘的智能手机(甚至平板电脑)型号,所以我也将在本章中介绍 onKey( ) 事件处理方法。
让我们为我们的应用添加一个键盘快捷键,使用通用的 X 键退出我们的每个活动屏幕功能区域,表示单词退出。
为此,我们需要在每个 Activity 子类中编写方法,使用 Android onKeyDown( ) 事件处理程序监听被按下的 X 键。按下一个 X 键会让我们的用户回到主屏幕。
onKeyDown()
方法接受两个参数,一个整数和一个对象,然后对它们求值。第一个参数包含键码,,它是一个整数值,代表被按下的键的指定数值(常数)。第二个参数是一个名为 Event 的 KeyEvent 对象,它是正在处理的键事件。
onKeyDown()
方法是公共的,所以任何东西都可以访问它,而布尔的,当它返回一个真(已处理)或一个假(未处理)返回数据值。
将传入该事件处理程序的键码与字母 X 的 KeyEvent 类常量(键码 _X )进行比较的代码使用了 onKeyDown( ) 事件处理方法中的基本 if 循环。代码如下所示:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_X) {
finish();
return true;
}
return false;
}
让我们将这段代码输入到我们的ConfigActivity.java
类中,就在onCreate()
方法之后,因为我们希望我们的活动屏幕“捕获”用户在任何时候按下 X 键返回主屏幕。
注意,当我们输入这段代码时,Eclipse 发现我们没有通过 import 语句声明使用的 Android KeyEvent 类。一旦您完成了方法的输入,将鼠标悬停在 KeyEvent 关键字上,让 Eclipse 为我们添加我们的import
语句,如图图 5-13 所示。
图 5-13。添加一个 onKeyDown 方法,并通过选择:Import KeyEvent 来删除错误突出显示
Java 代码在onKeyDown()
方法中所做的事情是相当基本的,但是我将在这里为那些不熟悉 Java 的人讲述它。我们在 if 循环中使用了一个==
数字比较运算符,或者在这种情况下,它更多地被用作 if 语句,将代表用户所按下的键的 keycode 整数与 KeyEvent 类中的KEYCODE_X
常量进行比较,以查看用户是否按下了 X。
如果按下了 X 键(如果两个值相等),那么执行 If 结构中的语句,并执行finish();执行代码语句,从被调用的方法返回一个 true 值,表示它已经成功完成了它的事件处理。
如果按下任何其他键(如果两个值不相等),那么这个 If 构造中的语句将被执行而不是,并且该方法将返回一个假值,表示该事件没有被处理。请注意,我们可能创建的任何其他事件处理程序仍然可以对该事件进行操作;事件从一个处理程序到下一个处理程序被称为气泡,直到它被处理(或者如果我们不指定那个键,就永远不会被处理)。
图 5-14 显示了我们完成的 ConfigActivity.java类事件处理代码,我们现在将在其他三个 Hello_World Activity 子类中复制它(如果我们真的聪明的话,可能通过剪切和粘贴)。
**
图 5-14。【ConfigPlanet 类的最终 Java 代码,用于处理我们应用导航的点击和按键事件
我们这样做是为了让用户在错误地进入应用的错误区域时,只需按下 X 键,就可以轻松退出我们的每个应用功能屏幕。您可能知道,键盘快捷键在大多数流行的软件应用中都很常见。
让我们先做我们的代码复制工作流程,在我们利用现在熟悉的 Run As Android Application 功能最后一次测试我们的应用之前,在我们继续攻克本书的用户界面设计和用户体验设计部分之前。
选择完整的onKeyDown()
方法,按下 Ctrl-C 键将其复制到你的系统剪贴板,然后点击标签区右边的数字(在图 5-14 中,是 12)下拉活动标签菜单。接下来,选择您的TravelPlanet.java文件,并使用 Ctrl-V 键盘序列将onKeyDown()
方法代码块粘贴到 TravelPlanet 类的底部,就在最后一个花括号之前。
我们还需要为我们的NewPlanet.java和AttackPlanet.java类做同样的事情,这样我们应用的所有四个 Activity 子类都实现了我们新的 X 键用于退出键快捷特性。现在复制这个工作流程,以便onKeyDown()
事件处理出现在我们所有四个功能屏幕活动子类中。
最后,我们需要测试我们在本章中完整添加的应用导航。右键单击我们的项目文件夹,选择 Run As Android Application 菜单选项,启动 Nexus S emulator,这样我们就可以看到我们实现的所有东西是否都工作正常。
要在应用运行后对其进行测试,请调用选项菜单,并单击四个应用区域中的每一个,然后按下计算机键盘上的 X 键(仿真器使用计算机的键盘来模拟设备键盘),并确保您的应用将最终用户返回到他们的主屏幕。
您会发现,如果您在此时测试onClick()
事件处理,这两种类型的事件可以完美地并行处理。我们的应用现在可以添加实际的功能了!
其他事件处理方法:OnFocusChange 和 OnLongClick
还有另外四种主要的 Android 事件处理方法,我们不打算在本章中介绍,但我们将在本书的后面实现它们,因为我们会在应用中添加更复杂的用户界面元素、图形设计、动画以及新的媒体素材和功能。
第一个是 OnLongClick( ) 事件处理方法,它相当于使用鼠标在计算机或其他设备上右键单击的 Android 方法。通过触摸并按住屏幕、轨迹球或任何 Android 设备上的中心选择按钮来调用长点击。
就像 onClick()事件处理程序一样,在 Java 代码中实现了一个onLongClick()
事件处理程序。
第二种类型的事件处理程序是一个 OnCreateContextMenu( ) 事件处理程序方法,它创建用于 Android 的上下文菜单。上下文菜单也类似于 PC 操作系统中的菜单,通过右键单击软件的对象或区域来获得该对象专用的上下文相关选项菜单。
第三种类型的事件处理程序是 OnFocusChange( ) 事件处理程序方法,用于处理当用户从一个元素前进到下一个元素时,用户界面元素发出的焦点事件。
当最终用户正在应用屏幕上使用给定的用户界面元素时,该 UI 元素被称为具有焦点,当用户停止使用该 UI 元素,并开始使用另一个 UI 元素时,焦点被称为已经改变。
当焦点从一个 UI 元素转移到另一个元素时,Android OS 会发出一个 FocusChanged 事件,该事件可以被OnFocusChanged()
事件处理方法捕获。这对于微调您的应用 UI 控件,以及精确跟踪用户如何访问您的 UI 元素,以及用户如何使用您的应用非常有用。
最后,Android 中还有一个 OnTouch( ) 事件处理程序,它只处理触摸屏事件。我建议对触摸屏设备(以及非触摸屏设备)使用onClick()
事件处理程序,用最少的代码覆盖最广泛的 Android 设备。
但是,如果您确定所有用户都将拥有并使用触摸屏 Android 设备,那么您可以使用onTouch()
事件处理方法以及onClick()
方法,或者代替它们。
OnTouch()
事件处理程序也用于只能使用触摸屏的 Android 操作系统功能,最好的例子是一个名为手势的 Android 功能,其实现超出了本书的介绍范围。
摘要
在这一章中,我们学习了 Android 编程语言中两个最强大和最有用的代码结构:意图和事件??。****
可以说,意图和事件允许我们“连接”我们的 Android 应用,以便当我们的最终用户点击我们的用户界面元素时,如我们的选项菜单项、图像按钮、文本按钮或图像视图,我们的应用能够处理这些点击(或触摸),并前往应用需要去的地方(意图调用活动屏幕),并做它需要做的事情(事件调用程序逻辑来实现某些东西)。
首先,我们了解了一个意图对象的结构,以及一个意图对象可以包含的七个主要功能区域(组件、动作、数据、类型、类别、标志、附加功能)。我们了解了为什么这些区域都很重要,为什么需要它们来处理意图对象,以及这些区域在意图处理请求中的作用。
我们了解了 AndroidManifest.xml 文件中的隐式意图和 <意图过滤> 标签,它们实现了这些意图推理引擎,可以由高级开发人员自定义构建。我们了解到意图过滤器将处理动作,然后是数据,然后是类别,以确定开发人员希望他们的意图如何被处理和执行。
然后,我们了解并在 Hello_World 应用中实现了显式意图,以实现我们在前面章节中创建的选项菜单结构,并让我们的菜单调用我们之前创建的四个自定义活动子类,以保存我们的应用功能屏幕。
一旦我们的菜单导航开始工作,我们需要学习事件处理,以便在每个应用功能屏幕中使用我们的用户界面元素,让我们返回到应用的主屏幕。
我们学习了onClick()
事件处理程序,以及如何在我们的 Java 代码中实现它来捕获各种 UI 元素(小部件)上的点击事件,以便我们的用户可以调用 Activity finish()方法并返回到主应用活动(主屏幕)。
然后,我们为各种流行的 Android 用户界面小部件编写了onClick()
事件处理程序,包括 ImageView 对象、 Button 对象和 ImageButton 对象。通过这样做,我们实现了应用中每个主要功能屏幕(活动)之间的无缝导航。
接下来,我们学习了 onKey()事件处理程序,并设置了 onKeyDown()处理程序,允许我们捕获点击 X 键的用户,这样我们就可以为我们的应用实现一个退出键盘快捷键。在那之后,我们回顾了其他一些 Android 事件处理程序,我们将在本书的其他三个部分中实现它们。
在本书的其余部分,我们将经常利用这些意图和事件能力;我只是觉得我们需要在这本书的第一部分,很早就注意到这些基本的意图和事件概念和原则。你是编码员—
你能处理好它。当然,没有双关语的意思。
在本书的第二部分(用户界面设计)中,在我们开始让应用看起来更原始之前,我在前面介绍了这个主题材质,因为这些 Intent 和 Event Java 对象和方法对于让我们的应用在更高级的层次上工作是非常重要的。
在本书的第二部分中,我们将采用我们在本书第一部分中创建的应用基础架构,并开始专注于我们的 Hello_World 应用的用户界面设计和图形设计,增强我们的用户体验(UX)并使 Hello_World 看起来更像一个专业的 Android 应用。
在下一章中,我们将学习 Android UI 设计小部件,并开始为 Hello_World Android 应用中的五个主要屏幕区域微调用户界面设计。然而,在第六章中,我们将主要使用 XML 标记(大部分)来精炼和增强我们的 UI 元素。
此外,在第六章中,我们将探索这些主要 UI 元素选项(作为 XML 参数)中的许多选项,用于微调我们的用户界面“外观”,以及探索我们的 UI 元素在我们的 Android 应用屏幕上的精确像素位置。**
六、Android UI 设计:通过 XML 使用视图和小部件
在本章中,我们将使用我们在本书第一部分中创建的 Hello World 应用,并致力于使其各种功能屏幕的应用用户界面设计更加专业。在 Android 中,这主要是通过 Android 用户界面小部件的 XML UI 参数来完成的,我们已经知道这些小部件是视图的子类。
同时,我们也将利用这一章,让那些不是用户界面设计者的读者领略在努力使用户界面设计看起来专业,同时对最终用户保持易用性时遇到的各种设计考虑。
我将再次尝试同时完成这些目标。极大地增强了 Hello World 应用的用户界面,同时还在一个统一的章节中介绍了关键的 XML UI 参数,该章节将概述在 Hello World 应用的上下文中使用 XML 定义用户界面元素的技术和功能。
Android 用户界面元素:Android 视图和子类
正如我在本书前面提到的,所有用户界面元素,至少是用户用来控制应用的可视元素,都是 Android View 类的子类,这意味着视图本身就是一个对象,也是 java.lang.Object 主对象类的子类。
一些最常见的用户界面小部件对象和布局容器对象,包括 ViewGroup、ImageView 和 TextView,都是 View 类的直接子类。其他我们已经使用过的,比如 Button、VideoView 和 ImageButton,是 View 类的间接子类。
间接子类是子类的简单子类;例如,Android 按钮类,可以通过 android.widget 包找到(导入)(你可以导入 android.widget.Button 以便使用按钮 UI 元素对象)是从 android.widget.TextView 类的子类。这是因为 button 对象具有 TextView 对象所具有的所有功能,以及附加功能(按钮外观和感觉元素),因此对 TextView 对象进行子类化以创建 Button 对象是合乎逻辑的。
我们将在本书后面看到的其他 View direct 子类包括 AnalogClock 和 ProgressBar 然而,我们将使用的许多用户界面小部件位于视图类层次结构的更底层(也就是说,它们是更专门化的用户界面特性实现),因此是 Android 视图类的间接子类。
在这一章中,我们将关注所有 Android 应用开发项目中使用的主流视图类或窗口小部件。我们的应用 UI 小部件将包括直接子类 TextView 和 ImageView ,因为图像和文本是任何应用中最重要和最常用的元素,以及按钮、 ImageButton 、 EditText 和 VideoView UI 元素小部件。
优化我们的 NewPlanet 活动 ImageView UI 小部件
让我们从我们的第一个选项菜单项和活动屏幕开始,在NewPlanet.java类中定义,我们在第三章中写了它。这个 UI 屏幕让我们单击一个行星的图像,在 Hello World Android 应用中创建基本的行星对象。如果你忘了它看起来像什么,这一章会再次展示,还有行星图片,在图 6-2 中。
现在,我们使用新的 Android 应用项目对话框系列在第二章中为我们创建的默认 Android 应用启动图标作为占位符图像。在这一节中,我们将最终用实际的行星图像替换这些占位符图像,稍后我们将使用 XML 参数对它们进行格式化,以使屏幕显示出专业的效果。
我为每个星球创建了四个不同分辨率的版本;一个是 256 像素见方的 ITV,一个是 192 像素见方的平板电脑,一个是 128 像素见方的触摸屏智能手机,一个是 96 像素见方的 240 或 320 像素屏幕的手机。
将我们的图像素材放入可绘制的 DPI 文件夹
接下来,将这些行星图像文件复制到资源文件夹下相应的 drawable 文件夹中。请注意,我们将很快在第七章中探讨开发这些不同分辨率图形素材背后的所有理论。现在,我将简单地介绍一下我们正在做的事情的基础,这样我们就可以在本章的用户界面设计工作中使用 drawables(位图图像)。
让我们从行星地球开始,因为这是我们在第三章中开发的 activity_add.xml 布局容器中的第一个 XML ImageView 标签。使用 Windows 资源管理器文件管理器,将earth256.png文件复制到 Hello_World 项目资源文件夹中的 drawable-xhdpi 文件夹中,该文件夹位于:
C:/Users/YourNameHere/workspace/Hello_World/res/drawable-xhdpi/
XHDPI 代表超高密度像素图像,这个文件夹包含我们最高分辨率的图像素材,在这种情况下,是一张 256 像素的正方形地球图像。接下来将 earth192.png 文件复制到 drawable-hdpi 文件夹,用于高密度像素图像,然后将 earth128.png文件复制到 drawable-mdpi 文件夹,用于中密度像素图像,最后将 earth96.png文件复制到 drawable-ldpi 文件夹,用于低密度****
接下来,我们将把这四个图像文件分别重命名为earth.png。我们这样做是因为 earth.png 是我们将在 XML 标记和 Java 代码中引用的图像素材名称,Android 会自动查看用户使用的屏幕大小,并进入正确的文件夹,获取分辨率密度图像素材。我们将在第七章中进一步探讨这个概念。
接下来,我们需要复制并重命名其他五个行星图像素材,这样我们就有六个了。四个可绘制文件夹中的 png 文件。四个文件夹中的每一个都应该有六个。png 文件,每个文件都以行星的名称开始,并且全部是小写字符。
接下来,我们必须使用 Eclipse 中的 Refresh 命令来更新我们的项目文件夹,以显示我们从 Eclipse“外部”(使用 Windows Explorer 文件管理器)复制到这些文件夹中的这些新素材。
为此,右键单击您的项目文件夹并从菜单中选择刷新,或者您也可以左键单击该文件夹并按下计算机键盘上的 F5 功能键。这样做的目的是告诉 Eclipse 扫描您所有的项目文件夹和文件,并更新 Package Explorer 导航窗格,以及它自己的跟踪例程,以便这些图像文件在我们的代码中被调用时,不会被标记为从我们的项目素材层次结构中缺失(或不存在)。
参考并对齐我们的行星图像源文件
接下来,我们需要进入我们的activity _ add . xmlrelative layout XML 文件,并更改我们在第四章中写回的 XML 标记,以指向正确的星球文件名,这些文件名在每个 ImageView 标签中使用 android:src 参数引用。更改占位符图片,目前为 @drawable/ic_launcher ,用小写字母表示每个星球的名称。第一个 ImageView 标签 imageEarth 的参数如下:
android:src="@drawable/earth"
为所有六个 ImageView 标签完成此操作后,单击编辑窗格底部的图形布局选项卡,并使用其预览功能查看是否显示了所有六个行星。如果是,那么您已经正确地完成了工作流程中的前几个步骤。请注意,作为一个整体,所有六颗行星都需要向右移动一点。为了实现这一点,我们将添加现在熟悉的Android:layout _ margin left参数。因为我们希望整个布局(作为一个整体的行星组)被移动,我们将把这个 marginLeft 参数放在父 RelativeLayout 标签中,如图 6-1 中阴影线所示。
图 6-1。添加 RelativeLayout 参数将所有行星居中,并将 android:src 参数设置为新图像
使用 22 密度独立像素 (DIP,或 DP)的设置值,通过以下 XML 标记行将新的 Planet selection ImageView PNG 图形居中显示在我们的 NewPlanet Activity 类用户界面屏幕上:
android:layout_marginLeft="22dp"
现在,返回并单击底部的图形布局选项卡,预览您添加的这个新的 android:marginLeft 参数,并查看这个 22 倾角设置如何将行星组推到显示屏的中心,以获得更专业的 UI 屏幕外观。这显示在图 6-2 中,以及右边的 Eclipse 属性窗格,你可以通过将鼠标放在属性窗格的顶部边框上来放大它,当你的光标变成一个双箭头“拖动以调整我的大小”指示器时,这时你可以向上拖动这个边框,给你自己一个更大的属性编辑区域。
图 6-2。activity _ add XML 代码预览,在属性面板中有行星图片和新的边距设置
请注意,您在 XML 标记视图窗格中编码的参数也在属性窗格视图中设置,因此如果您愿意,也可以用这种方式添加参数。蓝色突出显示的是我们刚刚添加的左边距布局参数 22dp 设置。另外,请注意中间的模拟应用用户界面屏幕,以及我们六颗行星的更专业的视图。这项活动进展顺利。
添加屏幕字幕文本和完成按钮
让这个屏幕完全专业的下一件事是在屏幕顶部添加一个 TextView UI 小部件(标签)来告诉用户这个屏幕是干什么的,以及一个按钮,当他们完成添加行星时可以点击它。doneadingbutton按钮 UI 元素(标签)调用我们的 finish() 活动方法,点击 planet ImageView UI 元素生成那些新对象。我们将在本章的后面写我们的 Java 代码;现在我们正在通过 XML 学习 UI 设计,所以让我们添加标签。
首先,让我们添加一个按钮用户界面元素标签,以便我们的用户在添加完行星后可以退出这个屏幕。最简单的方法是进入 activity_config.xml XML 标记,复制完成配置按钮标签,然后将该标记粘贴到 activity_add.xml 文件的顶部,RelativeLayout 容器标签下。
将 android:id 值改为 @+id/doneAddingButton ,将 android:text 值改为@ string/button _ name _ Done _ add,然后进入 strings.xml 文件,复制粘贴button _ name _ Done
接下来我们要做的是从 imageEarth ImageView 标签中剪切并粘贴(不是复制)两个标签Android:layout _ alignParentLeft和Android:layout _ alignParentTop,并在我们添加的按钮标签中使用它们。这是因为按钮标签现在是这个修订的相对屏幕布局的最顶层的屏幕布局用户界面元素。
因为我们想在屏幕的右上角右对齐新的 All Done 按钮 UI 元素,所以将Android:layout _ alignParentLeft更改为Android:layout _ alignParentRight并保留 android:alignParentTop。在我们的doneadingbuttonUI 元素中,我们并没有真正使用任何新的参数,我们只是“借用”了左上角 ImageView UI 元素中的参数,并对它们进行了更改,以创建一个右上角的按钮 UI 元素。接下来,我们将为我们的屏幕标题添加一个 TextView UI 元素(见图 6-3 中突出显示的行)。
图 6-3。添加一个文本视图标签来添加一个屏幕标题和一个 doneAddingButton 按钮标签来退出活动
接下来,让我们从我们的 activity_main XML 布局容器中复制第一个 TextView UI 元素(标签),并将其粘贴到我们刚刚创建的按钮标签上方。将 android:id 更改为 @+id/addPlanetText 并将 android:text 参数指向@ string/Add _ Planet _ caption,然后进入您的 strings.xml 文件,复制并粘贴hello _ world
通过编辑窗格底部的图形布局编辑器选项卡预览 UI 设计,您会注意到文本有点小,因此返回 XML 编辑模式,并在 TextView 标签中的 android:id 参数之后添加一个设置为 22sp 的 android:textSize 参数,如图图 6-3 所示。
文本字体使用标准像素(sp) 而不是密度无关像素来定义它们的值。如果您尝试将 dp 或 dip 用于任何与字体相关的大小调整,Eclipse 将标记它并警告您使用 sp 值来调整文本大小。
我们需要做的下一件事是将我们的屏幕标题文本与按钮 UI 元素对齐,以便它看起来专业并且对齐,位于屏幕布局的顶部。我们可以用一个名为Android:layout _ align baseline的参数来实现这一点,它将一个 UI 元素与另一个 UI 元素(在本例中是我们的按钮 UI 元素)的基线对齐。
现在让我们实现它,通过使用参数代码设置 alignBaseline 参数指向我们的按钮 UI 元素的 android:id 值:
android:layout_alignBaseline="@+id/doneAddingButton"
这一行代码也显示在图 6-3 中,当我们现在点击编辑窗格底部的图形布局编辑器选项卡时,我们可以看到在 UI 设计中实现了更大的字体大小和基线对齐。
还要注意,在图 6-4 中,Eclipse 实际上向我们展示了基线,它使用这个基线来对齐按钮标签 UI 元素中的文本和屏幕标题 TextView UI 元素。如果你仔细观察,你会看到一条绿线,证明我们的 TextView 和 Button 对象现在是基线对齐的。
图 6-4。查看相对于 doneAddingButton 按钮的 TextView layout_alignBaseline 参数结果
如果没有看到绿色基线,请返回到 XML 编辑模式窗格,并单击 alignBaseline 参数的代码行以突出显示它。
一旦该行代码被突出显示(浅蓝色),当您再次单击该选项卡时,它将是在图形布局编辑器视图中预览的代码。还要注意在图 6-4 中,我在属性窗格中突出显示了我们的文本大小参数,显示它也可以在那里设置。接下来,让我们以 Android 应用的身份运行,看看我们的 NewPlanet.java 活动屏幕用户界面布局在所有新变化到位后看起来如何。结果可以在图 6-5 中看到。
图 6-5。Nexus S 模拟器中我们的 NewPlanet 活动画面
添加 Java 代码来整合我们的新用户界面元素
最后,我们需要对我们的NewPlanet.javaJava 代码进行一些添加,以将我们在 XML UI 设计中所做的更改吸收到 Java 代码函数中,为我们的 Hello_World Android 应用添加一个新的 Planet 活动。我们需要做的第一件事是添加一个按钮对象。
在 Java 中设置按钮 UI 对象类似于设置 ImageView 对象来处理 Mars ImageView 对象上的点击。我们使用一行 Java 代码来声明我们的按钮对象,该代码声明了一个按钮对象,将该对象命名为完成按钮,,并通过引用一个完成添加按钮 ID 的 findViewById() 方法将其绑定到我们的 XML 定义,如下所示:
Button doneButton = (Button)findViewById(R.id.doneAddingButton);
接下来,我们利用熟悉的。setOnClickListener() 方法来设置一个新视图。OnClickListener() 为 doneButton 按钮对象。这又包含一个 onClick()事件处理程序,它监听点击,然后为我们的活动执行一个 finish() 方法,允许用户退出活动屏幕,并返回到主屏幕。代码如下所示:
doneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v){
finish();
}
});
接下来,我们需要从 onClick()事件处理程序方法中移除 finish()方法调用,该方法附加到我们的 marsImage ImageView 对象,因为我们现在有了用于退出活动的 doneButton UI 按钮,并且我们不再希望当我们单击其中一个行星时屏幕消失。
代替 finish()方法,让我们添加一个 Toast 对象和方法调用,让我们的用户知道 Mars 对象实际上已经被创建了。这是引入 Android Toast 对象的逻辑位置,因为它们通常用于用户界面和用户体验的优化和增强。
引入(并利用)Android Toast 类
Android Toast 对象用于将消息发送到您的活动屏幕上,该屏幕会在预定义的时间内向用户发送消息。在我们的例子中,当用户单击 marsImage ImageView UI 元素时,他们将发送消息“Mars Created ”,以便用户知道 Mars 对象是通过用户单击该行星的图像而创建的。
Toast 对象来自于 android.widget.Toast 包和类,并且是 java.lang.Object 的子类(而不是 android.view.view 的子类,因为它不需要任何 View 类特性),因为它们本质上很简单,尽管它们在使用时也非常有效。
声明一个 Toast 对象最简单的方法就是使用。Toast 对象的 makeToast() 方法。该方法需要当前上下文、要烘烤的消息以及常量形式的持续时间。有两个持续时间常数, LENGTH_LONG 和 LENGTH_SHORT ,我们将使用 LENGTH_SHORT 常数快速直观地确认我们的火星星球已经创建。Toast 调用编码如下:
Toast.makeText(NewPlanet.this, "Mars Created", Toast.LENGTH_SHORT).show();
注意,我们已经使用点符号将 Toast show() 方法附加到 Toast.makeText()方法调用的末尾。这是 Java 方法链接的一个很好的例子,这种技术允许我们编写更密集的代码,其中一整块代码可以写在一行代码中。要使用两行单独的 Java 代码“手写”编写这一行代码,您应该编写以下代码:
Toast myToast = Toast.makeText(NewPlanet.this, "Mars Created", Toast.LENGTH_SHORT);
myToast.show();
正如你在这里看到的,我们写的第一行代码要简洁得多!
现在添加我们开发的这行代码来将消息显示到屏幕上,代替我们之前的 finish() 方法调用,我们完成的 NewPlanet Java 代码将看起来像图 6-6 中所示的代码。
图 6-6。添加一个按钮对象和一个 Toast 对象。makeText 方法调用我们的 NewPlanet.java 活动
请注意,当我们作为 Android 应用运行来测试我们的代码时,我们现在会看到一个屏幕,上面有一个“全部完成”按钮,当我们单击火星图像时,它会创建一个火星对象,并向我们提供火星对象已经创建的反馈。
稍后我们将完成应用这一区域的 Java 代码,现在我们正专注于我们的用户界面设计,所以让我们继续,接下来微调我们的 ConfigPlanet 活动屏幕的 UI。
优化我们的 ConfigPlanet 活动 UI:按钮和文本 UI 小部件
让我们通过增强我们的 activity_config.xml 文件中的 XML 标记来改进我们的第二个选项菜单项 Configure Planet 的用户界面设计。首先,我们将通过在每行上放置两个标签参数来压缩一些按钮标签 XML 标记,这样我们就可以在屏幕上放置一些新的 UI 元素标签。请注意,标记参数只需用一个空格字符分隔;参数不需要在它们自己的行上。这可以在图 6-7 中看到,在 LinearLayout 中,我们的按钮标签现在只占了十几行标记。为了增强 UI 设计并使屏幕更具功能性,我们需要在 UI 屏幕的右侧放置一些可编辑的文本字段,与每个按钮相对。
图 6-7。添加 EditText UI 元素标签并嵌套两个 LinearLayouts 以创建并排的 UI 布局
使用嵌套布局容器创建复杂的用户界面设计
为此,我们需要将两个 LinearLayout 容器嵌套在一起,一个用于左侧垂直对齐的按钮 UI 标签,另一个用于右侧垂直对齐的 EditText UI 元素标签。让我们复制 LinearLayout 开始标签,粘贴到 LinearLayout 结束标签下面,开始一个新的容器,如图图 6-7 所示。在它下面复制一个 LinearLayout 结束标记,这样我们的结束标记也就到位了。
然后复制 LinearLayout 开始标签并将其粘贴到 LinearLayout 开始标签的上方,以创建一个新的顶级 LinearLayout 容器,并将其 android:orientation 参数从垂直更改为水平,因为两个嵌套的 LinearLayout 容器将彼此相邻。然后再次复制 LinearLayout 结束标记,并将其粘贴到布局容器的最底部,以便第一个水平** LinearLayout 标记包裹两个内部(嵌套)垂直 LinearLayout 容器标记。**
为了设置两个嵌套的 LinearLayouts 之间的宽度比,我们需要向第一个(左侧)标签添加一个 android:layout_width 参数,以定义它将拥有多大的屏幕( 170dp )。右边的 LinearLayout 负责剩下的部分。
如果你愿意,你可以稍微改变嵌套标签的缩进来显示嵌套的内容,如图 6-7 所示。接下来,我们要做的就是将我们的 EditText UI 元素标签添加到第二个 LinearLayout 的内部。
为可编辑文本字段引入 Android EditText 用户界面元素
要添加 EditText 用户界面元素,我们可以使用图形布局编辑器将 EditText UI 元素拖动到屏幕的右侧,或者通过编写一个元素的标记,然后复制五次,同时更改参数以匹配按钮 ID 和输入类型,来手动编码 EditText 标记及其参数:
<EditText android:id="@+id/editTextColonies" android:inputType="number"
android:ems="12" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_marginTop="12dp" />
第一个 EditText UI 字段在 Add Colonies 按钮的对面,有一个 editTextColonies 的 android:id 和一个 android:inputType 的 number (与整数数据匹配)。inputType 参数定义了字段使用的数据类型,并且需要与访问该字段的 Java 代码中使用的数据类型相匹配,稍后当我们编写 Java 代码来填充这些数据字段时,我们将会看到这一点。我们使用数字和文本数据类型作为 inputType 参数,但是如果需要的话,也可以使用其他类型,比如 numberDecimal,表示实数(十进制)。
android:ems 参数为数据字段中使用的文本值设置字体大小,我们将从 12 ems 的值开始,然后如果我们希望数据字段中有更大的文本,稍后再对其进行调整。我们的 EditText UI 元素标记的下一个参数是 android:layout_width ,它被设置为 match_parent ,告诉 android 将 EditText UI 元素(字段)的宽度与父容器对象(标记)匹配,在本例中是第二个嵌套的 LinearLayout。
我们的 EditText 标签中的 android:layout_height 参数有一个 wrap_content 值,它本质上与 match_parent 值相反。wrap content 常量意味着使 UI 元素的边缘符合其中包含的内容,而 match parent 常量则相反:将 UI 元素的边缘扩展到包含它的容器中。
最后,我们将使用 12dp 的一个 android:layout_marginTop 参数将我们的每个 EditText 字段与我们的按钮对齐,这样我们在数据字段之间就有了均匀的间距,并且每个字段都与紧邻其左侧的按钮 UI 元素的底部边缘对齐。
现在我们已经编写了一个代表性的 EditText 标签,让我们将它复制五次,并更改 android:id 参数以匹配按钮 id 名称,并将两个 Forcefield 指示器字段的 android:inputType 参数更改为 text ,这将包含文本值。
升级我们的 Configure Planet Activity screen XML 以在并排的 Linearlayouts 中包含 EditText 数据编辑字段的最终结果可以在图 6-7 中看到,它显示了我们最终的 XML 代码和我们在 Eclipse 右侧的 Outline 窗格中使用的 UI 元素的概述。通过使用作为 Android 应用运行工作流程,检查以确保当前 UI 设置在仿真器屏幕上看起来不错。
添加 Java 代码以整合我们的“配置一个星球”屏幕的新用户界面元素
接下来,让我们修改我们的ConfigPlanet.java活动类 Java 代码,将它“连接”到这些新的 UI 元素,这样它就可以在我们的应用中起作用了。
我们要做的第一件事是为我们的 doneButton Button 对象复制我们的按钮代码,这样活动屏幕上的所有按钮都可以正常工作。注意在图 6-8 中,我通过放置 finish()使 onClick() 代码块更紧凑一些;花括号内的语句放在一行上,因为目前只有那一条代码语句。
图 6-8。在 ConfigPlanet.java 的 Java 代码中添加按钮对象和 onClick()事件处理程序
接下来,我们需要将所有与 doneButton 相关的(5)行 Java 代码复制六次到 doneButton 代码块下面,如图图 6-8 所示,所以我们创建了 colonyButton、colonistButton、baseButton、militaryButton、forceFieldOnButton,最后是 forceFieldOffButton 代码块。确保在每个代码块中为每个按钮 UI 元素引用正确的 XML ID 名称,并确保。setOnClickListener() 方法连接到正确的按钮对象名,该对象名定义在它的上一行中。最后,点击每个代码块左边顶部的减号图标,折叠代码块,如图 6-9 所示,这样我们就有了更多的编辑空间。
图 6-9。添加一个名为 colonyText 的 EditText 对象,并通过:Import EditText 调用 Eclipse 的自动导入特性
现在是时候将我们的 EditText UI 对象添加到我们的 Java 代码中了,这样我们就可以利用用户在我们的程序逻辑中放入这些字段的数据值,因为我们将在整本书中继续构建这个应用。
您应该很擅长在 Java 代码中声明 UI 元素,使用它们的小部件类型、名称和 findViewById() 方法,如下所示:
EditText colonyText = (EditText)findViewById(R.id.editTextColonies);
注意在图 6-9 中,Eclipse 错误标记了我们的 EditText 对象,因为我们不允许在代码中使用它,直到我们导入它,所以将鼠标放在红色下划线的对象上,点击导入 EditText android.widget 包选项,让 Eclipse 导入我们的 android.widget.EditText 类。
我们需要做的下一件事是在我们的 EditText 字段中设置一个默认值,它符合我们建议用户添加的属性的数量。在菌落的情况下,我们使用“1”值,一次添加一个菌落。通过设置文本。带有两个参数的 setText() 方法,一个值,一个缓冲区类型常量。缓冲区类型可以是可编辑的、正常的(固定的)和可扩展的。对于我们的 colonyText 对象,我们的代码如下所示:
EditText colonyText = (EditText)findViewById(R.id.editTextColonies);
colonyText.setText("1", EditText.BufferType.EDITABLE);
现在,我们所要做的就是将最后两行 Java 代码复制到前两行的下面五次,并将 colony 更改为殖民者、基地、军队、forcefieldOn 和 forcefieldOff。然后,确保每个 EditText UI 元素的 XML ID 标记名与我们在 activity_config.xml 中的 XML 相匹配,然后将 colonistsText 的“1”值更改为“100”,将 militaryText 的“10”值更改为这些数据字段设置更合理的默认值。
对于我们的 forcefieldOn 和 forcefieldOffText 数据字段,我们将使用“Forcefield is Off”作为 forcefieldOffText 数据默认设置,并使用 null(空引号)设置作为 forcefieldOnText 数据默认设置。
所有五个复制的两行代码块的最终 Java 代码应该如下所示:
EditText colonistText = (EditText)findViewById(R.id.editTextColonists);
colonistText.setText("100", EditText.BufferType.EDITABLE);
EditText basesText = (EditText)findViewById(R.id.editTextBases);
basesText.setText("1", EditText.BufferType.EDITABLE);
EditText militaryText = (EditText)findViewById(R.id.editTextMilitary);
militaryText.setText("1", EditText.BufferType.EDITABLE);
EditText forcefieldOnText = (EditText)findViewById(R.id.editTextForcefieldOn);
forcefieldOnText.setText("", EditText.BufferType.EDITABLE);
EditText forcefieldOffText = (EditText)findViewById(R.id.editTextForcefieldOff);
forcefieldOffText.setText("Forcefield is Off", EditText.BufferType.EDITABLE);
现在我们只用了十几行 Java 代码就建立了 EditText UI 元素,如图 6-10 所示。稍后,我们将为按钮 UI 元素的 onClick()事件处理程序添加 Java 代码,这些代码将从我们使用设置的 EditText 字段中获取数据值。getText() 方法,并用这些值相应地设置我们的 WorldGen 对象数据变量。
图 6-10。复制六个 EditText 对象和。用 Java 实现 EditText UI 字段的 setText()方法
接下来,我们需要确定我们的 XML 标记和 Java 代码是否给我们提供了我们一直试图实现的更精炼的用户界面设计结果。右键点击项目文件夹,选择 Run As Android Application 菜单项,当 Nexus S 模拟器启动时点击菜单按钮和 Configure Planet 菜单选择,观察 UI 屏幕。
注意,我们对 ems 字体大小设置的估计是正确的,并且 EditText 字段中的数据是一个很好的大小,因此我们将保留那些标记参数设置。然而,EditText 字段的底部并没有与按钮元素的底部完美地对齐;看起来像是 12dp 的 android:layout_marginTop 值太大了,这将每个数据字段从其上的数据字段向下推得太远。因此,让我们将 marginTop 值减少 33%至值 8dp 和再次作为 Android 应用运行。
正如你在图 6-11 中看到的,我们的配置 Planet UI 屏幕排列良好,Java 代码中设置的数据值正确地出现在每个 UI 数据字段中,就像我们设计的那样。在第七章中,我们添加了半透明的按钮,让事情变得更加有趣,但现在,这个活动屏幕的用户界面设计已经升级,并在我们的 Java 代码中实现了,所以让我们继续前进,接下来拨入我们的星球旅行活动屏幕。我们在这个应用上取得了巨大的进步!
图 6-11。使用实现的可编辑文本字段配置活动 UI 屏幕
优化我们的 TravelPlanet 活动:Android 的 VideoView Widget
我们的 TravelPlanet.java 活动类拥有最简单的 XML 标记代码,并将拥有最复杂的 Java 代码(当我们进入视频章节时,第十一章和第十二章),因为我们将使用一种先进的新媒体类型:数字视频。我们在这一部分的目标是使我们的应用的“到行星旅行”部分在外观和用户体验上更专业,我们将通过去掉位于视频视图屏幕顶部的按钮来实现这一点,以便用户可以享受全屏视频播放。为了完成这个,我们需要从我们的 UI 中移除按钮 UI 元素。
配置我们的 VideoView 用户界面元素
进入 activity_travel.xml 编辑页签,将按钮 UI 元素标签从 FrameLayout 容器中全部删除,如图图 6-12 所示。因为我们不再需要点击按钮来退出我们的活动,我们将需要使视频视图本身可点击,所以让我们寻找一个参数来实现这一点。键入 android ,然后按下冒号键从 Eclipse 中获得参数选项助手对话框,然后向下滚动并寻找任何处理可点击性的参数。
图 6-12。移除按钮 UI 元素标签,改为使 VideoView UI 元素可点击
请注意,列表中有两个名为 android:clickable 和 android:longClickable 的参数,双击 android:clickable 来添加它,然后使用相同的工作流程返回并双击 android:longClickable 参数来添加它,此时我们正在这里编写标记。在本章后面的 Java 代码中,以及在第十一章和第十二章中,我们将更深入地讨论数字视频,我们使用 Android onTouch() 事件(因为视频正在触摸屏上播放)在视频屏幕上实现点击和长时间点击。
接下来,我们添加了另一个 UI 参数,当我们的用户在一个星球的表面旅行时,它可以增强我们在数字视频播放过程中的用户体验。许多 Android 设备优先考虑节能而不是用户体验,因为我们不希望我们的 Android 设备屏幕在用户的旅行体验中变暗(甚至变黑),所以我们将寻找一个参数来解决这个问题。
再次键入 android 关键字和冒号键激活器并从 Eclipse 中调出参数选项助手对话框,并寻找一个在 VideoView 播放期间保持我们的 android 设备屏幕打开的参数。
这里需要注意的是(如果你还没有注意到的话),这个 Eclipse 参数选项助手对话框将只用与你在其中输入 android 关键字和冒号激活符的标签相关的参数填充它的视图。因此,这是准确找出每个用户界面元素的标签有哪些可用参数的好方法。双击 android:keepScreenOn 参数并添加。
一旦您完成了 activity_travel.xml 文件的 XML 编辑,它应该看起来像图 6-12 。现在,我们只需要更改 Java 代码来访问我们在 XML 编辑会话中所做的事情,这样一切就像手套一样合在一起了。
添加 Java 代码,将我们的旅行融入行星屏幕的新用户界面元素
我们需要做的第一件事是删除实例化我们的按钮对象的 Java 代码,或者编辑它,用一个 travelVideo VideoView 对象替换我们的按钮对象,如下行代码所示:
VideoView travelVideo = (VideoView)findViewById(R.id.travelVideoView);
一旦这样做了,Eclipse 将会突出显示一个错误和一个警告;根据错误import Android . widget . VideoView导入 video view 类以供使用,另一个黄色警告强调了这样一个事实,即您不再需要import Android . widget . button语句,因此如果您想的话,现在可以删除该 import 语句(您不必非要这样做—
这是一个警告级别,不会阻止代码正确编译和运行)。
图 6-13。用 Java 中的 VideoView 对象和 onTouch()处理程序替换按钮和 onClick()处理程序
一旦我们创建了 travelVideo VideoView 对象,我们就可以在它上面设置一个事件监听器。因为数字视频最好通过触摸屏设备使用,所以让我们利用这个机会使用 onTouch() 事件监听器,而不是 onClick()事件监听器。要设置 OnTouchListener() ,使用以下代码:
travelVideo.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Toast.makeText(TravelPlanet.this, "Going Home", Toast.LENGTH_SHORT).show();
finish();
return true;
}
});
有了这段代码,我们的用户所要做的就是触摸屏幕(在我们添加视频后,视频最终会出现在屏幕上),然后 onTouch() 事件处理程序向屏幕广播一个 Toast ,上面写着“回家”,然后执行活动 finish() 方法,将用户带回主页。现在,我们的 VideoView UI 元素处理按钮 UI 元素曾经处理的事件,按钮不会阻挡我们查看视频视图屏幕。这样的 UI 设计有更专业的用户体验。
在第七章中,如果我们需要触摸事件来控制视频回放或视频传输,那么我们可以将触摸事件改为长点击。现在,我们只是把基础放在适当的位置,以便我们可以在以后对它进行扩展,但在某种程度上,它可以在当前的应用功能中很好地工作。这是开发我们代码的最好方法,通过确保它在开发的每个阶段都能很好地工作。
因此,让我们作为 Android 应用运行,并确保代码正常工作。一旦应用启动,点击菜单按钮,并选择旅行到星球菜单选项,它会带你到一个黑色的屏幕(空白视频视图屏幕),你可以在屏幕中间点击(模拟手指触摸)。一旦你这样做了,一个写着“回家”的祝酒信息就会出现,应用会返回到主屏幕。现在我们已经有 75%的应用活动屏幕在工作,我们需要微调我们的攻击星球屏幕,然后我们将准备进入一些图形设计!
优化我们的攻击星球活动 UI: ImageButton UI Widgets
在我们修改attack _ activity . XMLlinear layout 容器中的 ImageButton 标签的 XML 标记之前,我们应该将 ImageButton 标签将用于自定义按钮图标的图像元素放入正确分辨率的可绘制资源文件夹中。我们在本章的“配置行星”一节中已经这样做了,关于我们的 ImageView 标签使用的六个行星图像(只有这些图标更酷)。先这样吧。
将图像素材放置到位
复制五个 ImageButton 图形的 96 像素版本。png 文件到您的Hello _ World/RES/drawable-xhdpi/文件夹中。它们被命名为 attackbomb96px.png、attackinvade96px.png、attackvirus96px.png、attacklaser96px.png 和 attackexit96px.png。一个巧妙的方法是选择第一个,然后按住键盘上的 Ctrl 键,同时选择其他四个。这允许不连续的文件选择,而 Shift 键将选择一系列文件(单击第一个文件,按住 Shift,然后单击所需范围内的最后一个文件)。然后右键选择复制,然后选择目标文件夹,右键选择粘贴。
一旦它们在 /res/drawable-xhdpi 文件夹中,删除文件名的 96px 部分,这样文件(最终,一旦我们完成所有这些复制)都有相同的通用名称:attackbomb.png、attackinvade.png、attackvirus.png、attacklaser.png 和 attackexit.png。
接下来,对这五个文件的 80px 版本执行完全相同的工作过程,将它们复制到 /res/drawable-hdpi 文件夹中,并删除文件名中的 80px 部分,因此它们都是通用名称。再次进行相同的工作过程,将这五个文件的 64px 版本放入 /res/drawable-mdpi 文件夹,最后再将这五个文件的 48px 版本放入 /res/drawable-ldpi 文件夹。
参考、对齐和合成我们的攻击图标图像源文件
现在,我们已经准备好进入 Eclipse XML 编辑窗格,并添加增强新用户界面屏幕设计所需的标记参数。我们还需要更改标记的 android:src 参数中指定的图像源文件名,以便我们令人印象深刻的新自定义 ImageButton 图形图标在每个 ImageButton 标记中正确引用。
让我们首先修改 ImageButton 标签(图 6-14 ),使用新的文件名,以及功能参数,如声音效果、背景颜色值和边距,以便在图像图形之间的 UI 屏幕上留出一些空间。
图 6-14。升级 ImageButton 标签参数以引用新图像并添加背景透明度
因为最终(在第七章之后)我们所有的应用屏幕都将有深色或黑色的背景,以太空中发现的星际场和等离子场为特色,让我们给 LinearLayout 容器添加一个新参数,给它一个黑色背景。我们将通过 android:background 参数来实现这一点,正如我们将在第七章中了解到的,颜色黑色在计算机中通过 #000000 值来表示。
当我们添加 android:background 参数时,让我们为每个 ImageButton 添加相同的标记参数,并使用值 #00000000 ,这是透明的值,因为额外的两个零控制透明度。同样,我们将在下一章图形中学习为什么会这样。
接下来,我们将更改 launcher_ic 占位符图形(最后!),带有我们的通用攻击图形名称,这些名称现在位于我们的四个分辨率特定的可绘制文件夹中。如果你在包资源管理器 /res/drawable 文件夹中没有看到它们的名字,如图图 6-14 所示,那么你可能忘记了在之前使用 Windows 资源管理器将文件复制到正确的文件夹中之后,右击 Hello_World 文件夹并刷新IDE 在硬盘上的项目文件夹的“视图”。
一旦 Eclipse 可以“看到”新文件,将每个 ImageButton 的启动器图标 android:src 参数引用更改为新文件名的第一部分,如图图 6-14 所示。为了更专业地分隔屏幕上的 ImageButton UI 元素,添加一个初始值为 9dp 的 android:layout_margin 参数,以便在我们为 ImageButton 使用的每个新图形周围留出一些空间(没有双关语,至少现在没有)。
最后,让我们使用参数助手对话框来查找一个音效标签参数,方法是键入 android ,然后按下冒号键。向下滚动并搜索与启用 ImageButton 声音效果相关的标记参数。双击添加Android:soundEffectsEnabled参数,一旦在列表中找到,将其值设置为 true。。确保您为五个新的 ImageButton 标记中的每一个标记进行了所有四个更改和添加,如图图 6-14 和图 6-15 所示。接下来,我们将添加文本。
图 6-15。按下朝左的人字形<字符来调用 Eclipse 中的添加标签助手对话框
向我们的 XML 标记添加屏幕标题文本视图
为了确保用户知道当他们到达这个攻击 UI 屏幕时要做什么,让我们在我们的 UI 屏幕顶部添加一个标题来告诉用户当他们在这个屏幕上时要采取什么行动。在第一个 ImageButton 标签前添加一行空格(按回车键),如图图 6-15 所示,然后按键盘上的朝左的人字形<键(Shift-Comma) ,注意在 Eclipse 中会弹出一个添加标签助手对话框。
找到 TextView 标签,双击将其添加到您的 XML 标记中。请注意,这里有数百个标签,足以装满几本书,您可以使用这个弹出助手来探索一些不常用的标签。在本书中,我们将介绍您最常用来实现文本、图像、视频、动画 UI 元素等的标签,但是当您有时间时,可以通过这个很酷的功能随意探索更适合的 UI 元素。现在让我们配置我们的文本视图标题。
因为我们的 UI 屏幕背景现在是黑色的,让我们通过使用 android:textColor 参数和一个数据值 #FFFFFF,使我们的标题文本变成白色,我们很快就会知道它是白色的十六进制值。使用 android:textSize 参数将您的文本大小设置为 18 标准像素( sp ,并确保添加 wrap_content 的标准 layout_width 和 layout_height 参数设置;如果您忘记了这些,Eclipse 将标记您的 XML。
最后,在 strings.xml 文件中添加一个 attack_planet_caption
要预览到目前为止您所做的工作,您可以使用编辑窗格底部的图形布局选项卡,或者使用作为 Android 应用运行工作流程来获得您的 UI 屏幕在 Android 智能手机上的更精确的渲染视图。随着这个攻击 UI 屏幕变得更加复杂和详细,您会看到图形布局预览只是一个估计,并没有提供像模拟器一样好的预览。
正如您将在预览中看到的,UI 屏幕在顶部和左侧有元素,但有很多黑色空间,不是我们在本章中试图实现的专业外观的 UI 屏幕,因此我们需要在每个 ImageButton 图标的右侧添加一些文本,以告诉用户每个图标的作用。
使用嵌套的 LinearLayout 容器创建更复杂的用户界面设计
为了创建一个更复杂的 UI,我们将需要遵循一个类似于我们将配置星球 UI 屏幕提升到下一个级别的工作流程,因此让我们在一个父级水平 LinearLayout 中添加两个嵌套的垂直linear layout(一个用于 ImageButtons,另一个用于 TextViews)。
将顶部的 LinearLayout 标签复制到其下,并将第一个(现在是父级)LinearLayout 上的方向参数更改为水平。删除第二个 LinearLayout 的 android:background 参数,因为第一个(父)现在设置 UI 屏幕的全局参数。将第二个 LinearLayout 的 android:layout_width 从 match_parent 更改为 70dp ,因为我们现在希望左边的 LinearLayout 符合我们的 ImageButtons。
请记住,确保在 XML 定义的底部有两个嵌套的 LinearLayout 结束标记,然后复制第二个 LinearLayout 标记并将其粘贴到倒数第二个 LinearLayout 结束标记之后,以创建第三个 LinearLayout 标记。将 TextView UI 元素复制到第二个嵌套的垂直 LinearLayout 容器中,因为该容器将保存我们的 TextView UI 元素。同样,确保你的第二个嵌套的 LinearLayout 容器在 XML UI 定义的底部也有一个结束标签,并缩进嵌套的 linear layout,如图 6-16 所示。
图 6-16。嵌套 LinearLayout 容器以添加到 TextView 标签中来标记我们的攻击星球选项图标
接下来,让我们复制下面的Attack _ planet _ captionTextView 来创建一个Attack _ planet _ bombTextView 标签,我们可以为一些更大更丰富的文本进行配置,以标记每个攻击 ImageButton 图标的功能。将 android:textColor 值更改为 #FFFFBB 使其变为黄色并将 android:textSize 值更改为 25dp 使其比标题文本大 40%。添加一个带有 18dp 值的Android:layout _ margin top标签,将文本从屏幕标题下推至第一个 ImageButton 旁边。
现在,通过你的图形布局和运行 Android 应用工作进程来渲染UI 屏幕,看看它看起来怎么样。在我们将这个 TextView 标签复制四次以创建我们的其他 TextView 标签之前,我们仍然需要一点微调,所以让我们接下来做这件事。
微调我们的用户界面设计,给我们的攻击图标添加文本标签
添加一个Android:layout _ margin left参数,其设备无关像素(DIP,或 DP)值为 8dp ,以将我们的文本从 ImageView 图标向用户界面屏幕的中心推一点。
现在我们准备复制并粘贴这个attack _ planet _ bombTextView 四次,创建我们的其他 attack _ planet TextViews。确保将 android:text 参数更改为引用您在 strings.xml 文件中创建的< string >标签常量,这些常量根据每个文本视图的功能为它们创建文本标签(参见图 6-17 )。
图 6-17。Nexus S 模拟器中的攻击星球 UI 屏幕预览
将复制的 TextView 标签的 android:layout_marginTop 参数调整到 36dp 到 40dp 之间,使标签靠近每个图标图形的中心,如图图 6-17 所示。这是一个迭代的过程,您可以调整这些值,并在模拟器中来回移动,直到您获得将每个标签放在屏幕上每个图标中间的值。接下来,更改 textColor 值以设置从黄色到紫色的颜色渐变。
接下来,我们需要升级我们的AttackPlanet.java活动子类中的 Java 代码,以实现我们尚未实现的四个 ImageButton 对象。请记住,我们之前已经对 exitButton ImageButton 对象进行了编码,并让它调用我们的 Activity finish()方法,Java 代码我们将保持不变,实际上,我们将复制它,然后再粘贴四次,以创建我们的其他四个 ImageButton 对象。
添加 Java 代码来整合我们的攻击星球屏幕用户界面元素
复制包含 ImageButton 对象创建的代码块,并将。setOnClickListener 代码块在 exitButton 对象设置代码下实现了四次 OnClickListener()和 onClick()方法。
将 exitButton 代码保留为第五个也是最后一个代码块,转到第一个代码块,在所有三个位置(名称、引用和方法调用)将 exitButton 更改为 bombButton 。接下来,让我们将 finish() 方法调用改为 Toast.makeText() 方法调用,它告诉我们的用户当他们单击 ImageButton 图标图形时做了什么。
我们的。makeText() Toast 对象方法有一个 AttackPlanet 类上下文,一个炸弹掉了!文本消息和短的显示长度持续时间,如下所示:
Toast.makeText(AttackPlanet.this, "Bombs Away!", Toast.LENGTH_SHORT).show();
接下来,执行相同的工作流程,用 onClick()事件处理程序创建 invadeButton 、 infectButton、和 laserButton ImageButton 对象,这些对象向部队发送、病毒传播、和激光发射!分别消息。
图 6-18 显示了完成后五个代码块的样子!
图 6-18。在 AttackPlanet.java 为每个攻击方法添加 ImageButton 对象和 onClick()处理程序
现在,我们可以完整地测试我们的攻击星球活动用户界面。
右键单击项目文件夹并作为 Android 应用运行,以启动 Nexus S 模拟器。加载后,单击菜单按钮并选择攻击星球菜单项,在攻击星球 UI 屏幕出现后,单击前四个 ImageButton 图标图形中的每一个,并观看您的祝酒词消息完美地出现在 UI 屏幕的底部。然后单击第五个退出图像按钮,观看屏幕退出并返回您的家!
我们现在已经实现了 XML 标记和 Java 代码,使我们的整个 Hello World 应用在基本用户界面设计级别和导航级别上 100%可用。我们准备在本书接下来的几节中实现一些非常酷的富媒体元素。
我们将在接下来的几章中添加这些新的媒体元素,包括图形设计、动画、数字视频和数字音频。这应该会很有趣,因为我们现在要让这个应用对我们所有的最终用户来说真的很有趣,像游戏一样!
摘要
在本章中,我们仔细研究了一些最常用的 Android android.widget 包用户界面类,并在我们的 Java 代码中将它们实现为我们自己的 Hello World 应用的功能 UI 对象。我们了解到 UI 元素窗口小部件是从古老的 Android 视图类中派生出来的子类,我们看了看 Android 应用开发项目中最重要和最普遍使用的 UI 窗口小部件类。
在这一章中,我们学习了更多关于 Eclipse 特性的知识,特别是我们如何简单地通过在一个空行中键入 < (朝左的人字形字符),键入 android 关键字,然后键入冒号键激活器,就可以调出标签助手对话框和参数助手对话框。这是一个很好的方式来探索 Android 操作系统中在任何给定时间(在任何给定的 API 级别安装上)可用的数百个标签和参数。
对于我们的新星球活动,我们仔细研究了 Android 的 ImageView 类,以及 Android 的 TextView 和 Button 类,然后我们使用 XML 中的一些自定义参数在更高级的层次上实现了这些 UI 元素类型。然后,我们在 Java 代码库中实例化这些 UI 元素对象中的每一个,为新的行星选择屏幕创建一个高度可视化的用户界面,我们需要用户使用该界面在我们的应用中选择和创建新的行星。
对于我们的 Configure planet 活动,我们利用了 Android 按钮类,以及通过 Android EditText 类的文本编辑字段,然后我们用 XML 中的自定义参数实现了这些 UI 元素类型中的每一个,然后用 Java 实例化它们,以创建我们的数据输入用户界面,用于我们的用户能够在我们的应用中定义 Planet 属性所需的 Planet 配置数据输入屏幕。
对于我们的 Travel Planet 活动,我们查看了 Android VideoView 类,我们在 XML 中实现了一些高级参数,以便我们的 VideoView 可以被点击和长时间点击(或触摸和长时间点击)来实现功能。我们还使用一个 android:keepScreenOn 参数来确保任何 Android 设备屏幕在我们的视频内容播放过程中保持打开。最后,我们用 Java 实例化了我们的 VideoView 对象,并实现了一个 OnTouchListener() 和 onTouch() 事件处理程序,为我们的用户在我们的应用中旅行到他们的星球表面所需的星际旅行数字视频播放屏幕创建了一个光滑的用户界面。
对于我们的攻击星球活动,我们研究了 Android ImageButton 类,该类允许 Android 开发人员实现由图像和动画等图形素材构成的按钮。正如你可能想象的那样,我们将在下一章图形设计中更详细地讨论这个小部件,但是我们想在讨论这本书的这一部分之前先介绍一下基础知识。我们在 ImageButtons 中添加了自定义按钮图标图形,以替换我们正在使用的占位符图像,并添加了其他参数,为我们使该屏幕支持声音效果和合成做好准备,我们将在未来的章节中学习这些内容。然后,我们在 Java 代码中连接这些 ImageButtons,使这个 UI 屏幕在用户界面设计上既实用又专业。
在本章中,我们学习了 Android Toast 类和对象,以及如何实现 Toast,向我们的用户广播反馈消息,向他们提供可视化更新,告诉他们在他们使用我们的用户界面屏幕活动子类期间,我们的应用正在执行什么任务。
在下一章中,我们将学习在 Android 应用中使用图形,我们将开始为我们的 Hello World Android 应用定制屏幕布局和用户界面,使用 XML 标记代码(大部分)以及 Java 代码来定义和实现这些令人惊叹的用户体验和用户界面元素。接下来的几章将会非常有趣,因为我们将会使我们的应用具有商业可行性!**
七、Android 图形设计:概念和技术
在本章中,我们将了解 Android 中数字图像和图形设计的基本概念,以及这些概念如何在 Android 操作系统中实现,以及一些数字图像技术。
我们将了解 android.graphics.drawable 包和 android drawable 类,以及 Android 支持的多种类型的 Drawable 对象。我们还将学习什么是 ARGB ,什么是阿尔法通道,以及所有关于数字成像的概念,如像素、色深、抖动、分辨率、纵横比、图层、混合、图像压缩、和格式****
然后,我们将应用这些新知识,通过一种称为合成的数字成像技术,添加一些与空间相关的背景元素,并确保它们无缝实现,从而将我们迄今为止创建的 Hello World 应用提升到一个全新的专业水平,我们也将在本章中了解更多信息。
为了在我们的应用中实现所有这些新的数字图像功能,我们将继续学习以新的方式应用 XML 用户界面参数,正如我们从第二章开始所做的那样,也正如我们将在本书的其余部分继续做的那样。
本章将更多地涉及图形设计元素、数字成像概念和成像技术,而不是编程概念,因为这些天来,Android 应用越来越多地涉及视觉设计、3D、图形设计、动画、数字图像和其他新媒体元素,这使得本章变得非常必要。
让我们从学习 Android graphics 包和 drawable 类开始,它们让所有这些视觉魔法对我们的最终用户变得生动,并允许我们开发人员以逻辑和结构化的方式在 Java 代码和 XML 标记中实现我们心目中的东西。
Android 图形设计最高水平:Drawable 类
类似于我们在前一章中看到的与 UI 元素或小部件相关的 Android 视图类,Android Drawable 类也是与图形设计相关的对象的最高级别类,这些对象在 Android 中被称为 drawables ,因为它们被绘制到我们的显示屏上。
这就是为什么项目资源文件夹中的一些子文件夹以单词 drawable 开头,因为它们包含了应用的图形设计元素。到目前为止,我们已经使用了数字图像 drawables,但在我们完成之前,我们还将使用其他几种类型的 drawables,以实现动画或图像交叉淡入淡出效果。
和 View 类一样,Drawable 类通常不直接使用,但是它有无数的子类。与视图子类一样,Drawable 既有直接子类又有间接子类。这些 Drawable 子类完成了 Android 图形中的所有繁重工作,因此,在本书中,我们将在我们的应用中引入并利用其中的许多子类。让我们回顾一下哪些可绘制子类是直接的,哪些是间接的,以及这些可绘制子类对我们的应用有什么作用。
Android Drawable 类的直接子类
Drawable 类的直接子类本质上等同于我们可以在 Android 应用中使用的图形设计元素的类型。
到目前为止,Drawable 类最常用的直接子类被称为 BitmapDrawable 子类 ,它用于数字图像,例如我们目前在应用中使用的 PNG 文件以及 WEBP、JPEG 或 GIF。
ColorDrawable 是最基础的关卡 Drawable 类,它用于定义屏幕的颜色,就像我们在第六章中为我们的攻击星球活动用户界面屏幕定义黑色屏幕颜色一样。
GradientDrawable 用于创建从一种颜色到另一种颜色的渐变,可以由任何形状定义。Android GradientDrawables 支持的形状有:直线、矩形、椭圆(卵形)、圆形、环形(箍形或环形),可以画出一个渐变线性(直的,任意角度)径向(从一个点发出),或者扫掠(线性但绕一个点旋转)。
ShapeDrawable 用于在 Android 中创建一个矢量形状。向量或形状是定义 2D 体积外部的 2D 线或曲线,如心形或星形。熟悉 Adobe Illustrator 的人会熟悉矢量形状,以及渐变和彩色图形工具。
我们讨论的最后三个 Drawable 子类(ColorDrawable、GradientDrawable 和 ShapeDrawable)为 Android 操作系统提供了许多矢量软件包(如 Illustrator 或 InkScape)将提供的基本功能,只是我们在 Android 中的 Java 代码级别上提供了这些功能。这使我们能够创建与在 InkScape 中创建的相同类型的矢量艺术作品,然后通过 Java 使艺术作品具有交互性或动画效果。
LayerDrawable 让我们可以处理多层图像,就像人们在 Photoshop 或 GIMP 数字成像软件中看到的那样,以及它们的图像层功能。这是一个更高级的 drawable,用于高级游戏和实时合成应用。我们将在本章的后面了解更多关于图像图层和合成,以及它们是如何使用的。
InsetDrawable 允许我们使用一个叫做插图的屏幕区域或子集来显示图形(可绘制)元素。这用于像窗口小部件这样的东西,它们只使用显示屏的一部分作为它们的应用或目的所需的屏幕区域。设备主屏幕的时钟可能是使用 InsetDrawable 的一个很好的例子。
Android Drawable 类的间接子类
Drawable 的间接子类是更复杂或更详细的图形函数,它们利用直接子类来创建更复杂的图形设计效果和运动,如动画。
例如,AndroidTransitionDrawable,其中交叉淡入淡出两幅图像以创建图像过渡,是 LayerDrawable 的子类。因为这些 LayerDrawable 对象处理层中的多个图像,所以它们是创建 TransitionDrawable 类的子类的逻辑类,TransitionDrawable 类获取两个图像并制作它们的 alpha 通道值的动画(是的,我们将很快介绍这一点)以创建令人印象深刻且有用的图像交叉淡入淡出效果。
除了 TransitionDrawable 类,我们还将大量使用 Drawable 的Animation Drawable,它是 Drawable 的另一个关键的间接子类,在 Android 中实现了帧动画。我们将在后面专门关注动画的章节中介绍帧动画和矢量动画。因为我们在这里讨论了主要的 drawable indirect 子类,所以我现在将它放入正确的上下文中。
LevelListDrawable用于进度条和类似的应用,在这些应用中,需要根据某种活动的级别来替换屏幕上的图形元素。类似的 StateListDrawable 类 用于根据应用运行时可能遇到的不同状态变化来替换图形元素。StateListDrawable 可以以任何特定的顺序访问图形元素,也就是说,不按顺序,而 LevelListDrawable 则按顺序访问图形,从一个级别到另一个级别。
LevelListDrawable 和 StateListDrawable 都是从 Androiddrawable containerdirect 子类派生出来的子类,该子类通常不被直接使用或调用,但如果 LevelList 或 StateList drawable 容器不能完全满足您的需求,它可以用于创建您自己的自定义可绘制容器。
因为大多数应用(和大多数网站,就此而言)使用位图进行图形设计,本章接下来的几节将更详细地介绍位图图像的概念和特征,因为它们是创建既美观又专业的 Android 应用用户体验的关键。我们将从位图图像的基础开始,即像素,并从那里开始构建,就像数字图像本身是一次构建—
一个像素一样!
数字成像的最低级像素:像素
数字图像是由微小的彩色点组成的;如果你曾经使用过数字成像软件包中的变焦功能,比如 GIMP,你可能已经知道了。一张图片的每个元素被称为一个像素,它是单词图片(在流行俚语中称为 pix)和单词元素 (el)的变形。我打开了 GIMP 2.8,将我们攻击病毒的 48 像素版本从正常的(100%)放大到 800%,这样你就可以看到图 7-1 中组成图像的各个图片元素(像素)。我们将在本书中使用 GIMP 2.8 进行我们的数字成像工作流程,因为它是开源的,我们所有的读者都可以免费下载并安装它(就像我们在第一章中所做的那样)。请注意软件底部下拉小部件中的缩放设置,以便轻松访问新设置。还要注意,在屏幕的顶部有关于文件名、颜色深度、图层和分辨率的信息,所有这些我们将在本章接下来的三节中详细讨论。
图 7-1。我们的攻击病毒图片元素(像素)
像素是使用位定义的,作为程序员,我们都知道这些位是代表数据的二进制值,在本例中是颜色值。这就是图像被称为位图的原因,因为它们实际上是位值的映射,使用二进制(十六进制)数据格式定义每个像素的颜色值。这就是为什么我们有 8 位图像(8 位数据用于定义每个像素)和 24 位图像,以及 32 位图像,我们将在本章稍后关于色深的章节中很快了解所有这些。
使用像素塑造图像:分辨率和纵横比
在 GIMP 窗口右上角的图 7-1 中,你会看到一个 48x48 符号。这是以像素为单位的图像的分辨率 ,即 48 像素宽,48 像素高。图像分辨率用 2D 或二维来表示,即宽度乘以高度。图像体积或图像中的像素数是通过宽度乘以高度来计算的,因此我们的 48x48 像素图像在其体积中包含 2,304 个像素。我们的 Nexus S 模拟器屏幕分辨率为 800x480,包含 384,000 像素。
所以分辨率的概念相当简单,假设你知道如何使用计算器将两个数相乘!这个关于长宽比 的新概念有点复杂,因为它涉及到屏幕分辨率的多少像素宽与多少像素高的比。
当涉及到缩放一幅图像时,纵横比是最重要的,这是指放大或缩小图像的大小。如果您在缩放图像(或视频)时没有保持纵横比,您的图像将会失真。这是数字成像和数字视频中常见的错误;我们都见过那些看起来像锥子或者脸看起来有点宽的人的照片。这就是为什么数字图像缩放(调整大小)对话框有一个保持纵横比锁定功能,你应该总是选择这个功能。
计算纵横比很像在分数数学中寻找最小公分母,因为纵横比很像分数。纵横比是用宽:高还是宽:高来表示。因此,从技术上来说,我们的 Nexus S 的长宽比为 800:480,但在行业中,这通常会减少到可以显示该比例的最小的两个数字。
所以,让我们逆向工作(这是我如何在我的脑海里做这个计算,不使用计算器),并删除零。从技术上讲,80:48 也是 800x480 显示器的正确纵横比。因为它们是偶数,所以我们将它们分成两半,得到 40:24,再得到 20:12。现在我们离个位数的数字对越来越近了!既然它们都还是偶数,我们再把它们切成两半,得到 10:6,现在其中一面有个位数。它们仍然是偶数,所以再次将它们分成两半,我们得到 5:3,这是 800x480 屏幕的正确纵横比。
其他常见的宽高比包括 16:9 (HDTV),自 1920 年以来除以 16 乘以 9 等于 1080,4:3 是所有分辨率的原始计算机显示器宽高比,除了 1280x1024,它是 5:4 的宽高比。另一种流行的 Android 智能手机分辨率(和迷你平板分辨率)是 854x480,也是 16:9。因此,800x480 的屏幕是 5:3,854x480 的屏幕是 16:9,尽管它们在分辨率上看起来很接近,但在 UI 设计方面,它们相差很远,因为它们的纵横比非常不同。我们将在本书后面的第十二章中处理这两个特别接近(分辨率)但远离(纵横比)的分辨率之间的差异。
请注意,数字越接近相等,图像就越方;我们在图 7-1 中的 48x48 像素图像的长宽比为 1:1。2:1 宽高比的图像仅仅意味着图像的宽度是高度的两倍,而 3:1 或 4:1 宽高比的图像可以被称为完全的全景图像!
塑造像素的颜色:色深的概念
现在我们已经讨论了像素、分辨率和长宽比,我们可以进入像素着色本身,并讨论颜色理论。有两种类型的彩色显示,减色 用于印刷,其中油墨颜色彼此相减,以及加色,用于发光显示(显示使用光)产品,其中颜色值彼此相加。
在减色中,红色和绿色(油墨)值产生紫色,而在加色中,红色和绿色(浅色)值产生黄色。差别很大!减色法,用于印刷行业,遵循一种 CMYK 颜色模型 ,代表青色品红黄色黑色(我猜他们把黑色这个词的 K 拿错了头)。
加色在消费电子行业用于以显示屏为特色的产品,遵循 RGB 颜色模型 。 RGB 代表红绿蓝,通过使用这三种颜色的光,可以创造出可见光谱中的任何颜色。不同的颜色是通过改变每个 RGB 颜色成分的强度(暗,或完全关闭,到亮,或完全打开)而产生的。
在数字世界中,这些 RGB 颜色中的每一种都可以有 8 位或 256 级的颜色强度。因此,对于红色,0 将完全关闭,或者黑色(完全没有红色值)和 256 将完全打开,或者 100%红色。
由于我们对图像中的每个 RGB 颜色通道都有个 8 位的颜色值,所以该图像可以说是一个 24 位颜色图像 ,也称为真彩色图像。Truecolor 图像可以显示 16,777,216 种不同的颜色值,可以通过将 256 乘以 256 (65,536 种颜色,也称为 16 位颜色)然后将该值再次乘以 256 来计算。
正如我们在上一章中看到的,256 个级别可以用两个字符表示,使用十六进制,或以 16 为基数的数字表示,其中不是从 0 计数到 9(以 10 为基数),而是从 0 计数到 F,给我们一个字符“槽”16 个值因为 16 乘以 16 是 256,所以两个槽给了我们表示 256 个不同值的能力。
为了获得上一章图 6-17 中“入侵行星”选项的橙色颜色值,我们使用了十六进制颜色值 #FFDDBB ,这意味着红色的全强度(256),绿色的低强度(196,或 14 乘以 14),蓝色的更低强度(144,或 12 乘以 12)。
除了 24 位真彩色图像,还有 8 位索引彩色图像 。索引的彩色图像使用一个 8 位的索引,在调色板到中有 256 种颜色,近似于图像中的所有颜色。这对于某些图像比其他图像效果更好,例如,在晴朗的蓝天中蓬松的白云的图像可能作为索引颜色图像效果很好,因为使用的 256 个颜色值可以分布在仅使用 256 种颜色而不是 16,777,216 种颜色模拟真彩色图像所需的白色和蓝色阴影上。
索引彩色图像的数据占用空间(文件大小)通常比真彩色图像小得多(压缩前是真彩色图像的三分之一),可以很好地表现图像中的颜色。我们在第六章的攻击星球 UI 屏幕中使用的图标都使用索引色,正如在截图顶部的图 7-1 中可以看到的,它指定了索引色。因为我们的病毒细胞使用粉红色、紫色和红色的阴影,所以它是使用索引色模拟真彩色图像质量结果的非常好的候选,使用的数据少得多(2.86 千字节)。
即使图像中有多种不同的颜色,索引彩色图像也能产生令人印象深刻的结果,正如我们在下一节的图 7-2 所示的攻击激光图像中所看到的。一般来说,图像的像素分辨率越高,索引颜色的效果就越好。我将在下一节关于 Alpha!的压缩技术中向您展示如何优化索引图像压缩的结果。
图 7-2。攻击激光数字图像,其阿尔法通道通过棋盘图案显示
定义图像中的透明度:Alpha 通道的概念
24 位图像有三个(RGB)颜色通道,而 32 位图像有四个( ARGB )颜色通道。32 位图像中的第四个通道称为 alpha 通道 。像图像的每个 RGB 通道一样,alpha 通道也可以有 256 个级别,但这 256 个级别不是定义颜色强度,而是定义该像素的透明度级别(或半透明)。
对透明度进行微调、逐像素控制的主要用途是图像合成** ,这是将许多图像层合成在一起以创建一个最终图像或特殊效果的过程。攻击激光图像中的 alpha 通道,如图 7-2 所示,在激光炮周围是黑色的(0 或透明),在图像中你看到激光炮的地方是白色的(256 全不透明)。GIMP 通过一个棋盘图案 来表示透明度,你也可以在图 7-2 中看到。
如果你想看看 alpha 通道在将一幅图像合成到任何其他图形上时有多有效,看看图 7-2 中屏幕截图的左上角,GIMP 在窗口标题栏上合成了你的激光炮。看起来它是被设计在那里的,不是吗?我们在 Hello World 应用中使用的几乎所有图像,—
行星、激光炮、士兵和炸弹—
都有一个阿尔法通道,这样当我们将星际机场和等离子云放在它们后面时,背景将完美地显示图像中的每个细节。也就是说,图像无缝地合成了。
我们已经将 UI 元素参数变得完全透明,在 #00000000 的 XML 中使用了 ARGB 设置。前两个零指定不透明度** 为 100%关闭,因此透明度为 100%打开。不透明是透明的反义词,因此不透明程度和透明程度彼此相似。如果一个像素 30%透明,那么它 70%不透明。
Alpha 通道在应用开发中经常使用,因为我们经常想要使用内容开发引擎(在这里是 Android)作为图像合成工具。在图像合成中,合成中的每个层都带有一个 alpha 通道或 alpha 数据组件,以定义通过该层显示的内容以及该层上的哪些数据将以某种方式应用于最终的图像合成结果(最终的视觉图像)。
除了使用图像层及其 alpha 通道之外,合成还包括在每个层上使用一种混合算法 ,该算法对像素数据值进行求和(或求差),基于复杂的算法可以创建图像合成特效,如叠加、屏幕、变暗、变亮、异或等等。
android 通过 PorterDuff 算法 支持这些混合模式,在一个名为Android . graphics . porter duff的包和类中,因此,Android 不仅像 InkScape 一样被类固醇;还像打了类固醇的 GIMP 2.8.4!编码混合算法有点超出了像这样的关于 Android 的入门书籍的范围,但是如果你想将 Android 数字成像提升到一个新的水平,它将在我的“Pro Android 图形设计”书中(Apress,11/2013)出现!
Android 中的图像格式支持:PNG8、PNG24、web、JPG、gif
现在我们已经掌握了数字图像中的不同属性,我们可以讨论 Android 支持的一些不同的文件格式,因为这些图像文件格式中的每一种都有不同的组合和对颜色深度和 alpha 通道的支持级别。
Android 操作系统对其中一些格式的偏好超过了其他格式,这将决定我在本节中讨论这些格式的顺序。
便携式网络图形(PNG)格式
Android 最喜欢的格式是便携式网络图形或 PNG (读作 Ping )文件格式。PNG 有两种风格, PNG8 或索引色 PNG 和 PNG24 或真彩色 PNG。因为 PNG24 也可以“携带”一个 alpha 通道,从技术上讲,一个带 alpha 的 24 位 PNG 应该是一个 PNG32 。
Android 喜欢 PNG 格式的图像,因为 PNG 使用无损图像压缩,产生最高质量的结果,因为 PNG 图像在压缩过程中不会损失任何原始图像质量(或数据)。
联合图像专家组(JPEG)格式
下一个最适合在 Android 中使用的图像格式是 JPEG,它代表 ?? 联合图像专家组。这种图像格式采用有损图像压缩,即“丢弃”部分原始图像数据以达到更好的压缩效果,但以牺牲图像质量为代价。如果你放大一个 JPEG 图像,你会看到看起来脏或变色的区域;这些是压缩产物,也是 JPEG 格式不是 Android 开发首选图像格式的原因之一。
JPEG 图像格式的另一个重要方面是它不能携带 alpha 通道,因此它不能用于图像合成,除非它是图像合成层堆栈中的底层,或者除非 alpha 信息后来在应用内部附加到它。
图形信息(GIF)格式
Android 中最不理想的图像格式是 Compuserve GIF 格式,它代表图形信息格式。GIF 图像仅支持 8 位索引颜色,并且比 PNG8 文件具有更大的数据占用空间(较弱的压缩算法)。除非万不得已,尽量不要在 Android 开发中使用 GIF。
Web 照片(web)格式
自 4.0 (API Level 14 冰淇淋三明治)版本和更高版本以来,Android 操作系统中增加了一种额外的图像格式,称为: WEBP 。WEBP 代表网络照片,它类似于 PNG32,但数据占用空间要小 25%;即其压缩算法优于 PNG24 和 PNG32。
然而,如果你想将你的 Android 应用交付给数百万使用 Android 1.5 到 3.2 (API 等级 3 到 13)设备的用户,你可能会想使用 PNG24 和 PNG32 格式,以及 PNG8(当它工作良好时)(正如我们在本书中为 Hello World 应用所做的)。
减少图像数据足迹:图像压缩概念
一旦我们定义了我们的图像和 alpha 通道,以及我们将要使用的文件格式,在我们的图像被创建和合成以及任何 alpha 通道被定义和添加之后,我们经历的最后一步是压缩图像以获得最小的文件大小。
注意我们压缩图像的原因是因为我们的 Android 应用的总大小是我们所有图像加上我们的 Java 代码和 XML 标记的总和,因为我们都知道文本比像素压缩得更好,所以应用的大部分数据足迹反映了我们优化新媒体素材的效果。
在这一节中,我将介绍影响压缩算法的关键因素,以及最终应用压缩过程(压缩算法)后,图像文件大小的最终数据足迹。
分辨率是调整图像以获得更好压缩(文件大小)效果的最大因素。正如我们之前了解到的,要压缩的像素的绝对数量总是归结为像素宽度乘以高度。
如果不进行压缩,图像的原始数据大小将按如下方式计算:像素宽度乘以像素高度乘以 3(对于 RGB)或 4(对于 ARGB)。
因此,对于我们的 ARGB 96 像素激光炮,如图 7-2 所示,原始未压缩图像数据是9216像素(96x96)乘以 4 (ARGB),这是36864总像素要压缩,或四个图像通道中的每一个都是 9216 像素。36,864 除以 1,024(一千字节数据中的像素数)得到的原始文件大小正好是 36 千字节,或36 千字节。
我们的 96 像素激光炮图像的最终文件大小为 4.89KB,比小 7.36 倍(36 除以 4.89),或原始数据占用空间的 13.6% (4.89 除以 36)。100%减去 13.6%会产生 86.4%的压缩率。
我们做了什么来压缩这个图像几乎 87%,它仍然看起来很棒?因为我们无法改变它的分辨率,所以我们改变了它的颜色深度。颜色深度是图像压缩中第二重要的因素。这是因为您必须将像素的数量乘以颜色通道的数量,如果您可以减少这个乘数,您也可以极大地减少结果文件的大小。所以我们没有使用 32 位的 PNG,而是使用了 8 位的 PNG;通过这样做,我们将压缩的像素数量从 36,864 减少到 9,216。
正如你在图 7-2 的截屏顶部看到的,attacklaser.png 是一个 8 位索引彩色 PNG 图像,然而它看起来像是一个更高质量的真彩色图像。这是因为我使用了抖动,这是 8 位图像压缩的一个关键选项。
抖动包括在 8 位索引调色板中模拟超过 256 种允许的颜色。这是通过使用微妙的点模式来混合两种颜色,形成介于两种颜色之间的第三种颜色。这种技术欺骗眼睛,让它认为有超过 256 种颜色被用来创建图像。由于最近流行的 XXHDPI(超高密度像素图像)Android 屏幕中的像素尺寸较小(像素尺寸称为点距),抖动是一种有价值的技术。
抖动对 8 位图像特别有用,这些图像表现出一种称为条纹 的颜色深度转换特性,因为它可以通过这种技术模拟 512 种颜色来减轻或消除条纹。请注意,图像数据中的抖动模式会稍微增加文件的大小,因此请确保打开和关闭抖动选项,以查看图像质量是否提高到足以值得增加到图像数据占用空间中的那几个额外的数据字节。
提升我们的新行星活动:应用成像概念
现在是时候看看这些概念如何应用到我们的 Android 应用开发中了。让我们给我们的新行星活动屏幕添加一个星域背景,这样我们的行星就在它们所属的太空中了。
给我们的 HelloWorld 应用 添加一个星星图像背景
我们需要做的第一件事是将这些 starfield 图像素材复制到各自的文件夹中。将 stars320x480.png 复制到 drawable-ldpi 文件夹,将 stars480x800.png 复制到 drawable-mdpi 文件夹,将 stars1024x600.png 复制到 drawable-hdpi 文件夹,最后将 stars1280x720.png 文件复制到 drawable-xhdpi 文件夹。将每个分辨率版本复制到各自的 drawable 文件夹后,请务必将每个版本重命名为简单的stars.png。
首先,注意这些是索引的 PNG8 文件,大小从 6KB 到小于 24KB 不等,尽管最大的是 HD 分辨率。星星是用于索引文件格式的最佳图像类型的完美示例,因为有很多黑色区域,没有太多图像细节或颜色变化,所以在这种情况下,PNG8 的压缩结果令人惊讶。
编辑我们的屏幕布局 XML 来引用星星背景图片
接下来打开 activity_add.xml 文件,添加一个 android:background 参数,通过 @drawable/stars 路径寻址机制引用 stars.png 文件,如图图 7-3 所示。然后选择 Hello_World 项目文件夹,点击 F5 键刷新项目视图,或者右键单击项目文件夹,选择刷新菜单选项。然后点击编辑窗格底部的图形布局选项卡,预览我们的图像合成结果。正如你在图 7-4 中看到的,行星在星空背景下看起来很棒,但是我们的说明文字似乎已经消失了,在我们用户界面屏幕的左侧有一条 22 度倾斜的白色条纹。
图 7-3。在 activity_add.xml 文件中添加一幅 starfield 背景图像,以合成包含一些恒星的行星
图 7-4。使用 Eclipse 中的图形布局编辑器选项卡预览我们的 android:background 参数
这不是我们期望的专业用户界面结果,所以我们需要改变我们标签中的几个标签参数,以进行调整,现在我们有了深色的星空背景,而不是白色的背景色(见图 7-4 )。
调整我们的 XML 标签参数以适应新的星星背景图片
这里发生的事情是,我们已经在我们的 RelativeLayout 布局容器中使用了 marginLeft 参数来将行星作为一个组居中;现在我们已经为布局容器设置了背景图像,marginLeft 参数也将背景图像推过 22 度,从而在左侧产生白色条纹。
Android 操作系统使用白色的默认应用背景颜色,就像它对所有布局容器的默认颜色值一样。这就是所显示的,因为我们通过 android:marginLeft="22dp "参数推动整个布局容器。
为了纠正这一点,我们将从相对布局容器标签中移除参数 marginLeft ,取而代之的是,我们将把它放入我们的每个 ImageView 标签中,以相同的量推动它们,如图图 7-5 所示。
图 7-5。修改我们的 TextView 和 Button 标签,添加白色文本颜色参数和 MarginLeft 参数
我们还需要将 marginLeft 参数的另一个副本放入 TextView,并将其推回,这样它就不会触及用户界面屏幕的左边缘。
调整我们的 TextView 标签的 textColor 参数 来增加对比度
接下来,我们需要向 TextView 和 Button 标签添加一个设置为 #FFFFFF (白色)的 android:textColor 参数,这样我们就可以在 stars.png 背景下看到标题和按钮标签。在数字成像理论中,我们需要学习的另一个重要概念是对比度,为了获得可读的文本,我们需要高对比度,即深色背景下的亮色文本。该标签参数添加如图 7-5 所示。
现在我们已经对需要它们的标签进行了 android:marginLeft 和 android:textColor 参数更改,是时候使用作为 android 应用运行并调用 Nexus S 模拟器,并实际查看我们新升级的添加行星用户界面屏幕看起来是什么样子。
正如你在图 7-6 中看到的,由于白色文本和深色 starfield 背景之间的高对比度,文本现在是可读的,按钮看起来也不错。你有没有注意到,当我们把 stars.png 的图像放在后面时,按钮的背景变暗了?这是因为按钮背景不仅是灰色的,它也是透明的,事实上,你可以看到一个小星星就在按钮的顶部,透过这种透明显示出来。
图 7-6。在 Nexus S 模拟器中预览我们的图像合成结果
这是一个混合的例子,我之前提到过,其中按钮背景颜色是通过算法添加到每个背景像素颜色中的。这允许您将按钮放在图像上,以获得更专业的视觉效果。注意,这个混合量也可以在 XML 参数或 Java 代码中更改。
因为这看起来很好,让我们继续将 stars.png 图像添加到 AttackPlanet 用户界面屏幕背景。在应用中多次使用图像素材是从图像素材中获得更多收益的好方法,这有助于使应用的数据足迹更小。
升级我们的 TravelPlanet UI:为我们的攻击病毒 创建阿尔法通道
在我们将 android:background 参数添加到 activity_attack.xml 文件之前,就像上一节添加行星屏幕一样,我们需要为 UI 的一个图标图像创建一个 alpha 通道。您可能已经注意到,在将图标复制到项目子文件夹中时,其中一个图像,attackvirus.png,在病毒周围有一个实心的黑色背景,而不是像其他四个图标那样具有透明度。
让我们使用 GIMP 开源图像编辑软件来完成纠正这一问题的工作流程,这样您就知道这是如何完成的了,因此稍后,我们可以在任何背景上放置病毒,并获得无缝的结果。
添加透明度:创建一个阿尔法通道蒙版
启动 GIMP 2.8.4,然后使用文件打开菜单序列打开位于项目资源 drawable-xhdpi 文件夹中的attackvirus.png文件(见图 7-7 ),因为该文件的分辨率为 96 像素。在数字成像中,我们总是从最高分辨率开始,然后逐渐降低以避免任何像素化。
图 7-7。打开 drawable-xhdpi 文件夹中的 attackvirus.png 文件,然后调用“按颜色选择”工具
点击选择菜单和【按颜色 图像区域选择工具,这将使您进入连续颜色选择模式,该模式将选择包含具有相同 RGB 颜色值的像素集合的颜色区域。
一旦进入工具模式,将阈值(选择灵敏度)设置为 40 ,并点击图像四个角之一的黑色像素。由于我们的病毒细胞本身有几种不同的颜色值,所以选择病毒细胞中而不是的颜色,而不是试图选择病毒细胞的颜色,这是一个更合理的工作过程。一旦你的选择框(行进的蚂蚁轮廓)看起来像在图 7-8 中一样,使用键盘上的删除键删除黑色像素。
图 7-8。通过显示选择和选项对话框的颜色选择框进行选择,阈值设置为 40
一旦你按下 Delete 键,你会看到 GIMP 中使用的白色背景色(就像 Android 使用的作为其 app 背景色一样),这将使我们的 alpha 通道图像蒙版的进一步编辑(清理)更加容易(图 7-9 )。
图 7-9。删除选中的黑色值,显示 GIMP 默认的白色背景色
遮罩定义了将要在图像合成中显示或使用的图像部分,因此,我们在此进行的过程称为将攻击病毒细胞从攻击病毒细胞的图像中掩蔽。
接下来,再次下拉选择菜单,选择无选项,确保在我们编辑图像之前,图像中没有任何内容被选中。然后选择橡皮擦工具(在你的工具面板中寻找一个老派橡皮擦)并将工具设置为 1 像素、方形纵横比 (0 值)和角度设置为 0(与屏幕成直角),并从动态下拉菜单中选择铅笔通用。使用橡皮擦工具,单击未被选中的边缘黑色像素,因为它们与您单击以调用该工具的黑色像素值不够接近。
这个过程被称为清理你的遮罩,这样做是为了让你在攻击病毒图像对象周围获得一个更紧密的选择区域,我们稍后将在我们的 Android 应用中使用该区域进行合成效果。
通过比较图 7-9 和图 7-10 可以看出这次黑色像素清理过程的前后对比。请确保在边缘留下一些暗像素,以更好地混合我们将在 Hello World 应用中使用的暗背景。
图 7-10。使用橡皮擦工具清理我们的面具,准备创建一个阿尔法通道
反转 Alpha 通道选择病毒
现在,我们将再次使用按颜色选择工具,并点击当前图像角落的白色区域,通过刚刚清理的蒙版重新选择我们的新对象。这更精确地选择了病毒(或者更确切地说不是病毒),正如你在图 7-11 中看到的。接下来调用选择反转菜单操作,将我们的选择从非病毒改为病毒本身。
图 7-11。使用选择反转来反转选择
继续操作反转你的选择蒙版,得到一个正确选择的最终攻击病毒对象像素集合。现在,我们将使用编辑复制来复制攻击病毒像素到我们的系统剪贴板。
现在我们已经选择了攻击病毒图像对象的像素并将其复制到剪贴板,我们可以将它们粘贴到一个新文件中。当我们这样做时,GIMP 将在没有任何像素的像素位置放置一个 alpha 通道(透明)。
GIMP 2.8 还根据系统剪贴板中选择数据所使用的水平和垂直像素数,自动设置新的图像像素分辨率。在这种情况下,这是 90 乘 89,如在截图左下方带有 alpha 通道的新攻击病毒图像的窗口标题中的图 7-12 所示。在屏幕截图的右边,你还会看到新图像的 ARGB 通道,以及通过粘贴为新图像为我们创建的阿尔法通道。
图 7-12。使用粘贴为新图像创建新图像(左下)与阿尔法通道(右上)
在图 7-11 和图 7-12 中注意到同样重要的是,如果你把你的鼠标放在一个菜单项或者一个工具箱图标上,GIMP 会弹出一个工具提示,告诉你如果你选择使用它,这个工具会做什么。这是探索 GIMP 能做什么和了解它的特性的一个重要途径。
在图 7-12 中,不仅显示了我们将从下一个操作中生成的新的无标题图像,还显示了操作菜单选项(为了节省空间,我创建了一个二合一截图),您将看到编辑菜单有一个粘贴为子菜单,它本身有一个包含新图像粘贴功能的子子菜单,该功能将把剪贴板中复制的攻击病毒像素粘贴到一个全新的图像文件工具提示说:从剪贴板的内容中创建一个新图像。工具提示没有说明的是:GIMP 会在剪贴板中没有像素的像素位置自动创建 alpha 通道。
注意在图 7-12 右上角的 alpha 通道中,GIMP 在病毒所在的地方使用白色像素(开或可见),在我们最初编辑选择蒙版的地方使用黑色像素(关或透明)。还要注意,在图像通道上方,通道图标被表示为三维的 RBG 值层;这是考虑颜色通道的一个很好的方法,尽管最终是数学将这些 ARGB 值相加来定义每个像素的颜色和半透明级别。
以 GIMP XCF 本地格式 保存我们到目前为止的工作
因为我们的新图像是未命名的让我们使用文件另存为菜单序列给我们的带有 alpha 的新图像命名,并保存它,这样我们不会丢失所有的工作。我将文件命名为: attackvirusnew.xcf (GIMP 原生格式)用 attackvirusnew.xcf 放入 drawable-xhdpi ,如图图 7-13 所示。
图 7-13。将新的无标题图像保存为 attackvirusnew.xcf,这样我们的工作就被保存了
下一件事,我们想做的是把我们新掩盖的攻击病毒放回 96x96 像素图像容器的中心。我们需要在创建此图像素材的其他三个较低分辨率版本之前完成此操作,我们需要在 Android 项目资源可绘制子文件夹结构内的其他三个分辨率图像文件夹中使用这些图像素材。
使用画布大小工具将我们新蒙版的图像 重新居中
我们将使用图像画布大小菜单序列重新调整蒙版图像的中心,这将调用 GIMP 中的画布大小对话框。假设 GIMP 中新标题为 attackvirusnew 的窗口处于活动状态,让我们对 90x89 像素的图像应用画布大小图像操作,并将其转换为 96x96 像素的图像。
画布尺寸对话框如图图 7-14 所示,它允许您输入目标分辨率(画布尺寸),并通过使用对话框右侧的中心按钮来设置相对于每个 X Y 尺寸的偏移,或允许 GIMP 为您完成此操作。这就是我所做的,因为我是那种喜欢走捷径的人,因此 GIMP 在我这样做的时候在 X 和 Y 字段中分别输入了 3 个像素的值。
图 7-14。使用图像画布大小和中心功能,在 96 像素的正方形中重新定位蒙版图像
使用 GIMP 的文件导出(另存为)对话框 将新图像保存为 PNG32 文件格式
现在我们准备将我们攻击病毒的 alpha 通道版本保存为一个带索引的彩色 PNG 文件,这在 GIMP 中是使用文件导出菜单序列完成的。在 GIMP 中,使用保存会产生一个 XCF (GIMP 原生)文件格式,而导出通过其他流行格式保存。你可能更熟悉这个叫做另存为的软件操作。。。函数,在 GIMP 2 . 6 . 12 和更早的版本中是这样,在其他软件包中也是这样,所以你需要习惯 GIMP 2.8 和更高版本。
图 7-15 显示了 GIMP 中的导出图像对话框,在这里我们输入新的文件名 attackvirusalpha 和扩展名。png 来指定格式。
图 7-15。使用文件导出图像来导出带有 alpha 通道的新的 PNG8 版本的图像
在 GIMP 中,文件格式被指定为我们输入的文件名的一部分,或者如果您想查看可以导出的其他文件类型,您可以使用对话框左下角的选择文件类型(按扩展名)UI 元素。我通常使用。tif (TIFF 或标记图像文件格式)或。png,因为这些是无损格式,可以产生完美的视觉效果,但您也可以使用。jpg (JPEG,一种有损格式)甚至。tga (Targa,另一种无损格式)格式,如果你喜欢的话。
一旦我们将 attackvirusalpha 导出为 PNG8,我们需要进入我们的 OSes 文件管理器(Explorer 或类似的),并重命名一些文件,以便接下来我们可以访问 Eclipse 中图形的正确(alpha)版本。
图 7-16 中显示的是 Windows 7 资源管理器,我们已经将我们的旧版本(原始)attackvirus.png 重命名为 attackvirusold.png,然后将新的attackvirusalpha.png文件重命名为attackvirus.png,这在我们的 Hello World 应用的 XML 标记中被引用,如截图所示。
图 7-16。使用 Windows 资源管理器将 attackvirus 重命名为 attackvirus old,将 attackvirusalpha 重命名为 attackvirus
注意,我们的 GIMP attackvirusnew.xcf 工作文件现在也出现了;这不会影响我们在 Eclipse 中的项目,但是我们可以稍后删除这个文件,或者将它移动到我们硬盘驱动器上的另一个文件夹,比如说,一个用于原始艺术品或开发中素材的文件夹,如果您愿意的话。
使用图像调整工具 创建我们的其他分辨率密度图像版本
现在,我们的最高分辨率图像素材已经被遮罩,并包括一个 alpha 通道来定义其透明度,我们可以将图像调整到 80 像素、64 像素和 48 像素版本,这是我们的应用项目资源文件夹层次结构中的其他可绘制文件夹所需的(参见图 7-17 )。
图 7-17。使用图像缩放图像和三次插值将 96 像素图像调整为 80 像素
这个工作过程是通过图像菜单和缩放图像子菜单完成的,子菜单访问常用的缩放图像工具对话框,该对话框用于将数字图像调整到不同的分辨率。注意链图标,它允许你锁定图像纵横比,正如我们之前讨论的。
将宽度字段中的 96 替换为 80 ,然后注意,当您单击高度字段(或按回车键)时,其他字段也会自动键入 80,因为纵横比被锁定(默认)。第二组字段包含屏幕分辨率(像素密度),因此对于屏幕工作,将这些字段设置为 72 DPI(对于打印,使用 300 DPI 或更高)。
将缩放算法的插值质量下拉菜单设置为三次(在 Photoshop 中称为双三次),然后点击缩放按钮。
现在我们的图像是 80×80 像素,我们需要经历与之前完全相同的工作过程,从图 7-13 开始。将您的文件保存为 GIMP。XCF 如果你以后需要它,在 drawable-ldpi 文件夹中,然后将其导出为一个名为 attackvirusalpha 的 PNG8 ,然后进入你的文件管理器,这样你就可以将原始文件重命名为 attackvirusold ,并将新文件重命名为 attackvirus ,这样在我们刷新我们的项目视图后,正确的图像数据就可以用在你的 Android Hello World 应用中了。
对 drawable-mdpi 中的 64 像素图像素材,以及 drawable-ldpi 中的 48 像素图像素材再次进行这个工作过程,当你完成时,你将有机会熟悉这个工作过程。
现在,我们准备进入 XML 标记,将星星添加到 AttackPlanet 用户界面屏幕布局容器的背景中。值得注意的是,当您将图像缩放至 64 和 48 像素时,您应该从 96 像素版本的已保存图像开始,以便为图像大小调整算法提供最多的数据,从而获得最佳的缩放结果。
启动 Eclipse(如果尚未打开)并单击 activity_attack XML editing 选项卡,将您的 LinearLayout android:background 参数从 black (#000000)更改为@drawable/stars.png,并且不要忘记 Refresh !
每当您更改任何文件名或文件内容或添加新文件时,您必须总是右键单击您的项目文件夹并选择刷新或左键单击(选择)您的项目文件夹并点击键盘上的 F5 键。
现在让我们作为 Android 应用运行,看看我们使用深色背景和浅色文本的第二个用户界面屏幕后面的星星。
正如你将在图 7-18 中看到的,视觉效果和我们预期的一样令人难以置信,我们的星星通过图标和文本用户界面元素完美地显示出来。请注意,Android 中的文本元素有自己的 alpha 通道和不透明度设置,我们将在下一章中看到,届时我们将更详细地讨论用户界面元素(widget)图形设计技术。
图 7-18。 AttackPlanet UI 屏幕显示星星背景和白色背景上的伪装病毒
我们在 Hello World 应用的图形设计基础上取得了很大进展,现在我们一半的活动用户界面屏幕都展示了新的基于媒体的用户界面元素和背景。我们还学习了许多数字成像背后的基本原理,以及一些关于 alpha 通道和遮罩的数字成像工作流程。
摘要
在本章中,我们学习了数字图像和图形设计的基本概念,以及 Android 如何将这些概念融入其应用开发基础设施。为了在我们的 Hello World 应用中实践这些图形设计和数字成像概念,我们在一些活动中添加了 starfield 图像背景参数,并屏蔽了我们的 AttackVirus 以添加 alpha 通道,以便星星可以通过 UI 元素的透明区域无缝显示。
我们了解了 Android Drawable 类及其 direct 子类,它们定义了我们可以在 Android 应用中使用的所有各种类型的图形设计素材,包括形状、颜色、插入、图层、渐变和位图(位图图像,也称为基于像素的图像)。
我们学习了 Drawable 的更复杂的间接子类,比如动画、图像过渡、等级列表和状态列表。我们学习了 DrawableContainer ,并且我们可以创建我们自己的自定义间接多图形子类,使用那个超类作为它们的基础。
接下来,我们看了一下像素的概念,它是数字成像和数字视频的构建模块。然后,我们看了分辨率和长宽比的图像概念,以及如何使用简单的数学计算两者。
我们学习了色彩理论,以及数字成像中使用的不同色深,以及如何使用十六进制符号来表示颜色。我们学习了如何根据图像的色深计算图像中的颜色总数,以及关于索引色图像和 256 种颜色调色板的概念,调色板从真彩色图像中取样原色。
我们了解了图像通道和 RGB 颜色通道,以及第四个 alpha 通道,它保存像素的透明度值,以便图像可以通过使用层和混合模式用于合成更复杂的图像或特殊效果。我们通过Android . graphics . porter duff类了解了 Android 中的混合支持,并了解了它的主要混合模式,例如:叠加、屏幕、变暗、变亮、异或。
我们涵盖了 Android 中所有不同的图形文件格式,以及哪些格式更适合使用,为什么,以及哪些格式是无损的,哪些是有损的。我们介绍了 Android 4.0 和更高版本中新的 WebP 图像格式支持,并介绍了带有 alpha 通道图像的 PNG8 索引和 PNG24 真彩色和 PNG32 真彩色之间的区别。
最后,我们介绍了压缩概念和技术,如抖动,然后我们将学到的数字成像概念应用到 Hello World 应用中,通过将酷图标和文本与自定义背景 starfield 图像进行合成,添加更多视觉设计和专业功能。
在下一章中,我们将在本章建立的数字成像知识的基础上,添加自定义 UI 元素。****
八、Android 中的组合:高级图形用户界面设计
在本章中,我们将采用在前一章中掌握的基本数字成像概念,并构建更复杂的数字成像构造,以将更高级的基于图形设计(即基于位图)的用户界面元素添加到 Hello World 应用中。这将帮助你使你的应用在视觉上引人入胜,无论它是游戏、商业应用、实用应用,还是(希望)全新的、前所未有的东西。
我们讨论多状态用户界面元素,它改变用户在使用 UI 时看到的图像,并向最终用户提供关于他们正在触摸(或正在点击)什么的视觉反馈,以及哪个用户界面元素是最后使用的。我们将各种 UI 元素与其他图形设计元素组合在一起,创造出高级的、无缝的视觉效果;跨越几个不同标签及其参数设置的效果,但看起来像一个 UI 实体。
我们还完成了每个应用活动屏幕用户界面的设计和外观,增加了独特的(非标准)UI 元素,使我们的应用在所有视觉层次上都完全自定义。许多 Android 开发人员只实现和使用标准的用户界面小部件,因此他们的应用只具有标准的 Android 外观和感觉,而没有使用任何基于图像的视觉修改,这将使他们的 Android 应用与市场上的其他应用相去甚远。
多状态 UI 元素:正常、按下和聚焦状态
让我们将攻击星球活动用户界面屏幕上的 ImageButton 用户界面元素升级到下一个级别,并将其更改为多状态图像按钮。多态图像按钮交互改变按钮图形,这取决于用户正在做什么(或者刚刚做了什么)来与它们交互。在你的用户界面设计中加入一个交互式的用户界面元素,可以让用户对你的应用所展现的专业水平有一个全新的认识。
多状态图像按钮中的 正常状态是您第一次到达用户界面屏幕时看到的图形元素。在我们的攻击星球用户界面屏幕中,这些按钮的正常状态是我们已经有的按钮图标图形。你也可以称这个按钮状态为默认按钮图像状态。
每当用户触摸按钮时,多状态图像按钮中的 按下状态就会显示出来,这意味着用户在触摸屏上的手指触摸或导航按钮按下动作(如果用户使用导航键)。对于我们新的 AttackPlanet 多态 ImageButton 按钮按下的图像元素,让我们在图标周围添加一个金箍。通过这种方式,当用户触摸该用户界面屏幕上的攻击方法图标时,一个金环环绕该图标,显示 UI 元素正被触摸的交互反馈。
多状态按钮中的聚焦 状态发生在用户释放(停止触摸)该按钮并使用该用户界面元素时。用户界面元素被称为具有焦点,因为用户正在使用它,并且它不会失去焦点,直到用户选择另一个用户界面元素(触摸另一个 ImageButton UI 元素)或者附加到该 UI 元素的操作完成,例如我们的 Toast 消息当前在我们的 Java 代码中充当占位符。
我们利用图像按钮图标周围的银色圆圈来显示哪个按钮具有焦点,并且当前正在使用,因此哪个按钮是用户最后选择使用的。首先,我们需要为三种图像状态中的每一种创建 ImageButton 源文件,我们的正常状态已经创建好了,所以我们只需要使用 GIMP 2.8 中的一些高级多层合成技术来创建按下和聚焦的按钮状态。
创建我们的 ImageButton 多态图像源文件
让我们启动 GIMP 2.8,现在为我们的四个攻击模式图标,为 Android 要求的四个像素密度中的每一个都这样做。这是四个攻击模式图像按钮乘以四个像素密度,乘以两个按钮状态,这等于我们需要创建的三十二个不同的图像素材。如果你想做好,开发一个专业的 Android 应用无论如何都不是一件容易的事情。
添加我们已经拥有的四种像素密度的四个图像按钮,我们的四个攻击模式多状态按钮有 48 个图像。最后,为我们的退出攻击模式按钮添加另外四个像素密度素材,并且您有实施我们的攻击行星屏幕所需的 52 单独的数字图像素材,请注意,这不包括背景stars.png数字图像,这将这一个用户界面屏幕的总数带到 53 为每个屏幕密度创建这一高级用户界面屏幕设计所需的数字图像素材。我以为这是一本入门书!一旦我们读完这本书,你会发现你已经使用了数百种资源来开发你的 Android 应用!
GIMP 启动后,使用文件打开菜单序列在您的工作区文件夹中的 Android Hello_World 项目文件夹的 drawable-XHDPI 文件夹中找到您的 attackinvade 96 像素图形。
先设置好 GIMP 的工作环境,使用视图缩放
2:1 菜单序列将视图设置为 200%,,如图图 8-1 所示。
图 8-1。启动 GIMP 2.8,缩放至 200%,然后点击右边的图层标签(看起来像堆叠的白色牛皮纸)
接下来,点击右上角的图层标签(看起来像堆叠的白色牛皮纸),这样你可以看到我们的图像目前有一个名为【attackinvade.png】?? 的单层,这是我们刚刚打开的索引色 PNG8 士兵的脸图标图像。
如果您单击通道选项卡(一个堆叠的 RGB 颜色通道图标),您将看到一个包含 8 位图像数据的索引颜色通道。如果我们在图像处于索引颜色模式时添加我们的环层,环将只有几种颜色来描绘其 3D 金属外观,并且不会像我们希望的那样看起来像照片一样真实。我们需要做的是将我们的图像转换成真彩色模式,然后导入下一层图像合成功能。
在 GIMP 2.8 中,您的图像当前设置使用的色深可以在模式子菜单下的图像菜单中找到。如果您访问此图像模式菜单序列,您将看到几个关键的图像模式,包括 RGB (真彩色模式)灰度(单色模式)和索引彩色模式,您将看到这是当前选择的模式(该子菜单项旁边的点表示当前模式)。
让我们改变我们的图像的颜色模式,然后再继续我们的工作过程,这样我们就可以从我们在这个过程中可能添加的任何额外的真彩色图像层中获得最高的视觉效果。要将当前图像的颜色深度从索引(8 位)颜色模式更改为 RGB (32 位)颜色模式,选择图 8-2 所示的图像模式
RGB 菜单序列,将图像模式从 8 位更改为 32 位颜色。如果我们不这样做,我们添加的任何层将使用 256 色调色板的图像,这是从初始图像层(数据)。这将限制以后导入的任何图像(图层)使用这 256 种颜色;这将严重影响我们的视觉质量,尤其是当我们添加更多的层。
图 8-2。使用图像模式
RGB 菜单序列将图像模式从索引色转换为真彩色
一旦我们调用了图像模式
RGB 菜单序列,将我们的 GIMP 图像编辑环境切换到真彩色模式,我们可以再次单击右侧的通道选项卡,可以看到我们现在已经从一个通道(图像的 256 色索引调色板)变为一个图像编辑环境,其中我们有四个颜色通道(或三个颜色通道和一个透明通道)。这在图 8-3 中显示,由于我们的层被选中(以蓝色突出显示)用于图 8-1 中,所以我们的四个通道也被选中,以保持从层编辑模式(tab)到通道编辑模式(tab)的选择。
图 8-3。点击右边显示的通道选项卡,确认现在有红色、绿色、蓝色和阿尔法通道
图像编辑软件是模态,也就是说,无论工具、颜色和编辑模式在任何给定点被设置和激活,都会影响当你在图像上使用当前工具时会发生什么。因此,如果您处于图层模式,并且您在图层模式下编辑图像,这将影响图像中所有通道的数据。但是,如果您只想编辑图像的绿色通道上的数据,您可以切换到通道编辑模式进行编辑,方法是在使用这些编辑工具之前,单击通道选项卡和绿色通道(任何编辑工具都可以在任何模式下使用)。
这种模态软件操作在将软件特性组合到用户想要创建的任何工具集中的能力方面更加强大。因此,它产生更专业的结果,但也更难以有效地使用。在使用模态软件包的过程中,用户必须始终跟踪在任何给定时间什么模式、设置和工具当前是活动的。这些编辑模式、工具和设置实时结合,最终创建所使用的精确编辑功能。
导入我们的金环图像进行合成
现在我们准备进入正题,将我们的金箍棒图像导入到它自己的图层中,来设置我们的图像合成操作。为此,我们将使用 GIMP 的文件打开为图层……菜单序列,如图图 8-4 所示。正如工具提示告诉我们的,这将在我们的图层选项卡中打开一个图像文件作为图层。选择此菜单选项后,您会看到一个对话框,让您找到名为GoldHoop96.png的图像素材,这是一个 truecolor PNG32 文件,带有定义透明度的 alpha 通道。
图 8-4。使用导入环层,文件打开为层…菜单序列
选择此文件并打开它后,它会作为“层”标签中自己的层合并到项目中。通道选项卡显示合并的 RGBA 通道数据,如图 8-5 所示,您的图像视图显示无缝合成在士兵脸上的金箍。接下来,我们需要将这个包含士兵面部的新图层向下移动,这样面部就会移动到环的中心。我们这样做是为了让我们的士兵看起来像是在透过铁环看,这样眼睛就不会被遮住。值得注意的是,GIMP 使用 RGBA 通道排序,而 Android 使用 ARGB,也就是说,当在 Android 中以十六进制指定通道时,前两个位置是 alpha 值,而不是后两个位置,就像使用 Photoshop 或 GIMP 的人可能会简单地假设的那样。
图 8-5。导入箍层,并在 RGBA 通道中进行数据合并,如右边通道选项卡所示
通过移动图层位置将士兵的脸放在金环的中心
要将面部层向下移动到环中,选择带有面部图形的下层,如图 8-6 中的蓝色所示,然后也单击移动工具(四箭头十字,位于第二行中间)来设置模态编辑操作,以移动(移动工具模式)攻击入侵层(层编辑模式)。
图 8-6。选择图层标签和移动工具,将面向下移动到环中,这样顶部与环的内部相切
一旦你做到了这一点,你就可以用你的鼠标点击并拖动正面直接进入环的中心,然后,如果你喜欢,你可以使用键盘上的左右箭头键将图像移动到适当的位置,一次一个像素。这个微调你的图像居中操作,直到合成图像看起来像图 8-6 中的一样。
擦除金环外不需要的像素
现在,我们所要做的就是选择图 8-7 (第四排第二个图标)所示的橡皮擦工具,将 GIMP 置于擦除模式,擦除士兵脸部和衬衫在铁环外的部分。请注意,我们保持底部(面部)图层处于选中状态,因此我们的图像编辑软件模式包括:橡皮擦工具模式、 RGB 真彩色模式和图层编辑模式设置为 attackinvade.png 图像图层(仅在该图层上将橡皮擦工具编辑同时应用于所有四个通道)。
图 8-7。使用橡皮擦工具移除士兵图像中超出环圈的部分,如图所示
导出我们的攻击按下按钮图标状态
现在,我们准备好将 导出我们的 attackinvadepress 按钮图标状态到我们的 drawable-xhdpi 文件夹中,我们的三个图像状态按钮中的两个(正常和按下)将准备好用 XML 代码实现。
为此,我们使用 GIMP 的文件导出菜单序列,将文件命名为attackinvadepress.png、,最后,找到我们的workspace/Hello _ World/RES/drawable-xhdpi文件夹,我们要在其中保存这个 96 像素高分辨率(超高密度像素图像)的数字图像文件。在所有这些文件名、文件夹路径名和图像类型(。png 扩展名)信息,则可以点击导出按钮完成此操作,如图图 8-8 所示。
图 8-8。使用导出图像对话框保存真彩色。drawable-xhdpi 文件夹中名为 attackinvaderpress.png 的 png 文件
请注意,因为我们的 GIMP 颜色深度模式设置为 truecolor RGB,所以我们现在使用 alpha 通道导出 PNG32 truecolor PNG 文件,但我们的文件大小仅增加了一倍,从 8K 增加到 16K,因为我们删除(擦除)了大量图像数据,并且因为 PNG32 压缩算法非常高效。
如果可以很好地优化数据足迹,那么几乎总是希望使用 PNG32 格式而不是 PNG8 格式,因为在 PNG32 文件中,图像数据的质量水平以及 alpha 通道比在 PNG8 文件中好得多(使用了更多的颜色和透明度值)。
创建我们的 attackinvadefocus 图像按钮状态
最后,我们需要创建我们的 attackinvadefocus 图像按钮状态。这要容易得多,因为我们的第三个按钮状态的大部分图像编辑工作过程已经为我们的 attackimagepress 图像按钮状态完成了,包括改变图像模式,移动士兵的脸,以及擦除圆圈外围的像素。我们现在要做的就是加一个银箍来表示专注。
使用文件打开为图层……功能,如图图 8-4 所示,通过找到SilverHoop96.png图像,并将其导入到我们的 GIMP 2.8.4 项目中,将另一个图层添加到我们的图像合成堆栈中。
因为结果看起来很棒,如图 8-9 所示,我们可以继续再次使用我们的文件导出工作流程,如图图 8-8 所示,只是这一次,使用文件名:attackimagefocus.png,并使用相同的 drawable-xhdpi 文件夹位置,因为这是一个 96 像素的超高密度像素图像文件。
图 8-9。使用文件打开为层…菜单序列打开 SilverHoop.png 并将其添加到层堆栈上
创建我们的其他分辨率密度多态图像按钮图标
要创建 attackinvadepress 和 attackinvadefocus 的其他三种尺寸(80、64 和 48 像素)版本,请遵循图 7-17 中上一章概述的工作流程,使用图像缩放图像… 菜单序列和图像缩放对话框,将图像从 96 像素均匀缩放到 80 像素(之后为 96 像素到 64 像素和 96 像素到 48 像素)。确保在每个工作过程中点击 SilverHoop96 图层上的眼睛图标关闭其可见性,然后使用文件
导出将 drawable-hdpi 中的文件保存为attackinvadepress.png。
一旦完成,你可以再次点击眼睛图标,重新打开 SilverHoop96 层的可见性,并再次执行文件导出操作,在你的 drawable-hdpi 文件夹中创建一个attackinvadefocus.png。一旦你生成了这两个文件,使用编辑
撤销菜单序列回到你的 96 像素版本,并做这里概述的整个工作过程来创建 64 像素 MDPI 和 48 像素 LDPI 入侵图标素材。需要注意的是,在只有触摸屏的 Android 设备上(比如我们的 Nexus S 模拟器)不使用焦点按钮状态。这种聚焦状态仅在使用轨迹球、小键盘或键盘导航的 Android 设备上使用。一旦用户触摸了触摸屏(如果设备有触摸屏的话),Android 进入触摸模式,在这种操作模式下,不会使用聚焦按钮状态,但它仍然会安装并在不使用触摸屏的其他 Android 设备类型(如 ITV)上工作。
在 XML 中实现多态按钮:Android 的选择器标签
一旦您完成了上一节中概述的其他三个图像按钮图标(炸弹、病毒和激光)的工作过程,并创建了我们将需要在攻击星球活动屏幕中实现多态 ImageButton UI 元素的按下和聚焦状态的所有 32 个(4 x 4 x 2)图像,我们就可以开始编写实现 <选择器> 标记的 XML 文件(全部四个)。选择器标签允许 Android 从三个不同的按钮图像状态 <项目> 标签中进行选择,这些标签来自我们现在在 drawable resource 文件夹中的图像素材。请记住,我们的正常或默认状态图像已经完成,因此这个活动用户界面屏幕总共需要 48 个多态图像素材,加上 4 个 attackexit 图标的特定分辨率图像,因此总共有 52 个图像,当然不包括我们的 stars 背景图像。
创建一个新的资源类型的 Android XML 文件:Drawable
我们要做的第一件事是创建一个新的 Android XML 文件。右键单击你的 Hello_World 项目的 drawable-xhdpi 文件夹,然后选择新建 Android XML 文件创建向导对话框。该对话框如图 8-10 中的所示,并自动为您创建 XML 文件。
图 8-10。用一个<选择器>根元素创建一个新的 Android XML 文件
将资源类型设置为可提取,将项目设置为 Hello_World ,并将文件命名为:attack _ invasive。因为 Android 在 XML 和 Java 代码中不使用文件扩展名,所以确保不要将文件命名为 attackinvade,因为这是我们的 PNG8 文件名。因为 Android 文件名中允许使用下划线,所以在单词 attack 和 invasive 之间使用下划线,以模拟这些单词之间的空格字符。
接下来,找到名为选择器的根元素,并选择它作为我们的父标签(根元素)容器,最后点击完成按钮创建attack _ invasive . XML文件。在图 8-11 中需要注意的是,Android 将 attack _ invasive . XML 文件放在了一个 /res/drawable/ 文件夹中,这是作为新的 Android XML 文件操作的一部分而创建的。这是因为该文件引用了多个分辨率资源,所以它只在 /res/drawable 文件夹中出现一次,而不是在每个 drawable-dpi 文件夹中。
图 8-11。多态 ImageButton 的 XML 标记:在 attack _ invasive . XML 中的<选择器>标签内添加<项目>标签
将 Android
现在我们已经在编辑窗格中打开了 attack _ invasive . XML 文件,让我们添加三个指定三种图像状态的标签。项目标签包含一个用于状态定义的参数和一个用于可绘制素材定义的参数。按下并聚焦的多状态项目标签编码如下:
<item android:state_pressed="true" android:drawable="@drawable/attackinvadepress" />
<item android:state_focused="true" android:drawable="@drawable/attackinvadefocus" />
默认或正常的按钮状态项标签没有多态参数,只有图像的可绘制素材引用参数,这个<项>标签是最后添加的,如图图 8-11 所示。
在这种情况下,选择器容器中这些
从我们的活动屏幕布局 XML 引用我们新的多态 ImageButton XML 定义
我们需要做的最后一件事是打开我们的 activity_attack.xml 用户界面屏幕布局定义的选项卡,并将 attackinvade 按钮的引用改为指向attack _ invasive(XML)而不是 attackinvade (PNG)。这就是 Android 实现 ImageButton 所经历的逻辑链,现在它在如何处理用户界面元素的使用中包括了多状态 XML 选择器逻辑。
为了实现其他三个炸弹、病毒和激光攻击模式多状态按钮,我们必须再经历三次图 8-10 和图 8-11 所示的工作过程,添加 XML <选择器>对 attack_bomb.xml、attack_virus.xml 和 attack_laser.xml 多状态图像素材定义 XML 文件的定义。
每个选择器 XML 文件必须指向 drawable 子文件夹中正确的 PNG8(正常)和 PNG32(按下和聚焦)数字图像素材文件名,然后我们可以在 activity_attack XML 文件中引用这些新的 XML 文件定义,而不是引用默认的图像状态 PNG8 文件名。
一旦我们的四个攻击模式按钮中的每一个都完成了所有这些工作过程,我们的攻击星球用户界面屏幕将完全转换为多状态 ImageButton 用户界面元素!
请记住,当您接下来使用 Nexus S 模拟器测试用户界面屏幕时,聚焦图像状态不会显示,因为我们正在模拟的 Android 设备是一种仅具有触摸屏的消费电子产品。
图 8-12 显示了一个 Eclipse IDE,在包浏览器窗格的 /res/drawable 文件夹中(用蓝色突出显示)有我们刚刚创建的 attack_bomb、attack _ invasive、attack_laser 和 attack_virus 文件的 XML 文件(还要注意这些文件的标签仍然在 Eclipse 主编辑窗格的顶部打开)。
图 8-12。activity _ attack . xml 中的新 XML 标记引用了新的多状态图像按钮选择器 XML 文件
在图 8-12 中,我们的 activity_attack 用户界面屏幕定义的 XML 标记还显示,我们已经更改了每个 ImageButton 标签的所有 android:src 参数,以指向 XML 文件名,在每个文件名中间使用下划线,而不是指向中间不使用下划线字符的 PNG8 文件名。
最后,我们需要测试应用,以及我们新的多状态 ImageButton 用户界面元素,通过作为 Android 应用运行的工作流程。启动 Nexus S 模拟器,点击《入侵星球》的士兵脸图标,当你点击并按住鼠标左键(通过鼠标按下操作)时,你会看到士兵脸移动到环的中心,如图图 8-13 所示。
图 8-13。在 Nexus S 模拟器中运行我们的应用,测试我们的攻击外观 ImageButton 多状态图像按钮
确保并测试所有四个攻击模式按钮图标,以确保您已经正确实现了所有活动 XML 标记和
现在是时候将我们的配置行星用户界面屏幕提升到一个更专业的水平了,通过添加一个与空间相关的背景图像,然后改变我们的用户界面元素在新的碰撞星系图像上的合成方式,以便我们增强这个新 UI 设计的对比度(可读性)。
合成我们的 UI 元素:Alpha、Color、Gravity 和 TextStyle
让我们把我们的“配置一个星球”用户界面提升到下一个层次,因为这是关于 Android 图形设计合成的章节。首先,我们将添加一个视觉上令人印象深刻的背景,称为空间,它显示了两个碰撞的星系。接下来,我们将为用户界面屏幕元素标签添加新的合成和桌面发布(DTP)参数,以最大化它们的对比度(可见性),并使用新的背景以及新的字体、间距、重力和 alpha 效果来改善 UI 的外观。
首先,像以前一样,让我们将 space1280x720.png、space1024x600.png、800x480.png 空间和 480x320.png 空间文件复制到 XHDPI、HDPI、MDPI 和 LDPI 文件夹中,然后将它们全部重命名为space.png,这是我们稍后将在代码中引用的文件名。
修改 activity_config XML 参数,将空间背景图像与 UI 元素合成
我们要在我们的 activity_config.xml 文件中做的第一件事情是使用 @drawable/space 引用参数添加一个 android:background 参数指向我们新安装的space.png文件,如图图 8-14 所示。在添加新的数字图像背景后,如果您单击编辑窗格底部的图形布局选项卡,您将会看到我们现有的用户界面元素已经基本消失,因此我们必须对当前的 XML 标记进行一些相当剧烈的参数更改,以使我们的 UI 在这个空间背景下看起来非常专业。
图 8-14。编辑我们的 LinearLayout,添加 space.png 背景和按钮标签,添加白色 textColor 值
先从按钮标签开始,添加一个 android:textColor 参数,设置为 #FFFFFF 的白的十六进制值,如图图 8-14 所示。再一次,让我们使用图形布局选项卡作为快速简单的屏幕预览,看看我们的按钮是否变得更容易阅读。正如你在图 8-15 中看到的,这解决了按钮 UI 元素的问题,它们看起来很棒。然而, EditText 文本字段几乎不可能被识别,这将需要大量新的标签参数来使它们既可读又最终在外观上专业。
图 8-15。在图形布局编辑器中预览我们的新按钮和背景参数
使用背景色和 Alpha 透明度提高我们的编辑文本字段的对比度
为了让我们的 EditText 文本编辑字段与这个令人惊叹的碰撞星系背景一起工作,我们将不得不使用明亮的背景颜色(白色)、一些不透明度(alpha 通道)以允许星星穿过,以及一些更好的字体、对齐和编辑文本字段间距来匹配(平衡)屏幕布局对面的按钮间距。
我们想要添加的第一个 EditText 标记参数是 android:background 参数,我们将它设置为白色的十六进制颜色值(#FFFFFF ) ,这样我们就可以看到我们在这个深色背景上做了什么。为了使这个白色背景有点像半透明,让我们也添加一个 android:alpha 参数,它使用从零(透明)到一(不透明)的十进制范围来控制 UI 元素的不透明度 (alpha 通道值)。我们使用 50%的透明度设置 0.5 开始。现在我们可以看到我们的文本字段,接下来我们可以调整我们的Android:layout _ margin top参数来隔开它们。
对于第一个 EditText 标签,这相当于 13dp ,其余的需要一个额外的 10 dp ,或者一个 23dp 的最终设置,以使它们与每个按钮完全相对,正如你在图 8-16 中的 XML 标记中看到的。
图 8-16。编辑我们的 LinearLayout 容器标签和 EditText 标签及其参数
因此,现在我们的文本字段从上到下与它们各自的按钮完全隔开,我们需要从左到右隔开它们。他们正在触摸 UI 屏幕的右侧,这看起来很不专业。我们来调整一下,给 UI 屏幕设计增加一些对称性。
因为我们希望将所有 EditText 字段移动一个平均距离 5dp ,类似于屏幕另一侧按钮上的间距,所以让我们在保存它们的 LinearLayout 容器中用一个单个标签来完成,而不是添加六个标签(每个 EditText 元素标签一个)。
一旦你添加了如图图 8-16 所示的Android:Layout _ margin right = " 5dp ",点击你的图形布局标签,看看它看起来怎么样。为了更精确地了解它将如何排列,使用作为 Android 应用运行工作流程,并在 Nexus S 模拟器中查看当前的 UI 设计;你会发现它开始变得真正令人惊叹。
格式化 EditText UI 元素中的文本特征
现在我们已经将 EditText UI 容器与屏幕及其背景图像分隔开来并完美地合成在一起,让我们设置一些参数来优化包含在文本字段中的文本。对于数字数据输入文本字段,让我们指定一种等宽字体,这使得数字输入特别易读;这是通过Android:typeface = " monospace "参数实现的。
为了让文本在屏幕上更易读,我们还可以添加一个 android:textStyle 参数,设置字体使用粗体文本,使字符更粗,更容易在较小的显示器上阅读。最后,为了使文本在文本字段内看起来间隔均匀,并且在显示器的右侧对称,让我们使用一个中心参数将文本在每个编辑文本数据输入字段内居中。这通过使用设置为中心的 android:gravity 参数或 android:gravity="center" 标记来实现。Android gravity 参数以 Android 中的任何东西为中心,而不仅仅是文本,所以熟悉这个参数,并在其他类型的用户界面小部件和标签中寻找它,因为 gravity 可能非常有用。
现在是时候使用我们的 Run As Android Application 工作流程,并在 Nexus S 模拟器中预览所有这些用户界面元素标签参数修改和优化的结果。正如您在图 8-17 中看到的,配置活动用户界面屏幕看起来真的开始专业了。
图 8-17。在 Nexus S 模拟器中预览我们完成的配置活动屏幕
接下来,让我们通过添加自定义背景图像和我们所在星球的图像视图,将我们的 Hello World 应用主屏幕提升到下一个级别。我们将在稍后的动画章节中定制这些。最后,我们将为文本着色,以匹配星系背景图像中耀眼的等离子体光颜色。
升级我们的应用主屏幕:添加 ImageView 标签和 textColor 参数
我们需要做的第一件事是设置我们的全新主屏幕,将 galaxy480x320.png、galaxy800x480.png、galaxy1024x600.png 和 galaxy1280x720.png 的图像复制到它们各自的可绘制子文件夹中,就像我们之前做的几次一样。一旦这些文件分别位于 LDPI、MDPI、HDPI 和 XHDPI 文件夹中,只需将它们重命名为galaxy.png,我们就可以在 activity_main.xml 文件的 XML 标记中引用它们,该文件定义了我们的 Hello World 应用主屏幕用户界面设计。一旦我们做到这一点,我们就可以微调我们的 UI 元素颜色,以匹配银河图像。
因为我们想让这个新的背景图片跨越整个布局容器,我们将把参数 android:background 添加到 RelativeLayout 容器标签中,并将其设置为我们的galaxy.png文件的 @drawable/galaxy 引用路径,如图 8-18 顶部所示。
图 8-18。添加 galaxy.png 背景图片;定制 textColor 参数以匹配图像中的等离子颜色
使用图形布局选项卡预览图像,请注意,由于低对比度(可读性),文本现在需要我们的注意。让我们将新的文本颜色与背景图像右下方等离子体中突出的橙色和黄色色调相匹配。
让我们将左边的标签文本设置为橙色,以平衡背景图像右上角的橙色,并将数据文本设置为黄色,以匹配背景图像中心的黄色等离子体耀斑。为了创建匹配的橙色,通过使用两个 F 值将红色通道保持在最大强度,并通过将这两个值设置为 D ,在绿色通道中使用较小的强度(记住相等的红色和绿色值导致黄色),并通过将这两个值设置为 A ,在蓝色通道中使用更小的强度,因此明亮等离子体橙色的 #RRGGBB 值为因为我们知道相等的红色和绿色值会产生黄色,所以我们想要使用 #FFFF99 来获得一个完全饱和的黄色,类似于主屏幕背景图像中间等离子耀斑中的颜色。
更改你的 activity_main.xml 标记中所有的 android:textColor 十六进制值,如图图 8-18 所示,然后使用 Run As Android Application 工作进程,看看我们的新主屏幕此时是什么样子。正如你在图 8-19 中所看到的,新的屏幕在视觉上引人注目,看起来非常舒适,所以现在让我们添加一个行星图像,让用户也能看到他们当前所在的行星。
图 8-19。在 Nexus S 模拟器中预览我们新的 galaxy.png 背景图像和新的文本着色
要添加一个行星图像到我们的 UI 屏幕,使用 ImageView 标签和相对布局定位参数,如图图 8-20 所示。标记及其参数的 XML 标记应该如下所示:
<ImageView android:id="@+id/imageEarth"
android:src="@drawable/earth"
android:background="#00000000"
android:layout_width="wrap_width"
android:layout_height="wrap_width"
android:layout_marginLeft="12dp"
android:layout_marginTop="8dp"
android:layout_below="@+id/textView8"
android:contentDescription="@string/content_desc_earth" />
图 8-20。添加一个 ImageView 标签来保存我们的星球,并配置其背景 alpha 和边距参数
我们将参考我们的earth.png源图像,并设置一个# 00000000(# aarggbb)的 alpha 通道,它是 100%透明的。我们这样做是为了让背景星系图像的合成效果完全无缝。接下来我们将使用一个 android:layout_below 参数来引用它上面的 TextView,最后分别使用 12dp 和 8dp,的 marginLeft 和 marginTop 参数设置来完美定位我们的星球图像。
现在,让我们在 Nexus S 模拟器中,通过作为 Android 应用运行工作流程,对我们的工作进行最后的预览,注意我们现在有了当前世界的视觉图像,以及主应用主屏幕上那个世界的特征和设置,如图图 8-21 所示。
图 8-21。在 Nexus S 模拟器中预览我们的 Hello_World 应用主屏幕
这些用户界面设计的改进为我们的用户提供了关于他们正在处理的当前世界的视觉反馈,并且使应用的外观 100%更加专业。
在未来的章节中,我们将使用一些其他技术和标签参数,用动画和很酷的特殊图像效果来增强这个 UI 屏幕(以及我们的其他用户界面屏幕)。所以请注意,我们将在未来的动画和视频章节中使用类似的合成概念和技术,逻辑上需要合成,所以你很快就有机会建立在你最近的合成知识上,并获得合成经验。
因为这是最后一章专门讨论图形设计和合成,我想尽快为这个应用准备好所有的主要图像,并让它与我们将在“这个世界之外”Hello World 应用中使用的所有主要用户界面元素无缝合成。
接下来我们将最终升级我们的 Hello World 应用启动图标,因为众所周知,魔鬼在细节中,它不仅显示在 Android 设备图标启动屏幕上,还显示在应用的顶部。
自定义活动屏幕标题和应用图标:细节决定成败
我们 需要做的第一件事就是使用 Windows Explorer 文件管理实用程序,将 saturn36.png、saturn48.png、saturn72.png 和 saturn96.png 文件分别复制到 LDPI、MDPI、HDPI 和 XHDPI 可绘制文件夹中,如图图 8-22 所示。将 ic_launcher 重命名为 ic_launcher_old,将 saturn36.png 文件重命名为 helloworldicon.png。稍后,我将向您展示如何使用应用的 AndroidManifest.xml 文件中的 XML 标记来实现定制的应用图标文件名。
**
图 8-22。重命名 ic_launcher_old 并添加新的 helloworldicon.png 应用图标
一旦您的新自定义应用图标图像文件位于其各自的 drawable-dpi 文件夹中,接下来我们需要做的事情是在我们的资源值 /res/values/ 文件夹中的 strings.xml 文件中创建一些自定义活动用户界面屏幕标题,在 Android 中称为标签。
在 strings.xml 文件中创建活动屏幕标题常量
点击 Eclipse central 编辑窗格右上角的数字(在图 8-23 中是 14)下拉打开文件菜单。接下来,选择 strings.xml 文件进行编辑,将 app_name 文本值改为 Hello World - Home Screen 。将那个 <字符串> 标签再复制粘贴四次,这样我们就可以为四个应用活动用户界面屏幕中的每一个添加标题(标签)。
图 8-23。在 strings.xml 中添加<字符串>标签常量,用于我们的活动用户界面屏幕作为自定义标题
用 name = " Activity _ Title _ type _ planet "参数和文本值 Hello World - Screen Title 来命名每个
<string name="activity_title_new_planet">Hello World - Create a Planet</string>
由此产生的五个 <字符串> 常量标签来标注我们的 app 活动屏幕标题可以在图 8-23 中看到。
在我们的 Hello_World AndroidManifest XML 文件中配置活动屏幕标签
一旦我们在 strings.xml 文件中定义并放置了字符串常量,我们就可以从我们的 AndroidManifest.xml 文件中引用它们,我们将在这里为每个活动屏幕设置 android:label 参数,这将在每个活动的顶部安装一个定制的屏幕标题。
点击 Eclipse central 编辑窗格右上角的数字(在图 8-23 中是 14 ),下拉打开文件菜单。接下来,选择 AndroidManifest.xml 文件进行编辑,并在 <应用> 标签中更改当前的 android:icon 参数,以引用一个 @drawable/helloworldicon 引用,这是对我们新的 Saturn 图标图像 PNG 文件的引用。
接下来,在我们的每个 < activity > 标签中添加一个新的 android:label 参数,该参数引用了我们刚刚通过@string/activity_title 按名称创建的新的 < string > 标签常量,如图图 8-24 所示。
图 8-24。向我们的 AndroidManifest.xml 添加一个新的应用 android:icon filename 和 android:label 屏幕标题
对于 NewPlanet.java 活动用户界面屏幕,在应用顶部放置自定义标题的参数是android:label = " @ string/Activity _ title _ new _ planet "这相当简单,只要您知道 Android 希望您将所有内容放在哪里。因此,以 new planet Activity
<activity android:name="chapter.two.hello_world.NewPlanet"
android:label="string/activity_title_new_planet" />
现在让我们使用 EclipseRun As Android Application命令,启动 Nexus S 模拟器,看看新图标和屏幕标题在我们的应用顶部是什么样子。正如你在图 8-25 中看到的,结果是专业的,比之前的屏幕标题(Hello_World)和应用图标(Android Bot)有所改进,因为它们现在更具体地说明了我们的应用是什么以及它在做什么。
图 8-25。新的 Hello World 主屏幕,带有新的屏幕标题和应用图标
单击 Nexus S 模拟器右上角的菜单按钮,让我们看看使用新图标和屏幕标题后的活动 UI 屏幕。正如你在图 8-26 中看到的,我们的屏幕(我们已经完成的三个,我们将在未来实施的第四个 TravelPlanet 活动屏幕第十一章和第十二章涵盖数字视频)看起来很棒,并且由于对 UI 细节的关注,看起来更加定制和专业。
图 8-26。预览我们的 Hello World 应用活动屏幕,新的屏幕标题标签和图标已就位
注意,通过使用这个 android:label 参数来创建我们的活动屏幕标题,我们不必在屏幕容器的顶部使用 TextView UI 元素,因此我们只需要在两个屏幕上使用 TextView 来指示用户在到达屏幕时做什么。在 Configure Planet UI 屏幕上,我们的按钮标签要求用户采取行动,因此我们不需要在屏幕上显示任何 TextView UI 元素。
还要注意,我们的活动用户界面屏幕标题(标签)与我们的选项菜单标签非常匹配,因此在整个应用用户界面基础设施中为用户提供了出色的连续性。
现在,我们已经将基本的图形元素与应用的用户界面元素组合在一起,我们可以在接下来的几章中继续实现更高级的新媒体元素,如动画、数字音频和数字视频。
摘要
在本章中,我们仔细研究了一些更高级的用户界面元素和图像合成概念,将 Hello World 应用的用户界面设计提升到了一个新的视觉层次。我们学习了多状态用户界面元素。这些元素改变图像来表示用户界面元素的当前状态,即其默认或正常状态对其被触摸或按下状态,以及其使用中或聚焦状态。
我们学习了如何使用 GIMP 2.8.4 创建多通道图像,并学习了高级模态成像软件操作,以及数字成像颜色模式、编辑模式和工具模式。
我们使用这些模态数字成像工具和软件设置为我们的攻击星球图标创建了多状态图像按钮,并创建了 50 多个数字图像素材,用于我们 Android 应用中所有不同的图像分辨率密度级别。
我们学习了如何使用 XML <选择器> 标签及其子标签 <项目> 来定义使用 XML 标记的多态 ImageButton 用户界面元素,这样我们就不必编写任何 Java 代码来在 Android 应用中实现多态用户界面元素。
我们了解到,所有定义图像相关结构的 XML 文件,比如我们的
我们了解到, focus 按钮状态并不用于只有触摸屏的 Android 设备(如 Nexus S emulator)。聚焦状态用于具有轨迹球、导航键、迷你键盘或外部键盘导航硬件的 Android 设备。
我们了解到,一旦用户触摸了触摸屏(如果安卓设备有触摸屏的话),安卓操作系统就会进入触摸模式。在这种触摸操作模式下,不会使用对焦按钮状态,但仍会安装,并将在其他不使用触摸屏的 Android 设备类型(如 ITV)上工作。这对开发人员来说意味着我们仍然需要在我们的 Android 应用开发中实现和适应焦点按钮状态。
我们在活动屏幕用户界面中添加了新的背景图像,以取代黑白背景色。接下来,我们添加了新的标签和新的参数,通过颜色匹配文本、不同的字体、文本对齐、新的图像、改进的边距和基于 alpha 通道的半透明性来升级这些屏幕,以实现更专业的视觉效果。
我们通过 android:gravity 参数了解了 Android 中 gravity 的概念,该参数允许我们使用重力将用户界面元素推到或拉到某个方向。用户界面重力是一个概念,在一个关于行星和空间的应用中,这个概念对我们来说是非常合适的。
我们了解了对比度的概念,以及在颜色亮度和饱和度以及相应的背景图像暗度之间保持高对比度的必要性,并通过为我们的主屏幕开发与背景星系图像中的颜色相匹配的文本颜色,了解了更多关于 32 位# aarggbb颜色理论的知识。我们通过两个 RR(红色)、GG(绿色)和 BB(蓝色)通道插槽调整相对的十六进制颜色通道强度来实现这一点,这使我们可以控制每个通道的 256 种颜色强度变化,总颜色控制相当于 16,777,216 种颜色变化。
最后,我们学习了如何在我们的 AndroidManifest.xml 文件的标记开始处的 <应用> 标签内使用 android:icon 参数来实现我们自己的定制应用图标、以及我们自己的定制图标命名方案。我们还学习了如何通过在 AndroidManifest.xml 文件中使用新的 android:label 参数来实现自定义活动屏幕标题,该文件位于我们的 < activity > 标记内,靠近我们的 Hello_World Manifest XML 标记应用定义逻辑的底部。
在下一章,我们将开始为我们的 Hello World Android 应用的用户界面屏幕添加位图或基于帧的动画和图像特效,同样,我们将使用 XML 标记(大部分)来定义,甚至实现这些动画和特效元素。
一个没有大量超酷特效的太空应用算什么?我猜是卖得不太好的那种。让我们确保你不会落入那个陷阱,确保你在下一章中通过利用 Android 的内置动画类来实现光栅动画和数字成像特效,这些动画类正是为了这个目的而提供给开发者的。**
九、Android 图像动画:使用 XML 结构的基于帧的动画
在本章中,我们将通过使用 XML 标记向应用用户界面屏幕添加动画视觉元素,将我们已经给人留下深刻印象的 Hello World 应用带到新媒体认知的下一个层次。
Android 有两种不同类型的动画,第一种,我们将在本章重点介绍的,是基于帧的或光栅动画。
光栅动画使用一系列位图(称为 cels (如果你在 2D 动画行业)或帧(如果你在电影或数字视频编辑行业)。第二种类型的动画是程序或矢量动画,我们将在第十章中介绍,因为它完全不同。
在本章中,我们将介绍基于帧的动画理论,以及在从 LDPI 到 XHDPI 的所有四种不同图像像素密度分辨率下实现动画所需的 XML 逻辑结构。这将使我们的 Hello World 应用的 UI 设计达到一个新的水平。
基于帧的动画:概念和数据优化
由于基于光栅或帧的动画是由位图图像组成的,我们在过去两章中学习的关于图形设计和数字成像的许多技术概念在本章中也同样适用,并且每一个都在光栅动画中扮演一个角色,所以在本章中你也可以更多地练习这些数字成像概念。
可以使用索引色或真彩色位图图像来创建光栅动画。这些图像可以有一个 alpha 通道,因此基于帧的动画可以利用透明度,这对我们的特效来说是个好消息。我们可以用动画做同样的合成技巧,就像我们在前两章中对静态图像做的一样。
与我们的静态图像类似,我们还需要为每种屏幕密度提供密度匹配的图像,正如我们的静态图像一样,Android 会处理哪些像素密度用于哪些屏幕,只要我们的动画单元或帧支持所有不同的像素密度级别,就像我们在本书中一直虔诚地做的那样。
静态图像优化和运动图像(动画)优化之间的主要技术差异涉及动画的帧速率。帧速率通常用每秒帧或 FPS 来描述,这是一个定义一秒钟内显示多少图像的数字,相当于图像帧在屏幕上相互替换的速度。
更高(更快)的帧速率提供了更平滑的视觉外观,但需要更多的图像数据来创建,因为动画需要有更多的帧,并且每个帧都是 PNG32 图像,需要存储在应用的 drawable-dpi 文件夹中(或者是 PNG8 图像,如果可以这样优化的话)。
你在日常生活中熟悉的新媒体素材的典型帧速率是数字视频,它以 30 FPS 运行;电影胶片,以 24 FPS 运行;或者视频游戏,运行速度为 60 FPS 。值得注意的是运动错觉可以在少至 12 FPS 的时间内实现,我们将在本章稍后的动画数据足迹优化工作流程中使用这一鲜为人知的事实。
因此,我们光栅动画的数据足迹优化过程的一部分不仅涉及我们用于动画的图像帧的色深,还涉及我们将需要用来创建运动幻觉的图像帧的总数。较少的 PNG8 帧比大量的 PNG32 帧制作的动画数据更紧凑。
在本章中,我们将仅使用几帧和索引彩色图像来制作引人注目的真实照片动画,因此您可以看到如何使用很少的数据开销,让几千字节的图像数据在您的应用中创建令人信服的特殊效果。
我们将在本章介绍的另一个基本动画概念叫做循环。正如你可能知道的,动画可以播放一次,或者它们可以无缝循环并永远播放,或者它们可以通过我们的 XML 标记指定的变量循环播放一定次数。
循环有两种:无缝循环,帧循环播放,像这样 0 、 1 、 2 、 3 、 4 、 0 、 1 、 2 、 3 、 4 和乒乓循环 像这样 0 、 1 、 2 、 3 、 4 、 3 、 2 、 1 、 0 。
在这一章中,我们将会看到这两种类型的动画循环,并且我们将会看到这两种类型对于不同类型的特效是如何有用的。
用 XML 实现帧动画:动画列表标签
在我们可以用 XML 创建定义动画帧编号及其持续时间(帧速率)的动画标记之前,我们需要做的第一件事是将可绘制素材复制到每个相应的 drawable-dpi 文件夹中,在本例中,这些素材是动画的 cels 或 frames 。
找到名为:attack virus 96 frame 0到 attackvirus96frame5 的 PNG8 文件,复制到 /res/drawable-xhdpi 文件夹,最后重命名为attackvirus0.png到attackvirus5.png共六帧动画。请注意,我们在这里仅使用了总共六个帧来实现这个动画病毒细胞效果,并且我们使用了优化的索引颜色 PNG8 文件,因此我们在所有四种分辨率密度下实现该动画的总数据量仅为总数据的 120KB 或每个动画的 30KB 平均值(这又是每帧 5KB 的数据压缩平均值)。
创建一个新的资源类型的 Android XML 文件:Drawable for Frame Animation
一旦我们的源图像动画帧数据就位,我们需要创建引用这些帧的 XML 构造,并且作为单个动画素材名称(anim_virus)被引用。右击任意一个项目 /res/drawable 文件夹,从右键菜单中选择新建 Android XML 文件,打开如图图 9-1 所示的对话框。
图 9-1。创建一个新的 Android XML 文件,名为 anim_virus.xml,根元素为<动画列表>的
在 Hello_World 项目中选择 Drawable 的资源类型,并将文件命名为 anim_virus ,然后在对话框中间的选择框中选择 < animation-list > 的根元素类型。注意在资源类型的下拉菜单中:有几个与动画相关的选项—
不要选择它们(直到下一章),因为它们用于程序(矢量)动画,而不是基于帧(光栅)的动画,后者只利用可绘制图形(位图图像)。
这里需要注意的另一件事是,因为我们现在在图 9-1 和 9-2 中涉及到它,Android 在可绘制素材文件夹中保存帧动画素材和定义,而 Android 在动画素材文件夹中保存程序动画素材和定义。
图 9-2。将<项目>标签添加到<动画列表>标签容器中,对病毒细胞图像进行 pong 动画制作
类似地,Android 中每种不同类型的动画也有不同的类和包,所以不要混淆 Android 动画包(包含用于过程或矢量动画的类)和用于基于帧或光栅动画的 AnimationDrawable 类。
如果你想知道,程序动画使用更少的图像和更多的处理能力,因此产生更小的应用大小(更好的压缩),而帧动画使用更多的图像和更少的处理能力,因为 CPU 只是从内存中获取数据并将其放在屏幕上。
最终,帧动画使您可以控制每个像素在做什么以及在哪里做,并且在效果的结果中没有任何差异,但它可能需要应用有更多的数据。正如我们将在下一章中看到的,同时使用这两种技术可以达到一个很好的平衡。
在你点击了新的 Android XML 对话框中的 Finish 按钮之后(图 9-1 ,你现在应该有一个 anim_virus.xml shell 脚本在 Eclipse 中为你打开(图 9-2 )。
这当然是由对话框助手创建的,并且包含了 <动画列表> 标签元素的框架 XML 标记,正如你可能已经猜到的,这是一个父容器,我们将用动画帧 <项目> 标签填充它。
在我们开始通过
添加我们的帧动画
每个 <项> 标签指定一个帧参考和持续时间,格式如下:
<item android:drawable="@drawable/attackvirus0" android:duration="100" />
android:drawablefilename reference 允许 Android 从正确的 drawable 文件夹中获得每个帧的正确分辨率密度版本。
android:duration 参数指定在屏幕上显示该帧的时间长度,使用一个毫秒值,在本例中为 100 。
您可能想知道这个毫秒持续时间值如何转化为我们之前了解的帧速率。数学计算起来相当简单;由于每秒钟内有一千毫秒的 ??,我们可以用 1000 除以我们的每帧毫秒值,这就得到我们的每秒帧数,或 FPS 值。在这种情况下,我们使用 10 FPS 来获得足够令人信服的振动病毒细胞。
从优化的角度来看,这次我们讨论的是 CPU 处理优化,用于创建效果的帧越少(帧速率越低,或者每帧可以分配的毫秒持续时间越多)越好。从逻辑上讲,20 FPS 的 CPU 工作量是 10 FPS 的两倍。
这是因为随着时间的推移,当我们向应用添加动画素材时,我们会要求 CPU(中央处理器)执行越来越多的任务,因此我们给 CPU 单独执行这些任务的时间越多,CPU 在执行这些任务之间就有越多的时间来执行我们要求它执行的其他任务。
从我们的 attack_virus.xml 文件 引用我们的帧动画定义 XML 文件
接下来,我们需要在现有代码中引用这个新的动画病毒细胞可绘制素材,用于我们的 activity_attack.xml 用户界面屏幕布局 xml 定义。我们观察到我们的 attackvirus ImageButton 用户界面元素当前引用了 attack_virus.xml 多状态 UI 元素定义,因此这是我们新的 anim_virus.xml 文件名必须被引用的地方,以维护按钮的多状态功能。
为此,我们必须找到(并打开)用于 attack_virus.xml 标记的标签,如图图 9-3 所示,并将我们的可绘制引用更改为现在指向新的 anim_virus XML 动画定义可绘制素材。
图 9-3。将我们的多态 attack_virus.xml 的正常状态改为引用 anim_virus.xml 文件
记下图 9-3 中 /res/drawable 文件夹下的 anim_virus.xml 文件,以及现在已经就位的新Android:drawable = " @ drawable/anim _ virus "参数。如果你现在使用运行 Android 应用工作流程并进入攻击一个星球 UI 屏幕,你会注意到你必须点击病毒一次来激活(启用)它以使病毒开始活动。
这是 Android 中动画资源的默认行为,并且有一个非常合乎逻辑的原因。通常你希望当你点击一个 UI 元素时触发一个动画,我们也将在本章中设置这些动画类型之一。
因此,在默认的 Android 动画行为中,当你点击 UI 元素时,动画就会触发,并且,如果你将 oneshot 设置为 true ,你就会得到你想要的效果。在下一章中,我们将使用 LaserCannon pulse,向您展示这种类型的 XML 设置。然而,就目前而言,我们必须弄清楚如何让这种攻击病毒始终保持活动状态,即使用户没有首先点击它。
利用 Android state_enabled 参数使我们的病毒在活动屏幕启动时活跃起来
为了让我们的病毒在屏幕启动时有动画效果,我们添加了一个 Android:state _ enabled 参数到我们的默认(正常)按钮状态项目标签,在 attack_virus.xml 文件中指定,如图 9-3 中的所示。这将该按钮状态定义为在该用户界面屏幕加载时已经启用。当你再次作为 Android 应用运行时,当用户界面屏幕出现时,我们的病毒立即活跃起来。
现在,我们已经了解了如何为 ImageButton 用户界面元素实现动画源图像,让我们更进一步,为我们的应用的母星制作一个背景用户界面元素的动画(母星显示在应用的主屏幕上)。这显示了我们的星球力场设置,当它被激活的时候。
我们使用循环动画帧,而不是 pong 动画帧(我们用于动画攻击病毒细胞的来回帧类型的帧访问)来实现这一点,因此您有实现这两种类型的动画帧访问顺序的经验。
XML 中的高级帧动画:使用动画背景
为了实现我们的 Forcefield 动画,我们需要做的第一件事是确保我们的动画帧素材就位,所以让我们将 forcefield120frame0.png、forcefield160frame0.png、forcefield240frame0.png 和 forcefield320frame0.png 分别复制到 LDPI、MDPI、HDPI 和 XHDPI /res/drawable 文件夹中。我们通过 Windows 资源管理器文件实用程序来实现这一点。
一定要把每个文件名末尾编号为 0 到 6 的七帧全部复制到它们合适的分辨率密度资源文件夹中,然后简单的全部重命名:forcefield0.png到forcefield6.png,如图图 9-4 所示。一旦完成这些,我们就为 XML 和 Java 代码做好了准备。
图 9-4。将力场动画帧复制并重命名到每个 drawable-dpi 文件夹中,以准备我们的素材
现在,我们可以通过右键单击项目文件夹并选择 New Android XML File 来获得更多实现帧动画的练习,这次是循环而不是 pong。如果你忘记了这看起来像什么,参考本章开始的图 9-1 。
将文件命名为 anim_forcefield 并使用资源类型:Drawable 和项目:Hello_World 和根元素:< animation-list > 就像我们之前做的一样。
如果你想知道我们的命名惯例,以及为什么我们要复制 Android activity_name 命名惯例,这是因为当文件被 OS 或 Eclipse 排序(按字母顺序排列)时,逻辑文件被分组在一起,所以所有 attack_names 和 anim_names 以有序的方式保持分组在一起。
在 anim_forcefield.xml 文件中添加我们的动画帧
您现在应该在 Eclipse 中打开一个 XML 编辑窗格,其中包含新的 anim_forcefield.xml 文件,以及一个骨架 <动画列表> 容器,我们将在其中放置指定动画帧的< item >子标签。
我们要做的第一件事是添加 android:oneshot="false" 参数,这样我们的动画就会永远循环下去。注意, false 是 oneshot 的默认值,所以如果你愿意,你可以完全省略这个参数。因为我们将在本书的不同地方改变循环参数(并学习它们),所以我总是在父容器中编写这个参数,因为这是很好的编程实践。
现在我们准备为我们的动画帧添加七个
<animation-list xmlns:android=http://schemas.android.com/apk/res/android
android:oneshot="false" >
<item android:drawable="@drawable/forcefield0" android:duration="80" />
<item android:drawable="@drawable/forcefield1" android:duration="80" />
<item android:drawable="@drawable/forcefield2" android:duration="80" />
<item android:drawable="@drawable/forcefield3" android:duration="80" />
<item android:drawable="@drawable/forcefield4" android:duration="80" />
<item android:drawable="@drawable/forcefield5" android:duration="80" />
<item android:drawable="@drawable/forcefield6" android:duration="80" />
</animation-list>
图 9-5。将每个力场动画帧的<项>标签添加到父<动画列表>容器标签
接下来是在同一个用户界面元素容器中同时使用背景图像(或者,在本例中是动画)和前景图像(称为源)的新技术,在本例中是 ImageView 标记。
在单个 UI 元素中合成:同时使用源图像和背景图像
为了完成这个合成,我们将向主屏幕末尾的 ImageView 标签添加几个新参数,这是通过 activity_main.xml 文件中的 XML 定义的。在 Eclipse 中打开这个编辑标签,添加参数Android:background = " @ drawable/anim _ forcefield "来引用您刚刚创建的动画列表 XML,这样您就可以引用您的 force field 动画素材中的所有帧。
我们要添加的第二个参数与背景参数一样重要,因为它允许我们推出 ImageView 容器的边界,因为我们的背景动画素材比我们的星球图像大 25%(大小的 125%)。前景(源)图像资源和背景动画资源都利用阿尔法通道;这是为了让它们无缝地组合在一起。
添加 android:padding="30dp" 参数,将 30 个设备无关像素添加到 ImageView UI 元素容器内的中。由于我们将地球图片推离了 UI 屏幕的左侧,也推离了它上面的文本字段,所以让我们也从这个 ImageView 标签中移除或删除 android:marginTop 和 android:marginLeft 标签。
图 9-6 显示了我们新的 ImageView 标签及其参数,同样数量的参数(我们添加了两个,删除了两个)实现了更高级的特殊效果。如果你想调整行星和它的力场之间的空间大小,你可以改变 android:padding DP 设置,从 24dp(紧配合)到 40dp(间隔很远)。请记住,您可以使用 30dp 或 30dip 作为您的 dip 参数,Android 将接受它。
图 9-6。引用 anim_forcefield 作为 activity_main.xml 中 imageEarth ImageView 的背景图像
填充参数在 UI 元素的内部增加空间,而不是像 margin 参数那样在外部增加空间。这可以使用图形布局编辑器很好地可视化,因为 GLE 给你指南(蓝色和绿色)每个选择的用户界面元素相对于其他用户界面元素做什么。例如,在您删除边距参数之前,图形布局编辑器视图窗格显示了靠近行星图像的蓝色选择框和显示边距设置的绿色箭头,将整个容器推离父容器(屏幕边缘)和位于其上的文本元素。现在,注意图 9-7 中的 UI 容器本身已经扩展到适合我们新的背景动画和前景图像。
图 9-7。使用 android:padding 预览 imageEarth 周围的 anim_forcefield 背景图片间距
我们可以只使用一个参数设置来改变我们的效果,因为我们使用 alpha 通道来设置一切;这样,无论我们如何设置填充或动画速度,结果都是专业的。
例如,您甚至可以在 Java 代码中将这个填充值制作成动画,或者将行星周围力场的紧密程度与其他物体与行星的接近程度联系起来。
图 9-7 显示了图形布局编辑器中的 30dp 填充设置。要根据您自己的喜好设置该参数值,请在 activity_main.xml 选项卡(查看编辑窗格的底部)和图形布局编辑器选项卡之间来回切换,以查看 DIP 设置对最终用户界面元素容器填充值的结果,在本例中,我们设置该值以影响行星在力场动画中的居中方式。截至撰写本书时,该动画还不能在 GLE 播放。要观看动画,您需要使用作为 Android 应用运行的工作流程,并启动 Nexus S 模拟器。
用 Java 编写我们的 AnimationDrawable 对象来实现我们的力场动画
在我们能够启动模拟器并看到动画之前,我们需要添加一些 Java 代码,如图 9-8 所示,将我们的 ImageView 和 AnimationDrawable 类和对象添加到我们的 MainActivity.java 类中,以便 Android 知道它们在那里。记住,设计者可以用 XML 实现 UI 设计,但是程序员通常必须使用 Java 来实现它们。
图 9-8。编写一个 setStartUpScreenAnim()方法来设置我们的 AnimationDrawable 并调用它的 start()方法
的确,Android 使得实现动画 ImageButton UI 元素成为可能,而无需编写任何 Java 代码(我将在每章的开头展示最酷和最容易实现的技巧),并且只使用 XML 标记。然而,对于 ImageView UI 元素,由于它们更多地被设计为保存图像素材而不是用作 UI 元素,我们将需要编写一些 Java 代码,以确保当您的应用启动时动画正在播放。
这对我来说也很好,因为如果可以的话,我会在每一章中介绍一些 Java 编码,让这些概念在你的脑海中保持新鲜。
让我们在我们的 MainActivity.java 类中创建一个名为 setUpScreenAnim( ) 的新 Java 方法,该方法将初始化 ImageView,设置其背景图像,并调用 AnimationDrawable 类来制作我们在此设置的背景动画素材的动画。
在 Eclipse 中找到并打开 MainActivity.java 编辑选项卡,折叠 setStartUpWorldValues()、SetStartUpScreenText()、onCreateOptionsMenu()和 onOptionsItemSelected()方法,以便我们有一些空间来编写新方法。
像我们的 setStartUpScreenText()方法一样,将新方法设为 private 和 void ,并将其命名为 setStartUpScreenAnim。代码应该如下所示:
private void setStartUpScreenAnim() { animation code will go in here }
现在我们有了一个空方法,可以编写 Java 代码语句来实现 ImageView 及其 AnimationDrawable 背景元素。
我们需要做的第一件事是使用以下代码声明、命名和实例化我们的 homePlanet ImageView 用户界面元素:
ImageView homePlanet = (ImageView)findViewById(R.id.imageEarth);
下一行代码(它在图 9-8 中被注释掉了,因为我们不需要它,因为我们在 XML 中定义了一个背景)显示了如何在 Java 中设置你的背景素材。注意,因为我们在 ImageView 标签中使用了 android:background 参数,所以我们不需要在 Java 中重复这样做。然而,如果我们没有这个 XML 背景参数,我们将需要这行代码。因此,出于学习目的,我将它包含在这里:
homePlanet.setBackgroundResource(R.id.anim_forcefield); (commented out in Figure9-8)
下一行代码做了一些繁重的工作,并使用 Android AnimationDrawable 类设置了我们的 forcefield 动画,使用了以下单行代码:
AnimationDrawable forceFieldAnimation = (AnimationDrawable)homePlanet.getBackground();
最后,现在我们的 AnimationDrawable 对象名为 forceFieldAnimation 已经设置好了,使用来保存我们的动画资源。getBackground( ) 方法调用了 homePlanet 的 ImageView 对象,我们可以使用方便的。start( ) 方法使用下面这行非常简单的代码启动动画:
forceFieldAnimation.start();
现在,我们准备在 Nexus S 模拟器中使用作为 Android 应用运行的工作流程来测试我们的应用。一旦我们这样做,我们可以看到我们的力场动画正在我们的应用主屏幕上运行,地球现在使用填充值而不是边距值与屏幕左侧及其上方的文本元素隔开。通过这种方式,我们可以远离其他 UI 元素,并使用相同的标签参数为背景动画效果腾出空间。这都显示在图 9-9 中,尽管你必须自己运行模拟器,才能看到力场动画的运行!
图 9-9。在 Nexus S 模拟器中运行我们的 Forcefield 动画 ImageView UI 元素背景
在本节中,我们仅使用 400KB 的总 PNG8 图像资源,实现了所有四种分辨率密度的七帧动画力场效果。这相当于平均每分辨率 100KB 或平均每帧 14KB。
接下来,我们将在我们的 Configure a Planet 用户界面屏幕上实现一个更加令人印象深刻的视觉特效,因此我们在应用的每个主要用户界面屏幕上添加了帧动画(或基于图像的动画)特效。
让我们来看看如何通过内置的 Android 图像过渡特效,同时动画显示图像中的所有像素。
图像过渡是数字成像特效的基础,如变形,以及数字成像交叉渐变过渡。
全屏 XML 帧动画:背景图像过渡
这次让我们使用一个不同的工作流程来创建一个图像转换 XML 定义文件,这样您就可以看到在 Eclipse 中有不止一种方法可以做到这一点。右键单击项目的 /res/drawable 文件夹,选择新建文件菜单命令序列,弹出新建文件对话框(创建一个包含任何数据类型的文件,不仅仅是 XML 数据),如图图 9-10 所示。
图 9-10。使用新建文件对话框创建 tran_stars_galaxy XML 文件
请注意,新文件对话框会自动填充我们右键单击以访问新文件菜单序列的父文件夹,因此我们现在要做的就是指定文件名。将文件命名为 tran_stars_galaxy ,代表从恒星到星系的跃迁。
为图像转换 XML 定义设置
当在 Eclipse 中为 tran_stars_galaxy.xml 文件打开空白编辑标签时,键入包含 < item > 标签的 < transition > 父标签,这些标签定义了作为该图像转换 xml 定义一部分的图像可绘制元素。开始的<转换>标签包含 XML 模式地址,就像所有开始的容器标签一样,如下所示:
<transition xmlns:android="http://schemas.android.com/apk/res/android" >
在这个父图像过渡容器中,放置两个 < item > 标签,第一个用于源图像,第二个用于目标图像。<项目>标签使用 android:drawable 参数以通常的方式指向可绘制图像素材,通过对源图像的 @drawable/stars 引用和对目标图像的 @drawable/galaxy 引用。
一旦你的 tran_stars_galaxy.xml 文件看起来像我们在图 9-11 中所写的标记,我们需要做的下一件事是准备我们的现有代码来实现图像转换,打开我们名为 activity_add.xml 的 xml 文件,并使用 android:id 参数将一个 ID 值赋给我们的父 RelativeLayout 布局容器标签。
图 9-11。使用<过渡>标签父容器通过<项>标签定义数字图像过渡元素
我们添加它的原因是因为我们需要在 Java 代码中引用并在以后访问这个屏幕布局容器。这是因为这个 RelativeLayout 容器保存了用户界面屏幕的背景图像。由于这是 UI 元素(RelativeLayout 屏幕布局容器的背景图像),我们试图使用 Java 中的 image transition 对象将它渐变为新图像,因此我们必须能够通过 Java 代码中的 findViewById( ) 方法来访问它。
我们在父 RelativeLayout 容器打开标签的最末端添加了Android:id = " @+id/new _ planet _ screen ",如图图 9-12 所示。注意,标记参数的顺序没有区别;我们可以把 tag ID 参数放在前面,也可以把它放在参数列表的最后。
图 9-12。使用 android:id 参数命名 RelativeLayout 容器,以便在 Java 中引用
为 Hello_World 升级 AndroidManifest XML 中的应用 API 级别支持
为了在布局容器中使用图像过渡,我们需要做的下一件事是为我们的项目设置 AndroidManifest.xml 文件,以指定 API 级别 16 (Jelly Bean)。这是一项高级功能,需要高级 API 级别的支持才能执行。
为此,在 Eclipse 中打开包含您的 AndroidManifest.xml 文件的 Hello_World Manifest 选项卡。值得注意的是,在图 9-13 中,这是 Eclipse 项目中唯一一个不会简单显示您正在编辑的确切文件名的选项卡。您可以在底部的包资源管理器中看到这一点,其中实际的文件名显示为 AndroidManifest.xml,但是 Eclipse 在编辑窗格选项卡中将其修改为 Hello_World Manifest!请不要让这迷惑你;Eclipse 只是想变得聪明和先进。不要以为可以将 AndroidManifest.xml 文件命名为 Hello_WorldManifest.xml,否则项目将无法编译。记住,Android 操作系统是硬编码的只寻找(并找到)一个名为 AndroidManifest.xml 的引导文件,所以只使用那个文件名。
**
图 9-13。将我们的 android:minSdkVersion 调整到 API 级别 16,以允许屏幕布局背景过渡
为了指定我们使用的是 API 16 的最低 API 支持级别,以及 API 16 的当前 API 支持级别,我们为 xml 标记顶部附近的 AndroidManifest.xml 文件中的 < uses-sdk > 标记调整了这些标记参数,结果显示在图 9-13 中。
将 android:minSdkVersion 参数设置为 16 的值,表示Android 4 . 1 . 2Jelly Bean API 16 级支持。保留这个标签中的另一个参数, android:targetSdkVersion 设置为 17 的当前值,或者Android 4 . 2 . 2Jelly Bean Plus API Level 17 支持。我们的货单看起来不错!
您可能想知道这是否会降低您的应用在旧平台和设备上的功能。如果你还记得的话,在第一章中我们安装了一个 API 以及其他所有的东西,这些东西都是在旧 API 级别的 API 上模拟当前 API 级别的支持。我的猜测是,通过图像过渡来混合布局容器背景在任何 API 级别上都不会太难,所以这也可以在 Android 3.x 和 4.0 平台上工作。
这也是一种令人惊叹的应用特性,如果它不能在旧平台上工作(正确实现),它就不会发生;也就是说,背景图像过渡不会发生,但是最终用户不会意识到这一点,因为没有什么会明显出错,除非该用户已经在实际上支持该背景图像过渡的平台上利用了该应用。
在我们的 Java 代码中添加一个 TransitionDrawable 对象来实现图像转换
现在我们准备好了有趣的东西!我们需要在我们的 NewPlanet.java Java 类定义中添加三行关键代码,我在 Activity onCreate()和 setContentView()方法调用之后添加了它们,因为我们正在设置对象并将它们连接在一起。稍后在 marsImage 对象的 onClick()方法中,当用户点击 Mars 时,我们将调用这个效果。
第一行代码设置了我们的 TransitionDrawable 对象,我们将把它命名为 trans ,并且我们将引用: tran_stars_galaxy.xml 文件,该文件保存了我们的< transition >定义标记。Java 代码应该是这样的,如图图 9-14 所示:
final TransitionDrawable trans = (TransitionDrawable)getResources().getDrawable(R.id.trans_stars_galaxy);
图 9-14。在我们的 NewPlanet.java 代码中实现我们的 RelativeLayout 和 TransitionDrawable 对象
我们需要使我们的对象成为 final ,因为我们从类中更深层次的 onClick()方法内部引用它,final 关键字防止 trans 对象被编辑,本质上使它成为一个对象常量,这实际上是我们希望这个过渡效果—
可用—
并且每次都相同,由调用它的类中的任何方法使用。
接下来,我们需要实例化我们的 RelativeLayout 容器对象,将其命名为 newPlanetScreen ,并将其引用到我们的 activity_add.xml 布局中,在这里对其进行了定义,并在最近赋予了一个 android:id 值 new_planet_screen 。
这与我们在 Java 中实例化任何用户界面元素的方式相同,通过将它声明为一个对象,并将其命名,如下所示:
RelativeLayout newPlanetScreen = (RelativeLayout)findViewById(R.id.new_planet_screen);
既然我们已经声明了一个 trans TransitionDrawable 对象和我们的newPlanetScreenrelative layout 对象,接下来我们需要做的就是将它们连接在一起,这样它们就可以作为一个团队一起工作,来创建我们想要实现的背景图像过渡效果。
这是通过将我们最近的newPlanetScreenrelative layout 容器对象的背景元素(参数)设置为我们创建的 TransitionDrawable 对象来执行我们的图像转换来完成的。记住,我们将这个对象命名为 trans ,我们将通过使用一行简单但强大的 Java 代码来完成这个任务,这些代码通过将这两个对象连接在一起(或者更准确地说,引用 newPlanetScreen 对象内部的 trans 对象)。setBackground( ) 方法调用:
newPlanetScreen.setBackground(trans);
现在我们的数字图像特效已经用 XML(在三个 XML 文件中)和 Java 实现了,并且可以用。我们的 planet onClick()方法中的 start()方法调用,其中一个方法我们已经为我们的 imageMars 用户界面对象编写了代码,因此我们现在可以通过添加这一小段代码来测试所有这些新代码:
trans.startTransition(5000);
这行代码通过 trans TransitionDrawable 对象的调用它。start( ) 方法,它启动过渡,并传递给它一个以毫秒为单位的持续时间变量,在这个特定的例子中,我们使用了 5000 毫秒(或 5 秒)来创建当前 stars.png 图像到新 galaxy.png 图像之间的缓慢交叉淡入淡出效果。
所有这些新的 Java 代码都可以在图 9-14 中看到,在类的中间(我在 Java 代码的三个核心行周围留了一些空白),以及在底部,用浅蓝色突出显示的一行。
现在,我们可以在 Package Explorer 窗格中右键单击我们的项目文件夹,并选择 Run As Android Application 命令来启动 Nexus S 模拟器,并查看我们的背景图像转换效果!
当 Nexus S 模拟器启动且应用完成加载时,单击菜单按钮并选择新行星优先菜单选项,并加载新行星活动用户界面屏幕。然后点击火星星球图像,看星星背景慢慢交叉淡化,变成星系背景图像,如图图 9-15 所示。因为到目前为止我们已经在我们的数字图像中使用了 alpha 通道来开发这个应用,最终结果是 100%专业的无缝合成,即使在淡入淡出期间,我们的六颗行星也在我们的新背景图像上。
图 9-15。Nexus S 模拟器中显示的 galaxy.png 背景图像过渡
现在我们知道了如何使用 Android 中的 AnimationDrawable 和 TransitionDrawable 类实现位图动画和图像过渡,我们可以在下一章的过程动画中将我们的应用提升到更高的视觉效果艺术水平。
使用程序动画,其中我们使用 XML 参数修改静态图像和运动动画(是的,当它通过其帧进行运动动画时),用于平移(移动)、缩放、旋转、alpha 通道(混合)等,我们可以通过特殊效果实现更大数量级的视觉效果。
所以让我们总结一下我们在这一章中学到的关于基于光栅图像的动画的知识,然后直接进入矢量动画,这样我们就可以使用这些类和技术来合成 WOW!Hello World 应用视觉效果的因素。
摘要
在这一章中,我们利用我们在过去几章中学到的数字成像概念和技术知识,通过 Android 的帧动画功能将这些技术带入第四维时间,并应用它们来进一步增强我们的 Hello World 应用。
首先,我们学习了一些数字图像动画的基本概念,它建立在我们在前两章学习的静态图像概念的基础上。我们了解到,基于图像的动画通常被称为光栅动画或帧动画,这种类型的动画由 cels 或帧组成,它们随着时间的推移快速显示,以创建运动的幻觉。
我们学习了动画的核心概念帧速率,以及如何计算任何给定动画的 FPS 或每秒帧数。我们了解到视频游戏使用最快的帧速率 60 FPS ,而数字视频(电视)使用一半的帧速率,或 30 FPS 。电影(电影)使用的帧速率为 24 FPS ,数字媒体使用的帧速率甚至更低,范围从 10 FPS 到 20 FPS ,以节省数据空间。
我们学习了如何通过优化图像的色深(使用优化的索引彩色图像),以及创建我们正在寻找的视觉特效所需的帧数,来优化我们应用的基于帧的动画的数据足迹。
我们为 Hello_World 应用实现了几个动画效果。一个六帧动画在所有四个 Android 分辨率密度目标上仅使用了 120 千字节的图像数据,或者一个紧凑的 30KB 每分辨率屏幕密度。我们的力场动画有七帧,使用了 400KB,但这是一个包含整个星球的更大的效果,仍然平均只有 100KB 的每 Android 分辨率密度的总帧数据。
我们了解了循环的概念和 android:oneshot 参数,该参数控制动画是否无缝循环,或者是否只是播放一次然后停止。我们探索了 pong 循环的概念,其中一个循环在它的组成帧之间来回循环。我们还学习了无缝循环,其中动画的循环被预先设计为无缝循环,因此不需要反转其帧方向。
然后,我们开始在我们的 Hello World Android 应用中实现基于帧的动画,使用 <动画列表> 父标签及其子标签 <项目> ,它们定义了动画帧的可绘制素材以及每个帧的持续时间。
我们学习了 Android 的 AnimationDrawable 类,以及如何通过在 Android 应用活动的 Java 代码中声明的 AnimationDrawable 对象来实现基于图像的(可绘制的)动画。我们学会了如何称呼。我们的 AnimationDrawable 对象上的 start( ) 方法。
我们仅使用 XML 标记代码,在我们的“攻击一个星球”活动用户界面屏幕上生动地展示了我们的攻击病毒图标,并仅使用 XML 标记和参数以及零 Java 代码,将这种脉动病毒实现为 pong 循环动画用户界面元素。
我们通过用基于 XML 的帧动画定义替换 ImageButton UI 元素的源图像来实现这一点,然后我们使用一个 android:state_enabled 参数来在活动屏幕完成加载后自动生成病毒动画。
然后我们升级到更高级的 UI 技术,使用我们的背景图像板作为用户界面元素来保存我们的动画帧,并将它们与 UI 元素的前景(源)图像合成。
我们在应用主屏幕上实现了这种 UI 内合成技术,通过使用 ImageView 标签背景参数来保存无缝循环的 7 帧力场动画,我们再次使用
然后我们研究了 AndroidTransitionDrawable类,以及它在我们的 Android 应用中实现数字图像过渡的能力。我们完全从头开始创建了一个 XML 文件,并实现了一个 <过渡> 标签父容器,以及两个子 <项目> 标签,它们定义了我们想要在其间过渡的图像。
因为我们想变得更巧妙(阅读:高级)并在 RelativeLayout 用户界面屏幕布局容器元素的背景参数(图像容器)中使用图像过渡,我们需要在我们的 AndroidManifest.xml 文件中将我们应用的最低 API 支持升级到 Android API 级别 16 和目标 API 级别 17。然后,在我们的MainActivity.java类中,我们仅使用四行 Java 代码将所有东西连接在一起。
在下一章中,我们将通过学习程序性或矢量动画来建立我们在本章中学习的帧动画知识,这些动画可以与帧动画结构结合使用,或者与简单的静态图像结合使用,甚至与非图像 UI 元素结合使用,例如 TextViews。**
十、Android 矢量动画:通过 XML 结构的程序动画
在这第二章中,我们将学习 Android 中的“其他”类型的动画,这种类型使用代码,而不是像素,来产生它的魔力。这种基于代码的动画通常被称为程序动画,涉及到范围和枢轴点等内容。
业内使用了几个程序动画术语。如果你听到术语补间动画 ,它指的是一种创建动画的程序方式。这是因为补间实际上是插值,或者划分你想要你的动画跨越的帧数,在开始和结束值之间,或者范围,用于动画的计算。
我们将在下一节讨论插值和其他程序动画概念的数学问题,比如枢轴点。另一个你可能听说过的程序动画术语是矢量动画。矢量是一条射线或直线,但一般概念是动画是通过使用数学构造而不是使用栅格(像素)集合来创建的。
矢量和栅格是两种完全不同的方法;栅格是数据密集型,而矢量是数据密集型。这是因为文本(数学)可以很好地压缩,而像素(和帧)的数组就不行。矢量使用更多的 CPU 资源(在进行计算时),而栅格使用更多的存储或带宽资源(传输数据)。这是因为一个数学向量需要被渲染到显示屏上,这意味着数学被转化为图形元素,通常是运动图形,或 2D 动画。
因此,Adobe Illustrator(或 InkScape)是一个矢量成像软件包,而 Adobe Photoshop(或 GIMP 2)是一个光栅成像软件包。
程序动画概念:旋转、缩放和平移
让我们从学习矢量图像和动画的一些概念开始。首先,有两种主要类型的矢量平台, 2D 或二维(平面)矢量图形,如我们在 Illustrator 或 InkScape 中发现的,以及 3D 或三维(体积)矢量图形,如我们在 Blender 3D 等 3D 建模软件中发现的。
注意我们将在本章的这一节介绍的概念适用于 2D 和 3D 成像和动画。两者都使用向量;2D 的 2D 矢量,3D 的 3D 矢量,2D 和 3D 都涉及到核心概念平移(移动)旋转、缩放。在 2D,这些概念涉及 X 和 Y 轴,在 3D 中,这些概念涉及 X 、 Y 和 Z 轴。
2D 动画中有一个 Z 概念,但它不是 Z 轴,而是 Z 顺序。 Z 顺序在 2D 更像是数字成像中的图层,它涉及到每个 2D(平面)图层处于什么图层顺序,是在其他 2D 图层的前面还是后面。z 顺序是对 2D 合成中的层进行排序的数字,并且定义了给定 2D 层的前面和后面。实时更改 Z 顺序可以创建翻书特效。
2D 中的平移涉及到沿 X 轴和 Y 轴的移动,它是 2D 动画中可以完成的三个变换中最基本的。平移由移动的起点、移动的量(以像素或百分比表示)和移动的方向(沿着 X 或 Y 轴,或两者的某种相对组合)来定义。
2D 中的旋转是指绕给定的支点旋转,由该旋转的度数、方向(正或负)、旋转的支点(中心)位置定义。因为一整圈有 360 度,所以旋转数学专门涉及这个 360 的数字,就像 FPS 计算涉及数字 1000(一秒中的毫秒数)。
2D 的比例涉及给定形状的大小,由相对于该形状当前大小的十进制数定义。例如, 0.5 的比例将是当前形状大小的一半,而 2.0 的比例将是当前形状大小的两倍。像平移一样,缩放也有 X 和 Y 分量,如果值相同,缩放可以说是均匀缩放,如果不相同,缩放可以说是非均匀缩放**。
有趣的是,你也可以在你的缩放操作中定义一个枢轴点,这允许倾斜缩放,其中你的缩放操作会受到枢轴点位置的影响。对于不规则的形状,这可以更精确地控制缩放操作产生的形状扭曲效果。假设有时被缩放的 2D 形状是位图图像,使用不在图像中心点的枢轴点放置可以获得一些非常有趣的结果。**
实现旋转动画:攻击炸弹 UI 图标
让我们开始实现程序动画,在我们的攻击星球用户界面屏幕上制作一个静态图像的动画,使它看起来像一个动画图标。我们将旋转我们的 bomb ImageButton UI 元素,这样它就和我们的 virus ImageButton 一起被动画化了,我们在前一章中已经将 virus ImageButton 动画化了。因为我们为炸弹图像创建了一个 alpha 通道,这给了我们一个与空间图像背景无缝的 2D 效果。
右键单击您的项目资源文件夹,然后选择新建Android XML 文件菜单选项序列。在项目中选择补间动画的一个资源类型:设置 Hello_World 然后将文件命名为 anim_rot_bomb ,动画旋转炸弹图像的简称。接下来选择的根元素旋转>最后点击完成按钮,如图图 10-1 所示。****
图 10-1。用于补间动画(旋转)的新 Android XML 文件
这将打开一个带有 < rotate > 标签的空白 XML 文件,我们使用参数 填充该文件,以实现我们希望的旋转过程。
首先删除 < /rotate > 结束标签,因为这不会是一个父(容器)标签。将 <旋转> 开始标记拆分成一行 <旋转,然后在下一行添加一个结束或结束标记符号 / > ,如图图 10-2 所示。
图 10-2。通过 Eclipse 中的 android: parameter helper 对话框添加旋转动画参数
完成旋转标签
图 10-2 中还显示了我们输入 android: 的小技巧,以获得一个参数助手对话框,其中有一个可在 < rotate > 标签中使用的参数列表。在使用标记之前,您应该总是利用这种技术来调用和研究可用的参数,我们将在这个示例中尝试使用尽可能多的这些参数,向您展示它们都将做什么。
我们需要添加的第一件事是一个引用 http://schemas.android.com/apk/res/android
URL 的 xmlns:android 参数,这样我们添加的任何参数都可以通过位于 android 网站上的当前规范进行验证。在添加这个初始的 XML 命名模式(XMLNS)引用之前,您添加的任何参数都将被标记为红色 X 错误,因为它不能被验证为正确的参数。这是因为附加在每个参数前的 android:等同于这里定义的 URL。
在旋转动画中定义最重要的事情是旋转参数本身,以度为单位,使用 android:fromDegrees 和 android:toDegrees 参数。因为我们想要攻击炸弹图标的完整圆形无缝动画,我们将在这些设置中使用 0 到 360 度来获得炸弹图像的完整无缝旋转。
我们添加的下两个设置使用 X 和 Y 坐标建立了这个旋转的中心点,或枢轴点。在 Android 中,你的枢轴点是使用一个百分比建立的,从图像左上角的 0% 到 100% 进入 2D 图像。通过设定这个百分比,我们的图像有多少像素并不重要,因此这适应了我们需要支持目前市场上许多类型的 Android 设备的各种分辨率图像。
因为我们希望图像围绕其中心点旋转,所以我们将使用设置为 50% 的 android:pivotX 参数,以及设置为 50% 的 android:pivotY 参数。如果图像(和 alpha)没有完全位于像素中心,可能会有一些抖动。请注意,与返回到 GIMP 并计算炸弹图像上方和侧面的边框像素相比,这在这个 XML 标记中更容易解决(即,通过使用 49%和 51%的设置来稍微对角移动枢轴)。
接下来我们需要做的是通过使用一个 android:interpolator 参数,用一个在 Android OS 内部资源 R. 路径中定义的插值器常数,为我们的 2D 动画设置一个插值方法。
目前,Android OS 支持 13 插值器常量。每个插值器常量访问 Android 中的一个插值器类子类。
每个插值器子类都将数学曲线应用于动画帧速率,调整每个动画帧之间的过渡速度,以实现更复杂和逼真的运动效果。
如果您想详细研究这些插值器方法,Android 开发者网站上有一整页专门介绍这些方法,网址为:
http://developer.android.com/reference/android/view/animation/Interpolator.html
这里我们需要使用的插值器是一个线性插值器,它给我们一个很好的均匀移动沿着我们动画的所有部分。我们将在本章使用其他插值常量,所以你可以看到他们做什么,当他们应用到你的 2D 动画设置时看起来如何。如果您想在一个地方看到所有的 R .插补器常数,请访问:
http://developer.android.com/reference/android/R.interpolator.html
为了应用插值器常量,我们将使用 @android (操作系统)以及 :anim (动画资源)和 /linear_interpolator (常量的路径)从 Android 操作系统资源(R) 桶中引用它,如下所示:
android:interpolator="@android:anim/linear_interpolator"
接下来我们需要为动画定义 重复计数。这是通过一个 android:repeatCount 参数来完成的,该参数要么接受一个整数值(完成值的循环数),要么接受一个预定义的无限常数。
infinite 常量仅在存在需要永久动画的 UI 元素(如用户界面按钮或 UI 设计元素)时使用。
因为我们的攻击炸弹 ImageButton 图像图标属于这一类,所以我们将使用一个Android:repeat count = " infinite "参数来实现这个最终结果。
最后,我们需要定义动画的循环持续时间,也就是程序动画的循环发生的时间。
动画循环持续时间使用 android:duration 参数定义,动画的每个循环的确切时间(动画速度)以毫秒为单位定义。让我们从快速值 2000 毫秒开始,或每旋转 180 度一秒,稍后,如果这太快,我们可以将其值增加到 8000 毫秒或更多。
现在我们已经实现了 <旋转> 标签参数,我们需要这些参数来定义我们的炸弹旋转 2D 动画(使用大约一半的可用参数,如图图 10-2 所示),接下来我们可以用 Java 实现动画。XML 标记及其参数应该包含以下标记:
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="infinite"
android:duration="2000"
/>
图 10-3 显示了在 Eclipse 中完成的 anim_rot_bomb.xml 文件。
图 10-3。为 Eclipse 中的 anim_rot_bomb.xml 文件配置< rotate >标签的旋转定义参数
为旋转添加 Java 代码
我们的动画触发器的 Java 代码逻辑上定义在 AttackPlanet.java 文件中,因为该类控制攻击功能的活动屏幕。单击 Eclipse 编辑窗格右上角的数字,下拉打开文件菜单,然后选择:AttackPlanet.java。
在 Java 代码中,我们需要做的第一件事是在我们的.setContentView()
方法调用下添加一行空格,这样我们就可以使用 Android Animation
类声明一个Animation
对象。将这个Animation
对象命名为rotateBomb
,并像这样引用anim_rot_bomb
XML:
Animation rotateBomb = AnimationUtils.loadAnimation(this, R.anim.anim_rot_bomb);
注意,声明一个动画和加载一个过程化的 XML 定义有一点不同,在等号的左边声明和命名动画对象,并调用。loadAnimation( ) 方法通过从 AnimationUtils 类中取出点标记法。AnimationUtils 类使用其 loadAnimation 方法在其第二个参数中引用当前上下文( this )和 anim_rot_bomb.xml 中的动画 XML 定义。
当您键入这一行代码时,Eclipse red 会为您需要定义导入语句的类加下划线,如果您愿意,您可以单击这些并选择让 IDE 为您做这件事的链接。
现在我们已经设置了一个动画对象来为我们执行这个动画,我们所要做的就是将它连接到我们的 bombButton UI 对象并启动动画,这也是我们在一行紧凑的 Java 代码中完成的。
bombButton.startAnimation(rotateBomb);
这让我们的bombButton ImageButton
对象调用它的startAnimation
方法,并将它传递给我们刚刚在它上面的代码行中创建的rotateBomb Animation
对象。正如你在图 10-4 中看到的,实现一个程序化的动画只需要两行非常密集的 Java 代码,因为所有定义我们想要动画做什么的繁重工作都被卸载到 XML 中了。这使得设计人员可以专注于 UI 和动画设计,而 Java 编码人员则不必如此。相当天才的东西!
图 10-4。添加一个名为 rotateBomb 的动画对象,并通过。start animation()
接下来,让我们变得更复杂一点,在我们的攻击病毒图标 ImageButton UI 元素中,将一些程序性(矢量)动画与我们现有的基于帧的(光栅)动画相结合。通过这种方式,您可以看到如何使用矢量和光栅动画世界的最佳组合,使用相对少量的 Java 和 XML 代码以及半打数字图像素材(帧)来获得高级的最终结果。
实现标量动画:脉冲攻击病毒 UI 图标
我们开始创建另一个程序动画 XML 文件,就像我们在图 10-1 中所做的一样,只是这次我们为我们的 XML 文件选择了 <缩放> 根元素,而不是<旋转>元素。这是因为我们将使我们的攻击病毒脉冲或伸缩,除了其目前基于帧的病毒运动。让我们把这个 XML 文件命名为 anim_scale_virus 。
现在我们再次将
完成刻度标签
现在我们使用 android: 工作流程来打开 Eclipse 助手对话框,列出所有的 < scale > 元素标签参数,这样我们就可以仔细研究这个标签可以(并将)为我们做什么。这都显示在图 10-5 中。
图 10-5。创建我们的 anim_scale_virus.xml 并使用 android: parameter helper 对话框和< scale >参数
我们将在
因为我们想在 X 轴和 Y 轴上均匀地缩放我们的病毒,我们用完全相同的数值设置我们的 ?? Android:fromy scale 和 ?? Android:toYScale 参数。
正如我在本章早些时候提到的,通过设置枢轴点 X 和 Y 值(我们在这里设置这些值是为了让您熟悉这个参数),可以使用方向倾斜来缩放比例,但是因为我们希望病毒在其中心均匀弯曲,所以我们使用 X 和 Y 枢轴参数中的 50%值来使我们的枢轴点居中。
现在是使用 android:interpolator 设置动画插值器的时候了,我们将在这里使用一个 accelerate_decelerate 插值器,以获得更多的花哨,并给我们的病毒的伸缩运动赋予一点特性。
接下来,我们使用设置为 3000 毫秒或 3 秒的 android:duration 参数来设置我们的缩放动画的单个循环的长度。我们需要一个平滑缓慢的缩放,所以这应该是一个很好的起始值,如果需要,我们可以在以后调整(增加)以获得更真实的效果。
现在我们所要做的就是设置我们的重复参数,我们将准备在我们的 Java 代码中实现缩放动画定义。有两个重复参数,我们将用于这个比例动画, android:repeatCount ,我们以前见过,我们将再次设置为 infinite ,以及 android:repeatMode ,这对我们来说是新的,我们将设置为 reverse 。
RepeatMode 在 Android 中有两个常量,我们在这里使用的反向常量,它导致了 pong 动画效果,并使我们的病毒伸缩,重启常量,它创建了一个无缝循环,是默认值或设置。因此,如果您将 android:repeatMode 参数去掉或未声明,您的动画将无缝循环,前提是您已经将 android:repeatCount 参数设置为 infinite。您的 XML 标记应该如下所示:
<?xml version="1.0" encoding="utf-8"?>
<scale
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="1.0"
android:toXScale="0.75"
android:fromYScale="1.0"
android:toYScale="0.75"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:duration="3000"
android:repeatCount="infinite"
android:repeatMode="reverse"
/>
图 10-6 显示了完成的标签。
图 10-6。为 Eclipse 中的 anim_scale_virus.xml 文件配置< scale >标签的缩放定义参数
添加用于缩放的 Java 代码
现在是时候在我们的 AttackPlanet Java 代码中实现 scale 动画对象了。在 infectButton ImageButton 对象声明下添加一行空格,并复制它上面的两行代码,Animation rotateBomb 和 bombButton.startAnimation()并将它们粘贴到该空格中。
接下来将动画名称更改为 scaleVirus ,并设置 infectButton startAnimation()方法来调用这个 scaleVirus 动画对象,使用下面两行 Java 代码:
Animation scaleVirus = AnimationUtils.loadAnimation(this, R.anim.anim_scale_virus);
infectButton.startAnimation(scaleVirus);
实现 scaleVirus 的 Java 代码如图 10-7 所示。
图 10-7。添加一个名为 scaleVirus 的动画对象,通过。startAnimation()
最后,我们将使用作为 Android 应用运行工作流程来查看我们的攻击病毒 ImageButton 的新级别动画细节,它现在在大小上可以伸缩,也可以制作帧动画。尝试插值器设置并改变病毒的弯曲方式,因此您开始熟悉 Android 中的 13 个不同的插值器常数,以及它们在动画中的运动曲线上提供的不同效果。
接下来,我们将为我们的士兵设置阿尔法通道的动画,这样我们就可以把他传送到星球表面来入侵星球。
实现阿尔法通道动画:把我传送到一个星球上
接下来,让我们使用补间动画 XML 对话框中显示的 < alpha > 根元素将我们的士兵部队传送到一个星球表面,如图图 10-1 所示。右击你的 Hello_World 项目文件夹,选择新建 ** Android XML 文件对话框,在从资源类型:下拉菜单中选择补间动画并设置 Hello_World 项目,然后选择< ** alpha >根元素后,将新的 XML 文件命名为:anim _ alpha _ invasive。
完成阿尔法标签
一旦设置好一切,点击 Finish 按钮,您将在 Eclipse 中获得打开的 XML 文件,并且您可以设置您的< alpha >标签。就像我们之前对<旋转>和<缩放>标签所做的一样,让我们通过将标签拆分为 < alpha 和 / > 组件,并添加您的 xmlns:android 参数和 URL 来设置标签以获取我们的参数,如图图 10-8 所示。然后,要查看< alpha >标签为我们提供了哪些参数,键入 android: 并弹出参数帮助器对话框。
图 10-8。用< alpha >参数创建我们的 anim _ alpha _ invasive . XML 和 android: parameter helper 对话框
我们要添加的第一个 alpha 参数是 android:fromAlpha 参数,它取 1 (100%,或不透明)和 0 (0%,或完全透明)之间的一个整数值。因为我们想让士兵的可见性从不透明变为透明,我们将初始值设为 1.0 。
接下来,我们将添加 android:toAlpha 参数,该参数指定我们想要动画到的 Alpha 值。我们将把它设置为 0,这表示完全透明,就像 A 通道中的# aarggbb设置为 0 一样。
接下来,我们需要指定一种帧运动插值的方法,使用现在熟悉的 android:interpolator 参数,我们也将该参数设置为 accelerate_decelerate 设置,以实现逼真的淡出传送器效果。如果您愿意,您可以尝试其他 12 种运动曲线常量设置,以便更好地熟悉它们。
接下来,让我们将 android:duration 参数设置为 4000 毫秒(或 4 秒),给我们一个不错的缓慢淡出效果。由于我们不希望士兵不断淡入淡出,我们将尝试一个新参数,即 android:startOffset 参数,设置为 5000 毫秒,在每个动画周期中添加 5 秒钟的延迟,以便士兵保持可见(实心),时间比他透明(传送到星球)的时间长。您可以根据个人喜好设置这两个参数,以微调效果定时。
接下来,我们设置我们的 alpha 通道动画重复参数,将 android:repeatCount 再次设置为 infinite ,将 android:repeatMode 设置为 reverse ,这样士兵淡入(从星球返回)与淡出的方式非常相似,如下面的标记所示:
<?xml version="1.0" encoding="utf-8"?>
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:duration="4000"
android:startOffset="5000"
android:repeatCount="infinite"
android:repeatMode="reverse"
/>
这也显示在 Eclipse IDE 代码编辑器的图 10-9 中。
图 10-9。在 Eclipse 中为 anim _ alpha _ invasive . XML 文件配置< alpha >标签的 alpha fade 定义参数
为 Alpha 动画添加 Java 代码
现在是时候添加 Java 代码了,该代码创建所需的alpha invasive动画对象,并通过. startAnimation 方法将其连接到 invadeButton。这是通过以下代码行完成的:
Animation alphaInvade = AnimationUtils.loadAnimation(this, R.anim.anim_alpha_invade);
正如我们之前看到的,这一行密集的 Java 代码声明了一个动画对象,将其命名为alpha invasive,然后通过调用来加载我们的anim _ alpha _ invasiveXML
现在我们要做的就是通过和将 alphaInvade 动画对象连接到 invadeButton UI 对象中。startAnimation( ) 方法,现在使用下面一行简单但功能强大的 Java 编程代码就完成了:
invadeButton.startAnimation(alphaInvade);
请注意,您也可以通过在两行代码上使用复制和粘贴操作来完成所有这些编码,这两行代码是我们已经为另一个攻击图标 UI 元素和上面的动画对象调用编写的。
既然我们所有的 XML 标记都已经被吸收到我们的 Java 编码中,如图图 10-10 所示,是时候使用我们的运行为 Android 应用的工作进程了,看看我们的攻击士兵消失在太空中又回来了。
图 10-10。添加名为 alphaInvade 的动画对象,并将其分配给 invadeButton via。startAnimation()
虽然这本身很酷,但我并不满意,因为没有光束部分,什么是传送光束效果?因此,我们需要将这种效果提升到一个新的水平,这意味着通过将位图(帧或光栅)和程序(矢量或基于代码)动画结合在一起,创建一个更强大的效果,就像我们对攻击病毒所做的那样。
结合位图和程序动画
为了制作一个令人信服的动画(其中重复或循环的部分不容易被观众注意到),我们需要使用大约 12 个帧位图,我通过 beam64frame11.png 命名为 beam64frame0.png,并优化为 PNG8 索引颜色,这样所有 48 分辨率 DPI 版本的帧总共只有 190KB,或者平均每帧不到 4KB。
我们在动画的两侧留了一个 alpha 通道,这样光束动画可以在士兵的后面播放(最好在 ImageButton UI 元素的背景层内),而不会通过士兵图像的 alpha 通道(透明区域)显示,士兵图像将保存在前景层(android:src)内。这样,当我们对士兵的图像进行 alpha 淡出时,这种光束动画特效会显示出来,这既是士兵淡出过程的一部分,也是士兵图像消失后(当士兵图像不在时)它自己的效果。
我们需要做的第一件事是将我们所有的 44 个光束帧动画素材复制到它们各自的/res/drawable-dpi/ project 文件夹中,如图图 10-11 所示。
图 10-11。将我们的 transporter 特效动画帧复制到我们的 drawable-dpi 文件夹并重命名
现在您应该对这个工作过程很熟悉了,所以让我们再多练习一下,将 96 像素的光束帧复制到/drawable-xhdpi 中,将 80 像素的光束帧复制到/drawable-hdpi 中,将 64 像素的光束帧复制到/drawable-mdpi 中,将 48 像素的光束帧复制到/drawable-ldpi 中。
接下来,我们将把这些文件重命名为beam0.png到beam11.png,这样我们在 XML 中引用时就有了一个更简单的文件名。为此,我们删除了 48 个文件(每个分辨率密度文件夹中有 12 个)中每个文件的分辨率指示器编号和字框。
接下来,我们需要右键单击项目资源的 drawable 文件夹,并使用我们的 New > Android XML File helper 对话框工作流程,如前面章节中的图 10-1 以及第九章图 9-1 所示。一旦在 Eclipse 环境之外添加了这些文件,不要忘记使用 F5 或 Refresh 工作流程,以便您的项目可以“看到”它们。
将您的资源类型:设置为 Drawable ,然后选择 <动画列表> 的根元素:,最后将文件命名为 anim_effect_beam ,就像它将要成为的那样,一个动画效果命名为 beam,点击完成。
接下来,将这个传送器光束效果的帧添加到为我们创建的 XML 文件中。
在 XML 中配置动画
我们希望效果是无缝的,所以我们将使用 pong 动画效果,并在第 0 帧和第 11 帧之间来回反弹,因此第 1 帧到第 10 帧将在 XML 代码中使用两次。
请注意,我们没有复制第 0 帧或第 11 帧,因为这将导致动画暂停,并在无缝动画循环期间通过播放暂停向观众泄露其帧边界。我们想利用一个相当慢的帧速率****8 FPS来获得一个缓慢的闪烁效果,由于 8 除以 1000 共 125 次,我们的 android:duration 值将是 125 毫秒,在 XML 中表示为 android:duration="125" 。
这里显示了一个
<item android:drawable="@drawable/beam0" android:duration="125" />
配置我们的光束动画帧访问和顺序的 XML 代码如图 10-12 所示。我们从波束 0 开始,经过波束 11,然后回到波束 1,每个波束使用 8 FPS 设置。这个设置给了我们一个很好的,均匀的,无缝的传送器光束乒乓动画特效。
图 10-12。在 pong 配置中添加传送器光束特效帧到 anim_effect_beam.xml
接下来,我们需要引用 anim_effect_beam.xml 文件,通过我们的 attack invasive ImageButton 的参数 android:background 中其名称的第一部分,这样它就可以在 UI 元素中的前景源图像后面制作动画。因为我们已经精心制作了我们的阿尔法通道,背景效果只有在前景图像淡出时才可见,在由<阿尔法>创建的淡入淡出期间也是如此。这创造了一个我们正在寻找的传输效果,通过结合 Android 中这两种关键类型的动画,以及最少量的数字图像素材。
记住,前景源图像实际上是一个多态图像 XML 定义文件,我们将其命名为attack _ invasive . XML,在 android:src 参数中引用。这表明我们正在变得非常复杂,在我们的 UI 元素背景板中使用基于 XML 的帧动画合成特效,在我们的 UI 元素前景板中使用基于 XML 的多态图像按钮定义,如图图 10-13 所示。以下是图中所示的 invadeButton 的 XML 标记:
<ImageButton
android:id="@+id/invadeButton"
android:background="@drawable/anim_effect_beam"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/content_desc_invade"
android:layout_margin="8dp"
android:src="@drawable/attack_invade" />
图 10-13。将 anim_effect_beam 引用插入到 ImageButton 的 android:background 元素中
如果由于某种原因,这种使用单个 UI 元素的特效设置对于 Android 来说太复杂,或者不被支持,或者由于某种原因不工作,我们将使用位于同一屏幕区域的两个用户界面元素来设置这种特效,以向您展示 Z 顺序的概念。
让我们来看看我们使用 Run As Android Application 工作流程的效果。一旦应用启动,点击菜单按钮,并选择攻击一个星球的活动屏幕,然后观察入侵图标。
请注意,士兵像以前一样渐隐为黑色,但背景动画不可见,因此要么是不支持,要么更有可能是 alpha 渐隐应用于整个用户界面元素,而不仅仅是其前景图像组件。
在淡入淡出后实现背景
接下来我们应该尝试使用 android:background 参数,它是< alpha >标签参数的一部分,如图图 10-8 所示。这个参数应该允许我们在我们的渐变后面实现一个背景,所以将这个参数添加到我们在图 10-9 中显示的参数中,并将其设置为引用 anim_effect_beam,并从图 10-13 中显示的 ImageButton 标签参数中移除 android:background 参数。
再次使用运行 Android 应用的工作流程,并在攻击星球活动屏幕中再次测试效果。因为背景效果在士兵淡出后不再保留,所以我们需要通过一个单独的 UI 元素来实现它,一个 ImageView,它就是为了这个目的用来保存图像的。
在 invadeButton ImageButton 标签之前添加一个 ImageView 标签,并将其设置为 invadeEffect 的一个 android:id ,并将其 android:background 参数设置为 anim_effect_beam 来引用我们的帧动画特效。确保将 android:layout_width 和 android:layout_height 参数设置为 wrap content 并将Android:layout _ margin left设置为 8dp 进行对齐。
8dp 左边距将你的背景图像推到 invadeButton UI 元素后面的位置,因此效果与士兵一致。确保包含一个Android:content description参数集来引用@ string/content _ desc _ invasive常量,否则 Eclipse 会给你一个警告。
一旦您在前两个 ImageButton 之间添加了这个 ImageView 标记,我们就必须在 invadeButton 中更改并添加几个参数,因为您可能还记得,LinearLayout 是按行或列排列的,因此,这个 ImageView 会将我们的 ImageButton UI 元素向下移动一个等级,除非我们为此进行参数更改。
这些 UI 参数更改的另一个选项是将整个屏幕布局容器重新编码为一个 RelativeLayout 容器,正如您将在下一节中看到的,这使得 Z 排序 alpha 通道兼容的 UI 元素更加容易。现在,我们用两种方法中比较容易的一种。
我们需要调整的最重要的事情是入侵图标 ImageButton 的像素间距,这样它就可以覆盖入侵效果 ImageView。我们这样做是为了让我们的 transporter 效应在我们的多态 ImageButton 后面发挥作用。
删除在我们的按钮周围放置 8dp 像素间距的Android:layout _ margin = " 8dp ",并添加一个Android:layout _ margin left = " 8dp "将 ImageButton 从屏幕一侧推出,使其与其他 ImageButton 图标对齐。
接下来添加一个 android:layout_marginTop 参数,设置为—
54,—
没错,就是负五十四如图图 10-14 所示。这将把我们的入侵图标 ImageButton 向上推到我们的 invadeEffect ImageView 的顶部,它包含了我们的传送光束特效动画。下面是此时这个< ImageButton >标记的 XML 标记的样子:
<ImageButton
android:id="@+id/invadeButton"
android:background="#00000000"
android:layout_marginTop="-54dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/attack_invade"
android:contentDescription="@string/content_desc_invade"
android:layout_marginLeft="8dp" />
图 10-14。向我们的 activity_attack.xml 添加 ImageView UI 元素,以保持我们的 transporter beam 效果
新的
要将其余的 ImageButton 图标向上拉,并放回原位,请从 invadeButton 正下方的 infectButton ImageButton 标记中删除 android:layout_margin="8dp ",并添加一个Android:layout _ margin top = " 18dp ",以向下分隔其他图标,以及一个Android:layout _ margin left = " 8dp "。
为动画添加 Java 代码
现在是时候在 Java 代码中实现 ImageView 对象和 AnimationDrawable 对象了,这样我们就可以实现 transporter 效果了。
声明一个名为 invadeEffect 的 ImageView 对象,通过使用 findViewById( ) 方法,使用下面一行 Java 代码,将它设置为我们的 XML 文件中定义的 invadeEffect UI 标签,如图图 10-15 所示:
ImageView invadeEffect = (ImageView)findViewById(R.id.invadeEffect);
图 10-15。为 AttackPlanet.java 添加入侵效果 ImageView 和运输效果 AnimationDrawable
然后使用声明一个名为 transporterEffect 的 AnimationDrawable 对象。getbackground( ) 方法使用下面一行 Java 代码:
AnimationDrawable transporterEffect = (AnimationDrawable) invadeEffect.getBackground();
接下来,调用。使用下面一行 Java 代码为transporter effectanimation drawable 对象提供 start( ) 方法:
transporterEffect.start();
现在,我们已经准备好看到我们的应用中实现的最终效果,通过使用作为 Android 应用运行的工作流程。启动 Nexus S 模拟器,点击菜单按钮,选择攻击一个星球菜单选项,进入我们的活动界面。
正如你所看到的,当我们的士兵现在淡出时,通过我们的
请注意,我们只用了 190KB 的总素材、几行 Java 代码和几十行 XML 标记就完成了这项工作。如果我们必须单独通过数字视频或帧动画来实现这种效果,那么要达到类似的质量,需要十倍多的数据占用空间。
实现复杂动画:XML
接下来我们要做的是通过在一个单独的动画集中一起使用旋转、缩放和阿尔法标签来创建一个复杂动画。这是使用 < set > 标签完成的,您可能已经猜到了,因为您已经在之前的几个屏幕截图中看到过这个< set >根元素选项。
一个动画集就像一个组,所以这些<集>标签允许你将动画类型分组在一起,如果需要,甚至可以通过在其他集内嵌套集来对它们进行分组,从而创建越来越复杂的动画或特殊效果的子集。
我们将围绕我们的星球实现一个简单得多的力场动画,仅使用三帧 2D 图像动画,然后旋转、缩放和淡化这些光栅动画数据,以产生一个更复杂、数据更紧凑的星球力场效果。
为此,我们必须首先将 ring320frame0.png 到 ring320frame2.png 的文件(以及其他分辨率密度版本)复制并重命名到 drawable-dpi 文件夹中,并使用图 10-11 所示的常用工作流程将它们重命名为 ring0.png 到 ring2.png。复制完所有 12 个文件后,右击 drawable 文件夹,选择新建> Android XML 文件对话框,创建一个 Drawable XML 容器,带有 < animation-list > 根元素,命名为 anim_plasma_ring ,最后点击完成按钮。不要忘记利用 F5 或刷新工作流程,以便 Eclipse 可以看到您的可绘制素材。
添加三个引用 ring0、ring1 和 ring2 素材的 < item > 标签,并为每个 166 毫秒设置一个持续时间,这将每秒钟为你的力场环设置两次动画(6 乘以 166 等于 1000)。您添加的每个<项目>标记应该具有以下 XML 标记格式:
<item android:drawable="@drawable/ring0" android:duration="166" />
最终的 XML 标记显示在图 10-16 中的 Eclipse XML 编辑窗格中。
图 10-16。创建 anim_plasma_ring 帧动画 XML 定义,用于我们的复杂动画
然后进入您的 activity_main.xml 文件,将您的 ImageView 标签 android:background 参数更改为 reference @ drawable/anim _ plasma _ ring,如以下 xml 标记所示:
<ImageView
android:id="@+id/imageEarth"
android:src="@drawable/earth"
android:padding="24dp"
android:background="@drawable/anim_plasma_ring"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView8"
android:contentDescription="@string/content_desc_invade" />
这显示在图 10-17 中,一旦你修改了你的 XML 标记,加入了新的 android:background 参数,引用了你新的 anim_plasma_ring drawable,我们将准备好实现我们的动画集,它将在动画制作时进一步旋转你的背景,并在地球上缩放它,同时整个动画会稍微淡入淡出。
图 10-17。引用我们 activity_main.xml 文件 ImageView 中的新 anim_plasma_ring 帧动画
现在我们可以右击可绘制文件夹,选择我们新的 Android XML 文件菜单序列,创建一个补间动画 XML 资源类型,命名为 anim_set_ring ,然后选择 < set > 根元素,点击 Finish 按钮,如图图 10-18 所示。
图 10-18。使用新的 Android XML 文件对话框创建一个 anim_set_ring < set >复杂动画结构
配置设置标签
接下来,进入空的
图 10-19。使用通过<键访问的助手对话框,向我们的< set >父标签添加一个< rotate >子标签
确保将 xmlns:android 参数添加到您的< set >标签中,以引用http://schemas.android.com/apk/res/android
URL,这样我们放在
让我们将我们的旋转 android:fromDegrees 参数设置为 0(零),将我们的 android:toDegrees 参数设置为 360 ,这样我们就得到一个无缝的环旋转。
接下来,让我们将我们的 android:pivotX 和 android:pivotY 参数设置为 50% ,以动画环形元素的中心为中心进行旋转。我们将使用一个很好的均匀线性插值,因为我们正在循环动画,以及 10,000 毫秒的缓慢持续时间,或 10 秒的总旋转。
对于我们的重复参数,我们将使用Android:repeat count = " infinite "和Android:repeat mode = " restart "参数,以便我们可以实现一个无缝循环,永远持续下去。
我们可以在稍后的 Java 代码中使用 visibility 参数来打开和关闭这个力场效果,所以我们希望我们的力场永远保持动画效果,只要所有这些动画设置参数都是相关的。父<集>标签内的<旋转>标签的 XML 标记应该如下所示:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/linear_interpolator"
android:duration="10000"
android:repeatCount="infinite"
android:repeatMode="restart" />
</set>
你可以看到我们完成的
图 10-20。使用通过<键访问的助手对话框,向我们的<集合>父标签添加一个<比例>子标签
正如您在图 10-20 中看到的,我们再次利用 < 键调出标签选择器对话框,我们现在选择一个 <缩放> 标签添加到我们的缩放参数中,以添加图像缩放这个复杂的动画集。
添加图像缩放比例
我们将从最重要的 android:fromXScale="1.0" 或全比例(原始位图图形元素像素尺寸)开始,然后缩小到一个 android:toXScale="0.75" 或原始大小的四分之三比例。
因为我们想要一个统一的(纵横比保持不变)缩放,我们将对 android:fromYScale 和 android:toYScale 参数使用相同的参数。正如我们在本书前面所学的,通过在 X 轴和 Y 轴或维度(H & W 边)上等量缩放图像或动画帧,可以保持纵横比,减少空间失真。
由于我们希望均匀地缩放力场环,我们将 android:pivotX 和 android:pivotY 参数设置为 50%的值,这将缩放原点直接放在我们正在缩小的位图图像(或在本例中,基于帧的光栅动画)的中心,然后使用我们将要添加的接下来的四个参数再次返回。
因为我们希望平滑均匀地缩放,所以我们希望通过Android:interpolator = " @ Android:anim/linear _ interpolator "参数添加一个线性插值器。为了获得缓慢的缩放效果,我们将我们的 android:duration 参数设置为10000 毫秒 (10 秒),以获得更真实的效果。
最后,我们设置我们的重复参数来定义我们的缩放将如何随着时间的推移而执行。我们希望效果永远持续下去,所以我们将 android:repeatCount 参数设置为 infinite 。因为我们希望缩放效果在每个星球周围缓慢地进出,所以我们将 android:repeatMode 设置为 reverse ,这样缩放会自动反转。
到目前为止,XML 标记应该看起来像下面显示的标签和参数:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/linear_interpolator"
android:duration="10000"
android:repeatCount="infinite"
android:repeatMode="restart" />
<scale android:fromXScale="1.0"
android:toXScale="0.8"
android:fromYScale="1.0"
android:toYScale="0.8"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/linear_interpolator"
android:duration="10000"
android:repeatCount="infinite"
android:repeatMode="reverse" />
</set>
最终的刻度标签如图 10-21 所示。
图 10-21。使用通过<键访问的助手对话框,将< alpha >子标签添加到我们的< set >父标签
最后,我们使用 < 键添加我们的 < alpha > 程序动画标签,如图图 10-21 所示,以及我们完成的< scale >标签参数。
添加 Alpha 程序动画
标签有最少的总参数,因为它只是控制一个渐变效果,在这种情况下,从 100%不透明到 50%透明或一半透明,以显示一些恒星或星系背景图像。既然力场通常是透明的,为什么要打破常规呢?
首先使用 android:fromAlpha 参数,通过使用 1.0 的值将我们的源图像(或本例中的动画)透明度级别设置为 100%。接下来使用 android:toAlpha 设置值为 0.5 或 50%透明,为我们的力场环动画获得一个真实的半透明效果。
接下来让我们设置我们的 android:interpolator 参数为 linear ,为我们的渐变(混合)alpha 值提供一个平滑的线性插值。这是一个参数,您可以使用插值器常量来尝试获得更动态的透明度变化效果。
因为我们希望将这个效果的持续时间与其他两个缩放和旋转效果相匹配,所以我们将使用一个 android:duration 参数,并将其设置为 10000 毫秒。同样,由于渐变效果不需要与旋转和缩放效果同步,您也可以使用该数值,以获得更真实的力场脉冲效果。
最后,我们设置我们的重复参数,将 android:repeatCount 参数设置为 infinite ,将 android:repeatMode 参数设置为 reverse ,这样,这个淡出在动画周期结束后转变为淡入。XML 标记应该如下所示:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/linear_interpolator"
android:duration="10000"
android:repeatCount="infinite"
android:repeatMode="restart" />
<scale android:fromXScale="1.0"
android:toXScale="0.8"
android:fromYScale="1.0"
android:toYScale="0.8"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/linear_interpolator"
android:duration="10000"
android:repeatCount="infinite"
android:repeatMode="reverse" />
<alpha android:fromAlpha="1.0"
android:toAlpha="0.5"
android:interpolator="@android:anim/linear_interpolator"
android:duration="10000"
android:repeatCount="infinite"
android:repeatMode="reverse" />
</set>
Eclipse 中的 XML 编辑窗格应该看起来像图 10-22 所示的屏幕。
图 10-22。完成我们的<集合>父标签,包含旋转、缩放、阿尔法的复杂动画参数
接下来,我们需要通过当前的 XML RelativeLayout 容器将这个新的 forcefield 动画实现到我们的主屏幕用户界面屏幕中。我们使用一个设置为 planetEffect 的 android:id 参数,给这个动画用户界面元素一个自己的 ImageView 标签。
我们以这种方式设置它,因为如果我们将这个缩放动画应用到我们的原始 imageEarth ImageView,并将 anim_plasma_ring.xml 引用为其背景组件,整个 UI 元素将被旋转、缩放和混合,在这种情况下,我们希望行星图像保持不变。
事实上,我们在本书的前一部分看到了同样的考虑,我们需要分离我们的程序动画效果来处理它自己的 UI 元素容器,原因大致相同。
接下来,包含必需的 android:layout_height 和 android:layout_width 参数,这两个参数都被设置为 wrap_content ,以及android:content description参数,这也是 Android 对于任何图像相关标签所必需的。
接下来,我们将添加我们的相对定位标签, android:layout_below ,我们将引用我们的 imageEarth ImageView 引用的 @+id/textView8 ,因为我们希望这个 planetEffect ImageView 位于 imageEarth ImageView 占据的确切屏幕位置。还要确保您的 android:src 参数引用了包含我们的< set >复杂程序动画定义的@ drawable/anim _ plasma _ ringXML 文件。XML 标记如下所示:
<ImageView
android:id="@+id/planetEffect"
android:src="@drawable/anim_plasma_ring"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView8"
android:contentDescription="@string/content_desc_earth"
/>
最终的 XML 如图 10-23 所示。
图 10-23。添加带有 planetEffect and 和 anim_plasma_ring 源引用的 ImageView XML 标记
现在,让我们调整我们的 imageEarth ImageView 标记参数,以确保行星图像与 planetEffect ImageView UI 元素中包含的程序动画对齐并合成。将您的 android:padding 设置为 24dp ,并使用您的Android:layout _ margin left参数设置为 1dp 来微调效果中行星图像的居中。使用设置为# 0000000的 android:background 参数,确保 ImageView 是透明的,如以下 XML 标记所示:
<ImageView
android:id="@+id/imageEarth"
android:src="@drawable/earth"
android:background="#00000000"
android:padding="24dp" android:layout_marginLeft="1dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView8"
android:contentDescription="@string/content_desc_earth"
/>
结果如图 10-23 所示,我们将准备好编写 Java 代码来实现这些 ImageView UI 元素,以创建一个仅使用 200KB 数字图像数据的 planet forcefield 效果。
为动画添加 Java 代码
现在,我们要实现这个新的力场动画场景所要做的就是改变我们的 Java 代码来实现保存我们力场环动画的新 ImageView,并删除保存旧力场动画的 ImageView 对象,如图图 10-24 所示。
图 10-24。添加 effectPlanet ImageView,将其设置为通过引用 animSetRing 动画对象。startAnimation( )
正如你在图 10-24 的旧 Java 代码中看到的,我使用了双斜线注释功能,以消除编译器对这些代码行的考虑,但也保留给我们自己使用(这个功能的一个方便的用法,你可以考虑自己使用)作为学习参考,这样你就可以比较 Java 代码前后的情况。
第一行代码本质上和之前一样;声明 effectPlanet ImageView 对象,然后使用 findViewById( ) 方法将其设置为 XML 定义。Java 代码应该如下所示:
Imageview effectPlanet = (ImageView)findViewById(R.id.planetEffect);
剩下的我们只用两行代码就完成了,一行声明了程序动画对象,另一行将它连接到 effectPlanet ImageView 并启动动画运行。
为了声明一个程序动画对象,我们将使用 Android Animation 类,实例化一个动画对象,并在使用加载 XML 动画定义的同一行代码中命名它。loadAnimation( ) 方法,通过使用点标记法从 AnimationUtils 对象调用该方法。这可以在一行 Java 代码中完成,如下所示:
Animation animSetRing = AnimationUtils.loadAnimation(this,R.anim.anim_set_ring);
这是对 Android 操作系统和编译器的暗示:我想创建一个名为 animSetRing 的动画对象,并在当前上下文中,使用 AnimationUtils 类加载我在 anim_set_ring.xml 文件中定义的动画数据。一旦完成,我们开始动画。
。startAnimation( ) 方法是从 effectPlanet ImageView 对象调用的,我们在前面设置了两行 Java 代码,该方法被传递给一个动画对象,这个动画对象也是我们刚刚创建的,使用了一小段代码:
effectPlanet.startAnimation(animSetRing);
现在是时候看看我们新的行星力场在起作用了。右键单击项目文件夹,使用 Run As Android Application 工作流程在 Nexus S 模拟器中启动 Hello World Android 应用。
一旦应用运行,你可以在主屏幕的底部看到行星地球图像视图,以及新的力场动画用户界面元素,如图图 10-25 所示。forcefield 动画用户界面元素现在可以在三个不同的图像合成帧之间制作动画,同时还可以旋转、缩放和淡入淡出,使用我们编写的程序动画 XML 标记来控制该用户界面元素的许多属性。
图 10-25。运行我们的新主屏幕和特效
接下来,我们将为最后一个攻击星球的图像按钮图标实现程序动画:致命但美丽的激光加农炮。
实现运动动画:XML
最后,为了确保本章中使用的动手操作示例涵盖了 Android 中用于程序动画的所有根元素,我们现在将在攻击星球活动用户界面中为我们的 LaserCannon ImageButton 图标创建一个逼真的发射反冲脉冲。
让我们右键单击我们的 drawable 文件夹,选择 New ** Android XML 文件,创建一个新的补间动画** XML 容器,名为 anim_trans_laser ,然后选择一个 translate 选项,这样它就包含了一个 < translate > 根元素。
由于 XML 文件只有一个标签,请遵循以下工作流程:删除结束标签,然后将
如果你愿意,你可以输入 android: 来查看
图 10-26。添加一个<平移>根元素到我们的动画 _ 平移 _ 激光并调用参数助手对话框
配置翻译标签
我们最重要的参数是那些告诉我们的动画如何在屏幕上移动对象的参数:参数 android:fromXDelta 和 android:toXDelta 。这些 Delta 参数采用百分比值,因为我们只是要为我们的激光炮模拟一个短暂的脉冲逆火,我们将这两个参数设置为从 0%(定义它们当前的位置)到—
10%(沿负 X 轴向后一小段距离)。
因为我们希望我们的运动是沿着一条对角线,所以我们还会将 Y 轴参数设置为 0%和 10%,这样运动量就会相等,而且是完美的对角线(45 度,就像我们的激光炮一样)。
您可以使用这四个值来调整效果的大小和方向,并感受一下参数是如何操作的。
接下来,我们要设置我们的插值器,对于脉冲激光炮来说,反冲是一个重要的属性。幸运的是,对于这种效果应用,有一个完美的反弹插值器,所以让我们添加我们的Android:interpolator = " @ Android:anim/bounce _ interpolator ",以便我们可以将这种逼真的运动曲线效果放置到位。
因为这个动画的特点是一个短而快的运动能量爆发,我们将使用一个只有 80 毫秒的 android:duration 参数,这样运动就快速而锐利。另一方面,我们不希望激光加农炮经常发射,所以我们将使用 android:startOffset 参数设置为 8000 毫秒,这样激光加农炮就不会过于频繁地发出脉冲,最终在视觉上干扰我们的最终用户。
最后,我们需要设置我们的重复参数,所以让我们设置 android:repeatCount 到 infinite 和 android:repeatMode 到 restart ,这样激光炮每次都会以相同的方式发出反冲脉冲。标签的 XML 标记应该如下所示:
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0%"
android:toXDelta="-10%"
android:fromYDelta="0%"
android:toYDelta="10%"
android:interpolator="@android:anim/bounce_interpolator"
android:duration="80"
android:startOffset="8000"
android:repeatCount="infinite"
android:repeatMode="restart"
/>
最终标签如图 10-27 所示。
图 10-27。将翻译动画参数添加到我们的 XML 文件中,使激光炮脉冲发射
最后,我们需要进入我们的 AttackPlanet.java 活动类,并添加 Java 代码来启动所有这些(没有双关语)。
为翻译添加 Java 代码
现在大家应该都很熟悉了,毕竟熟能生巧,所以让我们首先实例化我们的动画对象并将其命名为 translateLaser,然后通过下面一行 Java 代码使用 AnimationUtils 类 loadAnimation()方法从名为 anim_trans_laser.xml 的 XML 文件加载我们的动画,如图图 10-28 所示:
Animation translateLaser = AnimationUtils.loadAnimation(this,R.anim.anim_trans_laser);
图 10-28。编码 translateLaser 动画对象并使用。startAnimation()方法将其连接到 laserButton
然后我们可以调用。startAnimation( ) 方法,使用下面一行 Java 代码将 laserButton ImageButton 对象的 translateLaser 关闭:
laserButton.startAnimation(translateLaser);
一旦这一切就绪(见图 10-28 ),我们就可以使用我们的作为 Android 应用运行工作流程,启动我们的 Nexus S 模拟器,看看我们的激光加农炮脉冲!
一旦应用启动,点击菜单按钮,选择攻击一个星球菜单选项,进入你的用户界面屏幕,观察激光加农炮反冲,从它的激光发射序列反冲。请记住,由于 startOffset 参数的原因,您必须等待 8 秒钟,第一个平移动画才会“启动!”现在,我们的《攻击星球》UI 屏幕已经完全动画化,可以供最终用户使用了。
摘要
在本章中,我们学习了如何在 Android 中实现程序动画。我们了解到这种类型的动画使用代码和算法,而不是位图,就像基于帧的动画一样,我们还了解了矢量动画的主要概念和组件,以及矢量图像、旋转、平移和缩放。我们还看了一下如何制作 alpha 通道值的动画。
然后,我们继续(没有双关语)在当前 Hello World 应用中为静态和动画位图图像添加程序动画,添加比例以使我们的病毒脉冲更真实,旋转我们的炸弹图像以使其动画化,alpha 通道混合到我们的入侵士兵以实现令人印象深刻的传输光束效果,并转换到我们的激光炮以使其反冲,就像它射出了一个激光脉冲一样。
我们还研究了如何创建更复杂的动画集,通过使用 < set > 标签将各种类型的程序动画标签分组,以形成更复杂的动画定义,其中所有这些组件都由 Android 同时并行处理。
在本书的下一部分中,我们将了解并在 Hello_World 应用中实现更复杂的新媒体元素,如数字音频和数字视频。在下一章,我们将学习数字视频概念、数字视频编辑和数字视频压缩。****
十一、视频介绍:概念和优化
在这一章中,我们将为提供一些基础知识,帮助你了解数字视频的工作原理,以及如何在你的 Android 应用中处理数字视频。
本章包括许多高级概念,这些概念将建立在你在第七章和第八章中学到的所有关于数字成像概念和技术的知识之上。
这种新媒体素材更难理解和优化,因为数字视频本质上是静态数字图像的移动集合。因为数字视频总是在移动,所以它引入了第四维度,或者说时间。这一点,加上当今强大而复杂的数字视频编码和解码算法所带来的复杂性,使得数字视频在本质上比数字成像更难学习,甚至比程序动画更难学习。
优化数字视频也很复杂,主要是因为数字视频对视频中的所有帧进行压缩,这需要更高级的数学知识,因此需要更高级的知识来了解压缩过程中到底发生了什么,才能有效地实施这些压缩算法。
我们将在如何应用于为您的 Android 应用最终用户创建最佳用户体验的背景下审视所有这些。
这意味着不仅要了解数字视频编辑的基本原理及其数据占用优化,还要了解哪些数字视频格式最适合用于 Android,以及在何种分辨率下使用它们。这些主题是如此先进,我们将利用两个章节来有效地彻底涵盖一切,这样你就可以很好地处理数字视频,以及如何针对 Android 进行优化。
数字视频的基础:像素,帧 ,FPS ,编解码器
您在第七章中了解到的所有适用于您的数字影像的概念(像素、分辨率、纵横比、颜色深度、alpha 通道、图层、混合和合成)同样适用于数字视频,并且当您处理数字视频时,它们对您来说同样重要,甚至更重要。
你在第九章中学到的一些关于基于帧的动画的概念也适用于数字视频。这是因为数字视频和光栅动画一样,是基于帧的内容,可以随着时间的推移快速显示。由于这个原因,帧率的概念,表示为每秒帧数,或 FPS,在您的数字视频数据足迹优化工作过程中也非常重要。
数字视频文件是用一种叫做数字视频编解码器的软件创建的,这是 ?? 公司的缩写。DV 编解码器是一个复杂算法的集合,既可以对装满数字视频帧的容器(文件)进行编码,也可以进行解码,以便最终用户可以存储、传输和查看。请注意,您的最终用户必须能够访问对视频帧进行编码的同一编解码器才能对其进行解码;这通常是通过操作系统(Android)或浏览器(Chrome 或 Firefox)完成的。
因为编码任何给定数字视频数据文件的相同编解码器(算法)也必须用于解码该数字视频数据,所以这证明了每个数字视频编解码器都具有编码组件或编码侧,以及解码组件或解码侧,其可以解密其编码器所写的内容。
因此,MPEG4 H.264 编码的数字视频文件只能使用 MPEG4 H.264 编解码器读取(解码),这是您的 Android 操作系统的一部分。类似地,由 VP8 编解码器编码的数字视频帧只能使用 VP8 编解码器播放(解码),Android 操作系统也支持 VP8 编解码器。
数字视频文件有不同类型的文件扩展名,告诉开发者和 Android OS 该文件中的数字视频数据是什么类型,以及使用哪种类型的编解码器(解码器)来读取(解码)该类型的视频数据。我们之前提到的 MPEG4 H.264 文件具有 .mp4 文件扩展名,因此如果我们使用这种特定的视频编解码器来编码视频数据,我们的火星旅行视频将被称为mars.mp4。
不同类型的数字视频编解码器具有不同的功能和性能规格。因此,我们想要查看的关键是任何给定编解码器提供的质量与文件大小的比率。一些数字视频编解码器(如 MPEG4 的 H.263 编解码器)质量较低,适用于低分辨率视频会议等应用,而其他编解码器(如 MPEG4 的 H.264 和 VP8)则更适合于高质量的内容,如电影和电视节目。在写这本书的时候,MPEG4 的 H.268 编解码器也已经发布,但是还没有被采用。为什么?因为:H.268 编解码器必须存在才能解码 H.268 内容!
重要的数字视频属性:标清、高清、流媒体和比特率
所以,现在我们知道视频编解码器将帧中的像素转化为数学;分辨率在哪里起作用,视频是如何存储和访问的?
存储视频:分辨率
高清普及之前使用的原始视频分辨率被称为 SD ,或 标清数字视频。在美国,SD 视频最初在高度上使用 480 行分辨率,因此 4:3 纵横比为 VGA 分辨率或 640 乘 480,宽屏纵横比 SD 视频为 720 乘 480。在欧洲,标清视频在高度上使用 576 行分辨率,因此欧盟的 4:3 标清视频将为 768×576,而欧洲的宽屏幕宽高比标清视频将为 1024×576。
最近高清 数字视频已经变得流行,并且使用宽屏 16:9 宽高比。高清分辨率有两种,第一种是 1280 乘 720 像素,我称之为伪高清分辨率,第二种,也是现在比较常见的高清分辨率,是 1920 乘 1080 像素,视频行业称之为真高清。
有趣的是,所有这些分辨率都非常接近 Android 消费电子设备上常见的屏幕尺寸。有 640×480 VGA 屏幕的入门级安卓手机,也有 800×480 WVGA 屏幕的主流安卓手机,接近 720×480 宽 SD 标准分辨率。还有 1024×600 的入门级(更小的外形)Android 平板电脑,接近欧洲范围内 1024×576 的标清分辨率。
较新的 Android 高清手机是 1280×720,或伪高清,最新的 Android 平板电脑是真高清 1920×1080。这非常方便,因为我们可以对视频内容使用广播分辨率标准,并且仍然可以达到大多数流行的 Android 屏幕分辨率。
访问数字视频数据:捕获和流式传输
那么,Android 设备首先是如何访问数字视频数据的呢?这可以通过两种方式之一来实现。数字视频数据(文件)可以被 捕获在您的应用本身中,在这种情况下,它是您的资源文件夹中的新媒体素材,就像您的图像和动画数据一样。
第二种访问数字视频的方式是通过一种叫做“??”的概念,从你的安卓设备和应用之外的外部视频服务器“??”解码(播放)数字视频文件。
流式传输数字视频数据的好处是它可以大大减少应用的数据占用量。这是因为您不必在您的中包含所有繁重的新媒体数字视频数据。APK 档案。
流式数字视频的缺点是,如果您的用户连接(或视频服务器)中断,您的视频文件可能无法始终呈现给最终用户播放和欣赏;因此,视频内容的可靠性和可用性是另一个需要考虑的因素。
流式数字视频的核心概念之一是数字视频数据的比特率。比特率是在压缩过程中定义的,因此我们将在本章的后续章节中更详细地讨论这个问题,但让我们在这里定义它,因为它是一个重要的视频概念。
比特率本质上定义了你的数字视频数据将被压缩到什么程度。比特率是在数字视频压缩过程中定义的,这就是为什么我将在本章关于数据占用优化的章节中更详细地介绍它。
比特率较低(数量较少)的数字视频文件将对数据进行更多的压缩,这将导致质量水平较低,但在更多的消费电子设备上播放会更流畅。
这是因为比特率是每秒比特或 BPS 的度量,可以有效地处理或传输。随着计算机处理器越来越快,它每秒可以处理更多的位,同样,随着数据带宽连接越来越快,它每秒也可以轻松地发送或接收更多的位。
正如你所看到的,位/秒不仅对流式数字视频内容很重要,因为它适合带宽,而且一旦它到达 Android 设备,它还会影响哪些内容可以足够快地处理(解码),以允许 Android 设备内的处理器顺利播放。因此,比特率之所以重要,有两个原因,一是因为流媒体视频,二是因为它只在一个方面(解码器处理速度)对 Android 应用中的捕获(嵌入)视频文件重要。
因此,在 Android 应用中嵌入了受控或嵌入的视频,视频资源的比特率越低,就有越多的 Android 设备可以顺利解码该视频资源(不会丢帧)。
原因很明显,每秒处理的数字视频数据位数越少,处理器的处理负荷就越轻。这不仅为视频播放带来了卓越的性能,也为整个 Android 应用带来了卓越的性能,同时也为 Android 设备内部的其他一切带来了卓越的性能。
因此,我们针对数字视频的数据占用优化非常重要,以较低的比特率获得良好的视频图像质量成为我们的最终目标,这也是我们希望使用最佳编解码器的原因。
在视频行业中,每秒一比特被写为:比特/秒,它通常包括一个大小修饰符,如前面的kbit/秒。这个 k 将表示千比特每秒,这意味着每秒几千比特。大多数视频压缩比特率设置在256 kbit/s(256 kbps)和 768 kbit/s 之间,尽管我们将尝试为我们的 Android 应用优化我们的数字视频数据到更低的范围,在 192 kbit/s 和 384 kbit/s 之间,但仍然可以获得出色的图像质量。如果我们能做到这一点,我们的数字视频将播放流畅,在所有不同类型的 Android 消费电子设备上看起来都很棒。这就是本章非常重要的原因。
数字视频格式:支持 Android 中的数字视频编解码器
Android 中支持的三种主要的数字视频数据格式: MPEG4 H.263 ,是质量最低(性能最差)的编解码器,可以有 .3gp 或 .mp4 文件扩展名; MPEG4 H.264 AVC ,可以有 .3gp 或 .mp4 文件扩展名;还有 VP8 ,可以有一个。webm 或 an 。mkv 文件扩展名,在 Android 2.3.3(及以后版本)中支持捕获播放,在 Android 4.0(及以后版本)中支持视频流播放。
第一个编解码器 MPEG4 H.263 主要用于视频会议,因此我们将更多地关注更高质量、更常见和更广泛的编解码器,MPEG4 H.264 AVC 和 VP8。 AVC 代表:高级视频编码。
这两种流行的编解码器在 HTML5 中都得到了很好的支持,因此在所有流行的浏览器中都得到了支持。MPEG4 H.264 AVC 在所有版本的 Android 中都支持回放,并且在 Android 3.0(和更高版本)中支持使用数码相机硬件对视频进行编码,如果 Android 设备包括一个的话。
VP8,也称为 WebM ,包含在 Android 2.3.3 中,用于播放受控数字视频文件,包含在 Android 4.0 和更高版本中,用于播放流式数字视频文件。当前版本的 Android 不支持将相机数据编码为 VP8 格式,但也许该功能将成为 Android 5.0 的一部分,这应该很有意思。
因为我们专注于 Hello World 应用的数字视频解码,并且我们正在项目资源文件夹中优化和使用捕获或嵌入的数字视频文件,所以本章我们将专注于 MPEG4 H.264 AVC 和 VP8 的优化,因为这些是目前我们可用的优秀编解码器。
这两种编解码器中最常见的数字视频编解码器是 MPEG4 H.264 AVC 编解码器,因此市场上的大多数数字视频编辑软件,如 Final Cut Pro 或 After Effects,都可以将数字视频编码成这种格式。这是因为目前互联网上几乎每个人都在使用 HTML5 制作数字视频,因此大多数流行的数字视频编辑包中都包含了 H.264 编解码器支持。
我将使用英国伦敦 EditShare 的开源 Lightworks 数字视频编辑软件包。几年前,EditShare 慷慨地将他们以前的六位数非线性数字视频编辑软件包开源,新版本 11 应该会在本书发布时发布,他们刚刚在我最近下载的版本 11.1 中添加了 H.264 支持。我们还将使用流行的 Sorenson Squeeze Pro 软件来了解数字视频编码,这是互联网 2.0 视频编码的行业标准。
VP8 编解码器支持稍微更新一些,目前在 Lightworks 中没有,所以我将在本书中使用流行的 Sorenson Squeeze 视频编码软件包,以便您可以看到一个支持 Android 中当前支持的所有格式的编码包。
数字视频优化:跨设备和分辨率播放
既然我们已经决定了 Android 操作系统中支持的哪些数字视频编解码器最适合我们在 Hello World 应用中的使用,我们现在需要确定我们将为不同密度级别的 Android 设备屏幕支持哪些分辨率。
我们需要选择三到四个关键分辨率,这些分辨率分布得足够远,以完美地适应各种屏幕尺寸,从入门级的 320 x 480 预付费 El-cheap 手机,一直到支持真正的 HD 1920 x 1080 或更高分辨率的新型大型平板电脑和 ITV。
如果我们要以 1920×1080 的原生 XXHDPI 分辨率支持真高清,那么我们应该有四种不同的分辨率(和比特率),其中之一就是那个。在这种情况下,我会使用 VP8 ,因为视频的每一帧都有 1920(宽)乘以 1080(高)乘以 3 (R,G,B)或 6 、 220 、 800 个像素值要压缩。如果我们想要一个快速的, 30 FPS 的帧速率,我们需要将该值乘以 30,这样我们每秒要压缩的像素数据总量为 186,624,000 像素。
符合逻辑的下一个支持的分辨率级别是伪高清1280×720XHDPI分辨率级别,用于高清智能手机、大多数平板电脑和一些 ITV。如果你只打算使用三个目标分辨率,这一个将成为最高级别,并且可以放大以适应 1920×1080 的显示器,并且看起来仍然相对不错,因为你在编解码器设置方面做得很好。如果你知道你在做什么,使用 VP8 这实际上是很可能的,它可以以低比特率产生惊人的质量水平和巨大的数据足迹。
合乎逻辑地支持的下一个目标分辨率水平也是一种流行的广播分辨率,美国的宽 SD 格式,或者 ?? HDPI ?? 屏幕的 720×480 像素。到目前为止,我们的四个目标分辨率中有三个也是非常常见的目标视频广播分辨率,因此您在为 Android 应用支持准备数字视频文件的工作过程中,也可以方便地为其他媒体和目的提供优化的数字视频素材,一切都不费吹灰之力。这一目标分辨率将适用于 800 x480 和 854 x480 屏幕分辨率的 Android 设备,主要是中级智能手机和迷你平板电脑。
符合逻辑的最终目标分辨率是四分之一高清,为 MDPI 屏幕的 480×270 像素。这符合原始智能手机 480 x320 的分辨率,因为仍有廉价的预付费智能手机在生产中使用这种分辨率。
请注意,480 乘 270 正好是高清的四分之一(因此 QHD 或四分之一高清),将分辨率缩小 1/2 或 1/4 可获得最佳质量结果。只要我们在视频压缩优化方面做得很好,我们应该能够将 MPDI 的比特率提高到 192 kbps。那么我们将为 HDPI 设定 384 kbps 的比特率,为 XHDPI 设定 768 kbps 的比特率。
数字视频创作:创造我们的火星表面飞越
在我们真正能够进入视频压缩软件包之前,比如 Sorenson Squeeze Pro ,我们需要创建一个我们星球表面的飞越。由于火星最近很受欢迎,我们将使用名为布莱斯 7 专业版的流行地形编辑软件包来创建 900 帧视频内容。我们将以每秒 15 帧的速度创建该视频内容,让我们有一分钟的时间飞越红色星球表面,我们可以用它来学习一些数字视频优化的概念。
如果你还没有,去 DAZ 3D 网站下载 Bryce 7.1 或谷歌“Bryce 7 Download ”,然后去下载该软件的网站。安装软件并启动,你会看到如图图 11-1 所示的 Bryce 7 Pro 启动画面。
图 11-1 。来自 DAZ 的 Bryce 7 Pro 是地形生成软件
转到参考资料中的 Bryce 文件夹。zip 文件,并在那里打开 Mars.br7 文件。这是一个简单的表面地形,用类似火星的材质绘制,有一些薄薄的橙色云,地平线上的薄雾,和一个黄色的太阳。我在地表附近放置了一个摄像机,显示了地面、天空和太阳,并制作了一个直线摄像机的动画来模拟在行星表面快速飞行。
生成未压缩的帧
在下一章中,我们将通过 Java 代码使用一些更高级的媒体播放器控件,我们可以调整我们的播放速度。现在,我们将在一个。AVI 文件格式压缩可以阅读练习视频压缩。
-
Once Bryce has launched, use the File
Open menu sequence, and locate the book resources folder and the Bryce sub-folder, and then open the Mars.br7 file, as shown in Figure 11-2. Notice the 3D data is only 77 KB.
图 11-2 。使用 Bryce 7.1 中的文件
打开菜单序列打开 Mars 文件
-
一旦这个文件被打开,你会看到一个平坦的陆地地形网格和地平线;在你渲染这个 3D 场景之前,你不会看到这个将会生成的图像。如果您想要渲染 3D 火星场景的第一帧,您可以在左侧 UI 面板中单击较大的(最低居中的)绿色球体。
-
接下来,我们使用文件
进行动画设置。。。菜单序列,用于设置我们的 3D 动画参数,包括当前开始时间或帧数、持续时间或结束时间或帧数、我们使用 FPS 的帧速率、我们的回放参数和帧显示类型(帧数或 SMPTE 时间)。
-
Set the Current time to 0:00:00.00 Frame Number 0 and the Duration time to 0:01:00.00 Frame Number 900 and set the FPS to 15 as shown in Figure 11-3.
图 11-3 。设置动画参数
注意注意布莱斯用术语描述 Pong 动画:钟摆动画!
-
我猜钟摆动画是对 Pong 动画描述的巧妙改进,但我们打算将我们的动画设置为播放一次,并控制 Android 中的任何其他播放参数。此外,将显示参数设置为帧数,这样一旦您实际渲染了这个火星动画,您将能够看到哪个帧当前正在 Bryce 中渲染。首先,我们必须配置我们的渲染引擎。
配置渲染引擎
接下来让我们设置我们的渲染选项。这些控制 3D 特征,决定每一帧视频渲染需要多长时间,如图 11-4 所示。
图 11-4 。为火星表面飞越动画设置我们的火星 3D 动画渲染选项
- 通过单击渲染球体旁边的向下箭头,打开一个菜单,并选择渲染选项来访问渲染选项对话框。
- 我们的火星动画在每个分辨率级别的渲染时间(或帧渲染速度)的主要决定因素是使用质量模式设置来设置的。我们对每一帧进行的反走样越多,渲染的时间就越长。为了获得最高质量的视频和合理的渲染时间,使用对话框左上角显示的常规设置。
*** 我们在每一帧上做了一个完整的渲染,用纹理渲染 ??,这样我们的火星表面看起来就像红色的岩石。我们还优化了统一场景和最小优化来节省每帧渲染时间。* 我们关闭所有的后处理选项关闭,因为我们不需要伽马校正,我们不希望额外的噪声通过 48 位抖动引入每一帧,因为那个选项会使我们的文件变大,正如本书前面所学的。* 我们使用透视投影(一个普通的相机镜头)和无遮蔽,因为我们没有使用任何阿尔法通道。最后,我们将关闭光学和灯光设置下所有“昂贵”的计算密集型选项,因为这些对于这个简单的行星表面飞越来说是不需要的,并且大多涉及有水的场景,而且火星上没有太多的水。* 对话框右下角的最后一部分控制着反走样设置,以及用于在我们正在创建的 3D 动画视频的每一帧上执行反走样功能的算法的类型。算法从上到下变得越来越复杂(和耗时),但提供了更好的结果,代价是处理周期。* 最后, AA 半径定义了每个像素周围需要抗锯齿的像素数量。把这一套留给: 1.0 。 AA 射线决定渲染引擎用于反走样的射线数量或射线跟踪计算的数量。较高的 AA 光线值会产生较长的渲染时间。最后,将 AA 公差设置为 15 ,将算法设置为盒算法,因为其他算法会使用更多的处理时间并增加渲染时间。**
**渲染动画
现在,我们准备渲染我们的火星表面飞越 3D 动画,所以下拉文件 渲染动画菜单序列,输入动画的持续时间,并指定输出参数和文件位置。参考图 11-5 中的了解我们将要经历的序列。
图 11-5 。渲染动画对话框和编辑按钮(中间)和设置按钮(右边)的子对话框
- 选择整个持续时间选项,显示我们在动画设置对话框中输入的信息,如图 11-3 所示。
- 接下来点击编辑按钮,并选择我们的视频数据的全帧(未压缩)设置,并将其存储在 AVI(音频视频交错)容器。这意味着我们不必使用带编号的文件,正如你可以想象的那样,900 帧的文件会有点乏味。
- 接下来单击 Set 按钮,并在 Documents 下的用户文件夹中为 Mars.avi 文件指定一个文件位置。创建一个名为 Android 的文件夹(如果还没有的话),然后在其中创建一个 Hello_World 子文件夹,用于这个特定应用的正在进行的数据文件。
- 如果您设置了网络,并希望在网络上的所有机器上进行渲染,则需要在所有工作站上安装 Bryce 7.1 Pro。一旦完成,你可以使用对话框底部的配置按钮来配置你的渲染农场。然后,你所要做的就是选择渲染网络单选按钮来启用这个功能。
- 一旦这个对话框中的一切都配置好了,如图图 11-5 所示,你可以点击对话框右下角的复选标记,900 帧火星表面动画视频的渲染就开始了。在六核 64 位工作站上,分辨率为 1080×1920,耗时 8 小时。
创建解析文件
工作流程的下一步是为 270×480(MDPI)、480×854(HDPI)和 720×1280(XHDPI)Android 设备分辨率密度创建其他三个分辨率源文件。这是在文档设置对话框中完成的,正如你已经知道的,双击绿色 Bryce 渲染球体可以进入该对话框,如图 11-4 左侧所示。
-
For each of our target DPI resolutions, we need to enter our desired target resolution into a Document Resolution field in this Document Setup (see Figure 11-6).
图 11-6 。在 Bryce 中设置文档分辨率、纵横比、抗锯齿和报告渲染时间选项
-
确保您的文档宽高比为 9:16 宽屏(在这种情况下,我们的应用在纵向模式下使用,或使用垂直方向),并使用上面显示的精确 Android 设备屏幕分辨率。
-
如果您的分辨率差了一个像素,取消选中“约束比例”选项(这将使您的纵横比保持锁定),并将像素更改一个像素,然后重新选中相同的“约束比例”单选按钮,以便在完成更改后再次打开它。如你所知,当我们全屏播放视频时,我们试图逐个像素地达到这些主流(常见)Android 设备屏幕分辨率,以获得最佳质量,而不需要 Android 为我们缩放数据,这可能会引入伪像。Android 目前不擅长的一件事是缩放图像和视频。
-
当您每次设置一个目标分辨率时,单击对话框底部的复选标记,如图 11-6 所示,您会注意到 Bryce 7 将视口缩放到该分辨率的像素数。
接下来,重复图 11-3 到 11-5 所示的工作过程,渲染三个较小的分辨率,并分别将文件命名为 Mars270.avi 、 Mars480.avi 和 Mars720.avi 。
请注意,在图 11-5 中, Mars.avi 高清数字视频文件(1080×1920 分辨率)已经渲染完成,我们正在该屏幕截图右侧的对话框中为我们的 Mars270.avi 文件设置文件名。还要注意,我们选择了一个选项来报告渲染时间,这样我们就可以从数学的角度来看渲染引擎在做什么。这在图 11-7 中显示,我把它包括在内,这样我们就能明白为什么我们的 3D 渲染过程在我们生成的 900 帧数字视频中的每一帧上花费了这么多时间。
图 11-7 。渲染报告信息
我在 480 乘 854 渲染时生成了这个截图,因为我们知道 480 乘 854 是 409,920,所以我们已经知道第一个数字是如何计算的。第二个数字是抗锯齿的像素数量,我们已经知道这意味着图像中有 149,699 个像素在两个像素之间存在剧烈的颜色变化(这简单地等同于图像中不同对象或颜色之间的边缘)。
渲染引擎投射出 146 万条光线来定义该图像中每个像素的颜色值,即平均每个像素 3.55 条光线。如果你想要确切的数字,3.55 乘以 409,920 等于 1,455,216。
因为我们关闭了阴影,所以渲染引擎没有投射阴影光线;你可以想象,这为我们节省了一些渲染时间!在投射的 146 万条光线中,有 125 万条击中了什么东西,206,376 条没有击中任何东西,因此没有用于创建像素数据值。
数字视频压缩:关键概念和技术
现在我们有了一个 3D 渲染的源星球(火星)飞越动画数据,我们可以用它来创建数字视频文件,我们可以在下一章中使用,在那里我们将学习如何实现这个数字视频内容,以及如何通过 Android VideoView 和 MediaPlayer 类在我们的 Android Hello World 应用中控制它。
在我们这样做之前,我们必须优化我们在上一节中渲染的这些文件,因为它们的文件大小从 400MB 到 4GB 不等,并且我们的。APK 文件只能是 50MB,所以我们必须了解数字视频编解码器的设置和数字视频压缩的概念,设置和技术。
用于优化数字视频的最佳软件叫做 Sorenson Squeeze。如果你曾经为 Adobe Flash 平台开发过,你可能会对这个软件包很熟悉,现在它已经是第九版了。
我们要做的第一件事就是推出 Sorenson Squeeze Pro ,版本 8.5.1。当你这样做时,你会看到一个如图图 11-8 所示的启动屏幕。
图 11-8 。启动 Sorenson Squeeze Pro 来压缩和优化我们的 MPEG4 和 WebM 视频文件
这个视频压缩软件工具有七个主要区域,如上面的图 11-8 所示,包括输入选项、编解码器预设、效果过滤器、发布平台、通知、序列器窗口(右下角)和视频编辑窗口(右上角)。
导入视频
我们需要做的第一件事是导入我们的数字视频 AVI 全帧未压缩格式文件。
-
This is done by clicking the icon on the upper right labeled Import File. This opens a file navigation dialog window, shown in Figure 11-9, which allows us to navigate through the hard disk drive, and find the 3D Mars.avi source digital video files that we created earlier in Bryce 7.1 Pro.
图 11-9 。使用导入文件对话框定位我们的 Mars.avi 源文件
-
找到你的 My Documents 文件夹(应该在你的 Users 文件夹下)和它下面的 Android 文件夹,里面包含了你的 Android 相关工作素材。找到您之前为您的 Hello_World 应用新媒体资源创建的子文件夹,选择 Mars.avi 文件并单击打开按钮。一旦你这样做了,你将在 Sorenson Squeeze 的视频编辑部分看到火星视频的第一帧(见图 11-11 )。
-
Locate the + icon on the left at the bottom of the Presets Pane, and click it to open a new presets dialog, so that we can develop a codec that is Android specific, as shown in Figure 11-10. Type Android 1080x1920 15 FPS in the Name: and Desc: fields, to label our custom settings, which will be saved for future use with other digital video. The Format Constraints are set to None, and the Stream Type is set to Non-Streaming as we are optimizing to a .MP4 file. The best Codec to use for H.264 is MainConcept.
图 11-10 。创建 1080×1920 Android 视频压缩设置,预设为 15 FPS 和 1 Mbps 数据速率
-
我们使用 MainConcept H.264 编解码器的原因是因为它是最先进的,并且具有一种多遍方法,该方法使视频数据多次通过,以尽可能实现最佳的质量与文件大小之比。这需要更多的时间,但我在这里假设您至少有一个四核工作站(如果不是八核的话),并且质量是您最终关心的问题。一旦选择了多遍,设置帧速率:到 15 FPS ,并设置目标数据速率为 1024 Kbps (也是 1 Mbps)。
-
选择约束最大数据速率,将最大数据速率设置为 1024 Kbps 的 150% ,即 1536 Kbps。这为我们的数据突发留出了一些回旋空间,如果在任何给定的帧上需要多一点空间的话。
-
将帧尺寸设置为 1920 年的1080以防止缩放,保持纵横比,让我们确保编解码器每 40 帧查看关键帧。最后,选择场景变化时的自动关键帧,并选择一个平均关键帧频率 50 作为该压缩设置预设的开始。
-
Once everything is set click the OK button shown in Figure 11-10 and you will be returned to the main Squeeze Pro program screen where you can now click the Apply button in the top left panel to apply these settings (see Figure 11-11).
图 11-11 。将 Android 1080×1920 15 FPS 视频压缩预设应用到导入的 Mars.avi 文件
-
最后,我们可以点击挤压它!软件屏幕右下方的按钮,开始我们的视频压缩过程。
当您的视频数据正在压缩时,我们将稍微讨论一下什么是关键帧,以及它们在整个视频压缩过程中的确切作用。
关键帧是您的动画数据的一帧,编解码器会查看该帧以存储视频在该确切帧的完整图像,这也是它被称为“关键帧”的原因
编解码器为您的视频节省空间(减少数据足迹)的方式是不必保存数字视频中的每一帧,在这种情况下,它是一个飞越火星表面的 3D 动画。
这是通过当今新媒体算法中一些最复杂的数学来完成的,并且肯定超出了一本学习 Android 书的范围。它本质上是进入第四维(时间)并查看它编码的每个关键帧之后的帧,以查看下一帧相对于它刚刚采样的关键帧发生了什么变化。
然后,编解码器只对帧与帧之间发生变化的数据进行编码,这可以在许多视频场景中节省大量编码数据。这方面的一个很好的例子就是正在说话的头部视频,例如教师或政治家。如果这个人保持冷静,静止,固定在一个地方,那么只有他们的嘴部(说话)动作会随着画面的变化而变化。
在这种情况下,因为包含嘴的像素区域只占整个视频帧像素的很小一部分,编解码器可能会逐帧编码这些像素,而不是整个图像。在这种情况下,视频帧的许多像素会随着时间的推移而冻结,这就是编解码器可以减少数据占用量的原因,这就是编解码器所关注的游戏名称。
因此,一个非常安静(零头部运动)的说话人编码得非常好,只要他们背后的背景不是太嘈杂或者没有编解码器需要解决的大量快节奏运动。因此,在一个繁忙的新闻编辑室中,一个正在说话的头的编码效果不如在一个具有纯色、均匀照明背景的特殊设置中的一个正在说话的头。
编解码器不能 很好地转码(跨帧编码)的东西是噪声,就像静态图像编解码器一样,以及全帧运动。全帧移动是指视频帧中的每个像素改变其在视频的每个帧中的位置。这样的例子包括非常快速的相机摇摄,例如在赛车的拍摄中使用的;镜头拉近或拉远,例如用于拍摄自然纪录片的镜头;以及像动作片和火星飞越中所用的摄像机飞越。
现在,您的数字视频应该已经完成编码,您将在软件屏幕右下角的 Squeeze Pro 工作区底部看到一个可播放的视频图标,如图图 11-12 所示。
图 11-12 。完成视频压缩(仅屏幕截图的底部区域)显示 MP4 视频准备播放
压缩视频
接下来,我们需要压缩我们的 MDPI 分辨率 Android 屏幕的视频素材:
-
Use File
Save to save your Squeeze Pro environment as it sits now. Next use File
New to set out a new blank canvas, so we can use the Import File button to bring in our Mars270.avi raw video data file to compress into H.264 MPEG-4 data, as shown in Figure 11-13.
图 11-13 。使用 192 Kbps 多通道 H.264 在 Squeeze 中以 15FPS 压缩 270×480 数字视频源
-
Follow the same work process that we did for the 1080 version and click the + button in the Presets Panel and set up a preset for our 270 by 480 resolution video that uses a Target Data Rate of 192 Kbps and a Max Data Rate of 256 Kbps, or a 133% burst data rate ceiling. Keep KeyFrames every 30 frames (30 total per 900 frames), and be sure to lock in the resolution by specifying it, and then using Maintain Aspect Ratio, or Same as Source, as shown in Figure 11-14.
图 11-14 。为我们的 270×480 视频文件输出设置编解码器设置
-
请确保使用顶部的“名称”和“Desc”字段来标记您的新 MDPI 编解码器设置;我使用了 Android 270×480 15 FPS 的标签,因为它很短并且具有描述性,非常适合预置面板。一旦一切都配置好了,点击 OK 按钮,然后在预置面板中选择新的编解码器定义,点击 Apply 按钮将其应用到你的 Mars 项目设置窗口。现在,你可以点击挤压它了!按钮,压缩你的全帧未压缩。AVI 转换成 MP4 文件。
创建的 MPEG-4 文件只有 1.3 兆字节,所以我们用这些设置获得了惊人的压缩,因为我们的源(原始)数据在。AVI 文件接近 342 兆字节。正如我们之前所了解的,这为我们提供了 263:1 的压缩率,它将 Android 设备必须处理的数据量从一分钟几百兆字节减少到一分钟内处理不超过一兆字节,这是 Android 设备处理器应该能够处理的。
值得注意的是,优化视频绝不是一次性的过程,如果您发现视频可以在所有设备上流畅播放,您可以通过指定更高的数据速率来提高质量。例如,256 Kbps和 384 Kbps 上限的将是您尝试的下一个设置,更多的关键帧,比如说,每 20 帧使用关键帧,而不是 30 帧(这相当于采样和存储 45 个关键帧,而不是总共 900 帧中的 30 个)。文件大小会增加,视频数据的视觉质量也会提高,并且将有更多的视频数据供处理器解码和显示。
接下来,我们需要压缩我们的 HDPI 分辨率 Android 屏幕视频素材:
-
Use File
Save to save your Squeeze Pro environment as it sits now. Next use File
New to set out a new blank canvas, so we can use the Import File button to bring in our Mars480.avi raw video data file to compress into H.264 MPEG-4 data, as shown in Figure 11-15.
图 11-15 。使用 384 Kbps 多通道 H.264 在 Squeeze 中以 15FPS 压缩 480×854 数字视频源
-
Follow the same work process that we did for the 1080 version, and click the + button in the Presets Panel, and set up a preset for our 480 by 854 resolution video that uses the Target Data Rate of 384 Kbps and a Max Data Rate of 512 Kbps, or a 133% burst data rate ceiling. Keep KeyFrames every 30 frames (a total of 30 per 900 frames), and be sure you lock in the resolution, by either specifying it and using Maintain Aspect Ratio, or by using the Same as Source option instead, as shown in Figure 11-16.
图 11-16 。为 480×854 视频文件输出设置编解码器设置
-
Finally, we need to compress our XHDPI target resolution Android screen’s video asset, so again, use File
Save to save your previous Squeeze Pro environment. Next use File
New to set out a new blank canvas, so we can use the Import File button to bring in our Mars720.avi raw video data file to compress into an H.264 MPEG-4 MP4 data file, as shown in Figure 11-17.
图 11-17 。通过 768 Kbps 多通道 H.264 在 Squeeze 中以 15 FPS 压缩 720×1280 数字视频源
遵循我们为 1080 版本所做的相同工作流程,并单击预设面板中的+按钮,为您的 720 x 1280 分辨率视频设置一个预设,该预设使用 192 Kbps 的目标数据速率和 256 Kbps 的最大数据速率,或 133%突发数据速率上限。每隔 30 帧保留关键帧(每 900 帧总共 30 帧),并确保通过在对话框 UI 中指定并使用保持宽高比或使用与源相同选项来锁定分辨率,如图图 11-18 所示。
图 11-18 。为 720×1280 视频文件输出设置编解码器设置
接下来我们需要将我们创建的 MPEG-4 H.264 视频素材放入适当的 Hello_World 项目资源文件夹,或者对于已经优化的 raw 视频数据文件,放入 Hello_World/res/raw 中,不需要 Android 进一步优化。这是我们在本书前面提到的一个概念,我们将在本书的下一部分详细讨论。一旦完成,我们的视频数据就可以使用了。
在 Android 中使用数字视频资源:资源的原始文件夹
现在我们已经创建了四个分辨率密度(DPI)目标分辨率视频文件,我们可以将它们复制到相应的资源子文件夹中,然后我们可以通过 XML 标记和 Java 代码在第十二章中访问它们。
-
Open your OS file manager utility, for Windows 7 or Windows 8 it’s Windows Explorer, and go to your C:/Users/YourName/workspace/Hello_World/res resource folder and right-click it to get a context-sensitive menu, shown in Figure 11-19.
图 11-19 。在 Hello_World resources (res)文件夹下新建一个名为/raw 的
文件夹来存放视频文件
-
At the bottom of this menu, select the New
Folder sub-menu sequence to create a new folder under the res (resource) folder and name it raw. Once created, the folder shows up under your menu sub-folder (see Figure 11-20).
图 11-20 。拖放四个 DPI 分辨率的版本的火星表面飞越到/raw
注意正如我在本书前面提到的,这个原始资源子文件夹在 Android 中用于包含已经由开发人员优化的新媒体素材,并且不需要 Android 操作系统的任何进一步优化或其他干预。本质上来说,/raw 文件夹中的新媒体素材是我们告诉 Android 存储在。APK 文件,并在应用代码中按原样访问和使用它们,无需任何进一步的修改。
-
创建/raw 文件夹并显示在文件管理实用程序的左窗格中后,选择四个文件中的第一个,然后按住 Shift 键并选择该组中的最后一个文件。这用于分组选择整组文件,然后你可以将所有四个文件拖放到 /res/raw 文件夹中,如图图 11-17 所示。
-
Next, we need to go into the /res/raw folder, and use the file manager to rename all the files from the detailed names assigned by Squeeze Pro to use simpler filenames, as we’ve been doing previously in this book. This process is shown in Figure 11-21 showing our new lower-case names and the raw folder.
图 11-21 。将火星视频的四个分辨率版本重命名为:mars270、mars480、mars720、mars1080
注意正如我们过去所做的,我们将遵循 Android 的小写字母和数字素材命名约定,使用行星名称(在本例中为火星)和数字视频的水平分辨率(以像素为单位)作为我们的视频文件命名约定。
-
Now let’s launch our Eclipse ADT IDE, and make sure that we did everything correctly and that our digital video assets are in place and ready for use in our next chapter covering how to code Java and XML mark-up to implement digital video in our Android application. As you can see in Figure 11-22, we now have our /raw folder in our Android Hello_World project resource folder and it has four MPEG-4 files inside it that we can use to play video in our Travel to Planet Activity screen. If you already had Eclipse running, be sure and use the F5 (or Refresh) work process.
图 11-22 。查看项目的/res/raw/文件夹中的四种分辨率版本的数字视频 MPEG-4 文件
接下来,我们将简单介绍一下 Android VideoView 类,我们将在第十二章中使用该类来保存和播放我们的数字视频素材,然后我们将了解将数字视频素材编码到 Hello_World Android 应用中所需的基础知识。
在 UI 设计中播放数字视频:Android 的 VideoView 类
Android 设计了一个用户界面小部件,使我们的应用中的数字视频播放变得相当简单。
它被称为 VideoView 类,它使用 XML 中的 < VideoView > 标签来通过各种参数实现其 UI 属性,正如我们在本书前面的章节中用 Java 和 XML 实现 TravelPlanet 活动类时已经看到的那样。
Android VideoView 类是 SurfaceView 类的子类,后者本身是 View 类的子类,我们知道,后者是 Java Object 类的子类。
VideoView 类实现了 MediaPlayerControl 接口,这样我们就可以访问与控制数字视频播放相关的方法。
这个接口是由Android . widget . media controller包提供的,所以它的完整路径是Android . widget . media controller . media player control,这样我们就能够在 Java 代码中播放或暂停我们的视频,如果我们愿意的话。
事实上,在 XML 和 Java 中实现 VideoView 和 MediaPlayer 功能是我们下一章关于 Android 中数字视频的内容,现在我们已经有了基础知识,知道我们到底在谈论什么数字视频概念、编解码器、功能和回放。
摘要
在这一章中,我们为理解数字视频新媒体素材打下了坚实的基础,这样我们就可以在 Android 应用中实现它们。
我们了解了像素、它们所在的视频帧以及将它们压缩和解压缩为可用文件的编解码器如何协同工作,以使大量移动数字图像数据更易于管理和使用。
然后,我们了解了数字视频领域的一些重要概念和标准,例如标准分辨率标清视频及其原生分辨率,以及更新的高清视频及其两种常见分辨率规范。我们了解到 Android 中的数字视频可以被捕获,或者保存在一个资源文件夹中,并包含在我们的应用中。APK 文件,或者它可以是流视频,并通过视频服务器远程提供。
我们讨论了比特率和每秒位数的重要概念,以及这种测量方法如何用于数字视频的数据占用优化过程。我们还了解到,比特率不仅决定了什么类型的带宽速度环境可以容纳数字视频,还决定了什么类型的 Android 设备数据处理(CPU)功能可以在视频流实际通过该带宽时对其进行解码。
我们发现即使机器人内部有视频文件。APK 容器,比特率和质量水平是获得清晰视频的关键组成部分,播放流畅,没有丢帧,以试图跟上数字视频文件所需的帧速率。
接下来,我们了解了 Android 支持的数字视频编解码器和格式,如 MPEG4 H.263 和 H.264 编解码器,以及 VP8,以及这些编解码器在 Android 操作系统内部和外部的支持级别有何不同,以及它们是 HTML5 规范的一部分。
我们还研究了我们需要在所有 Android 消费电子设备上支持的最佳数字视频分辨率,发现美国和欧洲 SD 和 HD 数字视频的主要广播分辨率与当前大多数 Android 设备的屏幕分辨率非常匹配。
我们学习了如何使用 Bryce 7 Pro,并创建了基本的火星表面动画。我们练习了使用 3D 地形生成软件为一些关键的 Android 设备分辨率实现数字视频文件素材生成。我们还学习了一些关于 3D 渲染的基本概念。
接下来,我们讨论了数字视频优化中的关键概念,并了解了除分辨率之外影响视频压缩的其他因素,如平移和缩放,以及将噪声引入帧图像数据的因素,如之前的压缩伪像。
我们通过在 Sorenson Squeeze Pro 内的 MainConcept H.264 编解码器中实际使用这些设置来了解关键帧和比特率,从而将我们的数字视频从原始、未压缩的原始数据状态优化为高度压缩的数字视频素材,以便在我们的 Hello_World 应用中使用。
我们通过优化四个目标分辨率密度级别的视频素材来实践这一压缩流程,从中等分辨率(MDPI) 480 像素屏幕、高分辨率(HDPI) 800 像素屏幕、超高分辨率(XHDPI)伪高清 1280 像素屏幕,一直到超高分辨率(XXHDPI)真高清 1920 像素屏幕密度。
然后,我们将这四个优化的数字视频素材复制到 resource 文件夹中,复制到我们创建的/raw 子文件夹中,以便在 Hello_World 项目文件夹中保存这些数字视频素材。我们重命名了这些文件,以便在代码中更容易访问,然后进入 Eclipse ADT,以确保一切就绪,我们可以开始在应用中编码、实现和测试这些数字视频素材。
为了了解如何在我们的 Hello World Android 应用中实现数字视频,我们学习了用户界面小部件 VideoView,这是我们在本书前面的 TravelPlanet 活动屏幕用户界面中最初实现的。
我们学习了 Android VideoView 类的层次结构,以及 XML 中相应的
在下一章中,我们将深入研究使用 Android VideoView 和 MediaPlayer 类及其方法实现数字视频所需的 XML 和 Java 编码,从而进一步了解数字视频新媒体元素。我们还将构建 Java 代码来控制我们的 VideoView XML UI 定义,并最终控制用户体验。**
十二、Android 中的数字视频:使用VideoView
类
在本章中,我们将利用在前一章中学到的数字视频概念、技术和格式的新知识,在我们自己的 Hello_World Android 应用中实际实现数字视频素材。
我们将在 TravelPlanet.java 活动子类中添加新的 Java 代码,以实例化 MediaController 对象,这样我们就可以在 VideoView 用户界面元素中播放视频,该元素是我们在本书前面的 activity_travel.xml 屏幕布局定义中设置的。
我们将介绍如何播放与源视频具有不同纵横比的视频,以及如何创建和添加视频传输控制用户界面元素。
使用 Android MediaController 类播放视频
因为我们已经在 FrameLayout 容器内的 activity_travel.xml 文件中添加并配置了 VideoView 用户界面元素,所以让我们打开我们在本书前面创建的 TravelPlanet.java Activity 类,并添加代码来实际播放我们的视频,现在我们已经将文件放在正确的资源文件夹/res/raw 中,可以开始播放了。
我们需要做的第一件事是为播放视频设置一个存储视频文件的路径,以便 Android MediaController 类可以找到它们,并最终播放它们。
这是通过统一资源标识符(【URI】)来完成的。URIs 在 Android 开发中如此重要,以至于有一个专门的类来定义和使用它们,叫做 Uri ,就像俄语名字一样。
Uri 在android.net包中,因为 URIs 经常被用来通过互联网访问数据,通常是通过我们都习惯使用的那个 HTTP:// 名字。你很快就会看到,要访问 Android 的资源文件夹中的东西,我们将使用 android.resource:// 路径访问定义。
Uri 类 是 java.lang.Object 类的子类,因此 java 中的 Uri 对象路径通过 android.net.Uri 向上通过 java.lang.Object。如果您对 android 开发者网站参考部分感兴趣,可以在以下 URL 找到有关 Uri 类的更多信息:
[`developer.android.com/reference/android/net/Uri.html`](http://developer.android.com/reference/android/net/Uri.html)
因此,让我们为我们的数字视频文件定义我们的 URI,使用 Uri 类的 parse( ) 方法,以及 getpackageName( ) 方法,它是 Android Context 类的一部分,该类也是从 java.lang.Object 子类化而来
在 TravelPlanet 活动中创建 Java videoUri 对象
让我们开始添加代码到我们的 TravelPlanet.java Activity 类,通过添加我们的第一行 Java 代码来设置将用于引用我们的视频数据的 URI。
让我们将定义视频 URI 的第一行代码放在活动的setContentView(r . layout . Activity _ Travel)方法调用的下面,该方法调用引用了我们对行星用户界面屏幕布局定义的访问。
设置我们的 Uri 对象的 Java 代码行的结构如下:
Uri videoUri = Uri.parse("android.resource://"+getpackageName()+"/"+R.raw.mars270);
这段相当密集的代码定义了我们的 Uri 对象,将其命名为 videoUri ,并将其设置为我们的【mars270.mp4】文件的 Uri,该文件位于我们的 /res/raw 文件夹中。
这是通过 Uri 类来实现的。parse( ) 方法,用于构造 Android 操作系统可以理解的有效 URI 对象,从而实现。
这里概述的 Uri.parse( ) 方法被传递了一个连接(通过 Java 语言+操作符完成),该连接是位于 android.resource:// 中的文件的位置与我们的 Hello_World 包名称和上下文的连接(通过 getpackageName( ) 方法获得),以及一个路径级别定义符,也称为正斜杠字符,最后是我们的资源标识符 R.raw.mars270,,这是我们的文件名(第一部分
你可以在图 12-1 中看到这一行代码,它就在我们已经在活动中编写的定义视频视图的代码行之前。
图 12-1。将 Java 代码添加到我们的 TravelPlanet.java 活动中,为我们的视频视图实现 MediaController
因为我们已经定义了我们的 travelVideo VideoView 对象,并且引用了我们的 travelVideoView XML 用户界面元素定义,所以我们可以使用。setVideoURI( ) 方法,通过下面一行代码将我们新创建的 videoUri 对象连接到我们的 travelVideo VideoView 对象:
travelVideo.setVideoURI(videoUri);
这相当简单,我们通过将 videoUri 对象传递给 travelVideo VideoView 对象。setVideoURI( ) 方法,从而定义哪个数字视频数据文件用于我们的 VideoView,以及这个数字视频文件在我们的 Android 项目中的位置。
接下来,我们需要设置一个 Android MediaController 对象来完成数字视频文件的播放,所以接下来让我们开始吧。
创建 MediaController 对象来播放我们的数字视频
为了在 Android 中播放我们的数字视频文件,我们将利用 Android MediaController 类,它是 Androidframe layout类的子类。
请注意,我们使用 FrameLayout 来包含我们的 VideoView UI 元素(小部件),并且记住 FrameLayout 类是 ViewGroup 类的子类,View group 类本身又是 View 类的子类,View 类又是 java.lang.Object master 类的子类。
为了创建一个 MediaController 对象,让我们使用我们常用的对象实例化 Java 代码结构和 new 关键字,使用下面一行 Java 代码创建一个 videoMediaController (命名)MediaController 对象:
MediaController videoMediaController = new MediaController(this);
接下来我们要做的是告诉我们的 MediaController 对象我们希望它在哪里播放我们的视频。这是通过完成的。setAnchorView( ) 方法,定义哪个 Android 视图对象,在本例中,我们的 VideoView 对象名为 travelVideo ,我们希望将此 MediaController 对象的功能锚定到该对象。这是通过下面一行 Java 代码实现的:
videoMediaController.setAnchorView(travelVideo);
现在我们已经告诉我们的 MediaController 对象我们希望它在哪里播放我们的数字视频数据,但是我们仍然需要告诉我们的 travelVideo VideoView 对象访问哪个 MediaController 对象来播放数字视频数据。这是通过。setMediaController()方法,使用以下代码从 travelVideo VideoView 对象调用该方法:
travelVideo.setMediaController(videoMediaController);
既然我们的 travelVideo VideoView 对象和 videoMediaController 对象已经连接在一起,我们所要做的就是调用 travelVideo VideoView 对象的一些关键方法来控制数字视频数据的位置(Z 顺序)、焦点和回放,这样我们就完成了在 Android 应用中实现基本的视频回放。
在我们的 travelVideo 视频视图中控制视频播放
既然我们的 VideoView 对象和 MediaController 对象相互接口,我们可以调用 travelVideo 对象的一些关键方法,以确保我们的数字视频数据显示在屏幕层堆栈的前面(顶部),具有焦点以便可以播放,并开始播放。
我们需要调用的第一个方法是,通过使用 travelVideo 对象的点符号。bringToFront( ) 法。这确保了我们的 VideoView 位于任何其他用户界面元素之上,这些元素可能与 VideoView 一起在其布局容器中定义。完成此任务的 Java 代码如下所示:
travelVideo.bringToFront();
在开始视频回放之前,我们需要做的下一件事是确保视频视图具有焦点,这是我们在本书前面提到的概念。这是通过。requestFocus()方法,它通过请求焦点来确保 VideoView 获得焦点。这样,即使 VideoView 当前拥有焦点,调用该方法也能确保我们拥有焦点。这是通过下面一行 Java 代码完成的:
travelVideo.requestFocus();
最后,我们准备开始播放数字视频数据,使用。start()方法,您可能已经猜到了。这是通过以下代码行完成的,该代码行通过点标记法调用了 travelVideo 对象:
travelVideo.start();
现在是时候启动我们的 Nexus S 模拟器,并在 Android 软件模拟器中测试我们的数字视频文件播放。请注意,在实际的 Android 硬件设备上测试数字视频文件回放总是更好;然而,对于这本书来说,这是不可行的,因为有成千上万不同的制造商和型号,你们任何一个读者都可能有。
因为我们拥有的唯一基线是最新的 Eclipse ADT 及其各种软件模拟器,所以我们必须在测试中使用该解决方案。
在 Eclipse ADT 的 Nexus S 模拟器中测试视频回放
让我们启动我们的 Nexus S 模拟器,当你的 Hello World 主屏幕出现时,点击右边模拟器中的菜单按钮,然后选择前往行星活动屏幕,这样我们就可以看到我们的数字视频文件回放,结果如图图 12-2 所示。
图 12-2。在 Nexus S 模拟器中播放 Mars 数字视频,并测试 onTouch()事件是否仍然有效
正如你所看到的,我们的数字视频保留了 16:9 的宽高比,即使在 5:3 宽高比的模拟器屏幕上,当我们点击视频屏幕时,我们会返回到我们的应用主屏幕,并显示回家祝酒信息。
因此,我们现在知道,我们现有的来自前几章的 Java 代码仍然可以工作。你可能注意到了,屏幕右侧有一个白条;这是因为布局容器的背景颜色未配置,默认为#FFFFFF 或白色值。您已经知道如何通过 FrameLayout 标记中的 android:background="#000000 "参数将它更改为黑色值。
您可能想知道:如果我想缩放这些数字视频数据以适合整个显示屏,会怎么样?这可能是一个选项,取决于它是什么类型的数据,以及主题对不均匀缩放或纵横比解锁的缩放的敏感程度。
我们正在使用的火星表面数据可以在用户没有察觉到主题材质变化的情况下进行缩放,而全屏人脸或说话的头部可能会看起来失真(肥胖或锥形)。
在 Android 中有一种方法可以实现这个最终结果,就是将 VideoView 用户界面元素放在 RelativeLayout 容器中,而不是使用 Framelayout 容器,这样可以保持纵横比。
用 RelativeLayout 容器而不是 FrameLayout 容器可以做到这一点的原因是,这两个不同的布局容器支持完全不同的 android:layout 参数。
使用相对布局缩放视频视图以适合屏幕
进入你的 activity_travel.xml 文件,将 FrameLayout 容器标签改为 RelativeLayout 容器标签,如图图 12-3 所示。
图 12-3。更改 activity_travel.xml 屏幕布局,使用 Relativelatout 容器和 layout_alignParent 标签
在 RelativeLayout 容器中实现这一点的关键是使用 android:layout 参数,这些参数将数字视频文件的四个不同边与父容器对齐,在本例中是相对布局。我已经将这四个参数添加到我们的 VideoView 标签中,就在 android:id 参数之后,如图图 12-3 所示。
诀窍是将所有四个Android:layout _ align parent参数设置为值 true ,如以下 XML 标记代码所示:
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
这将拉伸 VideoView 容器的每一侧,以匹配 RelativeLayout 容器的两侧。因为在 android:layout_width 和 android:layout_height 参数中,RelativeLayout 标签被设置为 match_parent ,因此,无论 android 操作系统试图保持(锁定)数字视频宽高比的正确倾向如何,参数设置都会强制 VideoView 填充屏幕。结果如图图 12-4 所示。
图 12-4。在 Nexus S 中测试我们的 relative layout layout _ align parent 标签
接下来,我们需要在 Nexus S 模拟器中测试 Travel to a Planet 活动,以确保一切正常。数字视频全屏播放,触摸屏幕会将我们的最终用户带回到主屏幕,就像我们在本书前面对该功能进行编码一样。
然而,有一个重要的问题对于 Android 新手来说可能不是很明显,那就是触摸视频应该会启动媒体控制器传输。视频传输是一组用于播放、暂停或浏览数字视频内容的基本用户控件。
目前,我们的 onTouch()事件处理阻止了我们的最终用户访问这个媒体控制器传输 UI 元素,因此,我们需要向我们的 UI 添加一个按钮 UI 元素,并将其与 onClick()事件处理程序连接起来,以释放触摸屏。这样,Android 的 MediaController 可以在屏幕被触摸时显示其传输实用程序。
修改我们的界面以支持媒体控制器传输
因此,我们需要做的第一件事是删除 onTouch( ) Java 代码,以确保我们可以访问 MediaController 传输控件。简单的方法是使用 Java 中的双正斜杠代码注释功能,临时注释掉这个代码块,第一行代码如下所示:
// travelVideo.setOnTouchListener(new View.OnTouchListener() {
这显示在图 12-5 的中,以及其他注释代码。
图 12-5。删除干扰 MediaController 传输 UI 元素的 onTouch()事件处理
正如您所看到的,Eclipse 会立即在不再需要的三个 import 语句旁边显示警告标志,因为这段代码的注释已经将它从 Eclipse Java 编译器的“视图”中删除了。
因为 Eclipse 中的警告不会阻止我们在 Nexus S 模拟器中运行我们的应用,所以让我们使用熟悉的 Run As Android 应用工作流程,看看我们是否可以在触摸我们的视频屏幕时显示媒体控制器传输。
正如你在图 12-6 中看到的,当我们在模拟器中触摸屏幕时,我们现在可以访问视频传输控件。然而,我们现在没有办法回到我们的主屏幕,所以我们需要添加一个用户界面元素来实现这一点。幸运的是,我们只是在一个 Android 模拟器中处于测试模式,这个模拟器的右上角有一个红色的 X 关闭图标,我们可以用它来退出!
图 12-6。点击或触摸屏幕即可调出媒体控制器传输
接下来,让我们在屏幕右上角的视频顶部添加一个按钮用户界面元素,它允许我们的用户退出 Travel to Planet 活动屏幕并返回到我们的 Hello World 应用主屏幕。
添加一个按钮 UI 元素让我们返回主屏幕
在 Eclipse 中找到并打开你的 activity_travel.xml 编辑标签,在 < VideoView > 标签下添加一行空格,然后按 < 键调用添加新标签助手对话框。找到按钮标签,如图图 12-7 所示,双击将标签添加到< RelativeLayout >容器中。现在我们准备添加参数来配置我们的按钮标签。
图 12-7。向 activity_travel.xml 屏幕布局定义文件添加一个按钮用户界面元素标签
首先,我们需要给我们的按钮 UI 元素一个 ID,以便我们可以在 Java 代码中引用它,所以使用 android:id 参数,并通过 android:id="@+id/travelButton "参数标记将按钮命名为 travelButton。
接下来找到您的 strings.xml 文件,为名为 travel_button_caption 的按钮添加一个 < string > 常量,值为 Return to Home Screen ,如下所示:
<string name="travel_button_caption">Return to Home Screen</string>
使用 android:text 参数来引用这个字符串常量,同时确保添加必需的 android:layout_width 和 android:layout_height 参数,这两个参数都设置为 wrap_content 常量值。
因为我们希望按钮在屏幕的右上角,不要挡住我们的视频内容,所以让我们使用Android:alignParentTop = " true ",以及Android:alignParentRight = " true "参数来实现这个最终结果。
最后,让我们将我们的按钮背景颜色设置为白色,并通过为 android:background 参数使用八个字符(AARRGGBB)的十六进制值为其赋予半透明的 alpha 值,如下所示:
android:background="99FFFFFF"
到目前为止,我们的按钮标记的 XML 标记应该如下所示:
<Button android:id="@+id/travelButton"
android:text="@string/travel_button_caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alignParentTop="true"
android:alignParentRight="true"
android:background="#99FFFFFF" />
整个 RelativeLayout XML 容器标记如图 12-8 所示。
图 12-8。配置我们的按钮标签参数,在屏幕右上方放置一个透明的 UI 按钮
现在是时候使用 Nexus S 模拟器再次测试我们的应用了,看看我们是否有一个媒体控制器传输 UI 元素,以及一个退出按钮 UI 元素,用于我们到行星活动屏幕的旅行。
调用作为 Android 应用运行菜单序列并启动 Nexus S 仿真器。当您的应用启动时,单击菜单按钮并选择“旅行到星球”选项,然后转到该活动屏幕。
正如你在图 12-6 中看到的(这里不需要复制截图)视频播放,点击或触摸屏幕会调出传输控件,但是按钮 UI 元素没有覆盖在视频的右上角,所以有些不对劲。
我做的第一件事,看看我是否能纠正这个问题,就是在按钮标签中键入 android: ,看看我是否能找到一个 z 顺序参数。
z-order 参数会将按钮带到层顺序的顶部,以便按钮显示在视频的顶部,就像我们希望的那样。唉,目前还没有这样的参数,所以我们必须想出另一种方法,通过一些巧妙的标签参数工作来接近我们正在寻找的 UI 结果,很可能是在
故障排除 我们的 VideoView 用户界面设计
让我们看看我们的代码,看看可以做些什么来让我们的顶部安装按钮在我们的数字视频素材的顶部可见。看起来 android:layout_alignParentTop 参数正在将 VideoView UI 元素拉至屏幕顶部,并覆盖在按钮 UI 元素上。
因为没有可用的 android:z-order 参数,所以我尝试的第一件事是在 RelativeLayout 中的 VideoView 标签之前剪切并粘贴按钮标签,以查看布局容器中的顺序是否影响 z-order。
当我在模拟器中测试它时,这不仅没有解决问题,还产生了一个运行时错误。然后,我恢复了之前的标签顺序,并寻找解决这个问题的另一种方法。如果我不能将这个按钮 UI 元素放在 VideoView 的顶部,我接下来会尝试将它直接放在 VideoView UI 元素的顶部,并与之连接。
我尝试的下一件事是将Android:layout _ alignParentTop参数从 VideoView 标签移动到 Button 标签,然后将 android:layout_below 参数添加到 VideoView,在我删除 alignParentTop 参数的地方,因此在< VideoView > :" 中查找以下参数
android:layout_below="@+id/travelButton"
这都显示在图 12-9 中。
图 12-9。添加按钮标签参数,将按钮元素放置在屏幕顶部,并将其置于 VideoView 的中央
然后,我再次使用我的运行 Android 应用工作流程来启动 Nexus S 模拟器,看看这是否解决了问题,正如你在图 12-10 的左侧看到的,它确实解决了问题。然而,现在我们的背景颜色和透明度设置与我们 UI 的外观和感觉不匹配。
图 12-10。在 VideoView UI 元素的基础上,测试和改进我们的“星球之旅”UI 屏幕按钮元素
因此,让我们将我们的 android:background 参数更改为黑色或 #000000 ,以便它与应用顶部使用的其他颜色相匹配。
接下来,我们需要建立一个与我们的火星大气相匹配的文本颜色,并为我们提供与黑色背景颜色的高对比度,所以让我们使用之前在 android:textColor 参数中使用的橙色值#FFDDBB,如图 12-9 所示。
在进行这些更改时,一定要使用位于 Eclipse 中 XML 编辑器底部的图形化布局编辑器标签,这样您就可以了解这些标签对您的用户界面设计有什么影响。举例来说,你会注意到,我们的文本,现在我们实际上可以看到,实际上太小了,需要变大。
所以,让我们添加一个 android:textSize="28sp" 参数,并确保我们的文本是漂亮和大的。请记住,文本(字体)使用标准像素(SP)作为它们在 XML 标签中的表示,而不是像其他图形或用户界面元素那样使用密度像素(DP)。现在,我们已经准备好使用我们的作为 Android 应用运行工作流程,并在 Nexus 模拟器中检查我们的新用户界面屏幕。结果如图 12-10 右侧所示,如你所见,我们的文本现在可读了,设计也匹配了。
接下来,我们需要将 Java 代码添加到我们的 TravelPlanet.java 活动类中,这样我们就可以实例化新的按钮用户界面元素,从而使它起作用,这样我们的用户就可以单击它返回到主屏幕。
在 Java 代码中增加了返回地球的按钮
让我们在我们的 travelVideo.start( ) 方法调用下添加一行新代码,并实例化我们的按钮用户界面元素。我们将其命名为 travelButton ,并使用下面的代码行将其引用到我们的 XML 参数:
Button travelButton = (Button)findViewById(R.id.travelButton);
接下来,我们将修改 TravelPlanet.java 活动中被注释掉的代码,将其从附加到 VideoView UI 元素的 onTouch()事件处理场景更改为附加到 Button UI 元素的 onClick()事件处理场景。注释掉的代码显示在图 12-5 中,而所有修改后的代码显示在图 12-11 中。
图 12-11。添加我们的 travelButton Java 代码来实例化一个按钮 UI 元素并附加 onClick()处理程序
因此,使用以下代码,将您的travel video . setontouchlistener()方法更改为travel button . setonclicklistener()方法调用:
travelButton.setOnClickListener(new View.OnClickListener() {new code goes in here}
我们需要做的下一件事是删除剩余的双斜线注释字符,这样我们就可以修改 OnClickListener( ) 方法的内部,将 onTouch( ) 方法及其内容改为 onClick()方法。新的 onClick( ) 代码应该如下所示:
@Override
public void onClick(View v) {
Toast.makeToast(TravelPlanet.this, "Going Home", Toast.LENGTH_SHORT).show();
finish();
}
请注意,图 12-5 截图中显示的三个警告突出显示中的两个已经消失,现在我们已经在新的 onClick( ) Java 代码中使用了视图和吐司类对象。因为 MotionEvent 类与我们已经移除的 onTouch()事件处理一起使用,所以您可以继续删除这个 import 语句,您的 Java 代码现在将没有错误和警告,并准备好在您的 Nexus 模拟器中进行测试。使用 Run As Android Application 菜单序列在 Nexus S 模拟器中运行你的 Hello_World 应用,并测试功能,如图图 12-12 所示。
图 12-12。测试我们的按钮用户界面元素和媒体控制器传输
我现在能看到的关于“去行星旅行”活动用户界面的唯一问题是用户体验 UX 设计的问题。
问题是,我们在屏幕顶部添加的按钮 UI 元素看起来像是屏幕的标题,而不是可以单击的按钮。这对于你来说似乎很小,但是对于一些认为屏幕顶部是一个标题而不是一个按钮的用户来说,这种假设会把他们困在这个屏幕上,没有办法回到主屏幕。所以,让我们解决这最后一个 UX 问题,我们将在这里完成!
微调我们的星球旅行用户体验设计
为了突出顶部居中的按钮是一个按钮而不是标题,我们需要在它周围放置一个边框,这样它看起来更像一个按钮。我做的第一件事是在我的按钮标签中键入 android: ,并在助手对话框的可用参数列表中查找 android:border 参数。令人惊讶的是,没有这样的选择,所以我不得不变得有创意,想出其他的东西来达到同样的视觉效果。XML 标记代码如图图 12-13 所示。
图 12-13。使用带有按钮边距参数的 RelativeLayout 背景创建一个按钮边框效果
我所做的是为父 RelativeLayout 容器设置一个Android:background = " # FFDDBB "标签参数。因为我们的 VideoView UI 元素充满了屏幕,所以我可以有效地使用背景色(我匹配了 android:textColor 参数值)在按钮周围创建一个漂亮的边框,还可以使用Android:Layout _ margin = " 4dp "参数将按钮 UI 元素的黑色背景拉得足够大,让这个相对布局背景色显示出来,并成为我的按钮的边框!
现在让我们使用作为 Android 应用运行的工作流程,看看我们的按钮现在看起来和行为是否更像一个按钮,而不是屏幕标题。
正如你在图 12-14 中看到的,活动用户界面屏幕顶部的按钮现在看起来更像是一个按钮,而不是一个屏幕标题,点击按钮会让用户返回到主屏幕,点击数字视频会显示一个 Android Media Controller 传输控制栏,所以两者现在都工作得很好。
图 12-14。测试旅行到星球 UI 屏幕和带边距边框的按钮
在第十三章中,我们将了解视频的另一部分,即音频,以及如何为您的 Android 应用创建、操作、压缩、编码、优化和处理音频。在我们学习了关于音频的基本概念和技术后,我们将开始把它整合到我们的应用中。
摘要
在涵盖数字视频的这一章中,我们仔细研究了如何在 Hello World Android 应用 XML 和 Java 代码以及用户界面和用户体验设计中融入我们在上一章中了解到的数字视频概念和素材。
我们学习了 Android MediaController 类,它用于在我们的 Android 应用中播放数字视频文件。我们学习了如何使用 Uri 类和 Uri.parse( ) 方法以及 getPackageName( ) 方法创建一个统一资源标识符对象,或 URI 对象。
然后,我们为 TravelPlanet.java 活动类编写 Java 代码,通过实例化名为视频媒体控制器的新对象来实现媒体控制器类,然后通过使用将媒体控制器对象锚定到 travelVideo 视频视图对象。setAnchorView( ) 方法。
然后我们通过调用完成了 MediaController 到 VideoView 的连接。setMediaController( ) 方法使用 videoMediaController 对象引用关闭我们的 travelView VideoView 对象。完成后,我们就可以使用 travelView 对象设置数字视频播放了。
我们调用了几个视图类方法来设置我们的数字视频回放,首先使用将视图放到最前面。bringToFront( ) 方法,然后通过调用来确保它有焦点。requestFocus( ) 方法。然后我们开始使用播放我们的 MPEG4 数字视频文件。start( ) 方法,并在 Nexus S 仿真器中测试了我们的程序逻辑。
接下来,我们进行了一些故障排除,以了解为什么我们没有在 VideoView UI 元素的底部获得 MediaController 传输条。
我们需要用一个按钮 UI 元素替换我们的 onTouch()事件处理,并实现 onClick()事件处理,以便我们的 VideoView 可以捕获 onTouch()事件并显示 MediaController 传输控制小部件。
最后,我们对用户界面设计做了一些微调,添加了一些影响按钮颜色的参数,增加了对比度,并与应用的外观和感觉相匹配。我们还在按钮 UI 对象周围添加了一个细边框,这样它就不会以屏幕标题的形式出现在最终用户面前,这可能是用户体验设计的一个错误。
在下一章,我们将了解 Android 操作系统支持的另一种主要新媒体类型:数字音频。我们将学习数字音频背后的核心数字音频概念、设计原则和制作技术,以及用于在我们的 Android 应用中实现数字音频的文件格式和编解码器。我们将了解开源音频编辑软件包 Audacity,以及如何使用它来创建用于 Hello World Android 应用的音频素材。
十三、音频简介:概念和优化
在这一章中,我们将为您提供理解数字音频工作原理所需的基础知识,并让您能够在 Android 应用中胜任数字音频工作。
数字音频是更难理解和优化的新媒体素材之一,因为数字音频本质上是数字采样声波的集合,而且,它们是肉眼不可见的。因此,我们不得不依靠我们的耳朵而不是我们的眼睛,我们大多数人都不习惯使用我们的耳朵来制作,除非我们是音频工程师,作曲家,音乐家或歌剧歌手!
因为数字音频数据是实时回放的,所以它像数字视频一样,在等式中引入了第四维度,即时间。这一点,再加上当今功能强大但复杂的数字音频编辑软件以及编码和解码算法所带来的复杂性,使得数字音频像数字视频一样难以理解。
我们将在如何应用于为您的 Android 应用最终用户创建最佳用户体验的背景下审视所有这些。
这意味着不仅要了解数字音频编辑的基本原理及其数据占用优化,还要了解哪些数字音频格式最适合用于 Android,以及使用哪些采样率。这些主题是高级的,所以我们将利用三个章节来有效地彻底涵盖一切,以便您能够很好地处理数字音频,以及如何针对 Android 进行优化。
模拟音频的基础:声波和空气
正如你们这些立体声发烧友已经知道的,声音是由声波在空气中脉动产生的,这就是为什么超低音扬声器是巨大的 12 英寸到 24 英寸的锥体,通过磁脉冲快速推出,将主要声波扔向摇滚音乐会上的 10 万名观众。
在数字音频出现之前,模拟音频是主要的消费电子行业。事实上,今天仍然如此,声波由复杂的模拟电子设备控制,包括电容器、电阻器、振荡器、晶体、电子管、电路板、扬声器锥体、心形麦克风和类似的模拟技术。正如我所说的,数字音频是复杂的,这种复杂性的一部分来自于需要将模拟音频和数字音频技术联系起来。
正如声音是由各种尺寸的扬声器锥体产生的,扬声器锥体本质上只是由一种或另一种材质制成的薄膜,通过脉冲或振动产生声波,因此我们的耳朵也可以接收并听到这些声波,通过接收空气或振动的脉冲并将它们转化为我们大脑可以处理的数据。
声波产生不同的音调,这取决于声波的频率。宽的或不常见的(长)波产生较低的(低音)音调,而更频繁的(短)波长产生较高的(高音)音调。有趣的是,不同频率的光会产生不同的颜色,所以模拟声音(音频或音乐)和模拟光(颜色或摄影)之间有着非常密切的联系,这种联系一直延续到数字领域。
声波的音量将根据该声波的振幅或高度(或大小)来预测。因此,频率是沿着 2D 的 X 轴的波的间隔有多近,振幅是沿着 Y 轴测量的波有多高。
声波本身可以有不同的形状,以承载不同的声音效果;基线类型的波被称为正弦波,我们在高中数学中学习过正弦、余弦和正切数学函数。
精通音频合成的人都知道,在声音设计中还有其他类型的核心声波,例如看起来像锯子边缘的 saw wave (因此得名),或者使用直角整形的 pulse wave (产生即时的开和关声音或脉冲)。
甚至随机波形,如噪音,也被用于声音设计,以获得尖锐的声音效果。您可能已经从本书中有关数据占用优化的内容中猜到,声波中出现的混乱或噪声越多,编解码器就越难压缩,特定声音的数字音频文件也就越大。接下来,我们将进一步了解这些模拟音频声波如何通过称为采样的过程转化为数字音频数据,这是声音设计和音乐合成的核心工具。
数字音频的基础:采样、采样分辨率和采样频率
将模拟音频声波转化为数字音频数据的过程称为采样。如果你在音乐行业,你可能听说过一种键盘或机架安装设备,它被称为采样器。
采样是一个过程,它将音频波(通常是复杂的正弦波)分割成段并以数字格式(0 和 1)存储它们的形状和任何其他波形属性(即噪声)。
音频声波的这些数字片段被称为样本,因为它们在任何给定的时间点对声波进行采样。样本的精度取决于定义每个波切片所用的数据量,与数字成像一样,这种精度被称为分辨率,或采样分辨率,通常定义为 8 位、16 位或 24 位。
在数字成像(和数字视频)中,分辨率用颜色的数量来量化,而在数字音频中,分辨率用多少位数据来定义每个音频样本来量化。
正如您可能已经猜到的那样,更高的采样分辨率,或者再现给定声波样本所需的更多数据,将产生更高的音频回放分辨率,从而产生更高的声音质量。这就是为什么 16 位(也称为: CD 质量)音频听起来远远好于 8 位音频的原因,就像 24 位彩色图像比 8 位彩色图像看起来更好一样。
在数字音频领域,我们现在有 24 位数据采样,在数字音频领域称为: HD 数字音频。高清数字广播电台使用 24 位样本分辨率,因此每个音频样本或声波片段包含 16,777,216 个样本分辨率单位。一些较新的 Android 设备也将支持高清音频,如你在广告中看到的那些智能手机,具有超高质量(高清)音频,这意味着它们具有 24 位音频硬件支持。
除了数字音频采样分辨率,我们还有数字音频采样频率、或在一秒钟内以该特定分辨率采集多少样本。在数字成像中,采样频率类似于我们使用的图像中的像素数。
采样频率也可以称为采样率,您可能熟悉 CD 音质音频,它被定义为使用 16 位采样分辨率和 44.1 kHz 采样率,它需要 44 个、 100 个样本,每个样本将包含 16 位数据,即每个样本中的总数据量为 65,536 个单位。
让我们计算一下,看看有多少数据样本用于提供一秒钟的原始(未压缩)数字音频数据。这是通过将 65,536 个单位乘以 44,100 个样本来实现的,以获得用于表示一秒 CD 质量音频的 2 、 890 、 137 、 600 个样本的数据值。
因此,要计算出音频文件中的原始数据,你需要将采样比特率的十进制值乘以采样频率,再乘以音频片段中的秒数。请放心,这将是一个很大的数字,但放松;音频编解码器在优化这些数据方面非常出色,数据占用空间非常小,质量损失非常小(听得见)。
因此,我们在数字成像和数字视频中的完全相同的权衡也存在于数字音频中。我们包含的数据越多,得到的结果就越(高)质量,但代价是数据占用空间更大。在视觉媒体中,这是使用色深、像素和(数字视频)帧来定义的,而在听觉媒体中,它是通过采样分辨率结合采样率来定义的。
数字音频行业常见的采样速率包括:22 kHz、32 kHz、44.1 kHz、48 kHz、96 kHz、192 kHz 以及最近的 384 kHz。较低的采样率,例如 22 kHz 或 32 kHz,将足以对基于语音的数字音频进行采样,例如电影对话或电子书的旁白轨道。
较高的采样速率更适合音乐和其他需要高动态范围(高保真度)的声音效果,这样可以再现出出色的“高保真”音质。
一些声音效果,例如我们将在 Hello World 应用中使用的,可以使用较低的 32 kHz 采样速率,只要使用的采样分辨率是 16 位质量。
关键数字音频属性:CD 音频、HD 音频、音频流和音频比特率
正如我们已经提到的,卓越音频质量的行业基准被称为 CD 音频标准,被定义为在 44.1 kHz 数据采样频率下的 16 位数据采样分辨率。这是 20 世纪用来制作音频 CD-r om 的材质,至今仍在使用。
还有一个更近的 高清音频标准****24 位数据采样,采样频率为 48 kHz (甚至 96 kHz),目前用于高清收音机和高清音频兼容的 Android 设备,如高保真智能手机。
如果您打算在 Android 应用中使用高清音频,您需要确保您的目标最终用户将拥有高清音频标准兼容硬件,这将需要利用这一更高水平的音频保真度。
就像您的数字视频数据一样,您的数字音频数据既可以在您的应用中被捕获(/ raw 文件夹中的数据文件),也可以从远程数字音频文件流数据服务器中流出。
流式数字音频数据的好处是,它可以减少应用的数据占用,就像流式数字视频数据一样。许多相同的概念同样适用于音频和视频。
流式音频节省了数据空间,因为您不必在中包含所有繁重的新媒体数字音频数据。APK 文件,所以如果你正计划编码一个自动点唱机应用,你可能要考虑你的数字音频数据流。否则,请尝试优化您的数字音频数据,以便您可以将它包含在。APK 档案。这样,当应用的用户需要它时,它总是可用的。
流式数字音频的缺点是,如果您的用户连接(或音频服务器)中断,您的音频文件可能无法始终供最终用户播放和收听!您的数字音频数据的可靠性和可用性是流式音频相对于专属数字音频数据的另一方面需要考虑的关键因素。
就像数字视频一样,数字音频流的一个主要概念是数字音频数据的比特率。
正如我们之前所了解的,比特率是在压缩过程中定义的,我们将在本章后面的章节中详细讨论这一点,因为它与数字音频有关。不过,让我们在这里重温一下,只是为了确保你理解它,因为它是一个基本概念。
比特率定义了您的数字音频数据将被压缩到什么程度。比特率将在数字音频文件压缩过程中定义,这就是为什么我将在本章后面的数字音频数据占用优化部分详细介绍它。
比特率较低(数量较少)的数字音频文件将对数据进行更多的压缩,这将导致较低的质量水平,但在更多的消费电子设备上播放更流畅。
如你所知,比特率是每秒比特数(BPS)的量度,可以被有效地处理或传输。随着计算机处理器变得更快,它可以处理更多的 BPS 同样,随着数据带宽连接变得更快,它可以更舒适地每秒发送或接收更多的比特。
因此,重申一下,位/秒不仅对流式数字音频内容很重要,因为它将适合带宽,而且一旦它到达您的 Android 设备,位速率会影响哪些内容可以足够快地处理(解码),以允许 Android 设备内的处理器顺利播放。
出于这个原因,比特率之所以重要,有两个原因:一是因为流音频,二是因为捕获(嵌入)数字音频文件(作为捕获文件保存在 Android 应用资源/raw 文件夹中)的一个原因(解码 CPU 的处理速度)。
因此,在 Android 应用中使用捕获或嵌入的音频,音频资源的比特率越低,越多的 Android 设备可以平滑地解码该音频资源(而不会丢失音频样本)。接下来,我们需要了解 Android 支持的数字音频文件格式和编解码器,并了解使用哪些格式和何时使用它们。
数字音频格式:支持 Android 中流行的数字音频编解码器
Android 中的数字音频编解码器远远多于数字视频编解码器(MPEG-4 和 VP8)。Android 支持 .MP3 (MPEG-3)文件,Wave (PCM 或脉码调制)。WAV 文件、 .MP4 (或. M4A) MPEG-4 音频、OGG Vorbis ( )。OGG 的音频文件,马特罗斯卡。MKS 音频文件,FLAC ( )。FLAC )音频文件,甚至 MIDI(。MID,。MXMF 和。XMF)文件,从技术上来说,这根本不是真正的数字音频数据。让我们先把 MIDI 去掉,因为我们不会在 Hello World 应用中使用它。
MIDI 代表代表乐器数据接口 ,它是数字音频和计算机最早合作的方式之一,可以追溯到 20 世纪 80 年代。第一台具有 MIDI 端口的计算机是 Atari ST-1040,它允许你将键盘合成器,如雅马哈 DX-7,插入该 MIDI 端口,并使用称为 MIDI 音序器的音频软件将 MIDI 数据播放和记录到计算机中。
MIDI 文件不包含样本数据,也就是说,它不包含音频,只包含演奏数据。MIDI 跟踪键盘上的哪些键被按下、何时被按下、按键持续时间、按键被按下的力度(称为:后触摸)以及类似的性能特征。当通过合成器回放 MIDI 文件时,它会复制播放器的性能,即使播放器不再播放那个轨道。
在音乐制作行话中,曲目是歌曲创作或表演的一个片段或一部分。在录音棚里,不同的乐器和声乐表演被保存在不同的音轨上,这样在后期制作中,工程师可以更精确地对它们进行“混音”。
这在 MIDI 序列软件中的使用方式是,您可以弹奏一个乐器轨道,将其录制为 MIDI 数据,当您弹奏它旁边的另一个乐器轨道时,序列器会为您回放它。这使得数字歌曲作者能够通过使用计算机来组合复杂的安排,而不是一个充满音乐家的工作室。
Android 支持 MIDI 文件的回放,但没有实现 MIDI 类,所以为 Android 编写 MIDI 音序器并不是一件容易的事情,尽管代码论坛上有人在谈论它。由于这个原因,它超出了本书的范围,我在这里提到它只是为了让你了解数字音频的历史和范围,因为 MIDI 在数字音频的发展中发挥了重要作用。
Android 支持的最常见的格式是 MP3 数字音频文件格式。由于 Napster 等音乐下载网站,我们大多数人都熟悉 MP3 文件,我们大多数人收集这种格式的歌曲,以便在流行的 MP3 播放器上使用,并通过 CD-ROM 和 DVD-ROM 收集音乐。
MP3 数字音频文件格式如此受欢迎的原因是因为它具有良好的压缩比和质量比,并且因为播放它所需的编解码器几乎可以在任何地方找到,甚至在 Android 操作系统中。只要你通过使用最佳的编码工作过程获得最佳的质量水平,MP3 将是 Android 应用中可以接受的格式。
值得注意的是,MP3 是一种有损音频文件格式,就像 JPEG 用于成像一样,其中一些音频数据(以及质量)在压缩过程中被丢弃,并且以后无法恢复。因此,如果您要使用 MP3 音频,请确保保存原始的未压缩音频数据文件。
Android 确实有一个无损音频压缩编解码器,叫做: FLAC ,代表 免费无损音频编解码器 。FLAC 是一个开源音频编解码器,由于软件解码器的免费性质,它的支持几乎和 MP3 一样广泛。因此,使用 PNG32 和 FLAC,在您的 Android 应用中使用完全无损的新媒体素材是可能的。
FLAC 也非常快(编码非常紧密),支持 HD (24 位)音频,使用它没有专利问题。如果您需要在合理的数据占用范围内获得高质量的音频,这是一个很好的音频编解码器。
FLAC 支持大范围的样本分辨率,从每个样本 4 位到每个样本 32 位。它还支持非常宽的采样频率范围,从 1Hz 到 655350Hz (65 kHz),增量为 1Hz,因此非常灵活。从音频回放硬件的角度来看,我建议使用 16 位采样分辨率和 44.1 kHz 或 48 kHz 采样频率。
Android 3.1 和更高版本支持 FLAC,因此如果您的最终用户使用现代 Android 设备,您应该能够安全地使用 FLAC 编解码器。
Android 支持的另一个开源数字音频编解码器是 Vorbis 编解码器,一个来自Xiph.Org
基金会的有损音频编解码器。Vorbis 编解码器数据通常保存在中。OGG 数据文件容器,因而 Vorbis 通常被称为 Ogg Vorbis 数字音频数据格式。
Ogg Vorbis 支持从 8 kHz 到 192 kHz 的采样速率,以及到 255 的数字音频离散通道(正如我们现在所知,这代表 8 位音频通道)。所有版本的 Android 都支持 Vorbis。
Vorbis 正在迅速接近 HE-AAC 和 WMA(Windows Media Audio)Professional 的质量,并且在质量上优于 MP3、AAC-LC 和 WMA。这是一种有损格式,因此 FLAC 仍然比 Ogg Vorbis 具有更高的质量水平,因为它包含所有原始的数字音频样本数据。
Android 支持最流行的 MPEG4 AAC ,或高级音频编码 ,编解码器,包括 AAC-LC , HE-AAC ,和 AAC-ELD 。这些都可以包含在 MPEG4 容器(. 3gp、. mp4、. m4a)中,并且可以在所有版本的 Android 中回放,只有 AAC-ELD 除外,它只在 Android 4.1 以后才受支持。 ELD 代表增强型低延迟,该编解码器旨在用于实时双向通信应用,如数字对讲机。
最简单的 AAC 编解码器是 AAC-LC 或低复杂度编解码器,这是使用最广泛的编解码器,应该足以满足大多数应用。与 MP3 相比,AAC-LC 应该以更低的数据占用量产生更高质量的结果。
下一个最复杂的 AAC 编解码器是 HE-AAC 或高效 AAC 编解码器。该编解码器支持 8 kHz 至 48 kHz 的采样速率,以及立体声和杜比 5.1 声道编码。Android 支持解码该编解码器的 v1 和 v2 级别,并且还在 4.1 版(Jelly Bean)之后的 Android 设备中以 HE-AAC v1 编解码器编码音频。
对于通常以不同于音乐的声波类型为特征的编码语音,还有两种其他 AMR 或自适应多速率 音频编解码器,它们对于编码语音或不需要高质量再现的短脉冲音效(如炸弹爆炸音效)等非常有效。
Android 中有一个 AMR-WB ,或自适应多速率宽带标准,它支持从 6.6 到 23.85 kbps 的 9 个离散设置,音频比特率以 16 kHz 采样,这对于语音来说是一个高采样率。例如,如果您正在创建一个交互式电子书应用,这将是用于旁白轨道的编解码器。
Android 中还有一个 AMR-NB 或自适应多速率窄带标准,它支持从 4.75 到 12.2 kbps 的 8 种离散设置,以 8 kHz 采样音频比特率,这是一个足够的采样率,如果进入编解码器的数据是高质量的,或者由于其噪声性质(炸弹爆炸)导致的音频样本不需要高质量。
最后,我们有 PCM 或脉码调制 音频,俗称波或。WAV 音频格式。你们中的许多人都熟悉这种格式,因为它是用于 Windows 操作系统的原始音频格式。
PCM 音频通常也用于 CD-ROM 和 DVD-ROM 内容,以及电话应用。这是因为 PCM Wave audio 是一种未压缩的数字音频格式,它没有应用于其数据流的计算密集型压缩算法,因此解码(CPU 开销)对于电话设备或 CD-ROM 或 DVD-ROM 播放器来说不是问题。
因此,正如您很快会看到的,当我们开始将数字音频素材压缩成各种格式时,我们可以使用 PCM 作为基线,但可能不会将其放入我们的。APK 文件,因为有其他格式,像 FLAC 和 AAC,它会给我们同样的质量,使用数量级更少的数据。
最终,真正找出 Android 中哪种音频格式对于任何给定的音频数据实例具有最佳数字音频编解码器的唯一方法是,实际上用我们知道支持良好且高效的主要编解码器对您的数字音频进行编码,并观察数据足迹结果,然后听听音频回放质量,以做出我们的最终决定。
数字音频优化:跨设备播放
优化我们的数字音频素材,以便在市场上的各种 Android 设备上播放,将比优化我们的数字视频甚至我们的数字图像更容易。
这是因为在 Android 设备之间,屏幕分辨率和显示器纵横比的差异要比数字音频播放硬件支持的差异大得多。这是因为用户的耳朵无法像眼睛一样感知视频中的音频质量差异。
一般来说,所有 Android 设备都有三个主要的数字音频支持“最佳点”,我们应该为我们的高质量音频提供支持。较低质量的音频(如旁白轨道或简短的声音效果)可以使用 8 位、12 位或 16 位分辨率的 22 kHz 或 32 kHz 采样。
这些高质量音频目标包括 CD 质量音频,也称为 44.1 kHz 的 16 位数据采样,HD 质量音频位于该音频频谱的另一端,也称为48 kHz采样率的24 位数据采样,以及未命名的“中间某处”规范,使用 48 kHz 采样率的 16 位数据采样。右手的最后一个选项可以产生类似于剧院中使用的 THX 质量音频的效果。
因此,我们在所有 Android 设备上优化数字音频素材的工作流程将是创建 44.1 kHz 和 48 kHz 的 16 位素材,然后以 Android 支持的不同格式对其进行优化(压缩),并查看哪些素材能够以最低的数据占用量提供最高质量的音频播放。
我们将使用最近发布的 Audacity 2.0.3 数字音频编辑和工程软件包来完成这项工作。这个软件包是开源的,因此我们所有的读者都可以访问,并且可以在所有流行的操作系统平台上使用,包括 Windows、Macintosh 和 Linux。
用插件和编解码器库设置 Audacity 2
如果你还没有下载 Audacity 的最新版本(在撰写本书时是 2.0.3 ),现在就去audacity.sourceforge.net下载你的免费版本并安装它。
请确保您还安装了最新的 Audacity 2 插件、FF-MPEG 和 LAME 编解码器,这样您就可以为数字音频文件格式导出最新的数字音频编码算法。
重要的是要注意,在不改变编解码器的解码器侧的情况下,编解码器的编码器侧可以变得更有效;因此,在任何给定的时间获得最新的编码器并不意味着 Android 需要更新其 OS API 库中的解码器支持,以便您能够从任何特定的编解码器中获得质量到文件大小的好处。
一旦你下载并安装了最新的 Audacity,你可以通过进入 Audacity 的下载选项卡(页面)并点击插件和库链接,用所有新的插件和编解码器来增强它。
要安装 90 多个免费的 LADSPA 插件,找到并点击链接“90 多个 LADSPA 插件”这会触发上的下载。如图 13-1 中 LADSPA 插件设置对话框的选择目标位置阶段所示。如果您没有将这些插件放在正确的文件夹中,它们将不会加载到 Audacity 中。
图 13-1。为 LADSPA 插件选择目标位置设置对话框,指定\Audacity\Plug-Ins 文件夹
如果你愿意,你也可以安装奈奎斯特和 VST 插件,也列在这一页,虽然他们不是本书所需要的,因为我们不打算深入到数字音频工程在这个时候!
接下来滚动到页面底部,右键单击 LAME FAQ 链接,打开 LAME MP3 编码器页面,然后单击顶部的下载页面链接。向下滚动一半并单击文本链接。EXE 文件读取Lame _ v . 3 . 99 . 3 _ for _ windows . EXE并下载安装。EXE 文件复制到其默认目录位置。
接下来,滚动到页面底部,右键点击 FFmpeg FAQ 链接,打开 FFmpeg 导入导出库页面,然后点击顶部的下载页面链接。向下滚动 LAME 链接下面的两个链接,然后单击。EXE 文件FFmpeg _ v 0 . 6 . 2 _ for _ Audacity _ on _ windows . EXE并下载安装。EXE 文件复制到它的默认目录位置。
一旦您将所有这些插件和编解码器安装到您的内容制作工作站上,您就可以启动 Audacity,它将在其启动初始化例程中找到并安装最新的数字音频编解码器库支持和插件。
数字音频创作:寻找 Hello World 音效
为了找到一些用于商业用途的免费音频样本,我将使用谷歌搜索引擎,并键入一个查询,以获得免费音频样本,或免费数字工作室样本,或免费音频文件,或免费数字音频文件,以及类似的谷歌搜索术语组合。
请注意,由于在提供这些数字音频素材的不同网站中使用了关键字,每一次谷歌搜索都会出现完全不同的结果。
有几十个好的网站都符合我们的需求,所以当你有空的时候,一定要深入调查这些网站。确保您用于应用开发的那些软件是免费用于商业用途的,并且没有任何版税、使用或版权限制。
我们要寻找的是最高质量的未压缩 PCM (波或。wav 音频文件格式)样本,使用 16 位或更好的(24 位或 32 位)格式,并且希望有 44.1 kHz 或 48 kHz 的采样频率。
请注意,如果您下载并使用. MP3 文件(大多数也提供),它们将已经被压缩,并可以使用,但您将无法控制压缩和优化过程,因为许多原始数据在压缩过程中已经被丢弃。
数字音频压缩:关键概念和格式
首先你需要通过点击任务栏上的快速启动图标来启动 Audacity 2.0 ,并使用文件打开菜单命令序列来打开我们从免费音频样本网站下载的 battle003.wav 文件。
第一次打开 Wave 文件时,你会看到如图图 13-2 所示的警告对话框。选择制作副本单选按钮选项,选中不再警告复选框,最后点击确定按钮将音频样本加载到 Audacity 中。
图 13-2。导入音频文件的警告和适当的复制设置
使用这些音频文件导入设置(使用文件的副本,而不是实际文件本身)被称为无损音频编辑,是数字音频编辑和效果行业的常见做法。
这样做的原因是,如果您在音频加甜和特效应用中出错,并损坏了音频数据,您可以通过返回并加载原始音频数据来从头开始。
一旦 battle003.wav 样本数据被加载到 Audacity 2 中,您将会看到一个与图 13-3 中显示的屏幕一模一样的屏幕。左上角包含音频传输控制,包括暂停、播放、停止、后退、前进和录制。紧挨着它的是编辑工具,最右边是电平指示器,当您的音频正在播放时,它会显示绿色、黄色和红色信号峰值指示器。下面是扬声器和麦克风音量的设置,以及系统音频设置选择器下拉菜单。在 Audacity 窗口的底部,您会发现以 Hz 为单位的项目采样率,以及用于选择开始、结束或长度的小时、分钟、秒和毫秒显示,以及用于微调的音频位置。
图 13-3。 Audacity 2.0 主编辑屏幕显示了 battle003 的 32 位浮点 11 kHz 样本数据
我们首先要做的是,在开始导出到我们所学的各种数字音频格式之前,确保我们的采样分辨率和采样速率设置正确,这样我们就可以看到每个音频编解码器可以为我们的 Android 项目提供的实际压缩结果。
在 Audacity 中设置采样率和采样分辨率
在位于音频样本可视显示左侧的蓝灰色控制面板中,您会看到 battle003 样本(文件)名称,以及旁边一个朝下的箭头。点击此箭头,下拉一个选项菜单,选择设置样本格式选项,然后从其子菜单中选择 16 位 PCM 选项,如图图 13-4 所示。这确保了我们导出到 Android 支持的 16 位样本分辨率。
图 13-4。在导出为各种格式之前,将音频样本分辨率设置为 16 位 PCM(未压缩)
现在我们可以导出我们的基线 16 位未压缩 Wave 音频。wav 文件格式,用于查看 2.5 秒炸弹爆炸的 16 位 11.025 kHz 音频样本的最大文件大小。
它的文件大小应该与我们在 Audacity 2 软件中打开(导入)的原始文件非常相似,但我们将把它命名为 blast.wav ,因为这是我们将为这个音频素材使用的更简单的名称(在我们的 Android Java 代码中,我们将在下一章中编写)。
导出未压缩的 PCM 基线。WAV 格式文件
为了在 Audacity 中导出文件,我们将使用文件导出菜单序列来打开导出文件对话框,如图 13-5 中的所示。这个对话框有几个关键区域,包括保存在:文件夹说明符,它指向我们的音频资源文件夹,文件列表窗格,它显示我们的原始 battle003.wav 文件,文件名:数据输入字段,我们将在其中命名我们的文件,在这个例子中是 blast ,在它下面是保存类型:下拉选择器,它包含所有注意我们在中设置了导出 blast.wav 的对话框。wav 16 位 PCM 格式,这就是为什么我们不需要指定的原因。文件名的一部分。
图 13-5。导出我们的基准 16 位 PCM。wav 格式文件,并在导出时显示编辑元数据对话框
如果您单击显示在导出文件对话框右下角的选项按钮,您将看到对于 Wave 音频文件格式,您将得到一个对话框,通知您没有 PCM 格式的编码选项。
仔细想想,这是合乎逻辑的,因为 PCM 波是一种未压缩的音频格式,因此数据根本没有编码,因此没有音频编码参数或选项。
点击保存按钮后,会出现另一个编辑元数据对话框,如图 13-5 右图所示。该对话框包含艺术家姓名、曲目标题、专辑标题、曲目编号、年份、流派和注释的文本值数据字段。
因为我在这里进行了优化,以尽可能减少数据占用,而且我们的应用不需要音频元数据,所以我现在将这些字段留空,这样我们就可以准确读取音频数据的压缩情况。
如果你想知道 Android 是否能够读取和支持元数据,即使我们将这些数据放入我们的音频文件,答案是响亮的是的!事实上,Android 甚至有一个MediaMetadataRetriever类,开发者可以准确地利用它来实现这个特定的目的。
如果出于某种原因,您的音频应用需要利用元数据,您可以使用“编辑元数据”对话框,每当您在 Audacity 中保存任何类型的音频文件格式时,该对话框都会显示出来,此外还有 Android MediaMetadataRetriever 类,您可以通过以下 URL 研究和了解所有相关内容:
http://developer.android.com/reference/android/media/MediaMetadataRetriever.html
如果您查看我们刚刚保存的 blast.wav 16 位 PCM 文件,您会发现文件大小与原始的 battle003.wav 文件相同。
我们从中可以推断出,即使 Audacity 告诉我们这是以 11.025 kHzi 的 32 位采样分辨率编码的,但实际上它实际上是 32 位数据容器(或大量未使用的余量)中的 16 位数据,因为文件大小完全相同,直到最后一个字节。
因此,对于这个 2.5 秒的炸弹爆炸样本,我们的基线未压缩数据足迹是 50 , 380 字节,我们可以使用这个数字来确定使用 Android 支持的所有主要格式将获得的压缩量。
导出无损的。FLAC 开源音频格式文件
我要尝试的第一种格式是 FLAC 音频编解码器,因为它使用无损压缩。这将使我们了解使用不丢弃任何原始音频数据的压缩可以减少什么样的数据占用空间,从而为我们提供与 16 位 PCM Wave audio 一样完美的结果。
再次使用文件导出菜单序列,这次我们将下拉另存为类型:菜单并选择 FLAC 文件格式,如图图 13-6 所示。再次将文件命名为 blast 并放入 Audio 文件夹。
图 13-6。导出名为 blast.flac 的 FLAC 音频文件,具有 8 级(最佳)质量和 16 位样本位深度
请注意,在此对话框的中心区域没有列出其他文件;这是因为现在我们已经选择了 FLAC 文件格式类型,该区域仅显示 FLAC 文件,而音频文件夹中目前没有 FLAC 文件。这是模态软件操作的另一个好例子,只是这次是在我们的音频工程软件中,而不是在数字成像软件中。
要设置我们的 FLAC 编解码器选项,请单击选项按钮,并将质量级别设置为 8(最佳),并将位深度设置为 16 位。请注意,在“位深度”下拉列表中,我们也可以使用 FLAC 来获得无损的 24 位高清音频。
一旦你输出了你的 blast.flac 音频素材,进入你的文件管理器,看看文件大小。你会看到是 33 、 537 字节,或者减少三分之一(33537 除以 50380 就是 0.66568 或者三分之二)。
接下来让我们看看另一种开源格式,Vorbis,看看它是否能给我们带来更小的数据占用空间。由于 Ogg Vorbis 是一种有损文件格式,它应该给我们一个比 FLAC 更小的文件。
出口有损 Ogg Vorbis 开源。OGG 格式文件
使用文件导出工作流程,如前打开导出文件对话框,从保存类型:下拉菜单中选择 Ogg Vorbis 文件。将文件命名为: blast (这将产生一个 blast.ogg 文件名)并将其放入 Audio 文件夹中。
点击选项按钮,在 0 到 10 之间选择一个质量设置等级。我使用默认设置 5 开始;在实际的数据占用优化过程中,您将尝试几种设置,以查看数据占用对质量折衷的影响。
图 13-7。以 5 级(默认)质量和 16 位采样导出名为 blast.ogg 的 Ogg Vorbis 音频文件
一旦你输出了你的 blast.ogg 音频素材,进入你的文件管理器,看看文件大小。你会看到是 12 、 995 字节,或者减少四分之三(12995 除以 50380 是 0.25794,或者等于四分之一)。这是一个重大的尺寸减少,音频听起来和以前一样(然后再一次,这只是一个基本的爆炸)压缩。
接下来,我们来看看市面上最常见的有损音频格式 MP3。看看 MP3 是否能给我们比 Ogg Vorbis 开源编解码器更小的数据足迹应该是很有趣的。
导出到一个有损 MPEG-3 格式的. MP3 音频文件
再次使用文件导出工作流程,调出 Audacity 导出文件对话框,将另存为类型:下拉选择器设置为 MP3 文件。
将文件命名为 blast 并选择音频文件夹,然后点击 MP3 选项按钮打开指定 MP3 选项对话框,如图 13-8 右侧所示。我使用了默认的质量比特率设置 128 kbps ,这对于音频数据来说是相当高的,以及一个恒定比特率模式设置和一个联合立体声通道模式,因为文件是单声道的。
图 13-8。以 128 kbps 恒定速率(比特率模式)编码导出名为 blast.mp3 的 MP3 音频文件
如果您愿意,您可以尝试几种不同的质量位速率设置,以及可变和平均位速率模式,以查看它们如何影响您的音频文件数据占用空间。
如果这样做,只需用文件名中的设置来命名文件。因此,例如,具有 128 kbps 质量设置和可变比特率模式的文件将被命名为 blast128vbr.mp3 。
通过这种方式,您可以比较您的音频文件大小,并进行简单的数学运算来计算您的数据占用减少百分比,正如我们接下来将对我们的 blast.mp3 (或 blast128cbr.mp3,如果我们遵循我们的命名约定)文件所做的那样,您只需单击 Save 按钮即可生成该文件。
blast.mp3 文件大小为 19,643 字节,表示数据占用空间减少了 61%。为了解决这个问题,19,643 除以 50,380 等于 0.3898 ,这是原始未压缩文件大小的 39%。100%减去 39%等于文件大小减少 61%。
既然我们已经看到我们的. MP3 文件大小并不令人印象深刻,甚至不如 Ogg Vorbis,让我们看看 MPEG4,或 MP4,数据压缩如何改善 MPEG3。因为 MPEG-4 是一种更新更先进的编解码技术,它应该为我们提供更好的文件大小质量比。
导出到有损的 MPEG-4 格式. M4A 音频文件
按照之前的文件导出工作流程调用 Audacity 导出文件对话框,从保存类型:下拉菜单选择器中选择 M4A (AAC)文件(FFmpeg) 。照常将音频目录中的文件命名为 blast (点击保存后导出器将命名为 blast.m4a),然后点击选项按钮打开指定 AAC 选项对话框,如图图 13-9 所示。我选择将质量设置提升到 500,看看当使用最高质量级别设置时,MPEG4 文件的大小是多少。单击“保存”按钮,立即导出 blast.m4a 文件。
图 13-9。导出名为 blast.m4a 的 MP4 AAC 音频文件,最高质量设置为 500
blast.m4a 文件大小为 10,513 字节,表示数据占用空间减少了 79% 。为了解决这个问题,10,513 除以 50,380 等于 0.2087 ,这是原始未压缩文件大小的 21%。100%减去 21%等于文件大小减少 79%。
现在,我们已经看到我们的 M4A AAC 文件大小是迄今为止最令人印象深刻的,让我们看看更专业的 AMR-NB 窄带数据压缩编解码器是否会比 MPEG4 AAC 进一步改善数据占用空间。
尽管 MPEG-4 AMR-NB 编解码器和格式是专门为语音应用而设计和优化的,但可能还有一些其他应用,如某些短脉冲声音效果,可能会从该编解码器获得良好的效果。
毕竟,任何编解码器都只是一个作为软件实现的复杂的数学方程,并不区分,所以真正找出答案的唯一方法是通过编解码器运行原始的未压缩音频数据,看看会发生什么。让我们接下来这样做;然后我们将完成对 Android 中支持的音频编解码器和 Audacity 中提供的音频编解码器的比较。
导出窄带 MPEG-4 格式。AMR 音频文件
按照文件导出工作流程调用 Audacity 导出文件对话框,从保存类型:下拉菜单选择器中选择 AMR(窄带)文件(FFmpeg) 。
像往常一样,我们将音频目录下的文件命名为 blast (点击保存按钮后,导出器会将其命名为 blast.amr),然后点击选项按钮,打开 AMR-NB 导出设置对话框,如图图 13-10 所示。
图 13-10。导出名为 blast.3gp 的 AMR(窄带)音频文件,最大比特率设置为 12.20 kbps
我选择使用 12.20 kbps 比特率设置,以获得这种编解码器和数据格式可能的最高质量结果。这个工作过程让我可以看到当使用最大质量级别设置时,AMR-NB 文件的大小。
让我们点击保存按钮,现在导出我们的 blast.amr 音频文件。如您所见,这是我们迄今为止获得的最小数据足迹,然而当我们回放它时,它听起来仍然非常像炸弹爆炸。
blast.amr 文件大小为 3,686 字节,表示数据占用空间减少了 93% 。为了解决这个问题,3686 除以 50380 等于 0.0732 ,这是原始未压缩文件大小的 7%。100%减去 7%等于文件大小减少 93%。
我们现在唯一的问题,幸运的是只有最后一个非常合适的编解码器和文件格式,是 Audacity 想要输出 AMR-NB 文件。AMR 文件扩展名,而 Android 希望看到使用. 3GP 文件扩展名的 AMR-NB 音频文件。总是有事,不是吗?让我们希望在未来版本的 Android 操作系统中,也许是 5.0,Android 决定友好地接受。amr 文件扩展名!
所以我们回到绘图板,看看 Audacity 文档。
Audacity 手动文件导出对话框部分告诉我们,我们应该能够指定某些非标准的文件扩展名,并且能够成功,所以让我们回到 Audacity 并尝试一下!
按照通常的文件导出工作流程调用 Audacity 导出文件对话框,并从保存类型:下拉菜单选择器中选择 AMR(窄带)文件(FFmpeg) 。
这次,我们将音频目录中的文件 blast.3gp 命名,然后点击选项按钮,在 AMR-NB 导出设置对话框中设置 12.20 kbps 质量选项,如图图 13-10 所示。
点击保存按钮后,您将看到如图图 13-11 所示的警告对话框。点击是按钮,将文件保存为 blast.3gp,我们将很快确定(在下一章)blast.3gp 文件是否会在我们的 Android 应用中播放。
图 13-11。Audacity on 文件中显示的警告对话框导出文件扩展名为. 3GP 的 blast AMR-NB 文件
接下来,我们将把这些优化的数字音频素材放入我们的 Android 项目资源文件夹层次结构中的适当素材位置,以便我们可以在下一章的 Java 代码中访问它们。
在 Android 中使用数字音频资源:项目资源原始文件夹
现在,让我们打开操作系统的文件管理软件,将目前已经优化的六种数字音频文件格式复制到 Android Hello_World 项目文件夹的 /res/raw 子文件夹中。
进入您的数字音频素材制作文件夹,在我的情况下,它在我的一个四核工作站上,在 MindTaffyDesign 文件夹中的 Users 文件夹下的一个 Book Audio 文件夹中,如图 13-12 顶部的所示。
图 13-12。选择我们的六个优化的 blast 数字音频文件,并将它们拖到/res/raw 文件夹中
按住 Control 键的同时,点击选择我们优化过的六个文件,拖动到你的 Hello_World Android 项目文件夹中的/res/raw 子文件夹,也如图 13-12 所示。
一旦这些都在/res/raw 文件夹中,如图图 13-13 所示,我们可以重命名它们,这样当我们在 Java 代码中引用 blast 时,Android 就知道使用哪个了。
图 13-13。我们的 blast 文件显示在/res/raw 文件夹中
如果我们在启动 Eclipse 之前不重命名这些文件,我们将在 Eclipse ADT 中位于主编辑窗格左下方的 Problems 选项卡中看到许多错误。
如果您想实际查看这些错误是什么,只是为了体验,那么现在就启动 Eclipse。请确保在重命名所有这些数字音频素材文件之前启动 Eclipse,否则您将看不到这些错误。如果你选择问题标签,你会看到十几个左右的 blast 错误,用红色文本标出。Eclipse 中的错误消息总是很难看!
这些错误将告诉您已经有一个 blast 素材,因此 Android 将把它找到的第一个 blast 数字音频素材作为要实现的素材,然后为其他素材生成错误。
这些错误将指定 Android 已经有一个 blast 音频素材可以利用,因此您必须删除以主文件名 blast 开头的任何其他音频素材,这是我们接下来要做的。
现在,让我们进入您的操作系统文件管理实用程序,我们将重命名所有这些音频素材,以便只有一个 blast.flac 可以被 Android 操作系统看到,我们将在下一章的代码中开始使用它。
图 13-14 显示了这个过程,正如你所看到的,我们已经将 blast.3gp 重命名为 blast_3gp.3gp,将 blast.m4a 重命名为 blast_m4a.m4a,将 blast.ogg 重命名为 blast_ogg.ogg,将 blast.wav 重命名为 blast_wav.wav,最后将 blast.mp3 重命名为 blast_mp3.mp3,依此类推,这是根据我们在 Android 中避免这些错误的数字音频素材占位符命名惯例。
图 13-14。重命名 blast 数字音频文件及其编解码器类型,以便只有一个名为 blast 的文件
稍后,当我们想要在 Android 中实现任何给定的数字音频文件格式时,我们会将 blast.flac 重命名为 blast_flac.flac,然后将我们想要使用的编解码器重命名为 blast_m4a.m4a,这样 Android 就可以看到音频素材 blast 的 MPEG4 AAC 编解码器版本。最后,记住在文件名中只使用小写字母和数字;这是 Android 中需要遵守的文件名约定。
现在让我们启动 Eclipse,或者如果你已经启动它来查看所有那些有趣的错误,那么使用右键单击项目文件夹并刷新工作流程,这样我们可以看到我们现在有了一个干净、无错误的 IDE 软件开发工作环境,如图图 13-15 所示。
图 13-15。我们的 blast 数字音频素材显示在 Eclipse ADT IDE 内的/res/raw 文件夹中
您现在可以看到,我们所有优化的(在 Android 之外压缩的)音频和视频资源都在一起,位于/res/raw 文件夹中,左侧显示的是我们首先要访问的 blast.flac 文件,该文件突出显示。
播放数字音频:Android MediaPlayer 类
使用 Androidmedia playerclass 在你的应用中回放 Android 中的数字音频素材。毫不奇怪,MediaPlayer 类是 Android 的 media 包的一部分,因此它将使用熟悉的 importAndroid . media . media player代码语句导入。
如您所知,MediaPlayer 类是 java.lang.Object 类的一个子类,因为它是自己的专用类,用于播放音频和视频新媒体资源,因此它没有专用的超类,它是从 java 中的 Object 类(将所有内容都定义为对象)派生出来的。
这是因为 MediaPlayer 在被实例化时,是在 Java 代码的一个对象中,你很快就会在第十四章中看到。我们还将在第十五章中看到一个更加强大的 Android 音频排序类,称为 SoundPool 类,因此在接下来的几章中有很多音频素材编码要学习,这应该是一个爆炸!
摘要
在这一章中,我们了解了数字音频的概念、技术、流行的编解码器以及 Android 操作系统对它们的支持。
我们从模拟音频和声波的基础知识开始,我们了解了核心音频概念频率和振幅,以及音调和波形以及噪声在音频采样中的作用。
接下来,我们了解了数字音频样本和采样过程,以及数字音频样本回放质量的核心决定因素,即以位为单位确定的样本分辨率(就像图像和视频一样),使用的主要样本分辨率为: 8 位(低质量,用于语音或声音效果)、 12 位(中等质量)、 16 位(高质量)和 24 位(高清质量)。很少需要 32 位采样分辨率。
我们还了解了采样频率,或者需要多少时间片来定义一个给定的模拟波形,以将其转换为数字数据。
常见的采样频率包括 8 kHz、11 kHz、22 kHz、32 kHz、44.1 kHz、48 kHz、96 kHz、192 kHz 和 384 kHz。我们了解到,对于我们的 Android 开发,我们应该使用合理的质量范围,因此使用 11 kHz(中等质量)和 48 kHz(高清质量)之间的采样率。
接下来,我们学习了如何使用 Audacity 2 优化我们的数字音频炸弹爆炸素材(在我们确保正确安装了最新版本的软件及其插件之后)。我们看了一下 Android 支持的所有主要数字音频格式,也在 Audacity 文件导出对话框另存为类型:下拉菜单中,幸运的是,这几乎是所有的格式。
我们将我们的基线 PCM 未压缩 Wave 音频导出到所有这些主要的 Android 支持的音频格式和编解码器,我们进行了数学计算,以找出哪些给了我们最小的数据足迹,同时仍然再现了我们正在寻找的音频效果。我们发现我们的无损 FLAC 格式使我们的数据占用空间减少了 33%,质量没有任何损失,而有损编解码器使我们的数据开销减少了 61%到 93%。
然后,我们将我们创建的六个优化文件复制到我们的 Android 项目中的 /raw 文件夹,并重命名这些文件,以便只有一个 blast.flac 音频素材对 Eclipse 可见,这样我们的 Eclipse ADT IDE 中就不会有任何重复音频素材错误。
最后,我们看了一下 Android MediaPlayer 类,我们将在接下来的几章中使用它来回放我们的数字音频素材。我们知道 MediaPlayer 在Android . media . media player包中,是 java.lang.Object master 类的子类。在 Android MediaPlayer 的下一章中,我们将学习如何用 Java 代码实现所有这些数字音频素材,这些代码目前存在于你的 Hello_World Android 应用中。那将是非常令人兴奋的!
十四、在 Android 中播放音频:MediaPlayer
类
在本章中,我们将采用在前一章中优化的数字音频素材,并教您如何在 Hello _ World AttackPlanet.java 活动事件处理代码中实现这些素材以供回放。
我们将在攻击一个星球用户界面的 ImageButton 元素中添加音效,这样当用户点击这些动画按钮时,数字音频将会播放,增加了我们在之前的图形设计和动画章节中已经创建的令人印象深刻的视觉效果。
现在你知道了如何优化 Android 中的音频,我们还将使用一些来自世界知名的声音设计师和音乐作曲家弗兰克·塞拉芬的高质量音频,他是我的一个朋友,他很慷慨地给了我一些音频样本供我们在本书中使用。
这些声音效果、背景音乐和周围环境声音将允许我们极大地增强我们的其他活动屏幕,再次将我们的用户界面和用户体验带到一个全新的水平。
我们还将了解数字音频回放中的更多概念,这些概念与我们在数字视频以及帧和矢量动画章节中观察到的概念非常相似,例如无缝音频循环,在这种情况下,您无法判断背景音频循环(seam)在音频回放循环中的位置。
由于我们已经在上一章中讲述了 Android MediaPlayer 类的基本起源,我们现在将继续讲述这个高级类中可用的各种 Java 方法。我们将在本章的第一部分做这件事,然后我们将开始写一些实现这些核心 MediaPlayer 功能的代码。一旦我们做到这一点,我们将能够给我们的一些用户界面元素和我们的活动屏幕一些非常酷的数字音频功能,这要感谢 Serafine 先生。
Android 媒体播放器:方法和状态引擎
在 Android 中,MediaPlayer 类可以被称为状态机,也就是说,一个数字文件回放实体,它在任何给定时间都具有某些状态。我已经大方地在本节中包含了来自developer.android.com
网站的状态机图(参见图 14-1 ),以便您可以在此处参考,以及本节中详细讨论它的文本。您可以在开发者网站上找到它:
http://developer.android.com/reference/android/media/MediaPlayer.html
图 14-1。MediaPlayer 状态机示意图(来自【developer.android.com】的的 MediaPlayer 类页面)1
当我们回顾我们的 Java 编程原则时,我们在本书最早的一章中学习了状态。您的 Android MediaPlayer 将始终处于以下状态之一:空闲、初始化(加载您的音频或视频数据)、准备、准备、开始(播放)、暂停、完成(播放已完成)、以及停止。
还有结束和错误 MediaPlayer 状态。让我们先覆盖这两个特殊的状态,并把它们去掉。一旦 MediaPlayer 对象从最初需要使用它的内存中释放出来,就会发生 End,而 Error 则用于媒体播放过程出错时。
当 Java 方法出现时,MediaPlayer 结束状态出现。释放()被称为。因此,如果我们的 MediaPlayer 对象被命名为 bombPlayer ,在本章的下一节中,我们的 bombButton ImageButton 对象也将被命名为,您可以使用以下代码语句从内存中释放 MediaPlayer:
bombPlayer.release( );
这将从主内存中清除名为 bombPlayer 的 MediaPlayer 对象,以便该内存可用于其他目的。
当 OnErrorListener 接口时,出现 MediaPlayer 错误状态。onError( ) 方法通过下面的 Java 回调构造公共抽象布尔 onError (MediaPlayer mp,int what,int extra) 调用
.
mp 参数包含 MediaPlayer 对象的名称,例如 bombPlayer, what 参数包含类型的错误常数,而 extra 参数包含错误特定代码常数。
哪些常量包括 MEDIA_ERROR_UNKNOWN 和 MEDIA_ERROR_SERVER_DIED,而额外的常量可以包括 MEDIA_ERROR_IO、MEDIA_ERROR_MALFORMED、MEDIA_ERROR_TIMED_OUT 和 MEDIA_ERROR_UNSUPPORTED。
关于 OnErrorListener 公共静态接口的信息位于:
developer.android.com/reference/android/media/MediaPlayer.OnErrorListener.html
现在让我们从 MediaPlayer 状态引擎图的顶部开始,如图 14-1 所示,一次一个状态地沿着状态树逻辑图向下,从空闲状态和它的 reset( ) Java 方法开始。
如果你用。reset( ) 方法通过一个bombplayer . reset();代码行,MediaPlayer 对象重置自身(模拟其初始启动)并进入空闲状态或模式。空闲后的下一个模式是初始化的状态或模式;这是通过使用实现的。setDataSource( ) 方法来设置数据源引用到您想要使用的媒体文件。
对于 /res/raw 文件夹中的数据资源,应该使用。onCreate( ) 方法(我们将在下一节中实现),并使用格式为 R.raw.filename 的第二个参数指定数据文件资源路径。
准备状态是从外部服务器访问数据文件时使用的,与外部服务器 URL 结合使用,由熟悉的 Uri.parse( ) 方法解析。尽管我们在应用中使用了内部媒体数据素材,但我将在这里向您展示这样做的代码,以完成我对这个 MediaPlayer 状态图的介绍。
你会用一个。setOnPreparedListener( ) 调用 new OnPreparedListener(),的 Java 代码块使用 onPrepared( ) 方法,如下所示:
MediaPlayer mp = MediaPlayer.create(this,Uri.parse("http://www.url.com/file.mp3"));
player.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
一旦媒体播放器达到准备好的状态,通过使用。onCreate( ) 方法或类似于上面指定的代码块,你可以接着调用。start( ) 方法将 MediaPlayer 状态设置为 Started 状态(或者 mode,如果您更愿意以模态的方式来看的话)。
请注意,这些状态类似于我们在本书前面讨论的模式,除了我们讨论的模式,在使用 GIMP 2 时需要同时考虑多个数字成像模式。
图中的下一层是暂停和停止状态,这取决于是否。pause( ) 方法是否被调用或者。调用 stop( ) 方法。正如你在状态图中看到的,一旦 MediaPlayer 对象被暂停,你就可以使用。start( ) 方法重新开始你的媒体播放。
当您的媒体文件完成播放时,将达到(或设置)播放完成状态。如果循环标志被设置为假(其默认值),那么 OnCompletionListener 将调用 onCompletion( ) 方法(如果您在其中定义了代码),然后这些任务将被执行。
注意,一旦你的 MediaPlayer 达到初始化或停止状态,那一个。prepareAsync( ) 方法可用于将您的 MediaPlayer 对象置于准备状态。
这种准备状态通常发生在从远程位置获取(传输)媒体数据文件时,通常是某种媒体服务器,如亚马逊 S3 或 Akamai 或您自己的自定义数据服务器。
请注意,如果您打算将应用功能集中在 MediaPlayer 类上,而不仅仅是使用它来播放音频或视频资源,那么您应该查看本节开头提到的 MediaPlayer 开发人员页面。
使用 MediaPlayer 类来创建您自己的高级媒体播放器超出了像本书这样的 Android 入门书籍的范围,因为 Android 中的 Media Player 功能非常复杂,足以编写一本关于它的书籍。
Android 中还有另一个数字音频排序 类,称为 SoundPool 类,如果你的应用使用大量声音,并且这些声音需要实时混合和匹配,就像你在游戏或动画电子书或类似的强大新媒体应用中所做的那样,它实际上可能更适合使用。
这个 Android SoundPool 类将在下一章中介绍,所以请放心,您将在本书的这一部分中接触到 Android 中的关键、主要数字音频类和工作流程。
设置 MediaPlayer 对象并加载数字音频数据
在实现我们的 MediaPlayer 对象时,我们需要做的第一件事是在 AttackPlanet.java 活动子类的顶部声明它,在一行代码中将它声明为一个私有访问对象,并通过下面的代码将其设置为一个空值(直到我们稍后使用它):
private MediaPlayer bombPlayer = null;
您会注意到 Eclipse ADT red 在 MediaPlayer 下面用波浪线给代码加下划线。这是因为我们还没有在代码顶部导入它的库。将鼠标放在红色波浪下划线上,选择Import Android . media . media player包引用链接,让 Eclipse 为我们编写这段代码。
这个导入语句和 MediaPlayer 对象声明可以在图 14-2 中看到。
图 14-2。声明一个名为 bombPlayer 的 MediaPlayer 对象,并将其值设置为空
接下来,我们需要编写一个方法来设置我们的 MediaPlayer 对象,以便在我们的攻击星球用户界面 ImageButton 图标中使用。我们将这样做,以便在单击每个按钮时播放不同的声音效果。
编写我们自定义的 setAudioPlayers( ) Java 方法
让我们调用这个定制的 Java 方法setaudiopayers()并添加我们的第一个 bombPlayer MediaPlayer 对象,它回放我们的 blast 音频素材。
首先,让我们使用下面的 Java 方法调用语句,将调用我们的新方法的代码行添加到 AttackPlanet 活动的顶部,就在 setContentView()方法之后:
setAudioPlayers();
接下来,我们需要编写 setAudioPlayers()方法本身,我们将在编辑屏幕的最底部完成,就在所有其他 ImageView、ImageButton、Animation 和 AnimationDrawawble 对象代码之后。
我们将 setAudioPlayers()方法声明为 private ,因为它仅由该类用来为我们的数字音频样本设置所有 MediaPlayer 对象,并且声明为 void ,因为它在被调用时不返回任何值。
在两个花括号内,我们使用添加了 MediaPlayer 对象创建代码。MediaPlayer 类的 create( ) 方法。此方法需要当前上下文,可以写成如下形式:
AttackPlanet.this
或者,您也可以利用 getApplicationContext( ) 方法,我们将在这里实现它,这样您也可以看到这个方法的使用。所需的第二个参数。create()方法是素材文件引用,我们知道它是 R.raw.blast ,因此我们创建 bombPlayer MediaPlayer 对象并加载数字音频数据的代码行应该编写如下:
bombPlayer = MediaPlayer.create(getApplicationContext( ), R.raw.blast);
当我们添加其他音效数字音频素材时,我们在这个方法中为每个素材添加类似的代码行。这样,当我们的 Activity 启动时调用这个方法,MediaPlayer 对象将被预先创建,并准备好供使用。
然后,在我们单独的 UI 按钮代码中,我们所要做的就是在最终用户点击相应的按钮时,从我们的事件处理代码中调用每个 MediaPlayer 对象(数字音频样本)。新的方法调用和方法体及其第一个 MediaPlayer 对象已经编码,如图图 14-3 所示。
图 14-3。创建一个 setAudioPlayers()方法来设置和创建用于声音效果的 MediaPlayer 对象
接下来,我们将添加启动 MediaPlayer 对象所需的编程逻辑。该 Java 代码存在于每个 ImageButton 对象的每个 onClick()事件处理程序例程中。接下来,让我们为 bombButton ImageButton 对象编写代码,这样您就可以知道它的基本格式是如何编码的,以及使用什么方法。
使用我们的媒体播放器对象:使用。start()方法
在 Eclipse 中点击 +图标(在左边,bombButton 对象的旁边)并展开该 Java 代码块(如果它还没有展开以供查看)。在这个代码块中,您有 onClick()事件处理方法,它包含您的 Toast 对象,很快我们将添加当按钮被单击时触发音频示例的代码。
在你的 Toast.makeText()方法调用的下面添加一行代码(如果你愿意,也可以在它的上面),引用我们在 setAudioPlayers()方法中创建的 bombPlayer MediaPlayer 对象,它显示在图 14-4 的最底部。使用 Java 点标记法将。start()方法调用 bombPlayer MediaPlayer 对象,方法是使用下面一行 Java 代码:
bombPlayer.start();
图 14-4。添加 bombPlayer MediaPlayer 对象 start()方法调用 bombButton onClick()事件处理方法
这个开始,或者更准确的说,播放,你的爆破数字音频样本,在你的中引用。在 setAudioPlayers( ) 方法中调用 create( ) 方法。
现在我们已经创建了 MediaPlayer 对象,它包含了我们的 blast 数字音频示例,我们已经将它连接到音频数据文件,该文件在上一章中放在 Hello_World 项目的/res/raw 文件夹中,最后在我们的 onClick()事件处理方法中触发音频示例以播放我们想要播放的音频音效的炸弹按钮。
我们已经导入了 Android MediaPlayer 库和类,编写了一个自定义的 setaudiopayers()Java 方法,将 MediaPlayer 对象连接到我们的数字音频示例,并使用了六行 Java 代码触发它进行回放。
现在,我们要为其他三个声音效果做的就是将它们添加到我们的 setAudioPlayers()方法中,然后在我们的 onClick()处理程序方法中使用 start()方法,在我们的每个 UI 按钮被单击时开始音频播放。
编码其他特效音频媒体播放器对象
复制private media player bombPlayer = null;位于 AttackPlanet 类顶部的一行代码,将它粘贴到自己下面三次。将您的 MediaPlayer 对象名称从 bombPlayer 分别更改为 transportPlayer 、 virusPlayer 和 laserPlayer ,如图 14-5 顶部所示。
图 14-5。完成我们的 setAudioPlayers()方法并声明我们的其他音效 MediaPlayer 对象
接下来,从您的 setAudioPlayers( ) 方法的第一行复制 bombPlayer 对象和 MediaPlayer.create( ) 方法调用,然后再将它粘贴到自身下面三次。再次将对象名称从 bombPlayer 分别更改为 transportPlayer、virusPlayer 和 laserPlayer。
现在我们准备添加一个 MediaPlayer 对象。start( ) 方法在每个 ImageButton onClick()事件处理代码块中调用,如图图 14-6 所示。
图 14-6。添加 MediaPlayer。start()方法调用我们的其他三个 ImageButton UI 元素
接下来,我们需要看看如何将无缝循环音频添加到我们的主屏幕 MainActivity.java 活动子类中。在这种情况下,我们可以简单地编写一个 setStartUpScreenAudio()方法来完成活动启动时的所有工作,比如声明一个对象、设置循环参数和启动。
循环播放我们主要活动的背景环境音频
在 MainActivity 类的顶部添加一行代码,在 onCreate( ) 方法中,这样您的音频 MediaPlayer 对象就与您的 World 对象值、屏幕文本和屏幕动画一起初始化了。
让我们调用我们的新方法 setStartUpScreenAudio( ) ,注意当您键入 setStartUpScreenAudio()时;Eclipse 用红色波浪线给它加下划线的代码行。将鼠标放在红色下划线的文本上,Eclipse 助手对话框就会弹出,并提供为您编写新方法的功能。选择显示的三个链接中的最后一个,创建方法 setStartUpScreenAudio()和 voila instant 方法(参见图 14-7 )!
图 14-7。为我们的 MainActivity.java 主屏幕创建 setStartUpAudio()方法来播放音频循环
接下来,我们将用三个语句填充 setStartUpScreenAudio()方法的内部。第一个将实例化、命名和。创建( )我们新的 audioPlayer MediaPlayer 对象,第二个将使用新对象来调用。setLooping( ) 方法设置为 true(循环)状态,第三个方法将调用循环音频以使用。start()方法,我们也将在新的 audioPlayer 对象中调用它。这三行新的 Java 代码如下所示:
MediaPlayer audioPlayer=MediaPlayer.create(getApplicationContext(), R.raw.ambient);
audioPlayer.setLooping(true);
audioPlayer.start();
Eclipse IDE 内部的最终代码可以在下面的图 14-8 中看到。请注意,我们将。在 media player 被实例化之后,将 Looping()方法设置为 true 的值,但是在之前的它是通过。开始()。
图 14-8。编码 audioPlayer MediaPlayer 对象和。setLooping()和。setStartUpScreenAudio()中的 start()方法
接下来,我们需要添加一个很酷的外星人的声音到我们的添加一个行星活动中,这样当用户点击火星来创建它时,就可以得到一些音频反馈。我们通过使用一个流行的开源语音合成软件包 eSpeak 来完成这项工作,然后将它与 Audacity 结合使用,在不到 60KB 的数据中创建一个陌生短语。
为我们的 NewPlanet.java 创建语音合成添加一个星球活动
让我们去 SourceForge 下载 eSpeak 语音合成器,或 TTS(文本到语音)技术软件。它位于以下 URL:
[`espeak.sourceforge.net/`](http://espeak.sourceforge.net/)
一旦你下载完操作系统所需的版本,安装软件,然后启动它,这样我们就可以创建我们的外星人画外音了。
在 eSpeak 对话框的顶部中间区域,输入您想要语音合成器朗读或合成的文本。我们希望我们的外星人说“火星创造了”,所以在文本字段区域输入(不带引号),如图 14-9 中用蓝色突出显示的所示。
图 14-9。运行 eSpeak 开源应用生成外星画外音
要测试你的外星声音,使用 Speak 按钮,它位于 eSpeak 对话框的右上角,第二个按钮。如果您想将当前的语音合成声音字体更改为不同的声音,请使用文本输入区正下方的声音下拉菜单。您可以使用此菜单来选择和测试不同的声音字体,方法是更改声音字体,然后使用“朗读”按钮来测试每种声音字体。
同样,如果你想微调你的声音,在 eSpeak 对话框的底部中间有滑块,用于调节语速和音量(振幅)。
现在我们已经微调了所有设置,让我们生成一个数字音频数据文件。在 eSpeak 对话框的最底部,有一个下拉选择器,我们将使用它来选择我们的目标音频采样速率和目标音频采样频率。
我选择了可能的最高质量, 48kHz 16 位立体声,这样我们就可以进入 Audacity,看看在全 16 位分辨率和 48kHz 专业级采样率下使用立体声(双声道)音频可以减少什么样的数据占用空间。
最后,我们需要点击保存到。wav 按钮,以 48 kHz 未压缩的 16 位 PCM 波形样本格式保存我们的 alien voiceover 音频样本。
当保存对话框出现时,导航到我们一直用于音频资源的同一个音频文件夹,并用文件名 mars.wav 保存文件。接下来,我们将使用我们的 Audacity 音频编辑和优化软件,将样本大小从近 300KB 降低到不到 60KB,同时仍然保持我们应用的最高质量。
使用 Audacity 2.0 优化我们的 Alien Voiceover 音频样本
现在让我们启动 Audacity,使用文件打开菜单序列找到并打开我们的 mars.wav 数字音频文件,这是我们之前使用 eSpeak 创建的。在图 14-10 中可以看到,有两个音频样本。
图 14-10。在 Audacity 中打开我们的合成语音样本来优化 MPEG-4 AAC 数据文件
之所以有两个样本,是因为这是一个立体声音频样本,这意味着我们现在有左声道和右声道音频样本。
这也意味着有两倍多的音频数据,因此,如果您可以使用单声道样本,就像我们在音效中使用的一样,您最终将获得更紧凑的数据足迹。
让我们看看在这个外星人画外音上我们能得到什么样的数据足迹优化。正如我们在上一章中所记得的,MPEG-4 AAC 为我们提供了最佳的数据占用减少,即使当我们使用最高(500)质量设置时也是如此,所以让我们在这里使用它,看看会发生什么。
如果您在操作系统文件管理实用程序中查看我们的 mars.wav 未压缩音频示例,您会发现原始数据大小为 293,894 字节的数据,或接近 300 千字节的原始音频数据。
让我们使用 Audacity File Export 菜单序列打开 Export File 对话框,如图图 14-11 所示,选择下拉到: M4A AAC FFmpeg 编解码器选择。然后点击选项按钮,选择最高质量级别的 500 ,让我们看看这款 MPEG-4 编解码器在压缩专业质量 48 kHz 16 位立体声音频样本方面的效率如何。
图 14-11。导出我们的 MPEG-4 AAC mars.m4a 音频数据文件,最高质量设置为 500(59KB)
再次进入文件管理实用程序,查看我们刚刚保存的 mars.m4a 文件。文件大小为 60,638 字节,即 59.2 千字节。这意味着数据占用空间减少了 80%,而质量没有任何损失,这是一个了不起的成绩。
如果你想知道我是怎么算出来的,用 60,638 除以 293,894,你会得到 0.206326,这意味着 60,638 大约是 293,894 的 20%。100%减去 20%会使该音频文件的数据占用空间减少 80%。
我们需要做的下一件事是将这个新的 mars.m4a 音频素材实现到我们的 NewPlanet.java 活动子类的 Java 代码中。
启动 Eclipse,如果它还没有打开的话,在中央编辑窗格中打开 NewPlanet.java 选项卡。在 Activity 类的顶部,添加一行代码,将 MediaPlayer 对象声明为 private ,还将其命名为 marsPlayer ,并将其设置为 null 。该行代码应该如下所示:
private MediaPlayer marsPlayer = null;
接下来,我们需要在 Activity 类的 onCreate( ) 方法中创建 MediaPlayer,以便为 marsPlayer MediaPlayer 对象创建 MediaPlayer 功能。那行代码看起来像这样:
marsPlayer = MediaPlayer.create(this, R.raw.mars);
请注意,在本例中,我使用了 this 来引用当前上下文,只是为了向您展示引用上下文的两种方式,要么使用关键字 this ,要么使用 getApplicationContext( ) 方法。
图 14-12。在 onClick() 中创建 Java 代码来实现我们的合成语音说出“行星火星已创建”
接下来,我们将进入 marsImage ImageView 对象的 onClick()事件处理方法,并添加启动 marsPlayer MediaPlayer 对象的代码行。这一行 Java 代码将如下所示:
marsPlayer.start();
现在我们应该准备好使用作为 Android 应用运行工作流程,并在 Android Nexus S 模拟器中测试我们在本章中已经完成的所有音频工作。
模拟器启动后,您应该可以在主屏幕上听到环境背景音频循环。接下来,单击模拟器上的菜单按钮,进入“攻击一个星球”活动,单击每个 ImageButton 图标按钮,聆听每个按钮触发的酷炫音效。
退出攻击行星屏幕,返回主屏幕,再次使用模拟器中的菜单按钮,转到添加新行星活动,并单击行星火星,听我们的外星人画外音说“行星火星已创建”,正如您所见,我们所有的音频素材都已实现!
为我们的“配置一个星球”活动创建按钮点击音频特效
接下来让我们添加一个用户界面按钮反馈音效;因此,在本章中,你会有实现所有不同类型音频的经验,因为所有不同的原因,你会在你的用户界面设计或用户体验设计中使用音频,用于你的 Hello World Android 应用开发或任何其他应用。
让我们首先声明一个私有 MediaPlayer 对象,将其命名为 clickPlayer ,并暂时将其设置为一个 null 值,使用下面一行 Java 代码:
private MediaPlayer clickPlayer = null;
接下来,我们需要使用。使用我们的上下文和数据资源引用创建( )方法,如下所示:
clickPlayer = MediaPlayer.create(getApplicationContext(), R.raw.click);
正如您在图 14-13 中看到的,我们在 ConfigPlanet 活动的最顶端(第一行)创建了 clickPlayer MediaPlayer 对象,并使用。在我们使用 setContentView( )方法设置活动内容视图之后,立即创建()方法。
图 14-13。将 clickPlayer MediaPlayer 对象添加到我们的 ConfigPlanet.java 活动并调用。创造()
接下来,我们需要使用下面一行 Java 代码,在每个用户界面按钮对象 onClick()事件处理方法代码块中添加启动 MediaPlayer 对象的代码:
clickPlayer.start();
我们需要为所有七个按钮对象添加这一行 Java 代码,如图 14-14 所示。一旦我们完成了这些,我们就可以在 Nexus S 模拟器中测试我们的 Hello_World Android 应用,然后我们将完成在每个主要活动中实现音频。
图 14-14。向按钮 UI 元素 onClick()事件处理方法中添加 clickPlayer.start()方法
这种代码设置比我们在本章中介绍的其他代码设置更加优化,因为我们只需创建一个对象并初始化它,但是我们的七个用户界面元素可以通过事件处理方法中的一小段 Java 代码来利用对象和 MediaPlayer 功能。
使用 Run As Android Application 工作流程启动您的 Android ADT Nexus S 模拟器,然后单击菜单按钮,选择您的 Configure a Planet 菜单选项,并单击每个用户界面按钮以确保它们正常工作。
摘要
在这一章中,我们仔细研究了 Android MediaPlayer 类,它可以用来回放音频样本,为我们的用户界面按钮实现声音效果,以及播放循环环境背景音频或循环背景音乐。
我们仔细查看了 Android 开发人员网站上的 MediaPlayer 状态引擎图,我们从上到下、一个状态一个状态、一种方法一种方法地浏览了一遍,以便更好地了解 MediaPlayer 的确切功能。
我们了解到,在初始化之前,MediaPlayer 处于空闲状态,需要通过使用让准备好。prepareAsync( ) (流)或。create( ) 方法(受控),在从远程服务器流式传输期间,它将处于准备状态,并且一旦准备好,它可以处于开始、停止、或暂停状态。
我们看了一下实现了哪些方法、接口、和回调来控制 MediaPlayer 的各种状态,或者播放/res/raw 文件夹中的强制音频数据文件,或者将音频从某种远程音频媒体服务器流式传输到您的应用中。
然后,我们为我们的 Attack a Planet Activity 子类编写了一个名为 setAudioPlayers( ) 的自定义方法,这样我们就可以为我们的用户界面按钮实现几个短脉冲音频音效。我们这样做是为了当我们的用户点击一个动画按钮时,会有一个动画对象实际声音的音频表示。
然后,我们编写了一个名为 setStartUpScreenAudio( ) 的自定义方法,用于我们的主活动主屏幕,并设置了。将 Looping( ) 方法设置为 true ,这样我们就可以循环播放环境空间背景音频以获得特殊的音频效果。
接下来,我们利用一个名为 eSpeak 的开源 TTS 技术语音合成器软件包,为我们的“添加行星活动”子类中的 NewPlanet.java 行星火星创建任务创建一个外星人语音。
然后,我们学习了如何使用 eSpeak 来合成和微调我们的外星人声音样本,然后我们优化了使用 Audacity 创建的音频数据。最后,我们在 Add a Planet 活动中编写了实现这个很酷的外星人画外音所需的 Java 代码。
最后,我们在 Configure a Planet Activity 用户界面中添加了一个点击按钮声音效果,以便在用户点击数据输入按钮时提供音频反馈。在这个例子中,我们利用一个 MediaPlayer 对象为七个用户界面元素提供数字音频效果。
因此,我们在 Hello World Android 应用中添加了音效、点击、背景环境音频和合成外星人画外音。我认为这很好地涵盖了数字音频的使用范围。
在下一章,我们将看看一个更高级的音频播放类,叫做 SoundPool ,它可以被用作音频序列器。音频排序和实时混合相当于我们在本书前面的数字成像、位图(基于帧)和程序矢量动画以及数字视频章节中了解的合成。
Android SoundPool 可以实时存储、触发和混合大量的音频样本(不尽然,因为音频并不太重)。这个音频类用于更高级的应用,例如游戏。它还可以用于其他高级音频应用,这些应用可能需要对大量样本进行更精细的管理,而不必为每个样本创建一个 Android MediaPlayer 对象,正如我们所看到的,如果有大量数字音频样本,这可能会变得难以处理。
1 本页部分内容转载自 Android 开源项目创建和共享的作品,并根据知识共享 2.5 归属许可中描述的条款使用。
http://developer.android.com/reference/android/media/MediaPlayer.html
十五、音频排序:Android SoundPool
类
在本章中,我们将了解 Android 中一个更专业的音频播放类,名为: SoundPool 。这个类明显不同于 Android MediaPlayer 类,以至于我决定在本书中专门辟出一章来介绍这个有用的音频类。
本章深入探讨了 Android SoundPool,并定义了它与 MediaPlayer 的不同之处,以及在什么类型的情况下,这些类中的每一个都应该用于数字音频素材播放。
简而言之,SoundPool 的特别之处在于它允许图像合成的音频等价物(分层和混合)。这意味着,像图像合成一样,与音频相关的新媒体素材可以被分解成它们的组成部分并单独控制。
使用 SoundPool,这些组件可以附加到 Java 代码上,甚至可以用 Java 代码进行操作。这使得开发人员可以将这些富媒体数字素材组件作为一个完整的作品呈现给用户,而实际上它们是由您的应用无缝合成的(或者用流行的音频术语来说:混音、移调和排序)。
这使得 Android 开发人员能够将交互性注入到他们的新媒体素材中,而在过去,传统媒体素材(如音乐、电影或电视)只是一个漫长的线性表演;可重复但总是相同的,因此最终用户会厌倦他们的用户体验。
在本章中,我们将首先介绍音频合成和序列的基本原理,然后我们将回顾 SoundPool 类及其功能,就像我们在前一章中对 MediaPlayer 类所做的那样。然后,我们将在 Hello_World 应用中实现 SoundPool 类,看看它如何让我们的声音播放得更快,并让我们能够灵活地组合它们并改变它们的声音方式。
MIDI 和音频序列:概念和原理
音频序列的最早形式利用了 MIDI,我们在前一章中了解到它代表乐器数字接口,允许通过计算机记录和回放演奏数据。
实现这一功能的早期计算机是 Amiga、Atari ST-1040 和 Apple Macintosh,它们运行的软件包名为 MIDI sequencers ,来自 Opcode (Vision)、Steinberg (CuBase)、Cakewalk (Sonar)、Mark of the Unicorn (Performer)、PropellerHead (Reason)和 eMagic (Logic)等软件公司。
这些 MIDI 音序器软件包中的大部分仍然存在(有几个也被其他公司收购了),并且直到今天仍然非常受全球数字音乐人的欢迎。
MIDI 音序器允许演奏数据排序,其中一名作曲家可以使用合成器将每个乐器部分播放到计算机中,合成器设置为给定的乐器样本,比如吉他或钢琴样本,然后计算机会在作曲家为其演奏的计算机回放版本伴奏时回放这些演奏数据。
当电脑播放出到目前为止创作的作曲曲目时,作曲家会在中演奏下一个声部或曲目,使用该歌曲、乐谱或叮当声安排中需要的下一种乐器。
最终,随着计算机处理能力的提高以及专用数字音频适配器(如 Creative Labs 的 SoundBlaster 和 X-Fi)以可承受的价格广泛上市,MIDI 音序器增加了数字音频功能和 MIDI 回放功能。
事实证明,音频序列的概念同样适用于由计算机直接处理的数字音频样本,就像它适用于 MIDI 演奏数据序列一样。随着电脑变得更加强大,更多的数字音频可以被采样和回放,尽管不像 MIDI 那么容易,因为 MIDI 只是演奏数据(音符开,音符关)。
计算机的功能越来越强大,内存越来越大,可以容纳更多的样本(SoundPool 的一个问题,我们很快就会看到),增加了更多的处理器(64 位多核处理器现在允许每个处理器有 4/6/8/16 个 CPU 内核)和更快的处理速度,64 位音频适配器和多核 DSP(数字信号处理器)功能现在都可以买到,而且价格合理。
由于这个原因,音频序列器现在允许的选项比 20 世纪 80 年代早期的 MIDI 序列器多一千倍,尽管它们仍然支持 MIDI 演奏数据,并与音频采样数据一起播放。这是因为 MIDI 非常高效,如果作曲家喜欢这样工作,它允许使用合成器键盘回放样本。音频音序器增加了通常只有合成器才有的过多功能;接下来我们将讨论这些特性、术语和概念。
数字音频合成:基本概念和原理
最初的一些 MIDI 键盘实际上只是数字音频采样器,它使用各种采样速率和采样频率来录制和回放数字音频样本。我们在本书的前几章中学习了样本,因此我们在这里要关注的是如何通过这些样本的进一步音频合成,甚至只是原始波形,如正弦波或锯齿波,将这些样本带到下一个层次。
合成器接收波形音频,无论是消费电子设备中电路板上的振荡器产生的波形,还是更复杂的采样波形,例如弹拨乐器弦的样本,然后对该波形进行进一步的波形处理,以创建新的不同音调、声音甚至特殊效果。我们都熟悉当今流行音乐中新的合成乐器声音;所有这些都是用数学和代码完成的!
可以应用于数字音频领域内音频波形的核心数学操作之一是称为音高移动的,它可以将声音或音调向上或向下移动一个八度音程(或一个八度音程的一小部分,称为音高或键),为我们提供该样本的一个范围,就像我们在合成器键盘的按键上上下弹奏一样。
正如我们之前所了解的,波形的音调可以由该波形本身的频率来确定,因此,通过将波长减半,或者通过将波长加倍,将音调(波)精确地上移一个八度,这就变成了一个相当简单的数学计算。其中的任何部分都会改变音高,这就是我们如何使用单个采样波形在键盘上获得不同的音符。数字音频合成是相当惊人的东西!
SoundPool 可以做到这一点(这就是为什么我们首先在这里学习这些概念),所以它确实有一些音频合成功能,可能会在未来的 Android 操作系统版本中添加更多功能。你需要知道这些概念,以有效和优化地利用它的功能,这就是为什么我们在这里详细讨论所有这些,这样如果你需要使用 SoundPool,你就知道如何正确地使用它,以及为什么你首先需要这样做。
另一个核心的音频合成数学操作是将两个波形的组合 ( 合成)在一起,即从单个振荡器硬件(扬声器)场景中同时播放两个声音。与数字成像或视频合成一样,这里我们将两个不同的样本数据值相加,以获得最终的听觉效果。
今天的音频硬件确实有相当令人印象深刻的多声道支持,并且可能有能力播放立体声(两个声道)或 四声道(四个声道)单个声音(效果、音乐、音轨等)。)直接从消费电子设备内部的音频硬件中分离出来。
如果我们想要像音序器一样实时组合 8 或 16 个轨道的数字音频,会怎么样?这就是 SoundPool 可以在您的应用中为您提供数字音频排序功能的地方。
重要的是,如果你打算尝试 8 到 16 个音频样本的实时音频合成,这些样本中的每一个都是非常好的优化。这使得我们在第十三章中学到的关于数字音频数据优化的知识在使用 Android SoundPool 时变得极其重要。所以你看,我的疯狂是有方法的!
例如,如果您并不真的需要 HD (24 位采样分辨率)音频来获得 CD (16 位)音频的质量效果,那么您可以节省大量内存,同时获得相同的最终效果。
同样,如果您可以使用 32 kHz 采样速率而不是 48 kHz 采样速率获得相同的音频质量,那么您可以少使用 50%的样本(内存)来实现这一点。对于画外音或声音效果,内存节省是显而易见的,因为通常您可以通过使用 8 位分辨率和 11 kHz 采样率来有效地对炸弹或激光爆炸进行采样,而您将无法检测到 16 位 48 kHz 声音效果的任何差异,但您将使用 8.7 倍的内存(16 乘以 48 除以 8 乘以 11)。
就像数字成像和数字视频播放一样,优化您的数字音频素材非常重要,这有两个完全不同但相关的原因。对于数字音频样本,尤其是在使用 Android SoundPool 的情况下,一旦样本被编解码器解压缩,并以原始的未压缩状态放入 Android 设备的内存中,就需要系统内存来容纳每个样本。
优化音频之所以重要的第二个原因是等式的处理部分。很明显,处理的音频越少,即使只是将音频发送到音频硬件,使用的 CPU 周期也就越少。因此,如果您可以使用更低的采样分辨率(每个音频片段更少的位)或更低的采样频率(每秒更少的波形片段)获得相同的基本音频质量结果,您将节省系统内存资源和 CPU 处理周期资源。
对于 SoundPool 而言,这变得越来越重要,因为您的应用中需要使用的数字音频样本的数量在增加。这同样适用于系统内存和系统处理周期的使用情况,因为随着样本的增加,这两种资源的利用率会越来越高,而且不要忘记您的应用还在做其他事情,例如用户界面渲染、成像、视频,甚至可能还有 3D。
高度优化的数字音频样本在使用 SoundPool 类时如此重要的另一个原因是,目前在使用 SoundPool 时,数字音频样本数据有一个一兆字节的限制。虽然这个限制可能会在这个音频类的未来 Android API 版本中增加,但它仍然是有效和高效地优化任何数字音频素材的最佳实践。
SoundPool 介绍:类规则和方法
Android SoundPool 类是 java.lang.Object 类的直接子类,而不是 MediaPlayer 类的子类。
与 MediaPlayer 类一样,它也是 android.media 包的一部分,因此该类的完整路径(在导入语句中使用)应该是: android.media.SoundPool 。
因为 SoundPool 是 java.lang.Object 的子类,所以我们可以推断它是自己的临时代码创建的。还需要注意的是,如果需要,可以同时使用 SoundPool 对象(即 SoundPool 类)和 MediaPlayer 对象(即 MediaPlayer 类)。
事实上,这两种音频播放类有不同的应用。MediaPlayer 最适合用于长格式的音频和视频数据,如歌曲、专辑或电影。SoundPool 最适合用于大量的短格式音频片段,尤其是当它们需要快速连续播放和(或)组合时,比如在游戏或游戏化应用中。
样本的 SoundPool 集合可以从两个位置之一加载到内存中。第一个也是最常见的是来自。APK 文件,我称之为受控新媒体素材,在这种情况下,它们将位于您的/res/raw 项目文件夹中。第二个可以加载样本的地方是 SD 卡或类似的静态内存存储位置(人们称之为 Android OS 文件系统)。
SoundPool 内部使用 Android MediaPlayer 服务将音频素材解码到内存中。它使用未压缩的 16 位 PCM 单声道或立体声音频流来实现这一点。出于这个原因,请确保使用 16 位采样分辨率来优化您的音频,因为,如果您使用 8 位,而 Android 将其上采样到 16 位,您最终会浪费空间。所以好好优化你的采样频率,不到万不得已不要用立体声采样。让您的工作流程符合 SoundPool 的工作方式,以在最大数量的 Android 消费电子设备上获得最佳结果,这一点非常重要。
当 SoundPool 对象用 Java 构建时,正如我们将在本章后面所做的,开发人员可以设置一个 maxStreams 整数参数。此参数确定可以同时合成或渲染多少音频流。使用数字图像合成类比,这将等同于数字图像合成中允许的图像层的数量。
将这个最大流数参数设置为尽可能小的值是一个很好的标准做法。这是因为这样做将有助于最大限度地减少用于处理音频的 CPU 周期,从而降低 SoundPool 音频混合影响应用性能的其他方面(如 3D、图像视觉或 UI 性能)的可能性。
SoundPool 引擎跟踪活动流的数量,以确保它不超过 maxStreams 设置。如果超过了音频流的最大数量,SoundPool 将中止先前播放的音频流。它主要基于您可以为每个音频样本设置的样本优先级值。
如果 SoundPool 找到两个或更多具有相同优先级值的音频样本,它将根据样本年龄决定停止播放哪个样本,这意味着播放时间最长的样本将被删除。我喜欢称之为洛根运行原则。
优先级值从低到高进行评估。这意味着较高(较大)的数字代表较高的优先级。当调用 SoundPool 时,评估优先级。play( ) 方法导致活动流的数量超过创建 SoundPool 对象时设置的 maxStreams 参数所确定的值。
在这种情况下,SoundPool 流分配器停止最低优先级的音频流。正如我提到的,如果有多个流具有相同的低优先级,SoundPool 会选择最早的流停止。在新流的优先级低于所有活动流的情况下,新声音将不会播放,play()函数将返回为零的 streamID ,因此请确保您的应用 Java 代码始终准确跟踪您的音频样本优先级设置。
通过设置任何非零循环值,样本在 SoundPool 中循环。例外情况是, -1 的值会导致样本永远循环,在这种情况下,您的应用代码必须调用 SoundPool。stop()方法来停止循环样本。因此,非零整数值会导致样本重复指定的次数,因此值 7 会导致样本总共回放 8 次,因为计算机开始使用数字 0 而不是 1 来计数。
每个样本回放速率可以通过 SoundPool 来改变,正如前面提到的,sound pool 将这个类变成了一个音频合成工具。因此,等于 1.0 的样本回放速率会导致您的样本以原始频率水平播放(如有必要,会重新采样以匹配硬件输出频率)。
样本回放速率为 2.0 会导致样本以其原始频率的两倍播放,如果是乐器音符,听起来会高出一个全八度。类似地,将样本回放速率设置为 0.5 会导致 SoundPool 以其原始频率的一半播放该样本,听起来好像低了整整一个八度。
SoundPool 的样本回放速率范围目前被限制在 0.5 到 2.0,但这可以在未来的 API 版本中升级到 0.25 到 4,这将为开发人员提供四个八度的样本回放范围。
接下来,我们将回顾一些关于 SoundPool 使用的注意事项,或者更确切地说,如何使用 SoundPool而不是,然后我们将深入一些相当健壮的 Java 编码,以便我们可以在 Hello World Android 应用中实现这个 sound pool 音频引擎,在我们的 Attack a Planet Activity 子类中。
Android 数字音频合成和排序注意事项
在 Android 应用中使用 SoundPool 进行数字音频合成和排序是一个平衡的行为,无论是在您目前测试它的设备中,还是在您的应用将在其上运行的所有设备中。如果给定的硬件平台(智能手机、平板电脑、电子阅读器、iTV)无法处理播放给定的音频数据负载,那么它就无法播放。
到目前为止,我们已经了解到,数字音频合成、排序和合成在很大程度上取决于处理器的速度、可用处理器内核的数量以及可用于存储所有未压缩格式的数字音频样本的内存量。
因此,底线是,你需要非常聪明地处理 SoundPool 中的事情。不在于如何编写代码,虽然这当然很重要,但也在于如何设置音频样本,以便它们使用更少的内存,并可以在应用中进一步利用。
Android 开发者在 SoundPool 方面犯的主要错误是试图将其更多地用作音频音序器,而不是音频合成器。
用户关注 SoundPool 加载多个音频文件波形的能力,但没有利用其通过使用 SoundPool 音高移位功能创建无数新波形的能力。
如果您将 SoundPool 用作音频音序器,系统内存可能会过载,这可能会关闭 SoundPool 的功能。因此,Android 开发人员必须以最佳方式利用 SoundPool 功能,并优化他们的样本。
这里有一个很好的例子。SoundPool 允许在两个完整的八度音程之间进行音高变换,从设置 0.5 (向下一个完整的八度音程,或原始样本波形的一半)到设置 2.0 (向上一个完整的八度音程,或原始波形宽度的两倍)。记住波形宽度等于频率或音高。
大多数用户甚至不使用这种音高变换功能,而是使用不同的样本来实现不同的音符,这将填满内存,结果是该应用在旧设备上的工作越来越差。
使用 SoundPool 的正确方法是获取样本,比如从吉他弹拨一根弦、从萨克斯管吹一次号、一次钢琴击键和一次鼓声,仅使用四个 16 位 32 kHz 高质量样本,您就可以制作一个包含四种基本乐器的基本合成器。
使用这个基本的合成器设置,您的用户可以上下弹奏两个完整的八度音阶。这个应用将只使用兆字节的内存来保存这些 16 位 32 kHz 的未压缩样本。如果您使用高质量麦克风进行采样,您会惊讶于如今使用 16 位 32 kHz 采样格式可以获得的高质量结果。尝试一下,看看你是否能听出 16 位 44 kHz CD 质量音频和 16 位 32 kHz 音频之间的任何真正差异。
使用 SoundPool 进行我们的攻击星球活动
在我们的 Hello_World 应用中,利用 Android SoundPool 的逻辑区域是在我们的 Attack a Planet 活动内部,因为这使用了许多音频样本,当我们的用户单击图标图像按钮时,这些样本应该会快速触发,以提供最专业的用户体验。
我们需要做的第一件事是打开 Eclipse,然后在编辑选项卡中打开我们的 AttackPlanet.java 类,这样我们就可以添加新代码了。
让我们删除在前一章中编写的声明、创建和启动 MediaPlayer 对象的代码。所以,删除类顶部声明四个特效 MediaPlayer 对象的语句,然后删除我们编写的setaudiopayers()方法及其方法调用,这样我们又回到了这个 Activity 类中没有音频实现的情况。
现在,我们准备添加所有新的音频处理 Java 代码,使用一个 SoundPool 类加载我们所有的音效样本,而不是使用四个 MediaPlayer 对象。由于我们需要在动画用户界面 ImageButton 元素中实现大量的声音效果,因此对于这个特定的 Activity 类来说,这样做应该更有效。
设置 sound pool:sound pool 对象
为了准备 SoundPool 音频引擎的使用,我们需要在 AttackPlanet Activity 类的顶部做的第一件事是实例化 SoundPool 对象。我们将通过类名声明它,将其命名为 soundPoolFX ,并使用下面的单行 Java 代码应用 private 访问控制:
private SoundPool soundPoolFX;
正如你在图 15-1 中看到的,当我们在我们的类声明代码行下面写这一行代码时,Eclipse 用红色波浪线给 SoundPool 对象加下划线。
图 15-1。声明一个名为 soundPoolFX 的私有 SoundPool 对象,并使用 Eclipse helper 添加导入
将鼠标放在这个突出显示的错误上,弹出 Eclipse ADT 助手对话框。这个对话框为我们提供了几个选项来删除代码中的这个错误标志。
选择第一个选项,导入“sound pool”(Android . media 包)作为您想要选择的解决方案,然后 Eclipse 为我们写入导入语句。
打开位于编辑窗格顶部的 import 语句代码块,在类声明的上方,但在包声明的下方,确保导入 Android . media . media player;代码语句已被删除。我们不需要显式声明(导入)MediaPlayer 类来与 SoundPool 一起使用,即使我们知道 SoundPool 引擎在幕后使用 Android 的 MediaPlayer 服务,也就是说,来播放我们的数字音频样本。
现在我们已经导入了 SoundPool 库供使用,您会注意到 Eclipse 在我们的 SoundPool 对象名 soundPoolFX 下用黄色下划线标出了一个警告。让我们把鼠标放在上面,看看 Eclipse 认为我们的代码现在有什么问题。
正如你在图 15-2 中看到的,Eclipse 弹出一个帮助对话框,告诉我们名为 soundPoolFX 的新 SoundPool 对象没有被使用。当然,我们知道这一点,所以我们现在不会担心这个警告界限,我们将继续声明我们的其他类和音频样本实例整数变量,我们将需要在 Java 代码中为我们的 AttackPlanet.java 活动子类实现这个新的 SoundPool 音频排序引擎。
图 15-2。检查我们的 Eclipse 警告消息并显示一条导入 android.media.SoundPool 语句
接下来,我们需要声明并实现一个名为散列表的 Android 实用程序类,我们用它来保存我们的数据值对,代表我们的音频样本和它们的文件引用 URI 数据。这样做是为了让 Android 操作系统能够快速轻松地找到并预加载这些音频素材。
SoundPool 使用更复杂的数据结构来加载音频内容;这样可以在运行时快速找到并加载您的样本,因为 SoundPool 的游戏名称就是执行速度。
如果您想更详细地研究 HashMap 实用程序类,您可以在下面的 Android 开发人员网站 URL 找到专门介绍它的整个网页:
http://developer.android.com/reference/java/util/HashMap.html
现在让我们看看如何在 SoundPool 代码中实现 HashMap。
加载 SoundPool 数据:Android HashMap 类
为了准备使用 HashMap 实用程序,我们需要在 AttackPlanet 活动的顶部做的第一件事是实例化一个 HashMap 对象。我们将通过 classname 声明它,将其命名为 soundPoolMap ,并使用下面一小段 Java 代码对 HashMap 应用一个私有访问控制:
private HashMap<Integer, Integer> soundPoolMap;
正如你在图 15-3 中看到的,当我们在我们的类声明代码行下面写这一行代码时,Eclipse 用红色波浪线给这个 HashMap 对象加下划线。将鼠标放在突出显示的错误上,弹出 Eclipse ADT 助手对话框。这个对话框为我们提供了几个选项来删除代码中的这个错误标志。
图 15-3。声明一个名为 soundPoolMap 的私有 HashMap 对象,并使用 Eclipse helper 添加导入语句
选择第一个快速修复选项,导入“HashMap”(Java . util 包),作为您想要选择的解决方案,Eclipse 继续写入我们的导入 Java . util . HashMap; Java 代码为我们导入语句。
接下来,我们需要通过使用以下几行 Java 代码声明四个整数变量来保存我们将在 SoundPool 中使用的样本数:
int sample1 = 1;
int sample2 = 2;
int sample3 = 3;
int sample4 = 4;
现在我们已经在活动的顶部声明了 SoundPool 对象、HashMap 对象和样本整数,在 onCreate()方法之前,如图 15-4 所示。现在我们准备使用 new 关键字并创建一个新的 SoundPool 对象,以便在我们的活动中对音频进行排序。
图 15-4。声明并设置四个样本整数,并显示 import java.util.HashMap 语句
接下来我们将学习 Android AudioManager 类,你可能已经猜到了,它用于在你的 Android 应用中访问音量,以及铃声模式控制。AudioManager 也是 java.lang.Object 的子类,是 Android Media 包的一部分,其导入语句路径为Android . Media . audio manager,我们将在本章下一节创建新的 SoundPool 对象时看到。
AudioManager 是 Android 操作系统常量的集合,这些常量与 Android 操作系统中不同音频相关功能的状态相关。该类还包含一个名为audio manager . onaudiofocuschangelistener的接口,这是一个回调的 Java 接口定义,当操作系统的音频焦点在任何时候被更改或更新时,都会调用该回调。
如果您想更详细地研究 AudioManager 类,并亲自查看 AudioManager SCO、Vibrate 和 Bluetooth 常量中的哪些常量已被弃用,以及它们在哪个 API 级别被弃用,Android 开发者网站有一个专门针对它的网页,网址为:
http://developer.android.com/reference/android/media/AudioManager.html
配置音池:使用 Android AudioManager
我们需要在活动的 onCreate()方法中创建 SoundPool 对象的新实例,该实例指定可以同时播放的声音数量,以及音频的类型和质量级别。这是通过 SoundPool 构造函数完成的,它采用以下格式:
public SoundPool (int maxStreams, int streamType, int srcQuality);
因此,让我们在我们的 setContentView()方法调用后添加一行空格,并使用以下单行 Java 代码,构造一个新的 SoundPool 对象,命名为 soundPoolFX :
soundPoolFX = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
注意,一旦您在 Eclipse 编辑窗格中键入这一行代码,Eclipse 红色会在 AudioManager 类引用下面加下划线。因此,让我们通过将鼠标放在错误突出显示上并选择导入“audio manager”(Android . media 包)选项来消除这个错误,这样 Eclipse 就会为您编写所需的导入 android.media.AudioManager 语句,如图图 15-5 所示。
图 15-5。配置 SoundPool 对象并使用 Eclipse 助手导入 AudioManager 包
接下来,我们需要对 HashMap 对象做同样的工作过程,并调用它的构造函数方法,使用 new 关键字。如果您已经忘记了什么是哈希表或哈希表,这里有一个简短的概述。
哈希表,也称为哈希表,是二维数据结构。这些专门的数据结构用于实现关联数组,这是一种可以快速将键映射到值的数据结构。哈希表利用一个哈希函数来计算一个索引到一个数据条目槽数组中,从中可以快速找到正确的值。
配置 HashMap:使用。put()方法
让我们在刚刚编写的 soundPoolFX 构造函数下添加一行空格,接下来我们将编写 soundPoolMap HashMap 构造函数 Java 代码。
创建空哈希表的构造函数代码行采用一个整数键和一个整数数据值对,编码如下:
soundPoolMap = new HashMap<Integer, Integer>();
现在我们已经定义并创建了一个空的哈希表结构,是时候用我们将在 SoundPool 引擎中使用的音频数据加载它了。这是通过 HashMap 类完成的。put( ) 方法,该方法允许我们将数据对放入(插入)空哈希表结构中,我们现在需要用音频素材数据填充,如图图 15-6 所示。
图 15-6。使用。put()方法来填充 soundPoolMap HashMap 对象
我们将在 HashMap 构造函数下使用四行 Java 代码,这些代码将利用点符号来调用。来自 soundPoolMap HashMap 对象的 put()方法。
的。put()方法传递整数变量 sample1 到 sample4,以及。对我们的 soundPoolFX SoundPool 对象的 load()函数调用,它将传递到我们的每个数字音频素材文件的当前上下文、R.raw 参考数据路径和样本优先级值。
这四个 soundPoolMap.put()方法调用应该类似于以下四行 Java 代码:
soundPoolMap.put(sample1, soundPoolFX.load(this, R.raw.blast, 1));
soundPoolMap.put(sample1, soundPoolFX.load(this, R.raw.blast, 1));
soundPoolMap.put(sample1, soundPoolFX.load(this, R.raw.blast, 1));
soundPoolMap.put(sample1, soundPoolFX.load(this, R.raw.blast, 1));
现在,我们已经创建了 soundPoolFX SoundPool 对象和 soundPoolMap HashMap 对象,并为四个示例中的每一个将这两个对象连接在一起,这四个示例现在已加载到 HashMap 中,并准备好供 Android SoundPool 音频引擎快速访问。
接下来,我们将编写一个方法,它将允许我们使用一个方法和两个参数来配置和播放 SoundPool 音频引擎,这两个参数指定要播放的样本和移动音高的音高移动值。
编码 playSample()方法:使用 SoundPool。play( )
接下来,我们将编写一个名为 playSample()的方法,用于控制 SoundPool 引擎的使用。此方法创建一个 manageAudio AudioManager 对象来获取 AUDIO_SERVICE 系统服务,并使用此对象从操作系统获取当前和最大音量设置,然后使用这些数据值来设置我们的音量设置。play()方法调用我们的 soundPoolFX SoundPool 对象,如图图 15-7 所示。
图 15-7。编写一个 playSample()方法来设置并调用我们的 SoundPool 对象。play()方法
playSample()方法中的第一行 Java 代码创建了一个名为 manageAudio 的 AudioManager 对象,并将其设置为通过上下文调用 getSystemService( ) 方法。AUDIO_SERVICE 常量。这是使用下面一行 Java 代码完成的:
AudioManager manageAudio = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
接下来的三行代码创建了浮点变量。我们将前两个浮点变量 curSampleVolume 和 maxSampleVolume 设置为当前音频流音量和最大音频流音量数据值,这是通过我们在第一行代码中创建的 manageAudio AudioManager 对象获得的。这两个浮点变量是使用以下 Java 代码设置的:
float curSampleVolume = manageAudio.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxSampleVolume = manageAudio.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
然后,我们使用这两个浮点变量数据值来计算我们的第三个浮点变量数据值,使用下面的 Java 代码行,它通过将当前音量除以最大音量来计算我们需要传递给 SoundPool 引擎的 setSampleVolume 音量设置:
float setSampleVolume = curSampleVolume / maxSampleVolume;
最后,我们将把称为音池。play( ) 方法,并用这些 float volume 变量以及我们传递给 playSample()方法的数据对其进行配置,最后一行代码包含在该方法中。这一行 Java 代码应该如下所示:
soundPoolFX.play(soundPoolMap.get(sample),setSampleVolume,setSampleVolume,0,0,pitchShift);
因此,现在从我们的 soundPoolFX SoundPool 对象调用. play()方法,并传递从 soundPoolMap HashMap 对象中提取的 sample soundID ,这是基于在方法参数列表中传递的样本变量(样本编号是用于索引我们想要的样本数据的关键字)。
其他参数是左右声道的浮点音量水平,由保存在 setSampleVolume 变量中的最终浮点计算指定。也在。play()参数列表是播放优先级,播放次数循环值,最后是变调系数,从 0.5 到 2.0,浮点格式。
注意,在我们的代码中,我们忽略的这个音高移位因子是用每个数字后的小写字母 f 指定的。在我们目前的代码中,这被写成 1.0f 。这个 f 代表浮点并将十进制数指定为浮点值。
当我们讨论我们的 pitchShift 变量的浮点值时,请确保在您的代码中试验这个值,因为您在 Nexus S 模拟器中执行了测试这个代码的下一步工作。
使用作为 Android 应用运行工作流程启动 Nexus S 模拟器,并使用菜单键进入攻击星球活动,单击 ImageButton 图标并触发您的一些示例。请注意,它们播放起来既快又流畅,几乎就像你的用户界面是一个视频游戏。这就是业内所说的 UI 游戏化。
我们需要做的最后一件事是解决 Eclipse 编辑器中的一个警告亮点,即有一个比 HashMap 类更好的类用于存储和访问数据对。我们将解决这个警告,现在我们已经向您展示了如何使用 HashMap,尽管我们的 IDE 编辑器中有一个警告消息,我们的代码仍然可以工作。我们将在下一节详细研究这个警告,因为它要求我们更改 Java 代码。
Android SparseIntArrays:使用 SparseIntArray 类
打开。单击 IDE 编辑窗格左边的 + 符号,onCreate( ) 方法内容,注意仍然有一条黄色波浪下划线突出显示您的新 HashMap < Integer,Integer>();构建 soundPoolMap HashMap 对象的 Java 代码语句的一部分。将鼠标放在这个警告高亮上,在 Eclipse 中弹出助手对话框,如图图 15-8 所示。
图 15-8。检查 Eclipse 中关于 HashMap 的警告消息,并选择解释问题选项
注意,其中一个选项是:解释问题(使用稀疏数组)。看起来 Eclipse 正在提供给我们一些它所知道的关于在这个特定的实现中使用稀疏数组而不是散列表的东西。
让我们点击这个选项,看看 Eclipse ADT 能为我们提供什么信息。请注意,这一特定信息更多地来自 Eclipse 的 Android ADT 插件部分,而不是来自核心 Eclipse IDE 本身。我们知道这一点,因为对话框中的信息与 Android 类的使用有关,而与 IDE 函数本身的使用无关。
一旦我们点击这个解释问题链接,就会打开另一个对话框,名为更多信息。这个对话框告诉我们,有一个替代 HashMap 的类叫做 SparseIntArray ,对于我们存储和访问 SoundPool 音频引擎的整数键值来说,这个类会更有效。
问题解释本质上是说 Android SparseArray API 比 Android HashMap API 更有效,因为 HashMap 自动将 int 值从 int 装箱为 Integer,而 SparseArray 没有。你可以在图 15-9 中看到这个问题的所有解释文本。
图 15-9。在更多信息对话框中查看稀疏数组问题的解释
根据 Android 开发者网站上找到的更详细的信息,在使用更大的阵列时,这种开关可以节省处理时间。为此,为了向您展示 SparseArrays API,我们将继续升级我们的 Attack a Planet Activity 子类 Java 代码,以利用 SparseIntArray 类,而不是使用 HashMap 类。
如果您想在 Android 开发人员网站上阅读关于这个 SparseIntArray 类的更多详细信息,它有自己的专门页面,可以在以下 URL 找到:
[`developer.android.com/reference/android/util/SparseIntArray.html`](http://developer.android.com/reference/android/util/SparseIntArray.html)
要在我们当前的 Java 代码中进行这种更改,我们需要删除当前构建 HashMap 对象的代码行,并代之以构建 SparseIntArray 对象的新代码行。
让我们也将我们的 SparseIntArray soundPoolMap 命名为,并使用下面一行 Java 代码,使用 new 关键字调用它的构造函数方法:
soundPoolMap = new SparseIntArray(4);
请注意,我们在该表中指定了索引值的数量,使得该表是硬编码的,从而提高了内存和处理效率。
根据开发者网站的说法,这是因为有两种方法来构造稀疏数组。一种是简单构造 SparseIntArray( ) 另一种是构造SparseIntArray(int initial capacity)。
第二种构造方法将创建一个 new SparseIntArray(),它最初不包含任何映射,但是它不需要任何额外的内存分配来存储这个指定数量的映射,因为通过指定这个数量,API 确切地知道要分配多少内存。
在我们的例子中,我们确切地知道 SoundPool 引擎将使用多少数字音频样本,因此我们在 Java 代码中为 soundPoolMap SparseIntArray 对象构造选择了更有效的选项。
一旦我们通过 new 关键字输入构造函数方法调用,我们会看到 Eclipse 在我们的 SparseIntArray 下给了我们一个错误的红色波浪下划线,我们知道这是让 Eclipse 为我们编写更多代码的一个途径。
因此,让我们将鼠标放在这个突出显示的错误上,弹出助手对话框,选择Import SparseIntArray(Android . util package)选项,让代码中的突出显示错误永远消失。
既然我们的 soundPoolMap 被构造为一个 SparseIntArray,正如 Android 所希望的那样,我们可以修改接下来的四行代码,以使用需要用于 SparseIntArray 对象的正确方法调用。
这意味着改变。将()方法调用(与 HashMap 对象一起使用,如图图 15-10 所示)交给一个。append( ) 方法调用,这是用于 SparseIntArray 对象的正确方法调用。
图 15-10。将 HashMap 对象改为 SparseIntArray 对象,并使用 Eclipse helper 添加导入语句
幸运的是,这是对我们现有 Java 代码的一个相当简单的修改,所以我们修改后的四行 Java 代码如下所示:
soundPoolMap.append(sample1, soundPoolFX.load(this, R.raw.blast, 1));
soundPoolMap.append(sample1, soundPoolFX.load(this, R.raw.blast, 1));
soundPoolMap.append(sample1, soundPoolFX.load(this, R.raw.blast, 1));
soundPoolMap.append(sample1, soundPoolFX.load(this, R.raw.blast, 1));
新的 soundPoolMap SparseIntArray 对象现在完全在我们的 SoundPool 引擎逻辑中实现,我们的 IDE 显示零错误或警告,如图 15-11 所示。
图 15-11。将我们的 soundPoolMap.put( ) HashMap 方法调用改为 soundpoolmap . append()SparseIntArray 方法调用
最后,我们准备在 ImageButton onClick()事件处理逻辑结构(代码块)中调用 SoundPool 引擎,这样我们就可以触发我们选择的样本,如果愿意,甚至可以改变它的音调。
调用 SoundPool 对象:使用 playSample()方法
接下来,让我们在每个 ImageButton onClick()事件处理程序方法内部调用我们在本章前面编写的 playSample(int sample,float pitchShift) 方法、。
这包括一行相当简单的代码,分别放在四个 ImageButton onClick()事件处理程序方法中,就在 Toast.makeToast()对象和方法调用之后(或者之前,如果您愿意的话)。
将以下代码行添加到 bombButton、invadeButton、infectButton 和 laserButton 中。setOnClickListener()方法,如下面四行代码所示(每个处理程序一行):
playSample (sample1, 1.0f);
playSample (sample2, 1.0f);
playSample (sample3, 1.0f);
playSample (sample4, 1.0f);
这四行代码在每个 ImageButton onClick()事件处理方法代码块中的位置如图 15-12 所示。
图 15-12。使用样本名称和音高移位参数调用我们的 playSample()方法来播放 SoundPool 样本
现在,在我们的 AttackPlanet.java 活动子类中实现 soundPoolFX SoundPool 对象音频引擎所需的所有 Java 代码构造都已就绪,是时候利用我们的作为 Android 应用运行工作流程,并在 Nexus S 模拟器中测试我们的所有代码了。
一旦 Nexus S 模拟器启动,点击菜单按钮,并选择攻击一个星球菜单选项,并启动我们刚刚在 SoundPool 中实现的 Activity 子类。单击动画 ImageButton 用户界面元素,并触发一些数字音频样本。很酷。
现在请注意,您的示例会立即触发,因为 SoundPool 已经将它们预加载到内存中。另请注意,您现在可以快速连续点击按钮,以更像游戏的方式触发音频。响应性反馈对于这种类型的多媒体用户界面设计很重要,我们在过去几章的活动中已经实现了这一点。
这项活动现在实现了过多的“技巧”新媒体用户界面元素,包括:3D 多态图像按钮、基于帧的动画、矢量或程序动画、数字图像和动画合成以及音频样本引擎,所有这些都无缝集成,效果非常好。
摘要
在这最后一章介绍 Android 中的数字音频时,我们仔细研究了音频排序和音频合成概念,因为它们与强大的 Android SoundPool 数字音频引擎类和 API 相关。
我们从学习 MIDI 和音频序列器的概念开始,这是 Android SoundPool 类的核心。我们还了解了排序是如何产生的,如今在哪里使用,以及与之相关的概念,包括曲目、声部和演奏数据。
接下来,我们进一步了解了数字音频合成,包括波形、音调、音高、八度音程、振荡器、音高移位的基本概念,以及将 SoundPool 类 API 转变为音频合成引擎所需了解的许多基本概念。
接下来,我们专门研究了 SoundPool 类和 API,考察了它的工作原理、方法和内存需求。我们仔细查看了 SoundPool maxStreams 参数,以及当超过 maxStreams 样本流数量时,它如何处理优先级。
下一个合乎逻辑的步骤是让我们了解一些关于使用数字音频排序和合成引擎的注意事项,该引擎会占用大量内存和处理资源,因此,如果我们要在应用中实现这一点,我们必须在代码和新媒体素材中考虑并优化某些因素。
最后,我们准备在 Hello_World Android 应用的 Activity 子类中实现 SoundPool 音频引擎。我们设置了 SoundPool 对象,加载了我们的音频样本数据,并了解了 Android HashMap 类 API,它允许我们创建哈希表。
然后,我们学习了 Android AudioManager 类和 API,它允许我们在 Android 应用和设备中管理音频焦点,然后我们使用这些知识来构建 SoundPool 对象。
接下来,我们编写了一个定制的 playSample( ) 方法,这样我们就可以将样本数据参数传递给我们的 SoundPool,比如我们想要播放的样本以及我们想要对这些样本进行多大程度的音高移动。
为了消除 Eclipse 中令人讨厌的警告标志,我们用一个 SparseIntArray 对象替换了 HashMap,并了解了两个 Android 实用程序之间的差异。然后,我们在 ImageButton onClick()事件处理程序中实现了对 playSample()方法的调用,并且实现了 SoundPool。
在下一章中,我们将开始了解 Android 服务,并使用后台处理来卸载计算密集型任务,以便它们不会以任何方式影响我们的用户界面设计或应用用户体验的响应。
十六、Android 意图:应用间编程
在这一章中,我们将深入探究 Android 的意图 。意图被开发者用来处理组成 Android 应用开发的四个主要功能区域内的模块间通信或指令:活动、服务、广播接收器和内容提供者。
我们已经了解了所有的活动,因为这些活动包含了你的 Android 应用的前端,包括你的设计、内容、新媒体、用户界面等等。在接下来的三章中,我们将讨论 Android 的其他三个主要功能领域:服务(处理)、广播接收器(消息传递)和内容提供者(数据存储)。
为了能够涵盖 Android 应用开发中的这三个更高级的领域,我们首先需要涵盖意图和意图过滤器这个庞大的主题。这是因为 Intents 在实现这些更复杂的“幕后”Android 应用组件时被更多地使用。
意图也可以用于活动,因为我们非常熟悉活动,所以我们将学习如何在活动中使用意图,在接下来的三章中,我们还将学习如何使用服务、广播接收器以及内容供应器来利用意图。
在这一章中,我们将仔细研究 Android Intent 和 Intent Filter 类,以及 Android Intent 的各种特性,以及如何在 Android 应用中声明这些特性、功能、设置、常量和类似的特性。
我们将进一步了解意图过滤器,它允许您在应用被其他应用使用时,自动处理如何在应用中利用您的意图。正如您可能已经猜到的,声明意图过滤器是通过您的 AndroidManifest.xml 文件完成的,使用 <意图过滤器> 标签。
这是 Android 中比较复杂的主题之一,因为它涉及到模块间通信、消息传递、AndroidManifest、过滤器以及 Java 和 XML 格式的类似高级编程主题。
Android 意向信息:首先,全球概述
意图消息是一个 Android OS 工具,在相同或不同应用内的应用组件之间提供后期运行时绑定。
使用 android.content.Intent 类实例化 Android Intent 对象,该类是 java.lang.Object 的子类。这意味着 Intent 仅仅是为了它自己独特的目的而开发的,而不是从任何其他类型的 Android 类派生出来的子类。它被打包在 Android 内容包中,你可以从之前概述的包名称中清楚地看到。
之所以将它与 android.content 包打包在一起,是因为 Intents 可以用来快速访问和操作内容提供者(数据库),这一点我们将在下一章中了解。Intent 对象的用途不仅仅是数据库访问,它还可以用于 Android 服务、活动和广播接收器。
Android Developer Reference 将意图定义为要执行的操作的抽象描述。这意味着 Android 设计意图是创建一个 Java 对象类型,可以用来轻松完成通常需要复杂编程代码的任务。
因此,意图本质上是一种编程捷径,它已经内置于 Android 操作系统和编程环境中,以使事情从长远来看更容易。我说“从长远来看”,因为首先我们需要学习如何使用意图和意图过滤器,然后一旦我们理解了它们,它们就会变得强大,使我们成为更高级的 Android 程序员。
众所周知,Intent 对象结构是一个被动的数据结构对象,因为它只是一个被动的数据和指令的集合,它们被捆绑在一个综合的 Java 对象中,可以很容易地在应用的不同功能模块之间传递。
意图对象数据结构应该包含一个描述,该描述包含需要执行的标准操作系统或开发者创建的动作,并且另外传递那些动作需要处理的数据。这通过其组件名传递给 Java 代码模块,指定哪个类是(接收)意图的目标。
我们将在本章的后面学习所有这些意向对象数据结构格式。除了这些特定的动作和数据,Android Intent 对象还可以指定数据类型 (MIME)规范,以及类别常量、标志,甚至额外的数据对,这些数据对与 Intent 动作需要处理的主数据包相关。在本章的剩余部分,我们将详细了解意图对象的各种功能领域。
Android 意图实现:三种不同类型的意图用法
Intent 对象有三种不同的用途,每一种都可以在 Android 操作系统中调用活动、服务和广播接收器之间的模块间通信。
然而,一种类型的意图,即意图对象,可以用于操作系统的三个特定区域中的每一个。意图对象使用的不同分类类型都不允许互换使用,以避免处理错误。
当然,您可以将您的意图对象命名为您喜欢的任何名称,但是您用来在您的应用模块中传递您的意图对象的意图处理方法调用的类型将最终决定该意图对象将涉及的使用类型,而不管您在构造意图对象时如何命名它。意图的使用类型决定了它是什么类型的意图(或者更准确地说,意图用于什么类型的目标)。
使用不同的意图对象方法调用来保证意图对象的这些不同使用不会彼此混淆,也就是说,它们不会与意图对象的任何其他实现相交、干扰、冲突或错误地被其使用。
例如, startActivity( ) 使用意图对象来启动活动,而 startService( ) 使用意图对象来启动服务。要将意图对象发送到广播接收机,可以使用 broadcastIntent( ) 方法,因此用于分发意图对象的方法决定了它的实现。
出于这个原因,我们将分别讨论意图对象的每个使用场景,以便我们可以看到基于意图的通信与活动、服务和广播接收方消息之间的区别。
在这本书的最后一部分,我们还将练习在 Android 操作系统的四个主要领域中使用意图对象。因为你已经是 Activity 的专家了,所以在这一章中,我们将看看使用 Intent 对象和 Activity 子类的代码例子。在接下来关于 Android 服务、广播接收器和内容提供者的三章中,我们还将看到 Intent 对象是如何被用来启动和控制 Android 中的其他功能区域的。
活动
使用 Context.startActivity( ) 或Activity . startactivityforresult()将与活动一起使用的意图对象传递给每个活动,以启动活动,或要求现有活动执行一些特定于应用的编程任务。
可以使用 Activity.setResult( ) 方法将意图传递回去,以便首先使用 Activity . startactivityforresult()方法将信息返回给发起意图通信的调用活动。
安卓服务
使用 Context.startService( ) 方法调用将与 Android 服务一起使用的意图对象传递给服务子类,以启动 Android 服务,或者向由启动的服务传递新指令。我们将在本书的下一章学习所有关于服务的知识。
还可以使用 Context.bindService( ) 方法传递一个 Intent 对象,以在调用应用组件和绑定的服务之间建立连接(绑定)。如果服务还没有启动并运行,意向者也可以发起绑定服务。
我们将在本书的下一章讨论绑定的服务,以及启动的服务和混合服务。我们将讨论意图在这个上下文中是如何使用的,因此,我在这里讨论它们是为了上下文和概述不同类型的意图。
广播接收机
传递给任何 Android 广播接收器方法的 Intent 对象被传递给所有感兴趣的 Android 广播接收器。大量的 Android 操作系统广播将源自 Android 系统代码,这在复杂的完整版本的 Linux 操作系统中是可以预料的。
Intent 方法调用的广播接收器特定版本包括 Context.sendBroadcast( ) 方法、context . sendorderedbroadcast()方法和context . sendstickybroadcast()方法,我们将在第十八章中介绍。
因为每种类型的意图都有一个独特的调用方法,Android 系统可以很容易地定位需要响应每个特定意图对象的适当的应用活动、服务或广播接收器。
由于 Android 意向系统的设置方式,这些意向信息系统之间没有重叠。广播接收机的意图只发送给广播接收机,不会发送给 Android 活动或服务。使用传递的意图对象。startActivity( ) 方法只传递给一个 Activity 子类,而从不传递给 Service 子类或 Broadcast Receiver 子类,等等等等。
安卓意图结构:安卓意图剖析
意图对象包含一个包的信息。从第一次让 Eclipse ADT 为您生成 Android 应用开始,您就已经熟悉了 Android Bundle 对象,因为 Bundle 对象在每个 Android 应用中都有使用,用于保存 Activity 子类的实例状态信息。也许你记得看到下面这行代码:
public void onCreate(Bundle savedInstanceState) {onCreate Method Logic is in here}
意图包含接收该意图的应用组件感兴趣的信息。这包括诸如接收应用组件要采取的动作的信息,以及需要采取行动的数据。意图还可以包括 Android 操作系统本身感兴趣的信息,例如接收组件的哪个类别应该处理意图,或者甚至是关于如何启动目标活动的指令。
一个 Android Intent 可以包含以下七个功能部分:
- 组件名:目标组件的全限定类名
- 动作:命名动作或预定义动作常数的字符串
- 数据:要操作的数据对象(文件)的 URI
- 类别:字符串命名类别,或安卓类别常量
- 数据(MIME)类型:要操作的数据的 MIME 类型
- 附加:用于意图的附加信息的键值对
- 标志:在意图类中定义的各种标志
让我们深入这七个在意图对象中非常重要的领域,看看为什么每个领域都很重要,以及意图对象的这些领域如何在不同类型的 Android 意图对象中实现。
意图对象组件:指定组件名称参数
您可以为您的意图对象指定的最重要的事情是您显式指定给该意图对象的句柄的应用组件的组件名称。
当您在意图对象中指定该组件名称时,您也创建了一个显式意图对象。我们将在本章接下来的几节中更详细地讨论这个明确的意图。
Intent 对象中的这个字段是一个 ComponentName 对象,它包含目标组件的全限定类名的组合。在我们的 Hello World 应用中,这可能是“chapter.two.Hello_World”。“攻击行星”为攻击行星活动的子类。
注意,包名是在 AndroidManifest XML 文件中为组件所在的每个应用设置的。如果您调用的组件位于不同的应用中,组件名的包部分和 AndroidManifest.xml 中设置的包名不一定要匹配。Intents 足够灵活,可以跨不同的应用进行通信,也可以在单个应用内部进行通信。
这里需要注意的是,组件名是可选的。如果设置了它,您的意图对象将被交付给指定类的实例。如果没有指定组件名称,Android 将查看您的意图对象中的所有其他信息来定位合适的目标。
在这种情况下,意图变成了隐含意图,因为 Android 必须暗示如何应用意图,通过推断将意图应用到哪个组件。这是通过查看意图中的所有其他信息并推断在哪里处理意图来实现的。夏洛克·福尔摩斯肯定会为自己是安卓开发者而自豪。
在你的意图对象中,如果你想指定一个组件名,它将通过使用来设置。setComponent( ) 方法,或者,通过使用。setClass( ) 方法,或者使用。setClassName( ) 方法。
另一方面,组件名可以通过使用 Intent 类的从 Intent 对象中读取。getComponent( ) 方法。这用于从 Intent 对象中提取组件名称信息,以便它可以与给定的应用组件匹配,如果匹配就进行处理。
意图对象动作:指定动作参数
使用命名要执行的动作的字符串或用于指定 Android 操作系统内部已经定义的那些动作的 ACTION_ constant 来指定意图动作参数。为所有 Android 活动子类定义的动作常量包括以下动作:
ACTION_MAIN
ACTION_VIEW
ACTION_ATTACH_DATA
ACTION_EDIT
ACTION_PICK
ACTION_CHOOSER
ACTION_GET_CONTENT
ACTION_DIAL
ACTION_CALL
ACTION_SEND
ACTION_SENDTO
ACTION_ANSWER
ACTION_INSERT
ACTION_DELETE
ACTION_RUN
ACTION_SYNC
ACTION_PICK_ACTIVITY
ACTION_SEARCH
ACTION_WEB_SEARCH
ACTION_FACTORY_TEST
在与广播接收器一起使用的意图的情况下,动作实际上指定了在过去发生的动作(已经发生了),并且因此被报告而不是被请求。Intent 类定义了许多广播接收器动作常量,包括:
ACTION_TIME_TICK
ACTION_TIME_CHANGED
ACTION_TIMEZONE_CHANGED
ACTION_BOOT_COMPLETED
ACTION_PACKAGE_ADDED
ACTION_PACKAGE_CHANGED
ACTION_PACKAGE_REMOVED
ACTION_PACKAGE_RESTARTED
ACTION_PACKAGE_DATA_CLEARED
ACTION_UID_REMOVED
ACTION_BATTERY_CHANGED
ACTION_POWER_CONNECTED
ACTION_POWER_DISCONNECTED
ACTION_SHUTDOWN
值得注意的是,您还可以定义自己的自定义操作字符串,以便在应用中激活自己的自定义组件。这允许你在你的应用中开发你自己的定制意图系统。
您自己设计和命名的操作应该包括一个应用包作为前缀,然后是您自己创建的操作常量。以我们自己的 Hello World 应用为例,您可以对操作使用以下完整路径名:
chapter.two.hello_world.ACTION_SHOW_PLANET_STATUS
意图动作参数在确定如何构建意图的其余信息参数时极其重要。
因此,最好使用尽可能具体的动作常数。您还应该尽可能将动作常数与意图对象的其他信息字段紧密关联,如数据、类别、数据类型和标志。
您想要做的是,为您的定制应用组件将要处理的意图对象定义一个完整的协议和一组常量,而不是孤立地定义一个动作。
您的意图对象中的动作常量或字符串值应通过使用来设置。setAction( ) 方法。相反,应该使用从意图对象中读取动作常量或字符串值。getAction( ) 方法。
意图对象数据:发送数据以供操作
意图对象数据参数包含一个 URI 对象,用于使用指定的动作处理数据。正如您可能已经猜到的那样,各种类型的动作与逻辑上对应的数据规范类型一起使用,这些数据规范类型与传递的动作参数非常匹配。
例如,如果传递给意图对象的动作参数的动作是一个 ACTION_DIAL 常量,那么数据参数将包含显示在智能手机拨号区域的电话号码。
如果 Intent action 常量参数是一个 ACTION_CALL ,那么 data 参数将是一个 URI 对象,该对象包含带有电话号码的 tel: 前缀引用,您的应用希望将该电话号码作为一个电话呼叫。
如果意图动作常量是 ACTION_VIEW 并且使用的数据参数是 http: URI,那么接收活动将被调用来下载和显示 URI 引用要查看的任何数据。
意图对象类别:使用类别常量参数
意图对象的类别参数包含一个字符串对象,该对象指定关于处理意图的组件种类的附加信息。任何数量的类别描述都可以放在 Intent 对象中,以帮助接收组件。
Android Intent 类还定义了许多类别常量,就像它为活动和广播接收器定义 ACTION_ constants 一样。这些类别常量都以单词 CATEGORY_ 开头,包括:
CATEGORY_DEFAULT
CATEGORY_BROWSABLE
CATEGORY_TAB
CATEGORY_ALTERNATIVE
CATEGORY_SELECTED_ALTERNATIVE
CATEGORY_LAUNCHER
CATEGORY_INFO
CATEGORY_HOME
CATEGORY_PREFERENCE
CATEGORY_TEST
CATEGORY_CAR_DOCK
CATEGORY_DESK_DOCK
CATEGORY_LE_DESK_DOCK
CATEGORY_HE_DESK_DOCK
CATEGORY_CAR_MODE
CATEGORY_APP_MARKET
Intent 类有几个方法允许您使用类别参数,包括。addCategory( ) 方法,它在一个意图对象中添加一个类别,。removeCategory()、和,前者在添加类别后将其删除。getCategories(),,它检索当前包含在 Intent 对象中的所有类别的集合。
意图对象数据类型:设置 MIME 数据类型参数
当将您的意图与能够处理给定的数据类型的组件相匹配时,了解您正在处理的数据值的分类或类型(数据的 MIME 类型)通常是非常重要的(当然,除了数据的 URI 位置之外)。
MIME 代表多用途互联网邮件扩展 ,它最初是为电子邮件服务器开发的,用于定义它们对不同类型数据的支持。
MIME 现在已经扩展到支持的数据和内容类型的其他平台定义,以及通信协议(如 HTTP)数据类型定义,还扩展到 Android OS,在这里也定义内容数据类型。可以说 MIME 已经成为无数计算环境中定义内容数据类型的主要标准。MIME 数据类型定义的示例包括以下常用的内容类型:
• Content-Type: text/html (HTML Data)
• Content-Type: video/mp4 (MPEG Data)
• Content-Type: image/png (PNG8 Data)
• Content-Type: audio/mp3 (MPEG Data)
• Content-Type: application/pdf (.PDF Data)
• Content-Type: multipart/x-zip (.ZIP Data)
为了举例说明为什么要在 Intent 对象中声明 MIME 数据类型,您可能希望确保显示视频数据的应用组件不会被错误地调用来播放音频文件,就像您不希望播放音频的应用组件(如我们的 playSample()方法)被错误地调用并传递视频或图像数据文件来播放一样。
在大多数情况下,您的数据类型可以从传递的 URI 中推断出来。对于 Android content:// URIs 来说尤其如此,它表明数据位于你的设备上的什么位置,并且由内容供应器控制。我们将在本书最后一节的后续章节中介绍 Android 内容供应器。
数据类型也可以是在你的意图对象中显式设置的数据类型。利用意向对象。setData( ) 方法只指定数据,作为 URI,而使用。setType( ) 方法调用仅使用 MIME 类型指定数据。
第三种方法叫做。setDataAndType( ) 结合了这两种方法,并将数据指定为 URI 和 MIME 类型。可以使用意图对象读取 URI。getData( ) 方法和数据类型可以通过使用意图对象读取。getType( ) 方法。
意图对象附加:在意图对象中使用附加
意向对象还可以包括键值对形式的额外内容(数据)。这些用于传递附加信息,这些信息应该被包括在内,以便于对意图对象进行正确的组件处理。
Android Intent 类还定义了许多额外的常量,就像它为活动和广播接收器定义 ACTION_ constants,以及 CATEGORY_ constants 一样,正如我们在上一节中看到的。这些额外常量始终以单词 extra 开头,包括以下内容:
EXTRA_ALARM_COUNT
EXTRA_BCC
EXTRA_CC
EXTRA_CHANGED_COMPONENT_NAME
EXTRA_DATA_REMOVED
EXTRA_DOCK_STATE
EXTRA_DOCK_STATE_HE_DESK
EXTRA_DOCK_STATE_LE_DESK
EXTRA_DOCK_STATE_CAR
EXTRA_DOCK_STATE_DESK
EXTRA_DOCK_STATE_UNDOCKED
EXTRA_DONT_KILL_APP
EXTRA_EMAIL
EXTRA_INITIAL_INTENTS
EXTRA_INTENT
EXTRA_KEY_EVENT
EXTRA_ORIGINATING_URI
EXTRA_PHONE_NUMBER
EXTRA_REFERRER
EXTRA_REMOTE_INTENT_TOKEN
EXTRA_REPLACING
EXTRA_SHORTCUT_ICON
EXTRA_SHORTCUT_ICON_RESOURCE
EXTRA_SHORTCUT_INTENT
EXTRA_STREAM
EXTRA_SHORTCUT_NAME
EXTRA_SUBJECT
EXTRA_TEMPLATE
EXTRA_TEXT
EXTRA_TITLE
EXTRA_UID
我们已经看到,一些动作与某些类型的数据 URIs 成对出现;类似地,一些动作通常与特定类型的附加动作成对出现。
例如,ACTION_TIMEZONE_CHANGED Intent 对象操作参数具有标识新时区的额外“时区”, ACTION_HEADSET_PLUG 具有指示耳机现在是插入还是拔出的额外“状态”,以及耳机类型的额外“名称”。
如果你要发明一个 ACTION_SHOW_PLANET_STATUS 动作,那么行星状态值将使用 EXTRA_STATUS_PLANET 键值对来设置。
意向对象有一系列的。putExtra( ) 插入各种类型的额外数据参数的方法和类似的一组。getExtra( ) 读取额外数据参数的方法。有趣的是,这些 Java 方法也与捆绑对象中使用的方法类似。
值得注意的是,这些额外的键值对可以通过使用作为 Android Bundle 对象来安装和读取。方法和。getExtras( ) 方法。如果你使用了大量的额外功能,这可能是最有效的设置方式,毕竟这是 Bundle 对象最初的用途。
意图对象标志:对意图对象使用标志
可以包含在 Android Intent 对象中的最后一种参数称为标志参数。标志是布尔值,作为一名程序员,你可能非常熟悉。标志对于以高度数据紧凑的方式设置状态和开关非常有用。
就意图对象而言,大多数标志参数会指示 Android 系统如何以某种方式启动或处理该活动,或者可能在启动后如何处理该意图。然而,Intent object flag 参数是足够开放和灵活的,您可以以任何您认为适合您的应用的创造性方式来使用它们,所以发挥您的创造性吧!
显性与隐性意图:使用哪种意图类型
安卓意图对象有两种分类:显性意图和隐性意图。显式意图是两者中较容易在你的应用中使用的,而隐式意图要复杂得多,通过意图过滤器,你可以允许其他开发者通过他们的意图对象隐式地使用你的应用组件。
明确的意图
正如我前面提到的,显式 Intents 对象使用组件名参数指定目标应用组件。这之所以称为显式,是因为您的组件(类)命名模式一般不会提供给其他应用的开发人员,所以如果允许他们使用 Intent 对象访问您的代码,他们必须通过组件名参数获得要调用的类名。
因此,显式意图通常主要用于应用内部消息传递(或内部应用消息传递)。一个很好的例子是启动一个从属服务类的活动或者启动一个相关的活动,我们将在本章的后面看到。
Android 总是向组件名中指定的目标类的实例传递一个明确的意图。当您使用显式意图时,除了组件名称之外,意图对象中的任何内容对于确定哪个应用组件获得意图都无关紧要。
另一方面,隐式意图不命名目标(意图对象中的组件名称参数为空)。
隐含的意图
隐式意图通常用于激活其他应用中的组件或 Android 操作系统中更一般化的特性或功能,在这些应用中很容易推断出需要什么,例如通过应用用户界面而不是通过操作系统电话拨号实用程序为用户拨打或呼叫电话号码。
当接收到隐式意图对象时,完全需要遵循不同的方法。在没有明确指定目标的情况下,Android 操作系统必须确定处理该意图对象的最佳应用组件。
这被称为意图解析,因为 Android 操作系统正在为你解析你的意图对象。意向对象解析可能导致启动一个活动类来显示一个新的用户界面,或者启动一个服务类来执行所请求的动作,或者甚至激活一个 Android 广播接收器来响应您的广播通知。
意图解析可以通过几种不同的方式来执行。如果 Intent 的内容清楚地表明了需要做什么,比如说在 Intent 对象中有一个 action_MAIN 的 ACTION 参数和一个 category_HOME 的 CATEGORY 参数常量,那么主屏幕将在 Android 设备的屏幕上启动。另一种解决包含更多定制动作的意图的方法是使用一个意图过滤器,开发者可以定义它来帮助其他应用将意图发送到它们适当的组件进行处理。
隐式意图解析:引入意图过滤器
在缺少填充有 Android 指定意图常数的意图对象的情况下(我们在本章前面已经详细讨论过了),通常通过将任何提交的意图对象的全部内容与该应用的 AndroidManifest.xml 文件中的现有意图过滤器定义进行比较来执行隐式意图解析。
意图过滤器是使用 XML 标记标签/参数逻辑在 AndroidManifest 内部创建的复杂逻辑结构。它们与您的应用中可能接收意图的组件相关联。
意图过滤器在涉及意图对象时执行一些重要的功能。首先,它们概述了您的应用组件所体现的功能,其次,它们用于指定它可以处理的意图的特征和限制。
意图过滤器 XML 定义使您的应用组件更有可能成功接收指定类型的隐式意图。在下一节中,我们将讨论可用于为您的应用定义意图过滤器解析结构的标签。
请注意,如果您的应用组件没有在您的 AndroidManifest.xml 文件中定义任何意图过滤器,它将只能接收显式意图。您的应用当然可以继续使用我们之前提到的 Android 操作系统意图常量发送隐含意图,因为这些是使用操作系统的意图过滤器结构定义的。
定义了意图过滤器的组件可以接收显式和隐式意图。
针对意图过滤器测试意图对象时,会分析该意图对象的三个主要参数。它们是动作(或动作常量)、数据(URI 和 MIME)和类别。
如果你想一想,这三个主流意图参数中有相当多的信息,正如我们之前所了解的,所以有了这些和意图过滤器结构(定义)来告诉我们如何应用这些信息参数,操作系统应该能够成功地解析任何和所有意图对象。
有趣的是,Intent 对象中的 extras 和 flags 参数在确定哪个应用组件接收给定意图的意图解析过程中完全不起作用。
接下来,让我们仔细看看 Android
http://developer.android.com/guide/topics/manifest/intent-filter-element.html
创建意图过滤器:使用
虽然自定义意图过滤器结构的编码超出了这本介绍 Android 应用开发的入门书籍的范围,但我将在本节中概述这些<意图过滤器>标签和结构,以便您理解这些结构是如何实现的,在您的 AndroidManifest.xml 应用引导文件中使用 XML 标记。
利用一个
一个
位于其父标签内部的
标签结构定义了它所包含的组件标签,因此它知道它可以接收指定类型的意图,并允许操作系统过滤掉那些对特定组件 XML 定义没有意义的意图。
正如你在本书前几章的
因此,正如您所看到的,这种方法与首先为您的 Android 应用设置和配置组件的方式是高效和无缝的。因此,如果您需要添加一个
这三个子标签中唯一一个在 <动作> 标签中绝对需要的子标签,它将定义采取什么动作来解析(完成)意图对象的消息或任务。< intent-filter >标签本身也可以有三个基本参数;一个用于图标图形,一个用于文本标签,一个给它一个优先级值。
接下来,让我们在我们的 Hello_World Android 应用中实际实现一个 Intent 对象,看看所有这些如何在我们现有的应用中协同工作。
在 Hello World 中使用意图对象启动活动
让我们创建一个名为 TimePlanet 的新活动子类,我们可以使用 ConfigPlanet 活动子类中的 Intent 对象调用它。我们将创建一个新的 Activity 子类,为它创建一个新的 XML 用户界面屏幕布局,将其添加到我们的 AndroidManifest.xml 文件中,以便 Android OS 知道它的存在,将用户界面元素添加到我们的 Configure a Planet Activity 中,以调用新的 TimePlanet Activity,然后编写实现 Intent 对象的 Java 代码,并使用它来调用(启动)TimePlanet 活动及其原子钟用户界面。
打开 Eclipse,然后打开你的 Hello World 项目文件夹,右键点击 chapter.two.hello_world 包名文件夹,最后选择新建类菜单序列如图图 16-1 所示。
图 16-1 。在 Eclipse 中创建新的类来保存我们的 TimePlanet.java 活动子类 Java 代码
这将打开新的 Java 类对话框,如图 16-2 所示,我们可以填写所有必要的参数来精确地创建我们希望 Eclipse 为我们生成的 Java 类的类型,在这种情况下,它是一个活动子类,将保存我们的行星时间用户界面元素。
图 16-2 。命名我们的 TimePlanet.java 类,选择一个 android.app.Activity 超类和公共修饰符
源文件夹:字段中应该已经有了您的 Hello_World/src 项目源代码文件夹。因为您右键单击包文件夹来调用这个新的 Java 类对话框,所以 Eclipse 会为您推断出文件夹信息。同样的,包:字段也应该自动填入你的包名。
接下来将您的 TimePlanet 类名放入 Name: 字段,然后单击浏览。。。按钮位于超类:字段的右侧,这样我们可以选择我们想要从中派生出新类的类的类型,在本例中,我们想要使用 Activity 类。
当图 16-2 右侧所示的超类选择对话框打开后,在选择类型:字段中输入一个一个字符,选择 Activity - android.app 选择选项后,点击 OK 按钮。一旦你回到新 Java 类对话框,点击完成按钮。
一旦你完成了这个新的 Java 类工作过程,Eclipse ADT 应该已经在 IDE 编辑窗格的中心部分为你打开了一个新的编辑标签,名为 TimePlanet.java(参见图 16-3 )。
图 16-3 。在 Eclipse 中打开一个新创建的 TimePlanet.java 活动子类,显示导入语句
应该已经为您编写了四行 Java 代码,如下所示:
package chapter.two.hello_world;
import android.app.Activity;
public class TimePlanet extends Activity {
}
这些声明了 chapter.two.hello_world 包名和一个 import 语句,导入一个 android.app.Activity 包及其相关类,供您在即将编写的 TimePlanet.java Activity 子类中使用。
您的名为 TimePlanet 的公共类也应该为您声明,并且应该使用 extends 关键字对 Activity 类进行子类化,并为您准备好开始编写 Java 代码的基础结构。
让我们编写标准代码来创建活动屏幕,并将其内容视图设置为 XML 用户界面设计文档,这是我们接下来要编写的。
首先,我们使用名为 savedInstanceState 的 Android Bundle 对象编写受保护的类 onCreate(),在该类中,我们使用 super 关键字从父 Activity 超类调用 onCreate()方法,并在 onCreate()方法调用中传递 savedInstanceState 变量,使用以下 Java 代码行:
super.onCreate(savedInstanceState);
接下来,我们将使用setContentView()Activity 类方法将我们的内容视图设置为一个 XML 文件,我们接下来将创建该文件,它将保存使用 XML 标记的用户界面定义。
Java 代码的 setContentView()方法调用行如下所示:
setContentView(R.layout.activity_time);
注意在图 16-4 中,Eclipse IDE 错误标记了 activity_time.xml 文件引用;这是因为我们还没有创建这个文件,而且因为我们接下来要这样做,所以我们现在可以忽略这个突出显示的错误。
图 16-4 。编写 onCreate()方法和 setContentView()方法来访问 activity_time.xml UI 布局
接下来,我们将创建我们的 XML 用户界面,这样我们就可以解决这个错误,然后一旦完成,我们将返回到这个编辑选项卡,并编写实现我们的意图对象的代码。
为我们的 TimePlanet.java 活动创建 linear layout XML
接下来我们需要做的是右击项目资源文件夹中现在熟悉的 /res/layout 文件夹,然后选择新建 Android XML 文件菜单命令序列来启动如图图 16-5 所示的新建 Android XML 文件对话框。
图 16-5 。创建一个新的 Android XML 文件和 LinearLayout 根元素
选择一个资源类型:布局的和项目:名称 Hello_World 并将文件命名为 activity_time 以匹配我们的活动 XML 文件命名约定。
接下来,选择根元素: LinearLayout 的类型,然后单击 Finish 按钮,这将创建我们的 activity_time.xml 文件,以及父 LinearLayout 标签。一旦你点击 Finish,Eclipse 会自动在 IDE 的中间部分打开一个编辑窗格,显示它为你编写的 XML 标记,如图 16-6 所示。
图 16-6 。在 Eclipse 中打开我们的 activity_time.xml 布局 xml 文件,并编辑< LinearLayout >标签
现在,我们可以添加我们的 LinearLayout 参数,以及文本标题元素、模拟时钟元素和按钮用户界面元素的几个子标签,当我们使用完 Planet Time 活动时,它会将我们返回到 Configure a Planet 活动。接下来,让我们单击 IDE 底部的图形布局编辑器选项卡,以便添加一个 AnalogClock UI 小部件。
打开图形布局编辑器后,点击屏幕左侧控件面板中的时间&日期抽屉,打开那个装满控件图标的抽屉,就可以看到模拟时钟控件,如图图 16-7 所示。我还在截图中展示了当您将模拟时钟小部件拖放到 Eclipse 中央工作区显示的空白应用屏幕模拟上时,您的屏幕看起来是什么样子。
图 16-7 。使用 Eclipse 中的图形布局编辑器选项卡在 UI 屏幕上拖放一个 AnalogClock 小部件
接下来,我们需要为行星时间屏幕创建两个字符串常量数据值,一个标题(标题文本)以及模拟时钟小部件下方按钮上的文本标签,如图图 16-8 所示。这两个<字符串>常量的 XML 标记应该如下所示:
<string name="time_caption_value">Home Planet Earth Time</string>
<string name="time_button_value">Return to Planet Configuration</string>
图 16-8 。将 activity_time.xml 文件所需的两个字符串常量添加到我们的 strings.xml 文件中
在我们使用完行星的原子钟功能后,我们将使用按钮返回到“配置行星”活动。
现在,我们准备在 activity_time.xml 用户界面 xml 规范中配置 LinearLayout 父标记和子标记。首先,我们需要向 LinearLayout 标签本身添加两个参数,这将允许我们从 Java 代码(android:id)控制它,并为它提供我们在本书前面创建的壮观的背景图像(android:background)。
给 LinearLayout 添加以下两个参数,如图图 16-9 所示:
android:id="@+id/timePlanetScreen"
android:background="@drawable/trans_stars_galaxy"
图 16-9 。为 TextView、AnalogClock、Button 和 LinearLayout 标签添加布局和文本参数
接下来我们需要添加格式化参数到 < AnalogClock > 标签,我们使用图形布局编辑器创建了这个标签,如图 16-7 中所示。让我们给它一个地球的背景图像,顶部 20 度倾斜,在屏幕上居中,确保我们的布局宽度和高度设置为 wrap_content 。我们可以使用以下 XML 参数完成所有这些事情:
android:id="@+id/analogClock1"
android:background="@drawable/earth"
android:layout_marginTop="20dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
接下来,让我们添加我们的 < TextView > 用户界面元素,在 GLE 或 XML 编辑器中,并将其配置为匹配我们的等离子背景的颜色,具有 25sp 的大文本大小和 40°的顶部倾斜边距,还将其居中,并确保它也使用 wrap_content 值,使用以下标记:
android:text="@string/time_caption_value"
android:textColor="#FFCCAA"
android:textSize="25sp"
android:layout_gravity="center"
android:layout_marginTop="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
最后,我们需要添加一个 <按钮> 用户界面元素,要么在 GLE 中,要么在 XML 编辑器中,并配置它以匹配我们的等离子体背景的颜色,上边距倾斜 20 °,并将其居中,确保它也使用标准的 wrap_content 布局值,使用这个标记:
android:id="@+id/timeButton"
android:textColor="#FFCCAA"
android:text="@string/time_button_value"
android:layout_marginTop="20dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
我们将颜色、间距和大小值与其他活动用户界面屏幕设计相匹配,以保持 Hello World 应用中所有用户界面屏幕的一致外观。整个用户界面设计 XML 标记可以在图 16-9 中看到。
接下来,我们将看看需要在我们的 AndroidManifest.xml 文件中做些什么来准备我们的应用以支持这个新的活动,并使用 label 参数在活动屏幕的顶部放置一个定制的屏幕标题。
为我们的 TimePlanet 活动配置 AndroidManifest.xml
在我们开始向 AndroidManifest.xml 文件添加标记之前,我们需要创建一个名为activity _ title _ time _ planet的字符串常量值,如图图 16-10 所示。
图 16-10 。为 TimePlanet.java 活动的屏幕标签添加一个<字符串>标签和参数
在 Eclipse 的编辑选项卡中打开 strings.xml 文件,添加一个新的 < string > 标签,其名称值和文本数据值如下:
<string name="activity_title_time_planet">Hello World - Planet Earth Time</string>
接下来,我们将把 TimePlanet 活动子类的
图 16-11 。为新的 TimePlanet.java 活动子类添加一个<活动>标签和参数
添加一个带有指定活动类名的 android:name 和引用我们刚刚在 strings.xml 文件中创建的字符串常量的 android:label 参数的
<activity android:name="chapter.two.hello_world.TimePlanet"
android:label="@string/activity_title_time_planet" />
既然我们已经在应用清单中告诉了 Android OS 我们的新活动,我们需要在我们的 Configure a Planet Activity 屏幕中添加一个按钮用户界面元素。一旦我们将这个 XML 添加到 activity_config.xml 用户界面屏幕定义中,我们就可以添加声明和实例化这个按钮元素的 Java 代码。完成后,我们可以添加事件处理方法,该方法包含我们需要发送到 TimePlanet.java 活动的意图对象,以便在应用用户忙于配置他们的行星特征时,在他们希望查看他们的行星地球原子钟的任何时候启动该活动。
向 activity_config.xml 文件添加一个原子钟按钮标签
我们需要做的第一件事是为添加到 activity_config.xml UI XML 定义中的原子钟按钮用户界面元素创建我们的
图 16-12 。将名为 button_name_time 的原子钟按钮标签字符串常量添加到 strings.xml 文件中
因为我们希望原子钟 UI 按钮元素位于文本数据输入字段之下(这样就不会与配置用户界面按钮混淆),我们将把它放在第二个 LinearLayout 容器标签中,如图图 16-13 所示。这样我们就有更少的参数需要把它放到合适的位置,只有一个 MarginTop 和一个 MarginLeft 和一个 textColor 来确保它匹配我们的空间等离子体和屏幕文本标题颜色。我们还引用我们的字符串常量,并将 ID 设置为时间按钮。
图 16-13 。在 activity_config.xml 的第二个 LinearLayout 容器的底部添加一个按钮标签
timeButton 按钮元素的 XML 标记应该如下所示:
<Button android:id="@+id/timeButton
android:text="@string/button_name_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FFFFFF"
android:layout_marginTop="11dp"
android:layout_marginLeft="11dp" />
既然我们已经在 Configure a Planet 活动中添加了一个原子钟 UI 按钮对象,现在是时候进入 Java 代码,实例化该按钮对象,这样我们就可以添加事件处理代码,允许我们将一个 Intent 对象发送到我们的 TimePlanet.java 活动类并启动它!
用 Java 为我们的 ConfigPlanet.java 活动编码一个意图对象
让我们折叠其他按钮对象代码块,如图 16-14 中的加号所示,并实例化一个新的 timeButton 对象,并使用下面一行 Java 代码将其引用到我们的 XML:
Button timeButton = (Button) findViewById(R.id.timeButton);
图 16-14 。在 ConfigPlanet 活动的 timeButton 事件处理程序中编写 callTimeIntent Intent 对象
接下来,我们需要使用将事件监听器附加到按钮对象。setOnClickListener( ) 方法,并创建一个新视图。OnClickListener( ) 事件处理方法使用的四行代码如图图 16-14 所示:
timeButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent callTimeIntent = new Intent(view.getContext(), TimePlanet.class);
startActivityForResult(callTimeIntent, 0); } });
正如您所看到的,我们在 onClick()事件处理方法中构造了 Intent 对象,使用了下面一行 Java 代码:
Intent callTimeIntent = new Intent(view.getContext(), TimePlanet.class);
这声明了 Intent ,将其命名为 callTimeIntent ,并使用 new 关键字来构造 Intent,将它传递给两个必需的参数:当前上下文,由 view.getContext( ) 方法表示,以及 Intent 的目标, TimePlanet.class 活动专有名称。
下一行代码使用我们刚刚创建的 callTimeIntent 对象启动 TimePlanet 活动,代码如下:
startActivityForResult(callTimeIntent, 0);
第一个参数是 Intent 对象,第二个是代码,其中零表示没有代码,任何非零正数都是在中返回的代码。活动退出时调用的 onActivityResult( ) 方法。
用 Java 为我们的 TimePlanet.java 活动编写一个意图对象
接下来,我们需要将我们的意图处理代码添加到我们的 TimePlanet.java 活动子类中,以便我们可以在使用返回星球配置按钮退出活动时返回一个意图结果。我们需要添加的 Java 代码是
Button returnFromTimeButton = (Button) findViewById(R.id.timeButton);
returnFromTimeButton.setOnClickListener(new view.OnClickListener() {
public void onClick(View view) {
Intent returnIntent = new Intent();
setResult(RESULT_OK, returnIntent);
finish();
}
});
首先,我们需要实例化 returnFromTimeButton 按钮对象,然后使用。setOnClickListener()方法。这将调用新视图。OnClickListener()方法,然后包含我们的 onClick()事件处理程序,最后包含我们的 Intent 对象声明和 setResult()方法调用,最后是我们的 finish()方法调用,将我们返回到 ConfigPlanet,如图图 16-15 所示。
图 16-15 。在我们的 TimePlanet 活动的 returnFromTimeButton 处理程序中编写 returnIntent 意图对象
现在,我们唯一需要做的就是为我们在 activity_time.xml 文件中指定的 tran_stars_galaxy drawable XML 文件添加 wow factor 背景图像 fade Java 代码,如图 16-16 所示。
图 16-16 。为我们的 TransitionDrawable 用户界面屏幕背景淡出效果添加 Java 代码
我们这样做是因为我们已经在前一章中完成了设置特殊效果的所有工作,所以为什么不在原子钟屏幕上利用它来增加特殊效果的剂量呢!
让我们从我们的 NewPlanet.java 活动中复制 TransitionDrawable trans 对象和相关代码,并将其粘贴到我们的 TimePlanet.java 类中的 setContentView()方法下,如图图 16-16 所示。
不要忘记 trans.startTransition()方法代码,当我们进入用户界面屏幕时,它将启动转换动画。最终的 Java 代码块应该如下所示:
final TransitionDrawable trans = (TransitionDrawable)getResources().getDrawable(R.drawable.tran_stars_galaxy);
LinearLayout timePlanetScreen = (LinearLayout)findViewById(R.id.timePlanetScreen);
timePlanetScreen.setBackground(trans);
trans.startTransition(5000);
现在,我们终于准备好利用作为 Android 应用运行的工作流程,并测试我们新的 TimePlanet 活动和意图处理代码。
一旦 Nexus S 模拟器启动,点击菜单按钮,从菜单中选择配置行星选项,并启动 ConfigPlanet.java 活动。然后点击 UI 屏幕右下角的原子钟按钮,如图图 16-17 所示。
图 16-17 。在 Eclipse 的 Nexus 模拟器中测试我们的 TimePlanet.java 活动和意图对象调用
正如你所看到的,我们新的行星地球时间屏幕平稳启动,从恒星背景到等离子体空间背景的动画过渡平稳地发生在我们用行星地球制作的令人印象深刻的模拟时钟后面,如图 16-17 右侧所示。
在行星地球时间活动屏幕的底部是你的返回到行星配置按钮,点击这个,它将返回到配置一个行星活动。
为了测试我们的意图对象和它们的有效性,多点击几次原子钟和返回行星配置按钮,以绝对确保我们可以在这两个活动屏幕之间来回切换,次数不限。恭喜,你现在已经使用了一个 Intent 对象在两个应用活动屏幕之间切换了!
摘要
在这一章中,我们仔细研究了 Android 意图对象以及 Android 意图过滤器。我们研究了它们是如何工作的,以及不同类型的意图,这样我们就可以在接下来的几章中使用它们来启动服务和调用广播接收器。
我们了解了 Intent 对象中包含的信息,Android 操作系统中指定的动作、数据、类别、附加内容及其常量,以及通常在 Intent 中指定的标志、数据类型(MIME)和组件名称属性。
我们了解了指定组件名称的显式意图之间的区别,这样,意图对象的目标是已知的,不需要进行推断,我们还了解了组件名称未知而目标通过动作、数据和类别进行推断的隐式意图。
我们了解了如何在您的应用和 AndroidManifest.xml 文件中创建您自己的意图过滤器,从而为解析隐含的意图对象(那些没有指定组件名称的对象)提供推理引擎,以及在什么条件下需要利用这些意图过滤器为通过意图使用您的应用组件的其他开发人员提供其他应用兼容性。
最后,我们在 Hello World 应用中添加了一个原子钟 TimePlanet.java 活动,这样我们就可以展示如何使用 Intent 对象在应用中的活动之间进行切换。我们将在本书的下两章中学习如何在服务和广播接收器中使用 Intent 对象,所以我们不在本章中讨论这些例子。
在下一章中,我们将仔细研究 Android 服务,了解已启动和已绑定的服务,并了解它们如何帮助我们的应用更加流畅和高效地工作。
十七、Android 服务:使用后台处理
在这一章中,我们将深入研究 Android 服务、,开发人员利用这些服务来执行后台异步操作,这些服务可以自行处理数据流或计算,而无需将与应用用户界面设计同步,或者以任何方式与应用内容(持续的用户体验)同步。
服务通常被用来处理需要在你的应用用户体验的背景中进行的事情,与安卓用户对你的应用的实时使用并行,但不与该应用的用户体验设计直接同步或实时连接。
Android 服务的使用示例包括:在用户使用您的应用时播放长格式数字音频(比如专辑音乐曲目),在后台与某种服务器或数据库对话,下载数据,管理文件输入输出流,流式传输新媒体内容,如数字视频流或数字音频流,处理网络(SMTP 或 HTTP)协议交易,处理支付网关交易,GPS 数据的实时处理,以及类似的复杂任务。
通常委托给 Android 服务类的任务是那些不应该与用户界面和用户体验联系在一起的任务,因为强制并发(同步)处理可能会导致用户体验变得不自然或不平稳(即,不能描绘平滑的用户界面响应,从而不能描绘平滑和愉快的用户体验)。
委派给 Android 服务的任务也是处理器密集型的,所以在开发处理器密集型应用时,要考虑终端用户的电池寿命。正如你可能猜到的那样,Android 电池的两个主要功耗是长时间处理和长时间保持显示屏亮着(我们在前面的视频章节中提到过)。
在这一章中,我们将仔细研究 Android 服务类,Android 服务的各种特性,以及这些特性、功能、设置、常量和类似的特性是如何在您的 Android 应用中声明使用的。您可能已经猜到了,声明要使用的服务是在一个 AndroidManifest.xml 文件中完成的。
这是 Android 中比较复杂的主题之一,因为它本质上涉及绑定、同步、进程、处理器周期、线程、访问控制、权限以及类似的高级操作系统主题。
Android 服务基础:规则和特征
服务被定义为可以在后台执行处理密集型功能的 Android 应用组件,而不需要任何用户界面设计或任何活动显示屏幕,并且不需要用户与需要完成的处理进行任何交互。
Android 应用组件可以使用 Intent 对象启动服务类,服务将继续在后台处理,即使该 Android 设备用户切换到不同的 Android 应用。
一个 Android 应用组件可以将绑定到一个服务上与之交互,甚至执行进程间通信,你也可能知道这就是 IPC 。在对 Android 服务的概述之后,我们将在本章的下一节更仔细地研究进程和线程。
绑定是一种高级编程概念,涉及在两个独立的应用组件进程之间建立实时连接,当发生变化以及需要在它们的逻辑绑定连接之间进行更新时,这些进程会相互提醒。
一个 Android 服务通常采用两种格式之一,绑定或开始。当一个应用组件(比如一个活动)通过调用来启动服务时,一个 Android 服务就变成了启动的。startService( ) 方法。
一旦启动,服务可以无限期地在后台运行,即使在启动该服务的组件随后被应用逻辑或 Android 操作系统破坏的情况下。
一个启动的服务执行一个单独的操作,并且不向调用实体返回结果,这很像一个被声明为 void 的方法。
例如,已启动的服务可能会通过网络下载或上传数据文件。最佳实践表明,当启动的服务操作完成时,该服务应该自动停止,以帮助优化 Android 操作系统资源,如处理器周期或内存使用。
当一个 Android 应用组件将绑定到一个服务时,一个绑定的服务被创建。这是通过调用来完成的。bindService( ) 方法。绑定服务提供了一个客户端-服务器接口,该接口允许组件通过使用进程间通信(IPC)与绑定服务进行交互、发送请求、获取结果,甚至跨进程进行这些操作。
绑定服务只在任何其他 Android 应用组件绑定到它时存在于 Android 系统内存中。多个应用组件可以同时绑定到该服务,但是,当所有这些解除绑定时,该服务就会被销毁(从系统内存中移除)。
我们将看看这两种类型的服务格式,以及一种混合方法,其中您的服务可以同时以这两种方式工作。这意味着您可以启动您的服务(这样它就是一个由启动的服务,并且可以无限期运行)并且允许绑定。
Android 服务是被指定为启动的服务还是被指定为绑定的服务取决于您是否实现了一些更有用的服务类回调方法。例如,服务类。onStartCommand( ) 方法允许组件启动一个服务,而。onBind( ) 方法允许绑定到那个服务。我们将在本章的后面部分详细介绍服务类的方法。
无论应用的服务是已启动、已绑定还是既已启动又已绑定,任何其他应用组件都可以使用服务,即使是来自单独的应用。这类似于任何应用组件都可以通过有目的地启动来启动活动。我们在前一章中详细介绍了使用意图对象,我们将在本章中介绍如何将意图对象用于服务。
值得注意的是,服务运行的优先级比不活动的活动优先级高,因此 Android 操作系统终止服务类的可能性比终止活动类的可能性小。
同样需要注意的是,您可以在清单 XML 文件中将您的服务声明为 private ,并阻止来自其他应用的访问,这通常是单个开发人员对他们的应用所做的事情。
默认情况下,服务将总是在主机应用的主进程的主线程中运行。在应用的这个主要进程内部运行的服务通常被称为本地服务。
程序员中一个常见的误解是,Android 服务总是运行在它自己独立的线程上。虽然如果你这样设置的话,这当然是可能的,但是默认情况下,服务会而不是创建自己的线程,因此除非你另外指定,否则服务不会在单独的线程中运行。我们将在本章的下一节讨论进程和线程,因为这是一个非常密切相关的主题。
这意味着,如果您的服务要执行任何 CPU 密集型工作(例如实时解码流数据)或阻塞操作(例如通过繁忙的网络协议进行实时网络访问),您应该在服务中额外创建一个新线程来执行这种类型的处理。
值得注意的是,除了服务类所在的线程(已经使用),您可能不需要为服务类使用另一个线程,例如,在本章的示例中,我们使用服务中的 MediaPlayer 播放音乐文件,而不需要生成另一个线程。
真正确定是否需要这样做的唯一方法是,首先尝试使用一个服务类进行后台处理,然后,如果它影响您的用户体验,则考虑在需要时实现一个线程类和对象。
进程或线程:有价值的基础信息
当你的 Android 应用的一个组件,比如说你的 MainActivity 类启动时,并且你的应用当前没有任何组件在运行,Android 操作系统将为你的应用启动一个全新的 Linux 进程,使用一个执行的线程,称为 UI 线程。一个进程可以生成或启动(或衍生)多个线程。
通常,所有 Android 应用组件都将在相同的初始进程和线程中运行。这通常被称为主线程。
如果您的一个 Android 应用组件启动,并且 Android 发现您的应用已经存在一个进程,由于您的应用中的另一个组件已经存在,那么该组件也将在相同的应用进程中启动,并且也将使用相同的线程。因此,本质上,要启动自己的线程,必须在 Java 代码中明确地这样做。
但是,您可以安排应用中的不同组件在单独的进程中运行,并且可以为任何进程创建额外的线程。正如我们将看到的,这是 Android 服务通常会做的事情。
如何指定流程:使用 android:process XML 参数
作为 Android 操作系统的默认设置,所有的应用组件都将在相同的进程中运行,大多数基本的 Android 应用不需要改变这种设置,除非有非常令人信服的理由。
对于高级应用(我们没有在本书中讨论,但是我们将在这里讨论这个概念,以彻底了解 Android 进程),如果您发现自己处于绝对需要控制某个应用组件属于哪个 Android 进程的情况,您可以在您的 AndroidManifest.xml 文件中指定这一点。
您的 AndroidManifest.xml 组件标签针对每种主要类型的应用组件,无论是活动
这个进程参数可用于指定应用组件需要运行的进程。您可以设置 process 参数,使每个应用组件都在自己的进程中运行,或者混合和匹配,使一些应用组件共享一个进程,而其他组件不共享该进程。
如果您想变得非常复杂,您还可以设置这些 android:process 参数,以便来自完全不同的 android 应用的组件可以在同一个 Android 进程中一起执行。
只有当这些特定的应用共享相同的 Linux 用户 ID,并且使用相同的证书签名时,才能实现这一点。
有趣的是,您的 AndroidManifest XML 文件中的全局 <应用> 标记也将接受 android:process 参数。
在您的
值得注意的是,Android 可以选择在任何时候关闭一个进程,例如,当内存不足时,或者如果您的进程所使用的内存被其他具有更高优先级的进程所需要,或者从最终用户那里获得了更多的使用(注意)。
在进程内部运行的应用组件在终止后会被销毁,或者从内存中删除。不要担心,因为这些进程中的任何一个都可以在以后重新启动,用于那些需要为用户完成某些事情的应用组件。
当决定终止哪些进程时,Android 系统会权衡它们对用户的相对重要性。例如,与托管可见活动的进程相比,它更容易关闭托管屏幕上不再可见的活动的进程。因此,决定是否终止一个进程取决于该进程中运行的组件的状态。接下来将讨论用于决定终止哪些进程的规则。
Android 进程寿命:如何让你的进程保持活力
Android 试图尽可能长时间地将应用进程保存在系统内存中,但有时需要销毁运行在操作系统中的旧进程。这样做是为了为更新或更高优先级的进程回收系统内存资源。
毕竟,今天大多数 Android 设备只配备了 1 或 2gb 的主系统内存,当用户玩游戏、启动应用、阅读电子书、播放音乐、打电话等时,这可能会很快填满。
即使设备开始配备 3gb 的主内存,您仍然会遇到内存管理问题,而使用进程和线程是这些内存管理问题的核心,因此我们了解 Android 操作系统中如何处理进程非常重要。
Android 操作系统通过优先级层次来决定保留哪个进程和终止哪个进程。Android 将每个正在运行的进程放入这个优先级层次结构中,这是基于进程队列中运行的每个组件,以及当前状态(运行、空闲、停止等。)的那些组件。
从 Android 设备中清除内存的方式是,首先终止优先级(重要性)最低的进程,然后终止下一个优先级最低的进程,以此类推,直到较高优先级进程所需的系统资源被回收使用。
在这个优先级层次结构中有五个流程优先级。一旦您看到它们是什么,您将意识到这个流程优先级层次结构是如何逻辑地建立的,并且您还将很好地了解服务(异步处理或重载)和活动(用户界面屏幕)如何适应这个整体流程优先级模式,这对于理解非常重要。准备好一些啊哈时刻吧!
最高优先级的进程级别是前台进程,它是当前正在运行(处理)的主进程,因此是用户当前参与的应用任务所需要的。
如果一个流程包含用户当前正在交互的活动(用户界面屏幕),或者如果它托管当前绑定到用户正在交互的活动的服务,则该流程被认为处于前台。
如果一个进程当前正在执行一个在前台运行的服务,它也被认为是一个前台进程,这意味着服务对象已经调用了。startForeground( ) 方法。
如果一个服务当前正在执行它的 onCreate()、onDestroy()或 onStart( ) 服务生命周期回调,我们将在本章中学习,或者当前正在广播一个 BroadcastReceiver 对象,它恰好调用它的【on receive()方法,它也将被 Android 操作系统赋予一个前台进程优先级状态。
在一个最佳的 Android 操作场景中,在任何给定的时间都只有少数前台进程在运行。这些进程只有在万不得已的情况下才会终止,例如,如果系统内存不足,以致操作系统或其应用无法继续有效运行。
下一个最高优先级的进程是可见进程,该进程不包含任何前台进程组件,但仍会影响用户在设备显示屏上看到的内容。
如果一个流程包含一个不在前台的活动,但在用户的显示屏上仍然可见,则该流程被认为是可见的,例如一个活动的。onPause( ) 方法已被调用。
一个很好的例子是前台流程活动启动了一个允许在后台看到调用活动的对话框。
包含已绑定到可见活动的服务类的流程也将获得可见流程优先级。可见进程被认为几乎和前台进程一样重要,因此它们不会被终止,除非绝对需要保持所有前台进程在系统内存中运行。
五个级别中的中间优先级进程级别是服务进程,它是包含已经使用启动的服务的进程。startService( ) 方法,但是 Android 没有将它归类到两个最高进程优先级类别中的任何一个。
因为服务进程没有用户界面屏幕,并且在后台进程中异步运行,所以与用户在显示器上看到的任何内容都没有直接联系。但是,服务仍在执行最终用户希望继续执行的任务(例如在后台播放音乐专辑或通过网络下载数据)。出于这个原因,Android 让它们继续处理,除非没有足够的内存来支持它们以及前台和可见的进程。
第二个最低优先级的流程级别是后台流程,该流程包含一个终端用户当前不可见的活动,例如活动。【onStop()】方法被调用。
因为这些后台进程对用户体验没有可察觉的影响,每当有必要为更高优先级(前台、可见或服务)的进程回收系统内存时,Android 就会终止它们。
经常有相当多的后台进程在运行,Android 将后台进程保存在一个被称为 LRU(最近最少使用)的列表中。这用于保证具有用户最近使用的活动的进程是最后终止的进程。
值得注意的是,如果您的活动正确地实现了它们的生命周期方法,并保存了它们的当前状态,那么终止该活动的过程将不会对您的最终用户体验产生任何影响。
这是因为当您的用户导航回活动的用户界面屏幕时,活动会恢复其所有可见状态(记住您的 Bundle savedInstanceState 代码)。
最低优先级的进程级别是空进程,它不包含任何当前活动的应用组件。如果您想知道为什么一个空进程会被保存在系统内存中,那么让一个空进程保持活动状态的战略原因是为了 缓存优化,这将在下一次组件需要在该进程中运行时缩短启动时间。
Android 操作系统经常终止这些空进程,试图在各种进程缓存之间以及与其底层 Linux 内核缓存之间平衡整体系统内存资源。
最后,由于另一个进程依赖于某个进程,因此该进程的优先级可能会增加。任何当前服务于另一个进程的 Android 进程都不会比它当前服务的进程排名更低。
假设进程 01 中包含的内容提供者(数据库或数据存储)正忙于服务进程 02 中的用户界面活动,或者,如果进程 01 中的服务被绑定到进程 02 中的应用组件,则进程 01 总是被认为至少与进程 02 一样重要。
接下来,我们将看看线程,它是更低级的线程,用于在进程中调度处理器密集型和用户界面任务。
关于线程的一些警告:不要干扰 UI 线程
在 Android 操作系统通过你的 AndroidManifest.xml 文件启动你的应用后,它的操作系统会产生一个执行的线程,通常被称为主线程。主线程负责操作系统和用户界面小部件之间的调度和管理事件,这一点我们在本书的前一章已经了解过。
主线程还控制绘制您的图形、视频和动画(可绘制)素材到活动显示屏,因此它立即执行大量繁重的工作,这就是为什么您可能需要生成自己的线程,如果您想对您的 Android 应用执行的某些操作可能会使主(或主要)线程上已经繁重的工作负载过载,主线程实际上运行您的整个应用。
主线程通常也被称为 UI 线程,或用户界面线程,因为它是应用组件与 Android UI 工具包中的组件进行交互的线程。Android UI Toolkit 包括来自 android.widget 和 android.view 包的所有组件(类),我们在本书的前三部分已经广泛了解了这些组件。
在主进程中运行的所有 Android UI 工具包组件都在这个 UI 线程中实例化,并且对每个所需组件的操作系统调用都从这个 UI 线程中分派。
因此,响应系统回调的方法,比如。onKeyDown( ) 事件处理程序,用于报告用户界面交互,或生命周期回调方法之一,如。开始()法,或者说是一个。pause( ) 方法,甚至是一个。destroy( ) 方法,总是在 Android 应用的主进程中包含的 UI 线程内运行。
当应用为响应用户界面交互而分派密集型处理时,单线程模型会导致用户体验性能降低,这就是为什么您必须正确利用线程的原因。
原因是显而易见的;如果 UI 线程中正在进行大量的处理,那么执行冗长的操作,如网络访问、复杂的计算或 SQL 数据库查询,将会阻塞整个用户界面的响应,因为这些处理周期和基本上阻塞了UI 相关事件的顺利(快速)处理。
当一个线程以这种方式被阻塞时,UI 事件不能被调度处理,这包括将图形(drawable)元素绘制到屏幕上。从用户体验的角度来看,您的应用似乎“挂起”或暂停了一段不期望的时间。
需要注意的是,如果你的应用阻塞 UI 线程超过几秒钟(超过五秒钟),你的用户将会看到一个对话框,其中包含非常不希望的(至少从用户体验的角度来看)“应用没有响应”(或 ANR)对话框。
同样需要注意的是,Android UI 工具包目前并不是所谓的“线程安全的”由于这个原因,任何时候你都不能从工作线程中操纵你的应用用户界面元素。
工作线程是任何非 UI 线程,通常也称为后台线程。换句话说,它是您在应用 Java 代码中产生的一个线程,用来卸载密集的“工人”后台处理,以便您的 UI 将继续平稳运行。
所以请记住,Android 线程处理的第一个关键规则是,您必须从 UI 线程内部对用户界面元素进行所有操作,记住 UI 线程是您的 Android 应用的主要线程。
第二条规则更一般化,就是不要因为任何原因在任何时候阻塞 UI 线程。这就是为什么要有工作线程的原因,这样,如果您需要做一些会导致 UI 线程被阻塞的事情,您可以在代码中生成一个工作线程来执行处理、流式传输、数据库访问或其他需要大量使用处理器的任务,也可能是高级应用编程。
我的 Android 应用应该使用服务还是线程?
Android 服务只是一个可以在后台运行的组件,即使用户没有与你的应用交互。如果你需要在主 UI 线程之外执行工作,但只是在用户与你的应用的用户界面交互时,那么你应该在你的应用的那个类中创建一个新的 Android 线程对象,使用一个 HandlerThread 对象或一个 AsyncTask 对象,而不是麻烦地编码(并在清单中声明)一个完整的 Android 服务子类。
假设您想在活动运行时从音乐服务中流式传输一些音乐。你想要做的是使用来创建一个线程。onCreate( ) 方法,使用启动它运行。onStart( ) 方法,最后使用一个将其停止。onStop( ) 方法。
正如我之前提到的,至少在你成为一名更高级的 Android 程序员之前,你可能会想要使用更精细的 Android Thread 子类,即名为 AsyncTask 和(or) HandlerThread 而不是更通用的 Thread 类。
所以,你可能想知道,什么时候会希望使用服务子类而不是在现有类中生成线程对象。如果您还记得上一节,包含服务子类的 Android 进程总是比利用后台处理活动(线程)的进程优先级更高。
如果您的应用要承担大量的处理、访问或流操作,您可能希望为此操作启动一个服务组件(类),而不是简单地创建一个工作线程。
如果后台功能很可能比您的活动持续时间长,这是一个特别重要的考虑因素。例如,将您使用 Android Camera 类创建的视频上传到 web 服务器的活动会希望利用服务类来执行此上传,以便此上传过程能够在后台继续,即使您的用户离开当前活动。
因此,您希望使用服务类而不是线程对象的原因是,使用服务组件将保证您的处理操作至少有一个服务流程优先级,而不管您的 Activity 子类发生了什么。
接下来,让我们学习如何编写我们的服务子类,以及如何使用 Intent 对象调用它。我们将使用 TimePlanet.java 活动子类来实现这一点,它是我们在前一章意图中创建的。
我们将通过创建一个名为 MusicService.java 的音乐播放器后台服务组件来实现我们的 Android 服务类生命周期。这将是一个服务子类,它将利用类声明中的 extends 关键字来子类化 Android 服务类。
在我们设置了访问这些服务生命周期方法所需的新用户界面元素后,我们将使用 Java 代码编写服务类生命周期方法,包括 onCreate( ) 、 onStart( ) 和 onDestroy( ) 方法。我们甚至将利用其中一个 Android Intent 对象来启动我们的后台服务,该服务将为我们的星球时间原子钟活动播放背景音乐,该活动是我们在本书涵盖 Intent 的前一章中创建的。
最后,我们还将了解如何将 <服务> 标签添加到我们的 AndroidManifest.xml 文件中,并且我们将在我们的 Nexus S 模拟器中测试我们的背景音乐服务子类,只是为了绝对确保一切都按照预期的方式工作。
在我们的 TimePlanet 活动中实现音乐服务
我们需要做的第一件事是创建启动音乐服务和停止音乐服务用户界面按钮对象,它们将控制我们的音乐服务。这些放在我们的 TimePlanet.java 活动类和 XML 定义文件中。为此,我们需要设置的第一件事是按钮 UI 元素标签所需的字符串常量,因此,通过使用以下 xml 标记,在/res/values 文件夹的项目的 strings.xml 文件中添加两个<字符串>常量定义:
<string name="start_button_value">Start Music Service</string>
<string name="stop_button_value">Stop Music Service</string>
如图 17-1 中的所示。现在我们准备添加两个按钮标签,它们将两个用户界面元素添加到我们现有的 UI 设计中。这些控制我们的音乐服务背景音乐组件。
图 17-1。将 start_button_value 和 stop_button_value 字符串常量添加到 strings.xml 文件中
在 Eclipse 编辑区域打开您的 activity_time.xml 文件,并复制我们在上一章中创建的 timeButton 用户界面组件,在其下复制两次。将第三个(底部)按钮标签保留为 timeButton,将第一个按钮 android:id 参数改为 startServiceButton ,将第二个按钮 android:id 参数改为 stopServiceButton ,如图图 17-2 所示。
图 17-2。向 activity_time.xml 添加 startServiceButton 和 stopServiceButton 按钮用户界面元素
接下来,让我们将这两个新按钮对象的 android:textColor 参数更改为 #FFAAAA ,或者一种漂亮的明亮的黄色颜色,以区分音乐服务按钮元素和返回配置按钮元素,后者是一种浅橙色,以匹配等离子背景。
接下来,更改 android:text 参数,使其指向我们在图 17-1 中设置的正确的字符串常量值;因此,对于第一个按钮标记,将 time_button_value 更改为 start_button_value,对于第二个按钮标记,将 stop_button_value。我们几乎完成了新按钮的参数化!
接下来,让我们更改两个按钮标签中的 android:marginTop 参数,使它们相互靠近,并与其他 UI 元素分开。
为此,我们将 startServiceButton marginTop 参数设置为 24 DIP (24dp ),该参数将启动音乐服务按钮元素从 AnalogClock UI 元素推开。然后将 stopServiceButton UI 元素 marginTop 参数设置为 6 DIP (6dp ),将停止音乐服务按钮元素放置在开始音乐服务按钮元素的正下方。
最后,因为我们的 startServiceButton 在 AnalogClock 下面 24 DIP,所以将 timeButton UI 元素的 marginTop 参数设置为相同的精确值。这导致音乐服务按钮 UI 元素在我们在前一章创建的现有用户界面设计中引人注目地居中。最终的标记如图 17-2 所示。
<Button android:id="@+id/startServiceButton"
android:textColor="#FFFFAA"
android:text="@string/start_button_value"
android:layout_marginTop="24dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button android:id="@+id/stopServiceButton"
android:textColor="#FFFFAA"
android:text="@string/stop_button_value"
android:layout_marginTop="6dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
为了更好地了解这个新的 TimePlanet.java 活动屏幕用户界面设计的外观,单击 XML 编辑窗格左下方的图形布局编辑器选项卡。如您所见,屏幕 UI 设计均匀分布,音乐服务按钮按功能分组。
要查看用户界面设计的真实外观,您需要使用 Run As Android Application 工作流程,因为我们从过去的经验中知道,Eclipse 中的图形布局编辑器工具并不总是以 Android 设备屏幕上呈现的方式显示边距参数。
既然我们的 Planet Time 用户界面屏幕上有了我们的音乐服务用户界面元素,那么是时候编辑我们的 AndroidManifest.xml 文件了。
配置我们的 AndroidManifest 文件以添加一个
当您将 Android 活动、服务或广播接收器组件添加到您的 Android 应用中时,您必须在您的 AndroidManifest XML 文件中声明它,该文件用于启动您的应用。
现在让我们通过在 Eclipse 的中央编辑窗格中打开您的 AndroidManifest.xml 文件来做到这一点。在现有 XML 标记的底部,添加一个 <服务> 标签,该标签位于结束</应用>标签之前,但位于我们在上一章中添加的时间表的最后一个<活动>标签之后。
这个
<service android:enabled="true" android:name=".MusicService" />
完成的 AndroidManifest.xml 文件和标记如图 17-3 所示。
图 17-3。将我们的<服务>标签和参数添加到我们的音乐服务类的 AndroidManifest.xml 文件中
现在我们准备编写 Java 代码,实现我们在本章前一节中创建的用户界面设计元素。
在我们的 TimePlanet 活动中编写 Java 代码来启动服务
在 Eclipse 的中央编辑窗格中打开TimePlanet.java活动类,并在其自身下再复制两次 returnFromTimeButton 按钮对象的按钮实例化和事件处理方法 Java 代码结构。我们这样做是为了不需要从头开始重新编写所有的 Java 代码,因为我们实现了两个非常相似的按钮对象,以及它们的事件处理基础设施。
命名(重命名)第一个复制的按钮对象startmusicebutton,引用其 ID 为 startButton 。删除 onClick(View view)方法调用内部的 Java 代码语句,这样我们就可以添加我们的新服务类相关的方法调用了。
接下来,重命名第二个复制的按钮对象:stopmusicebutton,引用其 ID 为: stopButton 。删除 onClick(View view)方法调用内部的 Java 代码语句,这样我们就可以添加我们的新服务类相关的方法调用了。
在我们的 startMusicServiceButton onClick()事件处理程序方法中,让我们添加一个 startService( ) 方法调用,以使用一个 Intent 对象启动我们的音乐服务组件,该对象引用我们当前的类上下文,代码如下:
startService(new Intent(this, MusicService.class));
这个方法调用使用一个 Intent 对象启动我们的服务,该对象是我们在 startService()方法调用中使用 Java new 关键字创建的,该关键字使用当前上下文和我们的 MusicService 类名引用作为参数来构造一个新的 Intent 对象,如前面一行 Java 代码所示。
接下来,在我们的 stopMusicServiceButton onClick()事件处理程序方法中,让我们添加 stopService( ) 方法调用,该方法调用使用一个 Intent 对象来停止和销毁我们的 MusicService 组件,该对象使用以下代码引用我们的当前类上下文:
stopService(new Intent(this, MusicService.class));
这个方法调用使用一个 Intent 对象来破坏我们的服务,这个 Intent 对象是我们使用 Java new 关键字在 stopService()方法调用本身内部创建的,这个关键字使用当前上下文和我们的 MusicService 类名引用作为参数来构造一个新的 Intent 对象,如前面一行 Java 代码所示。
请注意,在 Eclipse 编辑器中,我们放在两个 onClick()事件处理方法中的这两个 MusicService 子类方法调用都使用红色波浪下划线突出显示进行了错误标记,如图 17-4 所示。
图 17-4。将呼叫添加到。startService()和。stopService()和 TimePlanet.java 的新意向对象
红色波浪下划线的原因是因为我们还没有创建我们的 MusicService.java 服务子类,正如您所看到的,它在一个 Intent 对象参数中被引用,作为这个 Intent 对象需要传递到的类。
现在让我们创建 MusicService 服务子类,这样我们就可以在 Eclipse 中消除这个错误,更重要的是,因为这是我们实现这个服务组件的工作流程中的下一步!
为我们的 MusicService.java 类创建新的服务子类
让我们来看看在 Eclipse 中创建新 Java 类的另一种方法,将鼠标放在我们当前编辑窗格中看到的 TimePlanet.java 活动子类的红色波浪下划线上,然后选择弹出的助手对话框中显示的选项:Create Class " music service "在 Eclipse 中启动新 Java 类对话框。
这个助手对话框如图 17-5 所示,是我们调用新 Java 类对话框和超类选择对话框的另一种方式。
图 17-5。使用 Eclipse 错误对话框来调用一个新的>类对话框,这样我们就可以创建音乐服务类
我们看到的完成相同工作过程的另一种方法是右键单击 Eclipse Package Explorer 窗格中的 package name 子文件夹,然后选择我们现在熟悉的 New Class 菜单序列,它将为我们访问这些相同的新 Java 类创建对话框。
接下来,让我们填写新的 Java 类和超类选择对话框,将名为 MusicService.java 的新服务子类指定为 Hello_World/src 源代码文件夹中的 chapter.two.hello_world 包中的公共类。
正如你在图 17-6 中看到的,前五个字段已经为我们填写好了,所以只需点击浏览按钮打开一个超类选择对话框,键入一个“s”字符并选择 android.app.Service 类。
图 17-6。使用新的 Java 类对话框和超类选择对话框来指定我们的音乐服务类
在这两个对话框中点击 OK 和 Finish 按钮后,你将会看到 Eclipse 为你编写的公共类 MusicService extends Service (子类)Java 代码,如下面的代码和图 17-7 所示。
图 17-7。使用 IBinder()方法的新服务子类 MusicService】和为我们编码的导入语句
public class MusicService extends Service {
@Override
public IBinder onBind(Intent arg0) {
// TO DO: Auto-generated method stub
return null;
}
}
现在,我们所要做的就是添加我们的服务类生命周期方法调用,以实现我们基于 MediaPlayer 的音乐播放服务,我们将准备好在 Nexus S 模拟器中测试我们的应用。
用 Java 编写我们的音乐服务类服务生命周期方法
既然我们的 MusicService.java 类已经创建并打开以供编辑,让我们从在类的顶部添加一个名为 musicPlayer 的 MediaPlayer 对象开始,我们可以在我们的三个生命周期回调方法中使用它,接下来我们将编写代码。这将涉及下面一行 Java 代码:
MediaPlayer musicPlayer;
我们编写的第一个方法是调用服务类时第一个被访问的方法,那就是 onCreate( ) 方法。该方法创建一个 MediaPlayer 对象,并设置其使用,以及设置任何参数,在本例中为循环参数,如图图 17-8 所示。
图 17-8。编写 onCreate()、onStart()和 onDestroy 服务生命周期方法来控制 MediaPlayer
我们还使用一个 Toast 对象和一个。makeText( ) 方法调用向我们展示了操作系统在为我们创建服务方面所做的工作。onCreate()方法将被声明为 public,因此任何类都可以访问它,并且因为它不返回值而无效,我们需要将方法声明和三行 Java 代码放入该方法中,以完成 MediaPlayer 的设置和配置,如下所示:
@Override
public void onCreate( ) {
Toast.makeText(this, "Music Service has been Created", Toast.LENGTH_SHORT).show( );
musicPlayer = MediaPlayer.create(this, R.raw.music);
musicPlayer.setLooping(true);
}
接下来,我们将编写我们的 onStart( ) 方法,因为当我们的服务启动时,这将是生命周期中的下一个方法。请记住,当调用服务时,会调用 onCreate()方法来创建和设置服务,然后调用 onStart()方法来启动它的运行。因此,我们将利用 onStart()方法启动 MediaPlayer 对象来播放音乐,同样,还包括另一条 Toast 消息,让我们了解服务流程的具体情况。因此,onStart()方法的代码如下所示:
@Override
public void onStart( ) {
Toast.makeText(this, "Music Service is Started", Toast.LENGTH_SHORT).show( );
musicPlayer.start( );
}
最后,我们使用我们的 onDestroy( ) 方法来停止 MediaPlayer 对象,并且还包含一个 Toast 消息,让我们了解关于服务的情况。因此,我们的 onDestroy()方法的代码如下所示:
@Override
public void onDestroy( ) {
Toast.makeText(this, "Music Service has been Stopped", Toast.LENGTH_SHORT).show( );
musicPlayer.stop( );
}
既然我们的 MusicService.java 服务子类已经被编码了,让我们通过点击 Eclipse 顶部标签为 TimePlanet.java 的标签回到我们的 TimePlanet.java 活动子类,如图图 17-8 所示。
使用 TimePlanet.this 细化我们的 TimePlanet 类上下文引用
当您进入 TimePlanet.java 编辑选项卡时,您会注意到您的 startService()和 stopService()方法调用仍然使用 Eclipse 错误级别突出显示元素以红色波浪下划线标出。这是因为 Intent 对象的第一个(上下文)参数需要引用 TimePlanet 类上下文,并且它当前引用的是它所在的视图类,而不是位于我们当前类食物链顶端的活动类。
因此,我们需要修改这段代码,以允许这个上下文一直“看到”我们类的顶部。为此,我们需要在 startService()和 stopService()方法中修改 Intent 对象中的第一个 this 参数,并将该参数改为: TimePlanet.this ,以便 Intent 对象引用 TimePlanet 类的当前上下文。正如你在图 17-9 中看到的,这消除了我们 Java 代码中的所有错误,我们现在准备在 Nexus S 模拟器中编译和运行我们的服务组件智能应用,这样我们就可以测试它,看看它工作得如何。
图 17-9。一旦音乐播放器类就位且添加了 TimePlanet.this reference,无错误 TimePlanet 代码
右键点击你的项目文件夹,使用 Run As Android Application 工作流程启动 Nexus S 模拟器,当应用启动后,点击菜单按钮,选择配置行星活动,一旦出现在屏幕上,点击原子钟按钮,进入行星地球时间活动屏幕,如图图 17-10 所示。
图 17-10。带有音乐服务按钮的 Nexus S 模拟器中运行的 TimePlanet 活动
测试音乐服务组件
现在是时候测试我们的新 MusicService 组件了,它继承了 Android Service 类,看看它的工作效果如何。点击开始音乐服务按钮,无缝聆听美妙的音乐回放。屏幕上会出现一条提示消息,告诉您服务是何时创建的以及何时启动的。
在任何时候,点按“停止音乐服务”按钮,您会注意到音乐停止播放,并且屏幕上会出现一条提示消息,告诉您音乐服务已经停止。继续点击每个按钮几次,以确保应用没有错误,并且服务和 MediaPlayer 对象可以随时启动和停止。
摘要
在本章中,我们仔细研究了一些更复杂的 Android 操作系统特性和概念,包括服务、进程和线程。我们了解了它们之间的关系,它们之间的区别,以及在我们的应用中何时使用它们。
首先我们看了一下 Android 服务及其基本形式和规则,包括启动的服务和绑定的服务之间的区别。我们查看了一些关键方法,如 startService()、stopService()和 bindService(),稍后我们在自己的服务类中实现了这些方法。我们了解了服务类生命周期及其运作方式。
然后,我们仔细研究了 Android 进程和线程,因为这个主题与服务密切相关,对于 Android 开发人员来说,理解这个主题非常重要。我们研究了如何在 Android Manifest XML 文件中指定一个流程,以及流程的生命周期。
我们了解了不同类型的流程,以及 Android 操作系统如何按照重要性对它们进行排序,我们还了解了不同类型的 Android 组件(如活动和服务)如何适应 Android 流程使用的优先级排序系统。
我们研究了在现有类中使用线程和创建一个新的服务子类来进行后台处理之间的权衡。我们还学习了 HandlerThread 和 AsyncTask 类,如果您决定在 Android 编程中获得更高的水平,并在应用组件中利用线程,您可以使用这些类。
最后,我们从零开始编写了自己的服务子类,名为 MusicService.java。我们将用户界面元素添加到 TimePlanet 活动中,这样我们就可以从我们的原子钟显示屏上控制音乐服务,无论如何它都需要一些背景音乐。
我们学习了如何在 AndroidManifest.xml 文件中添加一个
在下一章中,我们将学习所有关于广播接收器的知识,它可以用来向您的 Android 应用组件以及其他组件发送重要的应用和系统相关消息。
十八、广播接收器:Android 应用间通信
在这一章中,我们 将仔细看看 Android 的广播接收器类。这个类专门用于 Android 组件之间的通信,每个组件都是主要 Android 类(Activity、Service、BroadcastReceiver 等)的子类。)正如我们在本书的 Java 编码经验中所看到的。
这可以包括您自己的应用组件之间的通信,但更广泛地用于不相关的应用组件之间的通信。这意味着与其他应用的通信。事实上,它更常用于在您的应用和 Android 操作系统中包含的组件之间进行通信。
想想看,任何人的 Android 手机上最常用的组件(应用)都是手机自带的组件,因此是 Android 操作系统不可或缺的一部分。没有人可以否认,人们在日常使用他们的 Android 设备时会在很大程度上使用他们的 Android 设备的电话拨号器、日历、闹钟、定时器、电子邮件客户端、浏览器、屏幕保护程序、壁纸、铃声等等,无论是智能手机、平板电脑、电子阅读器、手表、机顶盒还是 iTV 电视机。
在这一章中,我将尝试向您展示在您的 Android 应用中实现广播接收器(通常简称为接收器)的各种方法,在本例中是在我们的 Hello World 应用中。也就是说,这个特定的 Android 主题在某种程度上超出了所包含的系统(Eclipse IDE 及其仿真器),我们需要保持在该系统中,以确保我们的每个读者都可以同步跟进。
这是因为我们的每个读者都有不同的 Android 设备硬件,一旦我们开始在不同的 Android 设备功能和不同的外部应用之间进行广播(使用 BroadcastReceivers ),他们就会与不同的开发人员和应用合作。
出于这个原因,我必须非常小心地为本章选择我的 Java 代码示例,因为它们需要支持市场上每种类型和型号的 Android 设备都保证支持的设备特性。
幸运的是,这是一本关于 Android 的介绍性书籍,所以我可以在本章中介绍 BroadcastReceiver 类的理论和规则。
我们将学习如何实现广播接收器方法(Java)和标签(XML ),这样您将大致了解如何在自己的 Android 应用中实现广播接收器,以及从哪里开始。
Android 广播接收机:基本概念和类型
Android 的 BroadcastReceiver 类是 java.lang.Object 的直接子类,这意味着它位于 Android 类层次结构的顶部,这可能是主要操作系统功能所期望的。
它是 android.content 包的一部分,所以它在 import 语句中的完整使用路径是Android . content . broadcast receiver,我们将在本章后面的 Java 代码中看到。
就像有两种不同类型的 Android 服务(启动和绑定)一样,也有不同类型的可以接收的广播:正常广播和有序广播。
正常的广播是异步的,因此是自由浮动的,不与操作系统环境中的任何东西绑定(同步)。订阅普通广播的任何接收器方法都可以以任何未定义的顺序自由运行。
因为 Android 是一个多线程的多任务操作系统,这也意味着正常的广播可以通过它们的接收器方法在完全相同的时间(并行)被处理。
这意味着正常的广播本质上更有效,因为它们不基于任何其他系统事件,并且操作系统具有以最佳方式处理它们的自由。
然而,这也意味着正常的广播不能利用任何返回的结果(任何类型的返回值),或者终止任何 API 或组件。
正常的广播是通过使用 Context.sendBroadcast( ) 方法发送的,我们将在本章的后面看到,当我们开始为添加到 Hello World 应用基础结构中的广播接收器编写 Java 代码时。
另一方面,有序广播按顺序传送,一次传送给一个接收器。当每个接收器执行有序广播时,它可以将结果传播给下一个接收器。或者,它也可以选择在任何给定的接收器对象上中止广播,这样广播就不会传递到任何其他接收器。作为程序员,你可能会看到事件是如何在处理链中“冒泡”的。
可以使用 AndroidManifest.xml 中的< receiver >标签内的<意图过滤器>的 android:priority 属性来控制广播接收器的处理顺序,您将在该标签中定义广播接收器。在这一点上,您可能不会感到惊讶,因为大多数组件关键的应用基础设施将在您的 AndroidManifest 中定义,以便 Android OS 可以在启动您的应用时设置这些进程和内存空间。
值得注意的是,指定完全相同优先级的
通过使用context . sendorderedbroadcast()方法发送有序广播。
值得注意的是,即使在某些涉及正常广播的场景中,Android 操作系统也可能会恢复为一次一个接收器地传送您的正常广播,就像它是一个有序的广播一样。如果 Android 操作系统认为这种广播方法将为当前操作环境配置提供更多的处理或内存优化结果,则可能会发生这种情况。
另一个重要的考虑因素是可能需要创建进程的广播接收器。一次只应运行这些广播接收器中的一个,以便操作系统可以避免新进程使操作系统过载,每个新进程都会占用内存和处理资源。
在这种情况下,那些无序的(正常接收器)广播语义将总是成立的;这些创建进程的广播接收器将不能返回任何结果,也不能中止它们的广播组件。
总之,当你的 BroadcastReceiver 类通过你的 AndroidManifest.xml
如果您需要随时查看 Android 应用的基本生命周期和相关信息(当您正在学习 Android 操作系统及其运行方式时,偶尔这样做是个好主意),您可以在 Android 开发者网站上找到这些信息,网址如下:
http://developer.android.com/guide/components/fundamentals.html
接下来,我们将回顾为什么我们需要将我们的 Activity Intent 对象和我们的 BroadcastReceiver Intent 对象分开,然后,我们将探讨 Android 操作系统基础架构中的安全性、广播接收器生命周期和广播接收器处理(流程)等问题。一旦我们了解了所有这些事情,我们就可以开始编码了!
广播你的意图:活动与广播接收者意图
我们使用 Intent 类和 Intent 对象来发送和接收这些广播。正如我在关于意图的第十六章中提到的,意图广播接收器引擎完全独立于使用 Context.startActivity()方法启动活动的意图。
因此,广播接收机无法处理与一起使用的意图。startActivity( ) 方法。事实上,你的广播接收机甚至没有察觉到一个活动意图的存在!类似地,当您广播您的 BroadcastReceiver 意图时,那些意图对象将永远不会遇到任何 Activity 子类,因此永远无法启动任何 Activity 子类。
Android 操作系统需要将这些类型的意图分开的主要原因是因为这两种类型的组件和它们调用的操作利用了两种非常不同类型的 Android 进程。
正如我们所知,启动一个有意图的活动是一个前台流程操作,它发生在主要的 UI 流程和线程中。这种类型的 Android 进程直接修改用户当前实时交互的内容。
另一方面,接收意向的广播是一个后台进程操作,用户并不知道,因此在我们在前一章中了解的进程优先级排序中,它没有那么高的优先级。正如你所看到的,我有一个很好的理由进入所有关于进程和线程的技术信息,因为它不仅仅适用于利用 Android 中的服务。
安全广播:广播接收机安全考虑
您可能已经注意到,广播接收器是通过 Android 上下文类 API 使用的,例如,要调用广播接收器,您可以使用它们的方法调用上下文对象/类,如下所示: Context.sendBroadcast( ) 或Context . sendorderedbroadcast()。
因此,广播接收机的核心访问是一个跨应用的实现,因此,您应该非常明智地考虑您自己的应用之外的其他应用如何滥用您的 Android 广播接收机的实现。本节有效地概述了在 Android 应用中使用广播接收器时,您可能需要记住的一些主要问题。
首先,Android Intent 命名空间是全局的。出于这个原因,您需要确保您的 Intent 动作名称以及其他字符串常量都封装在您自己的名称空间中。如果不遵守这条规则,可能会无意中与其他应用发生冲突。
每当实现. register receiver(BroadcastReceiver,IntentFilter)方法时,请注意任何其他 Android 应用都可以向该注册的 broadcast receiver 发送广播。事实上,通过使用 BroadcastReceiver 权限,您可以准确地控制谁可以向您注册的 Receiver 对象发送广播,我们将很快介绍这一点。
当您在您的应用 AndroidManifest.xml 文件定义中发布一个
有一种方法可以防止其他应用向您的应用的
如果你决定使用。sendBroadcast(Intent) 方法或其相关方法,请注意任何其他应用都可以接收这些广播。
您可以通过使用权限来控制谁可以接收广播。如果您使用的是 Android 4(冰激凌三明治)或更高版本,您还可以通过使用intent . set package()方法调用将您的广播限制到任何单个应用。
这里需要注意的是,当您使用 LocalBroadcastManager 时,这些安全问题都不存在,我们将在本章的下一节中讨论这个问题,因为使用这个类的意图广播永远不会超出您当前的进程。
可以在广播的发送方或接收方实施广播访问权限。在等式的发送端实施权限的方法是使用提供一个非空权限参数。sendBroadcast(Intent,String) 或者,如果您使用的是有序广播,请使用以下命令:。sendOrderedBroadcast(Intent,String,BroadcastReceiver,android.os.Handler,int,String,Bundle) 方法调用。
只有通过在其 AndroidManifest.xml 文件中的 < uses-permission > 标签请求获得该许可常数的广播接收机,才能接收您的安全许可广播。
在接收广播时实施权限的方法是,在注册接收者时,再次提供一个非空权限。
这是在调用 Java 时完成的。register receiver(broadcast receiver,IntentFilter,String,android.os.Handler) 方法,或者,在 AndroidManifest.xml 文件中的静态 < receiver > 标记中。
只有先前被授予此权限的 BroadcastReceivers 才能向该 receiver 对象发送 Intent 对象。可以通过使用 Android 应用的 AndroidManifest.xml 文件中的 < uses-permission > 标记选项请求那些权限来授予权限。
广播接收机生命周期:规则和条例
BroadcastReceiver 对象只在接收者调用期间有效。【上下文,意图】on receive方法。一旦 Java 代码从这里返回。在 Receive()方法功能上,操作系统将认为该对象已经完成,它将不再是活动的。
这个广播接收器处理周期对于您在中究竟能做什么有着重要的意义。onReceive(Context,Intent) 方法调用实现。
您编写的任何需要异步操作的 Java 代码都是不允许的。这是因为您需要从函数返回来处理异步操作。但是,此时,您的 BroadcastReceiver 将不再是活动的,因此系统可以在异步操作完成之前终止该进程。
此外,您将无法显示任何对话框,也无法从 BroadcastReceiver 中绑定到任何服务类。
如果你需要在这种情况下显示一个对话框,你仍然可以通过使用 android.app 包中的 Androidnotification manager类 来实现这个目标。Android . app . notification manager 是 java.lang.Object 的子类,这个类通知您的用户后台发生的事件。通知可以有三种不同的格式:一种是存在于状态栏中的持久图标,可以通过你的启动器访问;第二种方法是打开或闪烁用户 Android 设备上的 LED 灯;最后,通过闪烁背光、播放声音甚至振动设备来提醒用户。有关 Android 通知管理器类的更多信息可以在以下 URL 找到:
http://developer.android.com/reference/android/app/NotificationManager.html
如果您需要启动一个服务类,正如我们在上一章中看到的,您必须利用 Context.startService( ) 方法调用向服务子类发送一个命令。
处理广播:广播如何影响 Android 进程
当前正在执行 BroadcastReceiver 对象的 Android 进程将运行 BroadcastReceiver 对象内部的 Java 代码。onReceive(上下文,意图)方法。Android 操作系统认为这是一个高优先级的前台进程,因此它将由操作系统保持活动和处理,除非可能在内存资源极度短缺的情况下。
一旦您的 Java 代码完成。调用 Receive()方法时,该广播接收器不再是活动的,它的宿主进程等级被重新校准,以便它与该进程中运行的其他应用组件一样重要,但不是更重要。
这一点尤其值得注意,因为如果该进程只是为了托管 BroadcastReceiver 而创建的(通常情况下),对于用户从未交互过或者甚至最近没有交互过的应用,那么当从。在 Receive()方法执行时,操作系统会将该进程视为一个空的进程优先级。
正如我们在前一章中了解到的空进程优先级,这意味着 Android 操作系统很可能会主动终止该进程,以便操作系统资源可用于其他更重要的进程。
这意味着,对于长时间运行的操作,您应该经常将服务与 BroadcastReceiver 结合使用,以在函数操作的整个过程中保持包含该服务的进程处于活动状态。
应用内部的广播:LocalBroadcastManager
如果您不需要在两个不同的 Android 应用之间发送 BroadcastReceiver,那么您最好使用 LocalBroadcastManager 类来利用 broadcast receiver 函数,而不是使用前面章节中描述的全局方法。请注意,如果您需要支持 3.0 之前的 Android 操作系统(例如最初的 Amazon Kindle Fire 的 2.3.7),这些操作系统不支持 LocalBroadcastManager 类。
LocalBroadcastManager 类提供了更有效的本地广播实现,因为不需要进程间通信。这也让你不用考虑其他 Android 应用接收或发送你的广播的各种安全问题。
这样做的原因是,通过使用这个 LocalBroadcastManager 类,您将知道您在应用中广播的数据不会离开应用的范围,因此,您不需要担心任何私人数据的泄漏。
第二,通过使用 LocalBroadcastManager,其他应用就不可能向你的应用发送任何广播,所以你也不必担心你的应用中有任何安全窗口会被其他程序员利用。
最后,就内存和处理而言,使用 LocalBroadcastManager 类比在整个 Android 操作系统中发送全局广播要高效得多。
注册广播接收器:动态与静态注册
有两种完全不同的方法来注册您的 Android BroadcastReceiver 对象,以便在您的 Android 应用中使用。
一种注册形式称为广播接收器注册的静态方法,这种方法是您最熟悉的格式,它涉及到使用 <接收器> 标签在您的 AndroidManifest.xml 文件中“预先”注册广播接收器以供使用(这就是为什么它被称为静态注册)。
另一种方法叫做动态广播接收器注册,这是使用 Java 代码而不是 XML 标记完成的。之所以称之为动态,是因为它是在 Java 代码中完成的,与此同时,您正在执行与 BroadcastReceiver 类相关的所有操作,而不是预先(静态)在 AndroidManifest.xml 应用引导文件中完成。
如果您想利用动态 BroadcastReceiver 注册,动态注册 BroadcastReceiver 类实例的方法是利用context . register receiver()方法调用。
需要注意的是,如果你在你的 Activity.onResume( ) 方法代码中注册了一个 BroadcastReceiver,你也应该记得在你的 Activity.onPause( ) 方法代码中取消对的注册。
这样做的原因是为了减少系统资源的浪费,因为您不希望在活动暂停时接收任何 Intent 对象,所以您添加了一个 onPause()方法并注销了接收者,这样您就不会让操作系统尝试将 Intent 对象发送到一个当前没有使用的活动。
确保不要犯在 Activity.onSaveInstanceState()方法中注销 BroadcastReceivers 的常见错误,因为如果用户在历史堆栈中向后移动,将不会调用此方法。
现在是时候尝试在我们自己的 Hello World Android 应用中实现 BroadcastReceiver 类和方法了。
在我们的应用中实现广播接收器
我们需要做的第一件事是为在 Hello World 应用中实现 BroadcastReceiver 打下一些基础,例如:创建字符串常量、XML 用户界面设计、AndroidManifest 条目等等。
当我们将添加到 TimePlanet.java 活动中的警报功能被触发并且警报响起时,我们的 BroadcastReceiver 将发送一条消息。因为计时器是与时间相关的,我们将把这个功能添加到 UI 屏幕上,在那里添加它是最符合逻辑的—
我们的星球时间 UI 屏幕!
现在让我们添加新的字符串常量,这样我们就可以标记将启动闹钟倒计时的按钮 UI 元素和文本编辑字段,这样它就会有一个提示,告诉我们的最终用户我们希望他们在这个数据字段中输入什么类型的信息。
我们将为按钮对象贴上标签:启动计时器倒计时,并通过编写以下 XML 标记,使我们的提示为:输入秒数:
<string name="timer_hint_value">Enter Number of Seconds</string>
<string name="timer_button_value">Start Timer Countdown</string>
这两个新的字符串常量进入我们的 strings.xml 文件,该文件在我们的项目文件夹下的/res/values 文件夹中,如图 18-1 所示。
图 18-1 。添加我们的按钮 UI 元素标签字符串常量和编辑文本 UI 元素提示字符串常量
既然我们可以在 TimePlanet.java 活动的新 UI 设计中引用这些字符串常量,那么是时候将这些新 UI 元素添加到 activity_time.xml 文件中了。
使用 XML 设计我们的警报广播接收器用户界面
在 Eclipse 中心区域的编辑窗格中打开 activity_time.xml 文件,右键单击/res/layout 文件夹中的文件名,并选择 Open(如果愿意,也可以使用 F3 功能键)。
我们将把我们的 setAlarm EditText 用户界面小部件放在我们的模拟时钟的正下方,这样屏幕上所有的按钮 UI 元素将保持组合在一起。在< AnalogClock >标签下添加一个 < EditText > 标签,并添加参数,这些参数将对其进行配置,类似于我们为 activity_config.xml 用户界面屏幕创建的 EditText 字段。
让我们使用透明度为 0.75 的 alpha 值, 12 ems 的文本大小,白色或 #FFFFFF,的背景颜色和粗体字体的文本样式。
使用Android:layout _ gravity = " center "在 UI 设计中将数据输入字段居中,使用Android:layout _ margin top = " 10dp "将我们的数据字段与 AnalogClock UI 元素隔开一点。
最后,让我们将我们的 android:typeface 参数指定为等宽,并将 numberDecimal 的 android:inputType 指定为计时器倒计时的秒数。
最终的 EditText XML 标记应该包含以下参数:
<EditText android:id="@+id/setAlarm"
android:hint="@string/timer_hint_value"
android:inputType="numberDecimal"
android:ems="12"
android:alpha="0.75"
android:layout_marginTop="10dp"
android:background="#FFFFFF"
android:textStyle="bold"
android:layout_gravity="center"
android:typeface="monospace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
在 Eclipse XML 编辑器中,我们在每行放置两到三个参数以节省空间,这样我们可以在一个屏幕上看到我们用户界面屏幕定义的所有 XML 代码,如图 18-2 所示。
图 18-2 。添加我们的按钮和编辑文本标签,并配置它们的参数用于用户界面设计
接下来,我们需要在 setAlarm EditText 元素下添加我们的 startCounter Button 对象,这样,一旦用户添加了持续时间值,用户单击以启动计时器运行的按钮元素就在它的正下方。
在< EditText >标签下添加一个 <按钮> 标签,并添加将配置该按钮的参数,类似于我们已经在当前用户界面屏幕下创建的三个按钮字段。
让我们将 android:id 参数设置为我们将在 Java 代码中引用的 startCounter ,并将 android:text 参数设置为引用我们之前创建的名为 timer_button_value 的字符串常量。
让我们使用黄色或 #FFFFAA 的背景颜色和Android:layout _ gravity = " center "的居中参数来将 UI 设计中的数据输入字段居中,并确保所需的 android:layout_width 和 android:layout_height 参数被包括在内并被设置为 wrap_content 。
最终的按钮标签 XML 标记应包含以下参数:
<Button android:id="@+id/startCounter"
android:text="@string/timer_button_value"
android:textColor="#FFFFAA"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
既然我们已经为计时器用户界面元素实现了 XML 标记,那么让我们也实现 XML 标记,我们将需要这些标记来声明我们的 BroadcastReceiver 组件子类,以便在我们的应用中使用。如您所知,这是在 AndroidManifest.xml 文件中完成的,所以现在让我们开始吧。
添加我们的 alarm receiver broadcast receiver Android 清单 XML
在 Eclipse 中心区域的编辑窗格中打开您的 Androidmanifest.xml 文件,方法是右键单击 Hello_World 项目文件夹底部的文件名,并选择打开菜单选项(如果您愿意,也可以使用 F3 功能键)。
让我们在清单底部的
为我们新的 BroadcastReceiver 子类添加一个 < receiver > 标记,我们接下来将用 Java 对其进行编码,并使用一个 name 参数引用应用组件名称的完整路径名,使用以下标记:
<receiver name="chapter.two.hello_world.AlarmReceiver" />
完整的 AndroidManifest.xml 文件声明了我们最新的 Hello World 应用组件,包括六个 Activity 组件,以及一个服务和一个 BroadcastReceiver 组件,如图 18-3 所示。
图 18-3 。为我们的 AlarmReceiver BroadcastReceiver 子类添加一个< receiver >标签到 AndroidManifest.xml
现在,我们已经设计了警报用户界面元素,并在清单中声明了广播接收器,是时候使用 Java 代码在我们的 TimePlanet.java 活动子类中编写警报控制用户界面元素和方法了,它们将在该子类中显示。
使用 Java 编写 startTimerButton 和 startTimer()方法的代码
让我们走一条程序员的捷径,用最简单的方法实现我们的startTimerButtonButton UI 对象!复制并粘贴 startMusicServiceButton 代码行(全部六行)到它们下面。
接下来将 startTimerButton 名称改为 startTimerButton,UI XML ID 引用由 R.id.startServiceButton 改为 R.id.startCounter,最后将 startService(new Intent(this,class))方法调用改为startTimer(view);方法调用。
现在,Eclipse 将红色错误突出显示我们正在使用的这个新方法名,至少直到我们对这个新方法进行编码,这是我们下一步要做的,在我们活动的底部。startTimerButton 按钮用户界面元素的新事件处理代码如图 18-4 所示,还有public void start timer(View View)方法 Java 代码,我们接下来将详细讨论这些代码。startTimerButton Java 代码块应如下所示:
startTimerButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startTimer(view);
}
}
图 18-4 。在 TimePlanet.java 活动子类中编写用户界面按钮和 startTimer()方法
我们将使用 public 访问来声明我们的 startTimer()方法,因此它可供公共使用,并使用 void 返回值,因为它不向调用实体返回任何内容,在本例中,单击 startTimerButton UI 对象。
编写我们的 startTimer( ) Java 方法
接下来,我们将声明我们的 EditText 用户界面元素以供使用,我们将它命名为 alarmText ,并使用 findViewById( ) 方法来引用它,以通过 R.id.setAlarm 指向我们的 setAlarm EditText 标签 XML 定义。
请注意,如果您还没有导入 EditText 类来使用,这段代码将会有红色下划线突出显示,您可以将鼠标悬停在它上面,让 Eclipse 为您编写这段 import 语句 Java 代码。
接下来,我们声明一个名为 i 的整型变量,并将其设置为一个整型对象,我们称之为。parseInt( ) 方法。这个方法通过调用解析一个整数值。toString( ) 转换方法,关闭。getText( ) 方法,从 alarmText EditText 对象中调用该方法,以检索用户输入到数据字段中的文本值。所有这些都是在一行 Java 代码中完成的,如下所示:
int i = Integer.parseInt(alarmText.getText().toString());
接下来,我们声明一个名为 intent 的 intent 对象,并构造一个新的 Intent 对象,使用 new 关键字,使用当前上下文 this 和 AlarmReceiver.class 的一个目标组件,我们将在下一节中对其进行编码。现在,Eclipse 红色下划线突出显示了这个引用,因为我们还没有创建和编码这个 BroadcastReceiver 子类。
接下来,我们将创建一个 Android 挂起内容对象,命名为告警内容,并通过调用来加载 Android 告警功能。getBroadcast( ) 方法 off ofofpending Intent类使用参数配置我们的 alarmIntent 与当前上下文,一个报警请求代码,我们的 intent Intent 对象,我们刚刚在前面的代码行中创建,以及一个零值用于标志参数,因为此时我们没有将标志值传递给 getBroadcast( ) 方法调用。
请注意,如果您还没有导入 PendingIntent 类来使用,这段代码将会有红色下划线突出显示,您应该将鼠标悬停在它上面,让 Eclipse 为您编写导入语句 Java 代码。如果您想更详细地研究这个 PendingIntent 类,可以在 Android 开发者网站上找到信息,网址是:
http://developer.android.com/reference/android/app/PendingIntent.html
接下来,我们将创建一个 Android AlarmManager 对象,我们将其命名为 alarmManager ,并使用 getSystemService( ) 方法加载该对象,该方法从 AlarmManager 类中调用,并将 ALARM_SERVICE 常量值作为参数传递。这为我们设置了一个警报功能,这是我们可以调用的许多 Android 操作系统功能之一,在这种情况下,通过使用 AlarmManager API。
请注意,如果您还没有导入 AlarmManager 类来使用,这段代码将会有红色下划线突出显示,您应该将鼠标悬停在它上面,让 Eclipse 为您编写导入语句 Java 代码。如果您想更详细地研究这个 AlarmManager 类,可以在 Android 开发者网站上找到信息,网址如下:
http://developer.android.com/reference/android/app/AlarmManager.html
接下来,我们需要使用 alarmManager 类的来配置我们刚刚创建的 alarmManager 对象。方法设置()。我们将通过。set()方法需要三个参数,一个整数(在这种情况下是系统常数),它表示报警的类型,一个以毫秒为单位的触发时间,它由一个长值表示,以及一个挂起内容操作,在我们的情况下是我们在此之前创建的两行 Java 代码的告警内容。
AlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (I * 1000), alarmIntent);
注意,我们在。set()方法调用,使用system . current time millis()方法,该方法获得以毫秒为单位的当前系统时间,并将其与用户在我们的 EditText UI 元素中输入的秒数相加,乘以 1000,将该秒值转换为毫秒,从而得到触发时间!
startTimer()方法中的最后一行 Java 代码使用一个 Android Toast 对象和一个. makeText()方法调用,通过以下 Java 代码将我们的警报设置确认消息发布到活动屏幕:
Toast.makeText(this, "Alarm set in " + i + "seconds", Toast.LENGTH_SHORT).show();
同样,我们使用字符串值和我们的整数值 I 连接来显示一条 Toast 消息,告诉用户在 X 秒内用他们输入的值设置警报,然后在最后使用方法链接将. show()方法调用附加到。makeText()方法调用,使整个 Toast 对象构造只使用一行 Java 代码。
我们已经准备好创建并编写我们的 AlarmReceiver 类,它将实现我们的on receive()broadcast receiver 方法,以使一切协同工作。
创建 AlarmReceiver BroadcastReceiver 子类
将鼠标放在图 18-4 中红色波浪下划线突出显示的地方,然后选择创建 AlarmReceiver 类选项,让 Eclipse 创建如图图 18-5 所示的新 Java 类对话框。
图 18-5。让 Eclipse 使用超类选择对话框创建一个名为 AlarmReceiver 的新 Java 类
因为 Source folder:,Package:,and Name: data 字段已经为我们填写好了,只需点击 Browse 按钮,在中输入 a " b ,选择位于超类选择对话框顶部的字段,然后向下滚动到broadcast receiver-Android . content选择,在对话框的匹配项:部分找到,并选择它,如图所示
最后,点击 OK 按钮,返回新建 Java 类对话框,然后点击完成按钮,创建你的 AlarmReceiver BroadcastReceiver 子类,如图图 18-6 所示。
图 18-6。我们的 AlarmReceiver.java broadcast receiver 子类基础设施展示了一个 onReceive()方法
请注意,已经为我们创建了on receive()BroadcastReceiver 方法,其中包含了适当的访问控制和参数,我们可以编写 Java 代码来实现我们的 broadcast receiver 方法的警报相关功能。
接下来,我们将用我们的代码替换图 18-6 中显示的 TODO 自动生成方法存根,以便在接收到我们的广播时向屏幕发送消息。
编码我们的 alarmreceiver broadcastreceiver 子类
让我们使用 Android Toast 类在调用 onReceive()方法并接收到广播给它的 Intent 对象时向屏幕发送一条消息。
使用烤面包片。makeText( ) ,方法使用传入 onReceive()方法的上下文对象作为其第一个参数,然后指定文本消息警报通知,最后是 Toast。LENGTH_SHORT 为方法参数的持续时间常数,然后链一个。show( ) 方法调用在 java 代码语句的末尾使用下面单行 Java 代码:
Toast.makeText(arg0, "ALARM NOTIFICATION", Toast.LENGTH_SHORT).show();
完整的 AlarmReceiver BroadcastReceiver 子类及其 onReceive()方法如图图 18-7 所示。
图 18-7。编写我们的 onReceive()方法,在 AlarmReceiver.java 类中显示我们的警报消息
AlarmReceiver BroadcastReceiver 子类的 Java 代码如下所示:
package chapter.two.hello_world;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context arg0, Intent arg1) {
Toast.makeText(arg0, "ALARM NOTIFICATION", Toast.LENGTH_SHORT).show();
}
}
现在是时候在 Android Nexus S 模拟器中测试我们的广播接收器实现了,看看我们在本章中编写的所有 XML 和 Java 代码是否能正常工作。
右键单击 Hello_World 项目文件夹,选择 Run As Android Application 菜单序列启动 Nexus S 模拟器,这样我们就可以测试我们最新的应用版本。
当主屏幕出现时,单击菜单按钮以启动选项菜单,然后选择配置行星菜单选项以启动配置行星活动屏幕。
在屏幕的右下角,单击原子钟按钮启动您的 TimePlanet.java 活动用户界面屏幕,并在行星地球模拟时钟用户界面元素下的文本数据字段中输入以秒为单位的计时器持续时间值。
接下来,单击位于输入秒数数据输入字段下方的启动计时器倒计时按钮,发送广播并启动 AlarmManager 对象。该屏幕的用户界面如图图 18-8 所示。
图 18-8。测试运行在 Nexus S 仿真器上的 AlarmReceiver BroadcastReceiver 子类
正如您所看到的,我们现在实现的 BroadcastReceiver 应用组件运行良好,在指定倒计时秒数的 Toast 消息出现后,我们的警报通知 Toast 消息随后出现在屏幕上,当然,这是在我们为警报功能指定的秒数过后。
恭喜你!您已经成功实现了三种主要类型的 Android 组件:活动、服务和广播接收器!酷!休息一下,喝杯冷饮,放松一下!
现在,我们已经成功地在我们的 Android 应用中实现了一个广播接收器,我们需要学习如何实现的唯一组件类型是一个内容提供者,我们将在下一章向我们的 Hello World Android 应用添加内容提供者功能时了解它!
摘要
在本章中,我们仔细观察了 Androidbroadcast receiver类及其 onReceive( ) 方法。
我们研究了两种不同类型的广播接收器广播,即正常广播和有序广播,了解了它们之间的区别,以及何时需要实施每种类型的广播。
接下来,我们研究了广播接收器的安全性考虑,这些考虑源于广播接收器被发送到您的应用之外,并且也允许其他开发人员的代码进入您的应用。
然后我们看了 BroadcastReceiver 生命周期,以及关于如何允许您使用 onReceive()方法调用和 Java 代码函数来处理各种类型的 Java 编程目标的基本规则和规定。
接下来,我们仔细研究了 Android 操作系统如何处理广播接收器,以及您对它们的使用将如何影响您的应用处理优先级,我们在第十七章中了解到了这一点。
我们仔细研究了用于封闭应用环境中本地化广播的 LocalBroadcastManager 类,以及如何在 Android 操作系统中动态(在 Java 方法调用中)和静态(在我们的 Android Manifest XML 标记中)注册广播。
最后,我们冒险在自己的 Hello World 应用中实现了一个广播接收器,以便在 TimePlanet 活动中实现一个计时器功能。我们在 activity_time.xml 文件中添加了 UI 元素,在 AndroidMainfest.xml 文件中添加了一个新的
在下一章中,我们将学习 Android 中所有的内容提供者,以及 SQLite 数据库引擎。我们把最复杂的主题留到了最后,这样在我们深入到像使用 Android 的内容提供者 API 构建 DBMS 引擎这样复杂的事情之前,您就可以最大限度地体验 Android 了。抓紧你的帽子!开始了。
十九、Android 内容供应器:访问数据存储
在这一章中,我们将学习关于 Android 操作系统的一个更高级的话题:内容供应器。内容提供者是指:数据存储。我们已经熟悉了 android.content 包,之前已经使用过它的类,我们将会更加熟悉 android.database 包,以及 android.provider 包。
Android 操作系统中最丰富的内容提供者之一是数据库 APISQLite,它是 Android 不可或缺的一部分。因此,本章还将涵盖 SQLite 数据库引擎的内在高级主题、设计原则和 Android 内容供应器 API 实现概述。
本章的主题比我们在前面章节中学习的其他主题更高级的原因是,本章包含两个不同高级主题的信息。
第一个是 SQL 和 SQLite 数据库设计概念,Android 的内部 ContentProvider 基于此,第二个涵盖了 Android 中当前可用的所有不同类型的内容提供者。
首先,我们将在一个相当高的层次上介绍数据库设计概念,这样你就有了关于我们将在本章的其余部分做什么的基础知识。
然后,我们将讨论不同类型的 Android 内容供应器,以及它们的概念和技术,以及如何通过统一资源标识符(URI)路径构造来访问它们。
接下来,我们将看看 SQL 数据库引擎和 Android SQLite 数据库包和 API。最后,我们将在 Hello World 应用中实现一个 Android 内容提供者,看看它们是如何协同工作的。
数据库基础:概念和术语
正如大多数程序员已经知道的那样,数据库管理系统,或 DBMS ,是一个高度结构化的数据存储系统,或“引擎”,它可以以表格格式存储有价值的数据,可以很容易地访问和更新。世界上最流行的开源 DBMS 数据库编程语言之一叫做 SQL ,代表结构化查询语言。
结构化部分来自于数据库被安排成表格格式的事实,而查询部分来自于这些数据表被设计成对某个数据值进行搜索的事实。语言部分来自于这样一个事实:SQL 已经发展成为一种如此复杂的数据库编程语言,以至于关于这个永恒的 RDBMS(关系数据库管理系统)主题的书籍可能比 Android 操作系统上的书籍要多得多。
对于那些从未使用过数据库技术的读者,我将在这里回顾一下基础知识,这样我们就可以使用相同的知识库。您可能熟悉的流行数据库软件包包括 Access 和 FileMaker,并且许多人可能使用过 Excel 以表格格式存储数据,这与数据库非常相似,只是更加直观。
MySQL 等关系数据库中的数据使用表存储,这些表支持数据的行和列。这类似于 Excel 这样的电子表格,除了数据库通常不会像在电子表格中一样一次全部可见,尽管一旦我们学习了所有相关的编程,如果您愿意,您可以生成报告来实现这一最终结果!
每个关系数据库表列在你的数据库记录结构中总是包含相似的数据类型和数据分类,这通常被称为数据库字段。
这意味着相反,数据库表中的每一个行将代表一个完整的数据记录。因此,一般来说,当您写入数据库记录时,当您第一次添加该记录时,您将写入一整行或一条完整的数据记录,但是当您搜索数据库信息时,您通常是通过某一列或字段来查找特定的数据或信息。
如果您的数据库表中有大量的数据字段(列),那么在数据库设计方法中,您可能希望有不止一个数据库(数据表)。
在现实世界的数据库设计中,其理论很大程度上超出了入门书籍的范围,出于访问(搜索)性能和组织的原因,您将希望有多个单一的数据库结构。事实上,Android 操作系统使用不止一种数据库结构来存储和访问终端用户的信息,这一点我们将在本章后面很快看到。
拥有多个数据库的方法是让每个数据库(表)中的每个记录都有一个唯一的键(唯一索引)。这样,使用该键,单个数据记录的信息可以跨越多个数据库表。在 Android 中,这个键被称为 ID ,并且总是通过 Android 的 SQLite 数据库中的常量 "_ID" 来指定。例如,如果您的 key 或 _ID 值为 137,您的电子邮件信息和电话信息可能在两个不同的表中,但存储在相同的 key (index)值下,因此与您的 Android 用户帐户准确关联。
MySQL 和 SQLite:开源数据库引擎
MySQL 是目前世界上最流行的开源关系数据库管理系统(RDBMS)引擎之一。如果你拥有自己的服务器硬件,你可以使用MySQL.com
网站下载并安装 MySQL,然后你就可以用很少的软件购买费用托管大量的信息数据库。
SQLite 是 MySQL RDBMS 引擎的一个小得多的版本,设计用于嵌入式硬件,如平板电脑、智能手机、电子书阅读器、iTV 电视机、手表、汽车仪表盘、机顶盒、家庭媒体中心和其他通常称为互联网 2.0 的消费电子设备。有趣的是,所有的 HTML5 浏览器中都有 SQLite。
SQLite 代表结构化查询语言 Lite ,是作为 Android 操作系统一部分的开源数据库引擎。Android 中有一个 SQLite API(包),它包含了实现 SQLite API 所需的所有 RDBMS 函数。这些包含在 android.database.sqlite 包中的一系列类和方法中。
SQLite 是专门为嵌入式系统设计的,类似于 JavaME (Micro Edition),因此只有 256KB 的内存,用于托管关系数据库引擎实现。
SQLite 支持最小的、标准的关系数据库功能和特性集,如通用 SQL 语法、数据库事务和预处理语句,这足以为 Android 提供强大的数据库支持。
SQLite 支持三种不同的数据类型:TEXT(Java 中称为字符串值)、INTEGER(Java 中称为 long 值)和REAL(Java 中称为 double 值)数据类型。
使用 SQLite 时,所有其他数据类型在保存到数据库字段之前必须转换为这些兼容的数据类型之一。
值得注意的是,SQLite 本身并不验证任何可能写入其字段(表列)的数据类型是否实际上是已定义的数据类型。这意味着您可以将一个整数写入字符串列,反之亦然。如果您想更详细地研究 SQLite,可以访问 SQLite 站点,该站点位于以下 URL:
http://www.sqlite.org/
要在 Android 中使用 SQLite,您需要构建 SQLite 语句来创建和更新数据库,然后由 Android 为您管理。当您的应用创建数据库时,数据库将保存在一个目录中,该目录将始终使用以下 Android OS 数据库路径地址:
DATA/data/YOUR_APPLICATION_NAME/databases/YOUR_DATABASE_FILE_NAME
接下来,我们将看看许多不同类型的 Android 内容供应器,以及如何通过 Android 操作系统及其 android.content 包及其类和方法来访问它们。
Android 内容供应器和内容解析器:简介
Android 内容提供者对象管理应用的数据访问。如果你想在不同的 Android 应用之间共享数据,Android 内容供应器是你想要使用的。
这通常是在系统内存中的数据库或文件中,或者在设备的 SD 卡数据存储设备上,或者在偏好设置(名称-值对)中,甚至在外部网络服务器上的某种结构化数据集。
Android 内容供应器的一般目的是以标准化的方式封装数据,同时为 Android 开发者提供某种机制来加强他们的数据安全性。
内容提供者是标准的 Android 接口,它将一个系统进程中的数据与另一个进程中运行的 Java 代码连接起来。如果您想访问内容提供者内部的数据,您可以使用当前应用上下文中的 ContentResolver 对象作为数据库客户机与该内容提供者通信。
ContentResolver 类是从 java.lang.Object 子类化而来,它是 android.content 包的一部分。如果您想研究关于这个类及其常数、构造函数和方法的更详细的信息,您可以在 Android 开发人员网站的以下 URL 找到专门介绍这些信息的整个网页:
http://developer.android.com/reference/android/content/ContentResolver.html
Android ContentResolver 对象与 ContentProvider 对象进行通信,正如我们所知,它是实现 Android 的 Content Provider 超类的一个类的实例。
内容提供者对象有点像 Android,它有自己的自定义数据库引擎,从应用组件客户端接收数据请求,然后在自己的进程中执行请求的数据解析操作,如果可以找到请求的数据,则返回结果。
值得注意的是,如果开发人员不需要在他们的应用之外共享他们的数据,他们就不需要编写他们自己的内容提供者子类,大多数应用都不需要这样做。
您需要创建自己的内容提供者的子类的场景包括在应用中提供自定义搜索建议,或者如果您需要在您的应用和其他 Android 应用之间复制或粘贴复杂的数据。
Android 包括特定于应用的操作系统内容供应器,这些供应器管理常见类型的新媒体数据,如音频、视频、图像以及文本数据,如个人联系信息。
你可以看看开发者网页上的 Android 预定义内容提供者类,它提供了 android.provider 包的参考文档。它位于以下 URL:
http://developer.android.com/reference/android/provider/package-summary.html
Android 操作系统预装了这些内容供应器数据库。这些通过存储日常数据来帮助 Android 用户,例如最终用户的联系信息、日历、电话号码和多媒体文件。
这些特定于应用的内容提供程序类为开发人员提供了预构建的方法,用于向这些定制的内容提供程序写入数据值或从中读取数据值。
与往常一样,AndroidManifest.xml 文件中列出了一些限制,Android 应用开发人员可以轻松访问这些预构建的内容提供者类,以便在他们的应用特性或功能中使用。
寻址内容供应器:使用内容 URI
如果你想告诉 Android 操作系统你想访问哪个内容供应器,理解内容 URI 的概念是很重要的。我们以前使用过 URI 对象,所以你应该熟悉它们在 Android 应用中准确引用数据(内容)路径的功能。内容提供者有一种特殊的路径格式,就像 HTTP 有一种特殊的格式 HTTP://内容也有一种非常相似的特殊格式(因此我们很容易记住),这就是:
content://
Android 内容供应器的完整 URI 遵循以下格式:
content://Authority/Path/ID
作为一个例子,这里有一个(假想的)Hello World 内容 URI:
content://com.helloworld.universedatabase/planets/earth/1337
在这个 URI 中,com . hello world . universe database是数据权威,行星/地球/ 是数据路径, 1337 是数据记录的 ID。
一个内容 URI 总是包含四个必要的部分:要使用的模式,在本例中是Content://;权威;数据的可选路径;以及您想要访问的数据记录的 ID。
内容提供者的模式总是单词" content ",冒号和双正斜杠" 😕/ "总是附加在 URI 的前面,用于将数据模式与数据授权机构分开。
URI 的下一部分被称为内容供应器的权限。如您所料,每个内容供应器的授权必须是唯一的。权威命名约定通常遵循 Java 包命名约定。
许多组织选择使用他们组织的反向. com 域名,加上您可能发布的每个内容供应器的数据限定符,因此我们前面的示例假设我们拥有helloworld.com
域名,当然,我们并不拥有。
因为 Android 开发人员文档建议您使用 ContentProvider 子类的完全限定类名,如果我们遵循这个示例内容 URI,那么我们可以将 ContentProvider 子类命名为 UniverseDatabase.java。
URI 标准的第三部分是数据的路径,虽然是可选的,但出于组织的目的,使用它是一种相当标准的做法。我们不会将数据放在服务器的根文件夹中,而是放在一个行星文件夹中,为每个行星数据库使用子文件夹。在我们的例子中,一个子文件夹是地球。
例如,Android 的 MediaStore 数据库(我们接下来将会看到)的内容供应器使用不同的路径名来确保音频、图像和视频文件保存在不同的数据类型位置。
通过使用不同的路径名,单个内容供应器可以容纳许多不同类型的以某种方式相关的数据,例如保存在 MediaStore 内容供应器中的新媒体内容类型。
对于完全不相关的数据类型,标准的编程实践是希望为每个数据库使用不同的内容提供者子类,以及不同的数据权限(和路径)。
最后一个 URI 参考规范组件是 ID ,您可能已经猜到了,它需要是数字。这个 ID,或 Android 中的 _ID ,在您想要访问单个数据库记录时会用到。如您所见,URI 参考规范从最通用或最高级(content://)的规范开始,经过授权机构(服务器名),向下经过数据路径(目录路径),最终到达数据记录本身(ID)。这是首先建立任何数据路径的逻辑方法,所以我不认为您在理解 URI 参考规范及其构造方面有任何问题。
Android 操作系统内容供应器:作为操作系统一部分的数据库
android 提供了一个 android.provider 包,其中包含了 Android 操作系统中标准的所有主要数据库类型的 Java 接口。
这些包括 Android 用户最常使用的数据库,如联系人数据库、日历数据库和媒体商店数据库。我们将在这一节中讨论这些及其组成部分。
这些功能用于个人管理、时间管理和多媒体管理,这是 Android 设备上最常访问的三项任务,无论是智能手机、平板电脑、电子书阅读器还是独立电视设备。
正如我们在数据库设计一节中了解到的,这些数据库被分割成多个逻辑子数据库,并通过使用一个键或索引 _ID 值来引用,就像它们是一个单独的数据存储一样。我们知道,这样做是为了系统性能(内存使用),以及数据访问速度和数据库访问的方便性的原因。
Android MediaStore 数据库
MediaStore 数据库包含 9 个不同的新媒体素材数据库, CalendarContract 数据库包含 11 个不同的日历组件数据库, ContactsContract 数据库包含的数据库最多,有 21 个功能数据库。
媒体商店数据库包括五个音频数据相关数据库以及一个图像和一个视频相关数据库。表 19-1 显示了媒体商店数据提供者接口,以及它们访问的数据类型(参考)。
表 19-1 。Android 供应器包中的 MediaStore 数据库及其包含的数据类型
数据库ˌ资料库 | 描述 |
---|---|
媒体商店。音频.白蛋白列 | 代表相册的数据库列 |
MediaStore。音频.艺术专栏 | 代表艺术家的数据库列 |
媒体商店。音频。音频列 | 代表跨越多个数据库的音频文件的数据库列 |
媒体商店。Audio.GenresColumns | 代表流派的数据库列 |
媒体商店。音频.播放列表栏 | 代表播放列表的数据库列 |
媒体商店。图像。图像列 | 代表图像的数据库列 |
MediaStore。视频,视频专栏 | 代表视频的数据库列 |
媒体商店。文件.文件列 | 所有媒体的主表的列 |
MediaStore。MediaColumns | MediaProvider 表的公共列 |
Android 日历合同数据库
日历合同数据库包括 11 个日历相关数据库,每个数据库支持各种日历功能,例如事件、与会者、提醒、提醒以及其他类似的日历相关数据支持功能。
android 操作系统通过其 android.provider 包为 Android 日历数据库访问提供预建支持的原因是,对于希望访问这些日历功能的应用来说,能够向现有的 Android 日历功能集添加很酷的新功能是合乎逻辑的。
表 19-2 显示了 CalendarContract 数据提供程序接口,以及它们访问的不同类型的日历函数数据(因此,它们将允许您使用内容提供程序直接引用这些数据)。
表 19-2 。Android Provider 包中的 CalendarContract 数据库及其包含的数据类型
数据库ˌ资料库 | 描述 |
---|---|
日历合同。日历栏栏 | 用于日历提醒功能的数据 |
CalendarContract。CalendarCacheColumns | 用于日历缓存功能的数据 |
CalendarContract。日历栏 | 其他 URI 可以查询的日历列 |
CalendarContract。CalendarSyncColumns | 供同步适配器使用的通用列 |
CalendarContract。颜色列 | 用于日历颜色功能的数据 |
CalendarContract。EventDaysColumns 列 | 用于日历事件日功能的数据 |
日历合同。事件列 | 事件数据库中的列(联接) |
日历合同。扩展属性列 | 日历扩展属性中使用的数据 |
CalendarContract。提醒栏 | 用于日历提醒功能的数据 |
CalendarContract。同步列 | 其他数据库使用的同步信息列 |
接下来,我们将看看新的 Android Contacts 数据库结构,从 Android 2.1(éclair)操作系统版本开始,它现在通过 ContactsContract 名字引用(而不仅仅是使用 Contacts)。
Android 联系人联系数据库
contacts Contact数据库包括多达 21 个与联系人数据相关的数据库表,这并不令人惊讶,因为如今联系人管理包括大量信息,如姓名、电话号码、电子邮件地址、社交媒体存在、状态、显示名称等。表 19-3 显示了 ContactsContract 数据提供者接口,以及它们访问的数据类型(以及它们将引用的数据类型)。
表 19-3 。联系人联系 Android 供应商包中的数据库以及它们包含的数据类型
数据库ˌ资料库 | 描述 |
---|---|
联系人联系人。CommonDataKinds.BaseTypes | 支持所有类型化数据类型 |
联系人联系人。CommonDataKinds.CommonColumns | 跨特定类型的公共列 |
联系我们。ContactNameColumns(联系名称列) | 原始联系人数据库中的联系人姓名和联系人姓名元数据列 |
联系我们。ContactOptionsColumns(联系人选项列) | ContactsContract 列。跟踪用户对联系人的偏好或与联系人的交互的联系人 |
联系我们。联系我们 | ContactsContract 列。接触是指固有的接触属性 |
联系我们。ContactStatusColumns(联系状态列) | 用于联系人状态信息的数据 |
联系人联系人。数据列 | 数据表中的列(连接) |
联系我们。DataColumnsWithJoins(数据列移动联接) | 组合 ContactsContract 返回的所有联接列。数据表查询 |
联系人联系人。显示名称来源 | 用于产生显示名称的数据类型 |
联络人协定。FullNameStyle | 用于组合成全名的常数 |
联系人联系人。组列 | 用于联系人分组信息的数据 |
联系我们。PhoneLookupColumns(语音查找列) | 用于联系人电话查找的数据 |
联系人联系人。PhoneticNameStyle | 姓名发音的常数 |
联系我们。PresenceColumns(存在列) | 附加数据链接返回 ID 条目 |
联系我们。拉瓦连络我们 | 用于 RawContact 数据库的数据 |
联系人合同。设置列 | 用于联系人操作系统设置的数据 |
联络人协定. status column | 用于社会地位更新的数据 |
联系我们。streamitemphotoscolumns | StreamItemPhotos 表中的列 |
联系人联系人。StreamItemsColumns | StreamItems 表中的数据列 |
联系人联系人。同步列 | 当表的每一行属于特定帐户时出现的列 |
接下来,我们将了解弃用的概念,因为它与 Android 数据库有关,因为你们中的一些人可能需要使用旧的 Contacts 数据结构来支持 Android 2.0,甚至 1.5 和 1.6 的古代用户。
过时的内容提供者:过时的数据库结构
作为一名 Android(以及任何其他类型,就此而言)开发者,你需要高度意识到弃用的概念。正如您现在可能已经知道的,当编程语言(以及操作系统)中的特性(包括类、方法、接口、常量,或者在这种情况下的数据库)已经停止使用,以支持新的特性和编程环境的特性集(以及随后的开发能力)的长期发展时,就会发生弃用。
Contacts 数据库是弃用的一个很好的例子,所以我将在它自己的部分中讨论这个主题,只是为了确保您理解在您的编程工作过程中容纳弃用的特性是多么重要。
如果您在上一节中查看了 Android Provider 包的链接,那么在查看所有这些信息时,您可能已经注意到,在 API Level 5 之后,原始的 Android Contacts 数据库结构被彻底修改了。
在 API 5 之后,组成原始 Android Contacts 数据库结构的九个数据库被替换为一个新的数据库结构,称为 ContactsContract ,我们在上一节中已经介绍过。
请注意,如果您的 Android 应用操作系统支持足够早,您仍然可以使用这些数据库,因为 API Level 5 相当于 Android 2。在 Android 开发者网站上有一个非常有用的部分,叫做仪表盘。这向您显示了的 API 级别、的操作系统版本,甚至每个版本的当前市场份额的百分比,所以请务必在有空时查看一下,它位于以下 URL:
http://developer.android.com/about/dashboards/index.html
这一切意味着,如果您支持任何早于 Android 2.1 的 Android 设备,即任何 Android 2.0、1.6 或 1.5 设备,那么您将需要自动检测您的最终用户的操作系统版本,并提供使用 9 个联系人数据库而不是 21 个联系人数据库的代码。
如果您查看前一个链接中的仪表板,您还会注意到这种支持只占当前市场份额的 0 . 2 %,这很可能不值得花费编码时间和精力!
内容提供者访问:在清单中添加权限
其余的 Android 内容供应器信息我们将在 Hello World 项目的上下文中学习,因为这更有趣、更高效,并且让我们在 Eclipse 中工作,而不仅仅是阅读一本书。
在 Eclipse 中打开你的 Hello_World Android 项目,右键单击并打开 AndroidManifest.xml 文件,该文件位于你的包浏览器窗格的底部附近,如图 19-1 中突出显示的所示。在中央 XML 编辑区点击底部的权限标签,打开 Android 清单权限可视化编辑器,如图图 19-1 所示,然后我们添加权限。
图 19-1 。使用 Eclipse 权限编辑器的选项卡来访问 Android 清单权限编辑器
点击 Add… 按钮打开如图图 19-2 所示的对话框,允许您选择要添加到 AndroidManifest.xml 文件中的权限标签的类型。选择使用权限选项,点击确定按钮关闭对话框,返回权限编辑器。
图 19-2 。权限类型为的添加权限对话框
现在我们已经有了显示在权限栏中的使用权限标签,如图图 19-3 所示,我们想使用右侧使用权限栏的属性来配置使用权限标签。找到名称下拉菜单,点击向下箭头,找到并选择Android . permission . read _ CONTACTS常量,如图图 19-3 所示。
图 19-3 。通过下拉菜单添加 READ_CONTACTS 使用权限属性到使用权限标签
一旦您选择了Android . permission . read _ CONTACTS常量,您将会看到屏幕左侧权限栏中的条目将会发生变化,以反映 <用途-权限> 标签的新名称参数。通过单击中央 xml 编辑窗格底部的 AndroidManifest.xml 选项卡,您可以随时看到为您编写的 XML 代码。
我们刚刚添加的 READ_CONTACTS 权限允许我们对 Android CONTACTS 数据库执行读取访问,您可能已经猜到了。
对数据库表的读操作在业内被称为非破坏性数据库访问操作。这是因为读操作不会改变数据库中的任何数据,它只是“查看”数据(读取数据),并可能获取读取的数据并将其显示在用户界面中,这取决于所涉及的 Java 代码。
接下来,我们想要添加一个 WRITE_CONTACTS 数据库权限,所以再次点击 Add… 按钮,并从我们在图 19-2 中看到的同一个对话框中选择另一个
图 19-4 。通过名称下拉菜单添加第二个 WRITE_CONTACTS 使用权限属性
接下来下拉名称菜单,找到一个Android . permission . write _ CONTACTS参数,选择它作为你的第二个< uses-permission >标签。要查看为你编写的 XML 代码,点击如图图 19-5 所示的 AndroidManifest.xml 选项卡。
图 19-5 。AndroidManifest.xml 文件标签显示了两个使用许可标签的 xml 标记
需要注意的是,要使权限列从使用权限图标更新到 WRITE_CONTACTS 参数,您必须点击添加…按钮(如果您正在添加更多权限),或者点击 AndroidManifest.xml 选项卡,然后再次点击权限选项卡以刷新权限编辑器可视编辑器,并在左侧的权限列中显示正确的条目,如图图 19-6 所示。
图 19-6 。Eclipse Android 清单权限可视化编辑器显示 twp 数据库访问权限
现在我们已经设置了对联系人数据库的读写权限,我们可以创建我们的 AlienContact.java 活动子类,它将保存我们的用户界面设计和我们的 Make Alien Contact 功能。
内容提供者活动:创建 AlienContact 类
让我们在 Hello_World Android 应用中创建我们的第十个(也是最后一个) Java 类,右键单击项目源(/src)文件夹,选择 New Class 菜单序列,在 Eclipse 中调出熟悉的 New Java Class helper 对话框,如图图 19-7 所示。
图 19-7 。创建一个名为 AlienContact.java 的新 Java 类,其超类类型为 android.app.Activity
使用您当前的 Android 项目信息,源文件夹和包数据字段应该已经为您填写好了,所以让我们将这个新的 Java 类命名为 AlienContact ,因为它将使用 Contacts 数据库来允许我们在我们遇到的任何新世界中跟踪我们的外星人联系人。
接下来单击超类字段旁边的 Browse… 按钮,这样我们就可以为我们的 AlienContact.java 活动子类指定一个活动超类类型。一旦超类选择对话框出现,你可以在顶部的选择类型字段中键入一个一个字符,然后在对话框的匹配项部分找到 Activity - android.app 条目。
一旦你点击 OK 按钮,你的超类字段应该被设置为 android.app.Activity 包类,它在你的新 Java 类中实现了所有这些 Android Activity 特性。设置完成后,您可以单击完成按钮在 Eclipse 中生成代码。
接下来我们将添加我们的。 onCreate( ) 方法创建活动的基本代码,并使用。setContentView( ) 方法来定义一个新的 activity_contact.xml 用户界面 xml 定义我们将编写来定义我们的活动屏幕。
我们还将在我们的 AndroidManifest.xml 文件中定义我们的活动,并给它一个 android:label 参数,以便它有一个活动屏幕标题。
内容提供者活动:准备 AlienContact 类
添加您常用的受保护的 void onCreate(Bundle savedInstanceState)方法,并调用super . onCreate(savedInstanceState);正如我们在前面章节中所做的那样,然后还添加一个引用 activity_contact.xml 文件的 setContentView()方法,我们接下来将创建该文件。代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact);
}
新的 AlienContact.java 活动子类和 onCreate()方法如图图 19-8 所示。请注意,对我们的 activity_contact.xml UI 定义的引用(我们接下来将创建它)以红色错误突出显示。因为我们知道这个引用很快就会被定义并放置到位,所以我们现在可以安全地忽略 Eclipse 中 Java 代码中的这个错误。
图 19-8 。创建 onCreate()方法并引用 activity_contact.xml UI 布局容器定义
接下来,我们需要在我们的 Android Manifest XML 文件中定义我们的 AlienContact 活动,使用一个 < activity > 标签和 android:label 参数,就像我们之前做的那样给我们的 UI 屏幕一个标题,如图图 19-9 所示。下面是我们将添加到 AndroidManifest.xml 文件底部的 XML 标记:
<activity android:name="chapter.two.hello_world.AlienContact"
android:label="@string/activity_title_alien_contact" />
图 19-9 。添加 XML 标记,向我们的 AndroidManifest.xml 添加一个新的 AlienContact 活动子类
现在,我们准备创建我们的活动用户界面屏幕 XML 文件,我们将按照 Android 惯例将其命名为 activity_contact.xml 。
内容供应器用户界面:创建 activity_contact.xml
在 Eclipse 的 Package Explorer 窗格中右键单击项目层次结构中的/res/layout 文件夹,并选择 New Android XML File 菜单序列。这将启动新的 Android XML 文件助手对话框,如图图 19-10 所示。
图 19-10 。使用一个新的 Android XML 文件对话框来创建我们的 activity_contact.xml 文件
选择布局的资源类型和 Hello_World 的项目设置,然后将文件命名为 activity_contact ,最后为你的用户界面设计容器选择根元素类型 LinearLayout 。
接下来我们需要做的是使用
<string name="activity_title_alien_contact">Hello World - Alien Contacts</string>
接下来,让我们添加三个外来的联系人 UI 按钮标签,我们需要在我们接下来要编写的 XML UI 标记中引用它们。这些将按如下方式编写,并显示在图 19-11 的顶部。
<string name="find_alien_button_value">List Aliens in this Galaxy</string>
<string name="add_spock_button_value">Add Spock to my Alliance</string>
<string name="add_worf_button_value">Add Worf to my Alliance</string>
图 19-11 。在 strings.xml 文件中添加活动屏幕标题和用户界面按钮元素 <字符串>标签
接下来,我们需要在父 LinearLayout 容器标记中添加 Button 标记元素,并向 LinearLayout 添加参数以添加背景空间图像,这样我们的用户界面看起来更专业,并与我们迄今为止使用的应用设计的其余部分相匹配。
第一个用户界面按钮标签利用了一个设置为 findAliens 的 android:id 参数,其文本颜色为黄色或 #FFFFAA ,并引用了我们之前创建的字符串常量。
此外,我们利用一个设置为 40dp 的 android:layout_marginTop 参数来将我们的 UI 元素从 UI 屏幕设计的顶部向下推一点,并利用一个设置为 center 的 android:layout_gravity 参数来使我们的 UI 元素很好地位于屏幕设计的中间。
最后,确保将默认的 android:layout_width 和 height 参数设置为 wrap_content 。您的按钮 XML 如下所示:
<Button android:id="@+id/findAliens"
android:text="@string/find_alien_button_value"
android:textColor="#FFFFAA"
android:layout_marginTop="40dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
第一个用户界面按钮元素,以及接下来的三个,我们将使用复制和粘贴操作来生成,以快速创建该用户界面设计的 XML 标记的剩余部分,如图图 19-12 所示。一旦我们复制了这个 XML 定义,并在它下面粘贴了三次,我们就可以修改参数,为我们的按钮创建新的 UI 元素标签,这些标签将添加外星人并退出活动。
图 19-12 。编写 XML 标记,将四个按钮用户界面元素添加到我们的 LinearLayout 容器中
让我们现在就这样做;将 findAliens (ID)按钮标签再复制粘贴三次到其下,并将 android:id 参数从 findAliens 分别更改为 addSpock 、 addWorf 和 returnHome 。
接下来,将 android:layout_marginTop 参数更改为用于 addSpock 和 addWorf 按钮标签的 25dp ,用于 returnHome 按钮的 60dp 。
我们将把 android:textColor 参数设置为 #FFFFAA 或黄色,将 android:layout_gravity 参数设置为 center ,最后,将 android:layout_width 和 height 参数设置为 wrap_content ,这样我们所有的按钮用户界面元素都被统一格式化。
接下来,让我们使用 XML 编辑窗格底部的图形布局编辑器选项卡,看看我们的新用户界面设计是什么样子的。因为我们编辑的最后一个按钮元素是 returnHome 按钮,所以它应该在视图中被选中,因为它是 XML 文本编辑选项卡中光标所在的位置。
视觉效果可以在图 19-13 中看到,我们的设计看起来相当专业,并且与我们的其他五个活动屏幕设计匹配良好。这意味着我们已经完成了 XML 用户界面设计标记,我们可以继续编写 Java 代码来实现用户界面设计的功能。我们将使用按钮对象及其事件处理方法,这些方法最终将包含我们的 Java 代码,这些代码将访问存储在 Android 操作系统中的联系人数据库结构。
图 19-13 。在 Eclipse 图形布局编辑器中预览我们的 activity_contact.xml 用户界面设计
接下来,让我们回到 Eclipse 中的 AlienContacts.java 编辑选项卡,添加代码行来实例化每个按钮对象,然后将事件处理逻辑附加到它,这样我们就有了一个框架来触发我们的数据库操作。
在 AlienContact 类中编码用户界面元素
首先,我们需要为对象创建和事件处理编写第一个 aliensButton Button 对象 Java 代码,然后我们可以在其下复制三次,并创建其他三个用户界面按钮。
完成此任务的 Java 代码如图 19-14 所示,并已被复制,以使用以下 Java 代码块为我们的 aliensButton、spockButton、worfButton 和 homeButton 用户界面元素创建按钮对象:
Button aliensButton = (Button)findViewById(R.id.findAliens);
aliensButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}
图 19-14 。为我们的按钮 UI 元素及其 onClick()事件处理方法添加 Java 代码
我们在 onClick()事件处理代码块中使用了 finish()方法调用,这样每个按钮都有一个功能(返回到主屏幕),直到我们在本章后面用数据库相关代码替换它。这使得测试 Java 代码变得更加容易,并且不会出错。
如果您愿意,您可以使用您的运行 Android 应用工作流程来启动 Nexus S 模拟器,并查看其用户界面屏幕,点击每个按钮返回应用主屏幕。
接下来,我们将编写 aliensButton 对象的代码,以便它调用一个 listAliens( ) 方法,该方法使用 Toast 类列出我们星系中的所有外星人,以便所有外星人的名字都广播到我们的用户界面屏幕上。
使用 ContentResolver:编写 listAliens()方法
我们需要做的第一件事是用 listAliens()方法调用替换 aliensButton 事件处理代码块中的 finish()方法调用。当你这样做时,Eclipse 会使用红色波浪下划线来突出这个方法不存在的事实,如图 19-15 所示。
图 19-15 。在 type 'AlienContact' helper 选项中添加 listAliens()方法调用和 Create 方法' list aliens()'
图 19-15 中还显示了当你的鼠标停留在这个突出显示的错误上时出现的帮助器对话框,以及当你选择第二个名为的选项时 Eclipse 为你编写的空方法,在类型‘alien contact’中创建方法‘find aliens()’,瞧!看看你的编辑屏幕的底部:空的 listAliens( ) 方法出现了!
现在是时候编写第一个与数据库相关的 Java 代码来访问 Contacts 数据库了,我们已经请求允许在 Android Manifest XML 文件中使用该数据库。在这种情况下,我们最初将使用 READ_CONTACTS 权限,因为我们将读取联系人数据库,然后在用户界面屏幕上列出我们银河联盟中的所有外星人。
首先,我们使用 Android Cursor 类,用于在数据库表中导航,并创建一个名为 AlienCur 的游标对象(我很想将该对象命名为 alienCurse,但没有这样做)。在同一行代码中,我们可以使用下面一行 Java 代码,通过使用 getContentResolver( ) 方法,用我们想要遍历的数据库表加载这个游标对象:
Cursor alienCur = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
这调用了 ContentResolver 类 getContentResolver()。query( ) 方法,该方法接受您想要查询的数据库 URI 的参数,以及投影、选择、参数和排序顺序。因为我们只是通读整个 Contacts 数据库,所以我们不打算使用任何其他数据库访问说明符参数,因此我们在该参数槽中使用了空值和空值(作为一个未使用的指示器)。
现在我们已经创建了 alienCur 游标对象,并加载了我们希望它遍历和读取的数据库内容,我们可以构造一个 Java while 语句来读取这个数据库表中的所有记录(字段)。因为我们希望光标指向。moveToNext( ) 或者在 while 循环仍然有效时移动到下一条记录,我们从以下内容开始:
while (alienCur.moveToNext()) { our while loop processing Java code goes in here }
在 while 循环内部,我们有两个 Java 语句,它们在 while 循环运行期间处理数据库内容(有效)。这是由 while 循环的条件定义的,该条件指定 while 循环的内容应该在该循环仍然可以移动到 Next 时执行(此时数据库表中还有另一个记录要读取)。
第一行代码创建一个名为 alienName 的字符串对象来保存我们将要读取的数据,然后将它设置为使用我们在第一行代码中创建的游标对象从数据库表中实际读取数据的操作。完成此任务的 Java 代码如下:
String alienName =
alienCur.getString(alienCur.getColumnIndex (ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
我们将我们的 alienName String 对象设置为来自的结果。getString( ) 方法,在 alienCur 光标对象上调用,该对象从获取其结果。在 alienCur 游标对象上调用的 getColumnIndex( ) 方法。
如您所见,赋予光标对象的参数。getColumnIndex()方法调用指定主显示名称数据库记录中保存的数据列(字段)。这通过使用 DISPLAY_NAME_PRIMARY 常量来引用,该常量本身通过Contacts contact数据库来引用,并且位于 Contacts 表中。如图 19-16 中的所示。
图 19-16 。编写使用 getContentResolver()访问 ContactContracts 数据库的 listAliens()方法
现在我们有了想要显示的数据,以我们需要的字符串格式,我们可以简单地调用 Toast 对象,通过它的。makeText()方法,并将数据显示到新的用户界面屏幕上。
我们将通过使用下面一行熟悉的 Java 代码来做到这一点,这些代码链接了和。makeText( ) 和。show( ) 方法一个接一个地关闭 Toast 对象。像这样:
Toast.makeText(this, alienName, Toast.LENGTH_SHORT).show();
进入结束 while 循环定义的右括号后,我们最终需要关闭(从系统内存中移除光标对象)alienCur 光标对象,它当前保存着我们的数据库数据。
顺便说一下,这个 Cursor 对象可能会占用大量的系统内存,所以当我们完成时,使用 Cursor 类的非常重要。close( ) 方法将我们已经定义的光标对象清除出系统内存。
这是通过调用 alienCur 对象的一个简单方法来完成的。这是使用 Java 点符号完成的,如以下 Java 代码所示:
alienCur.close();
现在,我们准备将外星人添加到我们的 Android 模拟器的联系人数据库中,这样我们就可以测试我们刚刚在前面几节中编写的权限、Java 代码和 XML 标记。
向 ContactsContract 数据库添加外来联系人
假设您仍然打开 Eclipse,通过使用窗口 Android 虚拟设备管理器菜单序列启动 Android 模拟器。该工作流程启动 Nexus S 模拟器,而不需要模拟器自动加载和运行 Hello World 应用。
这里还需要注意的是,如果由于某种原因一个作为 Android 应用运行的工作进程由于某种原因没有启动 Android 的仿真器功能,那这就是用的方式硬启动Android 仿真器。
当 Android 虚拟设备管理器对话框出现时,从我们在本书前面设置的模拟器列表中选择 Nexus S 模拟器选项,并点击开始… 按钮。
当启动选项对话框出现时,取消从快照启动,以及保存到快照选项,点击启动按钮。
这将打开启动 Android 模拟器对话框以及一个进度条,显示模拟器正在加载和启动。一旦它启动,你就可以关闭 Android 虚拟设备管理器对话框。
当 Nexus S 模拟器启动时,您将看到一个智能手机开始屏幕模拟器,如截图左侧的图 19-17 所示。
图 19-17 。使用联系人数据库图标(左下方第二个图标)创建新联系人
管理联系人数据库的工具非常重要,它就在智能手机开始屏幕的左下角,由蓝色联系人表图标模拟中的一个小对话头指示。
点击此图标启动仿真器的联系人管理实用程序,该程序显示在图 19-17 的右侧。
点击顶部按钮在您的智能手机(模拟器)联系人数据库中创建一个新的联系人。这将弹出一个对话框,通知您由于您使用的是模拟器,您将要输入的新联系信息将不会被备份,因为您使用的不是真正的电话。
如果你想使用模拟器创建真实的联系人数据库信息,可以通过真实的 Android 智能手机帐户访问,对话框会询问你是否要添加一个帐户来备份你在模拟器上在线输入的联系人。
因为我们只是将它用作本章中学习的联系人数据库代码的测试应用,所以我们将选择左边的按钮选项“ Keep Local,”,这样我们输入的信息就存储在我们工作站的硬盘驱动器上,而不是像以前那样“在云中”。
该保持本地对话框如图 19-18 所示,一旦我们选择保持本地选项,我们将看到联系人管理用户界面。
图 19-18 。保持我们的联系人数据库在本地,并在数据库中添加一个名为 Goran Agar 的新外星人
一旦联系人管理界面出现,如图 19-18 右侧所示,我们将输入我们的第一个外星人名字,戈兰·阿加尔,杰姆·哈达尔种族之一,出现在流行的星际迷航系列中。
一旦在位于用户界面屏幕顶部的深蓝色线上输入 Goran Agar 的名称,点击位于文本输入字段右上方和左侧的浅蓝色 Done 按钮,如图图 19-18 所示。
要查看您刚刚在联系人数据库中输入的条目,单击屏幕顶部的向左箭头,您将被带到联系人数据库列表屏幕,如左侧的图 19-19 所示。
图 19-19 。查看添加的外星人联系人,并重复添加库达克·艾坦和雷玛塔·柯兰的工作过程
在联系人数据库列表屏幕的右下角,在这个时间点上,只显示了我们的外星朋友 Goran Agar,如果你仔细看,你会看到一个方形的灰色头像图标,旁边有一个+符号。这允许你添加另一个联系人到你的联系人数据库,所以现在点击这个图标,我们将添加更多的外星人到我们的银河联盟。
添加另外两个来自 Jem Hadar 种族的外星人,名为 Remata Klan 和 Kudak Etan,使用与前面概述的相同的工作流程。一旦你这样做了,联系人数据库列表屏幕应该看起来像在图 19-19 中的截图的右边。
现在,我们的外星人联系人数据库中有足够多的外星人来测试我们的应用!
在测试我们的 AlienContact.java 活动之前,我们需要能够从我们的主屏幕选项菜单访问它,所以让我们接下来这样做,以便我们可以测试我们的 listAliens()方法,现在我们已经有了(Alien)联系人数据库中的数据来这样做。
将 AlienContact 活动添加到主屏幕菜单
要添加新的选项菜单项,我们需要做的第一件事是在 strings.xml 文件中为菜单标签创建一个
按照我们其他菜单字符串常量的格式,让我们创建一个名为< string >标签的 menu_contact_planet ,菜单标签值为 Make Alien Contact ,并将其放在我们其他菜单字符串常量下的 strings.xml 文件中,如图图 19-20 所示。您的<字符串>标记的 XML 标记应该如下所示:
<string name="menu_contact_planet">Make Alien Contact</string>
图 19-20 。将名为 menu_contact_planet 的第五个菜单项标签 Make Alien Contact 添加到我们的 strings.xml 中
完成后,我们将准备好更改 activity_main.xml 文件中的占位符第五个菜单项,该文件位于 /res/menu 子文件夹中,带有“创建外国人联系人”菜单选项,该选项将调用我们的 AlienContact.java 活动子类。记住我们的应用有两个 activity_main.xml 文件,一个在用于 UI 的/布局中,一个在用于菜单设计的/菜单中。
右键单击 /res/menu 文件夹中的 activity_main.xml 文件,选择打开选项(或者在包浏览器中选择文件名,然后点击 F3 )。编辑文件中最后一个占位符项,使 android:id 设置为 menu_contact ,编辑 android:title 参数引用@ string/menu _ contact _ planet,如图图 19-21 所示。
图 19-21 。将我们的第五个菜单<项目>占位符标签更改为 menu_contact 以访问创建外星人联系人
因为我们的 onCreateOptionsMenu( ) 方法已经扩展了我们的菜单 XML 文件定义,所以我们接下来逻辑上需要做的是修改我们的 onOptionsItemsSelected( ) 方法,并添加一个 case 语句,它处理 menu_contact ID 并启动我们的AlienContact.java活动子类。
在 Eclipse central 编辑窗格中打开您的 MainActivity.java 活动,并关闭除 onOptionsItemSelected()之外的所有方法,如图 19-22 所示。然后,添加一个 case R.id.menu_contact: 语句,创建一个 intent_contact Intent 对象,并将其设置为调用新的 AlienContact 类。最后,调用。startActivity( ) 方法,使用这个 Intent 对象,然后使用break;退出本节的 case 语句。
图 19-22 。在 switch()方法 case 语句中添加 menu_contact 条目,打开 AlienContact 活动
第五个菜单项 case 语句代码应如下所示:
case R.id.menu_contact:
Intent intent_contact = new Intent(this, AlienContact.class);
this.startActivity(intent_contact);
break;
既然我们已经使我们的用户(和我们自己)能够启动 AlienContact.java Activity 子类,我们可以测试我们的 activity_contact.xml 用户界面设计和实现它的 Java 代码,以及将读取我们在上一节中刚刚创建的 Alien Contacts 数据库条目的 ContentResolver。
右键单击你的 Hello_World 项目文件夹,然后选择 Run As Android Application 菜单序列,或者,如果你喜欢,利用窗口 Android 虚拟设备管理器工作进程,我们在本章前面使用它来启动模拟器。
测试 AlienContact 活动中的 listAliens()方法
一旦 Nexus S 模拟器启动,找到 Hello_World 图标(土星)并启动应用。如果您使用了“作为 Android 应用运行”菜单序列,这将为您自动完成。
一旦 Hello_World 主屏幕出现,点击模拟器右上角的菜单按钮,调出我们刚刚创建的选项菜单,如图 19-23 左侧所示。
图 19-23 。选择创建外国人联系人菜单项,启动外国人联系人活动测试返回主页按钮
选择菜单底部的创建外来联系人菜单项,打开图 19-23 右侧显示的外来联系人活动用户界面屏幕。
单击“返回主页”按钮以及“添加 Spock”和“添加 Worf”按钮,并确保它们调用 finish()方法,将您带回 Hello_World 应用的主页屏幕。
一旦你完成了测试,再次点击模拟器中的菜单按钮,进入 AlienContact.java 活动子类,这样我们就可以测试列出这个星系中的外星人按钮,并确保它遍历我们的外星人联系数据库,使用光标对象,并列出精英杰姆·哈达尔星系间种族的所有外星人联系。
当你点击图 19-24 左侧所示的“列出这个星系中的外星人”按钮时,你会看到在用户界面屏幕的底部出现 Toast 消息,列出了你的外星人联系数据库中的所有三个 Jem Hadar 种族成员。
图 19-24 。使用列出银河系中的外星人按钮及其 Toast 消息测试列出外星人方法
Goran Agar 的 Toast 消息显示在图 19-24 中截图的右下方,因此我们的 ContentResolver Java 代码运行良好。接下来,让我们编写另一个名为 addToAlliance( ) 的 Java 方法,它向我们展示了如何使用 Android ContentValues 类,通过使用我们自己的定制 Java 代码,而不是必须使用 Android 操作系统的联系人管理实用程序,来使我们能够向我们的联盟数据库添加新的外星人联系人。
Android ContentValues:编写一个 addToAlliance()方法
打开 Eclipse 中的 AlienContact.java 编辑选项卡,用对我们将要编写的新 addToAlliance( ) 方法的方法调用替换 onClick()事件处理程序中对 spockButton 和 worfButton 代码块的 finish()方法调用。该方法调用的格式传递一个类似于 addToAlliance(字符串值)的字符串参数,因此 Java 代码行如下所示:
addToAlliance("Spock");
一旦为 Spock 和 Worf 添加了 addToAlliance()方法调用,我们就需要编写protected void addto alliance(String new alien)方法。
我们在 addToAlliance()方法中做的第一件事是使用 Java new 关键字构造一个名为 alienContact 的 ContentValues 对象,使用下面一行 Java 代码:
ContentValues alienContact = new ContentValues();
之后,我们可以用传递到方法中的 newAlien 参数字符串数据加载 alienContact ContentValues 对象。我们通过使用来做到这一点。put( ) 方法,从 alienContact ContentValues 对象调用,第一个参数指示我们希望将数据放在哪里,第二个参数是要放入该字段的数据本身。这显示在屏幕底部附近的图 19-25 中。
图 19-25 。编写我们的 addToAlliance()方法来访问 ContentValues 类,通过它来添加外星人。放( )
如您所见,这是通过使用下面两行 Java 代码为我们希望在 ContentValues 对象中指定的数据库表 RawContacts 中的 ACCOUNT_NAME 和 ACCOUNT_TYPE 列(数据字段)完成的:
alienContact.put(RawContacts.ACCOUNT_NAME, newAlien);
alienContact.put(RawContacts.ACCOUNT_TYPE, newAlien);
然后我们创建一个名为 addUri 的 Uri 对象,并将其设置为等于一个getcontentresolver . insert()方法调用的结果,使用数据库要写入的参数(插入数据)和要写入的数据。这是 RawContacts 数据库,使用 RawContacts 指定。CONTENT_URI 常量和alien contactCONTENT values 对象,现在加载了我们的 newAlien 字符串数据。这是使用以下单行 Java 代码完成的:
Uri addUri = getContentResolver().insert(RawContacts.CONTENT_URI, alienContact);
接下来我们创建一个名为 rawContactId 的 long 变量,并将其设置为等于一个content URIs . parseid(addUri)方法调用的结果,这将把 addUri Uri 对象转换为与兼容的 long 值格式。put( ) 我们即将调用的方法。这是使用以下单行 Java 代码完成的:
long rawContactId = ContentUris.parseId(addUri);
请记住,当您键入这个新的数据库写操作 Java 代码时,您调用或使用的任何 Android 类,比如尚未导入的 ContentValues、ContentUris 或 ContentResolver,也就是说,在类的顶部为它编写的 import 语句(正如您已经看到的,Eclipse 将很容易为您完成)将在有问题的方法调用下面用红色波浪线突出显示。
既然我们在变量 rawContactId 中有了正确(长)数字格式的数据库 ID,我们现在可以使用。clear( ) 方法来清除我们的 alienContact ContentValues 对象,以便我们可以再次使用它,将更多的数据值写入与 Android Contacts 数据库相关的其他数据库中。这一行 Java 代码应该如下所示:
alienContact.clear();
这就是 Android 中数据库编码难的原因;正如我在本章开始时提到的,与用于访问(或写入)数据的 Java 代码相比,它更多地涉及如何建立数据库层次结构的复杂性。您将在这里看到这一点,因为我们将讨论如何使用 rawContactId 将您写入 RawContacts 数据库表的相同数据写入相关的 data 和 StructuredName 数据库表。
首先,我们需要将 rawContactId 值放入数据中。RAW_CONTACT_ID 数据库表,使用。put()方法,只需一行 Java 代码:
alienContact.put(Data.RAW_CONTACT_ID, rawContactId);
然后,我们需要使用下面一行 Java 代码,将 StructuredName 数据库表的 CONTENT_ITEM_TYPE 数据列中的数据值放入 data 数据库表的 MIME_TYPE 数据列中:
alienContact.put(Data.MIME_TYPE, StructuredName.CONTENT_ITEM_TYPE);
然后我们需要将我们的 newAlien String 对象(别名)放入 StructuredName 数据库表的 DISPLAY_NAME 数据列中,如下所示:
alienContact.put(StructuredName.DISPLAY_NAME, newAlien);
最后,我们可以使用我们在过去几行代码中构建的 CONTENT_URI 和alien contactCONTENT values 对象,将所有这些与我们已经建立的 RawContacts 数据库相关的数据插入到数据数据库表中。我们将再次通过使用我们可信的 getContentResolver()来实现这一点。insert( ) 方法调用,使用下面的 Java 代码:
getContentResolver.insert(Data.CONTENT_URI, alienContact);
现在,我们准备向最终用户敬酒,并告诉他们我们刚刚做了什么,以便他们知道 newAlien 已经作为新联盟成员添加到 RawContacts(和 Data)数据库表中。这是使用下面一行代码完成的,它将两个方法链接在一起:
Toast.makeText(this, "New Alliance Member: " + newAlien, Toast.LENGTH_SHORT).show();
让我们通过 Android Nexus S 模拟器中的 AlienContact.java 活动来测试我们的新 addToAlliance( ) Java 方法,看看我们的新联盟成员 Spock 和 Worf 是否被添加到外星人联系人数据库中。
启动 Nexus S 模拟器,当 Hello_World 主屏幕出现时,点击菜单按钮,选择与外星人联系菜单选项。
一旦活动屏幕出现,点击你的添加史巴克到我的联盟和添加武夫到我的联盟按钮,并确保吐司消息出现在用户界面屏幕的底部。目前看起来很棒!
现在,我们剩下要做的就是确认外星人的联系人数据确实已经正确地添加到 Android 联系人数据库中,就像我们已经使用之前使用的联系人管理实用程序完成的一样。
如果我们正确编写了数据库代码,那么当我们下一次输入 Android 联系人管理实用程序时,我们的新外星人联系人数据将会出现在该实用程序中,就像我们使用该实用程序直接添加它一样。如果我们没有正确地编写数据库结构,数据就不会出现在那里。
退出 Hello_World 应用,返回到 Smartphone 模拟器主屏幕,并像我们在本章前面所做的那样,单击屏幕左下角的联系人管理图标。当你的(外星人)联系人列表出现时,当你向下滚动时,你会看到斯波克和沃夫现在出现在你的联系人数据库列表中,并且按字母顺序排列正确!
请注意,如果您愿意,您也可以使用应用中的“列出外星人”按钮进行测试,以确保这两个外星人也已添加。
摘要
在这最后一章中,我们仔细研究了 Android 操作系统中最复杂和详细的领域之一:数据访问、内容提供者和 SQL 数据库管理系统。这是一个确实需要单独的书来讨论的话题,所以,为你出色完成的学习工作而自我表扬一下吧。
我们首先看了一下数据库术语和基础知识,这样我们就在同一页上了。我们学习了 SQL 和数据库表、数据行(记录)和数据列(字段),以及如何通过 ID、键或索引访问数据。
然后我们看了一下开源 MySQL 数据库技术,以及这个开源 DBMS 引擎的内存高效版本,它是 Android 操作系统 SQLite 的一部分。我们学习了 SQLite 数据类型以及在 Android 操作系统中使用 SQLite 的注意事项。
接下来,我们概述了 Android 内容供应器和 Android 内容解析器,以及 Android 操作系统可以访问存储数据的不同位置,包括首选项、内存、SD 卡、内部文件系统、内部 SQLite 数据库和远程数据服务器。
然后,我们看了看如何使用内容 URI 访问数据库表中的数据。我们查看了内容 URI 结构的不同部分,将一些样本 URIs 放在一起,最后看一下在 Android 操作系统中使用内容 URI 的约定。
接下来,我们看了一下最著名的内容供应器 Java 接口,这些接口是 Android 操作系统的一部分,用于用户经常使用的联系人、日历和媒体商店功能。我们列出了这些数据库的各种子表,然后查看了 Android 2.1 之前使用的废弃数据库。
最后,是时候为我们自己的 Hello World Android 应用添加内容供应器功能了。我们使用 Eclipse 中的可视化权限编辑器向 AndroidManifest.xml 文件添加了权限,然后创建了一个 AlienContact.java 活动和所有字符串常量、用户界面设计、菜单设计和方法,这些都是实现对内置 Android Contacts 数据库结构进行读写所需要的。
最后,我们学习了 Android 联系人管理实用程序,这样我们就可以测试我们的数据库代码,并创建读取我们的外星人联系人数据库并将新的外星人写入我们的外星人联系人数据库的方法。
恭喜你,你已经完成了对 Android 操作系统及其许多关键特性、包、方法和类的全面介绍。现在,请确保并实践您在这里学到的知识,并通过查看 Android 开发者网站上的所有最新信息来建立这些知识。
二十、附录 A:为 4.12 版和更早版本构建 Android IDE:获取、安装和配置 Android 开发环境
这个附录与这本书的第一章紧密匹配,事实上,这本书的第一章是我在 Android OS 处于 4.1.2 版本时写的,没有 ADT 捆绑包,因此 Android 开发环境必须在开发人员这边从头开始构建。随着 Android 4.2 的出现,谷歌将所有东西都放在了他们这边,并允许开发人员只需下载一个 400MB 的“ADT 捆绑包”,从而从工作流程中删除了几十个步骤。这些步骤包含在本附录中,以便您可以了解它们是(曾经是)什么。
通读本附录将使您对 Eclipse IDE 和 Android SDK 如何通过 ADT 插件和 Eclipse 4.2 的 Google 插件联系在一起有所了解。
因此,本附录中的某些内容与第一章中的内容相同或相似。
对我们这些 Android 应用开发者来说,幸运的是,只需几百美元就能买到非常强大的 64 位电脑。如果你有一台 32 位的电脑,这对于 Android 应用开发来说同样适用,因为 Android 4.1 操作系统目前也是 32 位的。Android 4.2 还增加了跨 Eclipse 和 ADT 运行 64 位开发环境的选项,因此从 4.2 版本开始,使用 64 位系统的用户现在可以使用 64 位软件进行 Android 开发。
此外,我们在本书中用于应用开发的大多数软件都是免费的商业用途,也称为开源软件,因此,现在启动自己的 Android 应用软件开发业务的成本确实很低。
我们的攻击计划
在本附录中,我们确保我们的系统拥有最新版本的 Oracle Java 6 软件开发套件(Java SDK,也称为 JDK 或 Java 开发套件)编程环境以及 Android 软件开发套件(Android SDK)。在下载并安装了我们的 JDK 之后,我们将下载并安装一个名为 Eclipse 的集成开发环境(IDE)。
Eclipse 通过提供一个灵活的图形用户界面(GUI)使开发 Android 应用变得更加容易,我们可以用它来编写、运行、测试和调试我们的 Android 应用代码。Eclipse 运行在 Java 运行时环境(JRE)之上,因为 Eclipse 是用 Java 编写的,因此使用 Java 平台运行自己的代码,这些代码构成了 Eclipse IDE 用户界面和特性集。这是我们首先下载并安装 Java 6 JDK 的主要原因,以便 Java SDK 和 JRE 在我们的工作站上就位,以便当我们开始安装 Eclipse 时,它可以找到 Java 运行时环境(以便 Eclipse 可以启动或运行),并且它可以使用 Java SDK 为我们的 Android 开发环境构建编程代码基础。
一旦我们在 Java 6 上安装了 Eclipse 并顺利运行,我们将下载 Google Android 软件开发工具包(SDK ),并将其与 Java 6 SDK 和 Eclipse Juno 4.2 for Java EE 一起安装。一旦正确安装和配置了 Android SDK 和 ADT 插件,它们将使用额外的 Android 相关工具和特性来修改(增强)Eclipse IDE,所有这些将从本质上把 Eclipse Juno 4.2 for Java EE IDE 变成一个高度定制的 Android 应用 IDE。
作为 Android SDK 安装过程(第二部分)的一部分,我们将安装 Android Developer Toolkit (ADT)插件,这些插件将驻留在 Eclipse 内部(插件),用于将 Android SDK 和 Java 6 SDK“桥接”到一个统一的 Android 软件开发环境中。作为 ADT 安装过程的一部分,您需要连接到互联网才能执行,您的工作站与 Google 服务器上的 Android 软件存储库进行对话,并检索更多的 Android 软件 API,以完善您的 Android 开发环境。
从鸟瞰图来看,如果这个过程被公式化为一个方程,它看起来会像这样:
JDK 6(Java 6 SDK)+Eclipse 4.2 IDE+Android SDK+ADT Eclipse 插件=自定义 Android IDE
让我们现在就开始这个漫长而复杂的过程,这样我们就可以完成它并开发应用了!
Android 编程基础:Java 6
从编程和集成开发环境(IDE)的角度来看,Android 应用开发的基础是 Java 6。Android 应用是使用 Java 编程语言编写的(也使用 XML,我们将在第二章中更详细地讨论),Android 应用是在 Eclipse IDE 中开发的,Eclipse IDE 也是用 Java 编程语言编写的,并且运行在 Java 6 运行时环境(JRE)之上。委婉地说,你设置组成你的 Android 开发环境的软件组件的确切顺序是非常重要的!
因此,我们既有 Java 编程语言(我们可以通过 Java 开发工具包(JDK)获得)又有 Java 运行时环境(JRE )(它是 JDK 的一部分) ,我们将转到 Oracle TechNetwork,下载最新的 JDK6 安装软件并将其安装在我们的计算机上。我们这样做首先是因为 Eclipse 需要 Java 来运行,也就是说,Eclipse 可以说是在 Java 平台和语言的“顶部”运行。Android 的 Android 开发工具(ADT)插件也需要 Java 和 Eclipse,所以我们首先安装 Java 平台和环境,然后是 Eclipse,最后是 Android。
安装 JDK
我们必须做的第一件事是进入 Java SDK 下载页面。有两种方法可以做到这一点;一个是通用的,一个是精确的。即使 Oracle 更改了其 Java SDK 下载页面的位置(很可能不会更改),通用的方法仍然有效,那就是使用带有关键字短语“Java SDK Download”的 Google 搜索,这将显示 Oracle TechNetwork Java 下载 URL。第二种方法是直接在浏览器中键入页面的 URL。这是:
[`www.oracle.com/technetwork/java/javase/downloads/index.html`](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
这指向 Internet (HTTP)和 Oracle 网站,它们位于 Java 区域(子文件夹)中的技术网络区域(文件夹)或下载区域(子子子文件夹)中的 Java SE 或“标准版”区域(子子文件夹)。Java 有三个主要版本:面向个人用户的 SE 或标准版,面向大量用户的 ee 或企业版,以及面向移动翻盖手机的 ME 或 Micro 版。如今,大多数现代智能手机都使用 Android 和 Java SE,而不是 Java ME。Android 非常酷的一点是它像 PC 一样使用 Java 的完整标准版。这是因为 Android 运行在完整版本的 Linux 操作系统内核之上,所以对于所有实际用途来说,Android 消费电子设备本质上是一台计算机。
在你输入这个 URL 之后,你就到达了 Java 6 JDK 下载页面,你需要找到页面的 Java 6 JDK 下载部分,它看起来就像图 A-1 所示的(部分)页面部分。
图 A-1。Oracle TechNetwork Java SE 网页的 Java SE 6 JDK 下载部分
点击图所示 JDK 下的蓝色下载按钮(记住,JDK 是同时是JDK 和 JRE)进入 Java 6 JDK 下载页面,在这里您将接受软件许可协议并下载 Java 6 的 Windows 32 位版本。因为 Android SDK 和 ADT 目前都是 32 位软件,所以 Java EE 我们会用 32 位版本的 Java 6 和 Eclipse 4.2,这样一切都是 100%兼容的。
请记住,32 位软件在 64 位操作系统和工作站上运行良好,因此不会有任何问题。一旦 Android SDK 和 ADT 出现 64 位版本,我们最终可能能够拼凑出一个 64 位的 Android 开发环境(相信我,我已经尝试过了)。也许 Android 4.2 将使用 64 位 Java 6 和 Eclipse,如果是这样,我将包括一章关于组装 64 位 Android IDE 的内容,我保证。
才能下载 x86 Windows 32 位。exe 文件,必须点击网页部分顶部“接受许可协议”选项旁边的单选按钮选择器,如图图 A-2 所示。
图 A-2。接受 Java 6 SE 许可协议,下载 Windows x86 32 位版本。exe 文件
一旦你这样做了,右边的下载链接就会变成粗体,你可以点击它们来下载安装文件。点击jdk-6u37-windows-i586.exe链接(或者任何最新的更新版本),然后下载到你工作站上的下载文件夹。
在我们安装当前的 Java 6 JDK 之前,我们应该检查我们的系统,以确保我们的工作站上没有安装现有的(旧的)Java 版本。这是在 Windows 中通过控制面板完成的,控制面板可通过 Windows 开始菜单访问。现在启动控制面板,找到标记为“程序和功能”的图标,启动如图图 A-3 所示的程序和功能对话框。
图 A-3。右键单击现有的 Java 安装,选择卸载选项将其从系统中移除
注意,在我的工作站上,我已经安装了 Java 6 Update 31。因为我想在这个安装过程中安装最新的开发软件,所以我将使用完全相同的工作过程卸载以前的 Java 6 Update 31 安装,以及任何旧版本的 Eclipse 和 Android(如果它们存在,但它们不在这个特定的工作站上)。要做到这一点,你可以选择旧版本的 Java,右击它,选择卸载选项,如图图 A-3 所示。然后,Windows 将从您的系统中删除旧版本的 Java(如果需要,还有 Eclipse 和 Android)。
现在进入你的下载文件夹(或者你的桌面,或者任何你放下载的地方),双击jdk-6u37-windows-i586.exe文件开始 Java 6 的安装。接受安装的默认设置,如果您有一个配有高速硬盘和至少 3GB 内存的现代工作站,安装过程应该会很快。一旦 Java 6 安装完成,您就可以下载并安装 Eclipse Juno 4.2 for Java EE 了。接下来让我们这样做,这样我们就有了一个用于 Android 开发的 IDE。
安装集成开发环境:Eclipse
Android 的开发环境基于一个独特而强大的开源软件 Eclipse。Eclipse 是一个 IDE,或集成开发环境,它非常类似于一个字处理器,但它不是发布文档,而是用于编写、测试和调试编程逻辑。Eclipse 支持许多不同的编程语言,其中最流行的(您将很快从 Eclipse 下载页面中看到)是 Java。对我们来说幸运的是,Java 是 Android 用于其开发环境的东西,因此这也是我们将要下载和安装的东西。
我们必须做的第一件事是访问 Eclipse IDE 网页,有两种方法可以做到这一点;一个普通,另一个精确。即使 Eclipse 改变了其网页的位置(它很可能不会改变),通用的方法是使用 Google 搜索,关键字短语“Eclipse IDE”会显示 Eclipse 网站的 URL。第二种方法是在浏览器中直接输入网页的 Eclipse 网站 URL。这是:
[`www.eclipse.org/`](http://www.eclipse.org/)
一旦你输入这个 URL,你就到达了 Eclipse 网站的主页,你需要找到 Eclipse IDE 下载横幅,它看起来像图 A-4 。
图 A-4。下载 Eclipse 主页上的 Eclipse 按钮
单击 Download Eclipse 按钮后,就会转到下载页面,在这里选择第一个也是最受欢迎的下载,Eclipse for Java EE。图 A-5 显示了 Eclipse 下载页面的相关(顶部)部分,并显示了在哪里点击 32 位版本下载链接。
图 A-5。Eclipse Juno 4.2 IDE for Java EE 开发者下载 32 位 Windows 版本链接
点击Eclipse IDE for Java EE Developersfor Windows 32-bit 链接后,您将被带到该 IDE 版本的下载页面(Eclipse Juno 4.2 Service Release 1 for Java EE 32-bit Windows XP、Vista、7 或 8 版本是本书撰写时的最新版本)。在那里,您可以从附近的一个镜像站点下载 Eclipse Juno JEE 软件。
226MB 下载完成后,打开 Windows 资源管理器文件导航和文件管理实用程序,找到。系统下载文件夹中的 zip 文件。如果找不到,请输入。zip 文件(如图 A-6 所示,用于此下载和安装),并在左侧选择您的计算机或 C:\ drive,Windows 资源管理器会为您找到该文件。
图 A-6。从一个镜像站点选择下载 Eclipse JEE Juno Service Release 1 for Windows 32 位 ZIP 文件
找到 Eclipse Juno for Java EE 安装程序文件后,右键单击它以获得一个上下文相关的菜单,其中包含可以对该文件执行的所有操作,然后选择 Extract All。。。选项提取里面的所有文件和文件夹。压缩档案到你的硬盘上。这显示在图中,A-7 用红色圈出。
图 A-7。从 Eclipse 中提取所有文件。在下载文件夹中右键单击下载的文件
接下来,您会看到一个提取压缩文件夹对话框,要求您选择一个目标目录,将 Eclipse 文件和文件夹层次结构提取到其中。如图 A-8 的顶部对话框所示,存档提取实用程序将文件的当前位置放入您的下载文件夹中作为其目标路径,这并不奇怪,因为它确实不知道您想将它放在哪里,所以它将自己放在。可以说,zip 文件目前是“立着的”。
图 A-8。将解压压缩文件夹路径从下载目录更改到更高的配置文件 C:/(根)位置
因为我们并不真的希望我们的 Eclipse 4.2 for Java EE IDE 安装在我们的 Downloads 文件夹中(我们主要用它来保存文件下载),所以将它移到 C:\ primary hard disk drive 的顶部或“根目录”的最简单的工作过程是在文件名中单击 Eclipse 前面的光标,然后退格到 Users(这里是您的名字),并下载自动生成的路径名的各个部分。
这样,在 eclipse 前面就有了 C:\ hard drive 说明符。您刚刚下载的 zip 文件名作为您的文件夹名称。这将把 Eclipse JEE Juno 安装在您的 C:\驱动器的根目录(顶部),在您的主硬盘分区上,您可以在任何需要的时候方便地看到和找到它。产生的路径如图 A-8 中屏幕截图的下部所示。
一旦你点击提取,你会得到一个小的进度对话框,显示所有的 3,427 个项目(250 兆字节),正在迅速复制到您的硬盘驱动器。完成后,打开您的 Windows 资源管理器文件查看工具,并找到 eclipse.exe 可执行文件。
为此,打开左边的 C:驱动器,找到您下载并安装的 eclipse-jee-juno 文件名,在该文件名下,您将找到 eclipse 文件夹及其子文件夹,如图 A-9 所示。
图 A-9。找到 eclipse.exe 可执行文件,右键单击访问“固定到任务栏”选项,创建启动图标
在单词 eclipse 旁边寻找一个带有应用文件类型的紫色球体,这就是您的 Eclipse IDE 软件可执行文件。接下来,右键单击 Eclipse 应用文件以获得命令的上下文相关菜单,然后选择 Pin to Taskbar 命令。这将 Eclipse 作为一个快速启动图标安装在任务栏程序启动区域。该工作过程也如图图 A-9 所示。
从右键菜单中选择“固定到任务栏”命令后,Eclipse Juno 紫色球体启动图标会出现在 Windows 任务栏上,它通常位于屏幕底部,但也可以停靠在屏幕的顶部、左侧或右侧(通过拖动)。图 A-10 显示了我的系统上的 Eclipse for Java EE 启动图标以及其他开源软件(Gimp、Blender 3D、Audacity、Lightworks、OpenOffice、Chrome),其中一些我们将在后面的附录中安装。
图 A-10。 Eclipse Juno 4.2 for Java EE 启动图标显示在 Windows 任务栏的左侧
单击一次 Eclipse 启动图标,确保 Eclipse 启动;如果是的话,这意味着您已经正确安装了 Java 6,并且 Eclipse 已经成功地找到了 Java,并且正在使用它来运行它的集成 Java 开发环境。现在我们有了用于 Java 开发的 Eclipse 4.2.1,让我们去获取 Google Android SDK,我们将使用它来定制 Eclipse,使 Eclipse 成为一个定制的 Android 开发环境以及 Java SE 和 Java EE 开发环境。
Android SDK 和 Android 开发者工具
Android Software Development Kit,或 Android SDK,本质上是需要安装在 Eclipse 中的所有代码和实用程序,这样 Eclipse IDE 不仅可以用于 Java 开发,还可以用于更专业的 Android 应用开发。
让 Android SDK 和 Android Developer Tools 插件成为 Eclipse Juno 4.2 for Java EE 的无缝组成部分实际上是一个六步流程:
- 下载 70MB Android SDK 安装文件。
- 运行安装程序,将 110MB 文件解压到硬盘上。
- 运行 Android SDK 管理器,从 Google Android 软件仓库中获取更多 API 文件。
- 安装 Android 开发工具和 Google Eclipse 4.2 插件。
- 一旦 Android“接入”Eclipse,就可以在 Eclipse 中查看更多的软件更新。
- 启动 Eclipse,看看能否创建一个新的 Android 应用项目。
如果您可以创建一个新的 Android 应用项目文件,那么您已经成功地使 Android 成为 Eclipse IDE 的一个无缝的、不可分割的部分,本质上使它成为一个 Android IDE。至此,您可以通过定制您的配置、检查最新的文件版本并安装一个模拟器来完成本书中对您的应用的测试。
首先,进入 Android 开发者网页。同样有两种方法可以做到这一点;一个普通,另一个精确。即使谷歌改变了其 Android 开发者网页的位置(它很可能不会改变),通用的方法总是有效的,那就是使用谷歌搜索关键词短语“Android Developer Website”,这应该会显示出Developer.Android.com
网站 URL。第二种方法是在浏览器中直接输入 Android 开发者网页的 URL。在这里(我建议你记住这个):
[`developer.android.com/index.html`](http://developer.android.com/index.html)
一旦你输入这个网址,你就到达了 Android 开发者网站主页,你需要找到页面底部的Get SDK按钮,它看起来像图 A-11 。
图 A-11。选择获取 SDK 按钮,转到Developer.Android.com
站点上的 Android SDK 下载页面
一旦你点击获取 SDK 按钮,你就会被带到获取 Android SDK 页面,在那里你会看到一个蓝色的大按钮,上面写着:下载 Windows SDK(或者 Linux 或 Macintosh,如果你正在使用这些操作系统的话)。单击此按钮下载 SDK 安装程序。exe 文件,如图图 A-12 所示。
图 A-12。在Developer.Android.com
网站的获取 Android SDK 页面上,点击下载 Windows 按钮
下载文件后,使用 Windows 资源管理器在下载文件夹中找到它,就像我们对 Java 和 Eclipse 所做的那样,然后双击。exe 文件来运行安装程序(或使用右键单击并打开)。
一旦您启动 Android SDK 工具安装程序,您将看到选择安装位置对话框,如图图 A-13 所示。接受 Android SDK 工具设置实用程序建议的默认 Windows 程序文件文件夹,谷歌希望看到它被命名为 Android ,并在它下面有一个名为 android-sdk 的子文件夹。在 64 位工作站上,比如我正在使用的工作站,完整路径将是:C:\ Program Files(x86)\ Android \ Android-SDK,而在 32 位操作系统上,它将改为C:\ Program Files \ Android \ Android-SDK。
图 A-13。选择 Android SDK 工具安装过程的安装位置对话框显示默认的 Android 文件夹位置,点击下一步
这个对话框还显示了安装所需的空间,110MB,以及硬盘上的可用空间(我有一个 1TB 的硬盘,还有 867GB 的可用空间)。请注意,您将需要比这大得多的空间来安装其余的 Android APIs 和文档,因此请确保您的硬盘驱动器上有足够的 GB 可用空间。
一旦你点击下一步,你会看到选择开始菜单文件夹对话框,我也建议你接受 Android 为你选择的默认文件夹名称,即 Android SDK Tools ,如图图 A-14 所示。现在,您可以点击安装按钮,开始安装 Google Android SDK 了。
图 A-14。在 Android SDK 工具设置过程中选择开始菜单文件夹对话框,显示默认的 Android SDK 工具文件夹
一旦 110MB 的 Android SDK 文件在您的系统上安装完毕,就会出现一个名为的对话框,完成 Android SDK 工具安装向导,如图图 A-15 所示。确保选中 Start SDK Manager 复选框,这样一旦你点击 Finish 按钮,Android Developer Tools (ADT)插件和 Google Eclipse 4.2 插件就可以安装了。
图 A-15。在 Android SDK 工具设置过程中启动 SDK 管理器对话框(保持选中状态并点击完成按钮)
这可能有点令人困惑,因为您正在单击一个完成按钮,但您甚至还没有接近完成!谷歌完成了为 Android SDK 安装下载的“引导”文件,但 SDK 管理器接下来要做真正的繁重工作,通过互联网从位于硅谷的大型服务器农场上的谷歌 Android 存储库中安装更多文件。
无论如何,继续点击完成按钮,并启动如图图 A-16 所示的 Android SDK 管理器实用程序窗口。请注意,Android SDK 管理器预先选择了当前 Android 4.1 OS API 级文件进行自动安装。Android 开发者网站还推荐安装该屏幕上列出的另外两个软件包(我不确定为什么它们没有被检查为默认安装),在图 A-16 中用圆圈标出(实际上是红色方块)。
图 A-16。 Android SDK 管理器自动选择 Android 4.1.2 并选择 Android 支持库和 SDK 平台工具
这些选项代表 Android SDK 平台工具和 Android 支持库。所以你也应该检查这些选项,一旦安装按钮显示“安装 11 个包”,然后点击它,现在在你的系统上再安装 11 个 Android 相关的包。这可能需要一些时间,尤其是如果你有一个较慢的互联网连接。
点击安装按钮后,您会看到选择要安装的包 对话框,您会发现有一个简单的接受所有单选按钮,您可以使用它来安装所有这些包,这样您就可以针对所有这些 Android 硬件设备技术(ARM、Intel、Mips 等)进行开发。)支持 Android 4.1 API Level 16。接下来,选择接受全部单选按钮,点击安装按钮继续,如图图 A-17 所示。
图 A-17。选择要安装的软件包对话框(选择全部接受选项,然后单击安装按钮)
单击 Install 后,您会在 Android SDK 管理器的底部看到一个进度条,类似于图 A-18 中所示的内容,它会实时(如果您有足够的兴趣观看)向您显示当前正在下载哪个包,以及该包的 API 级别、修订号、完成传输的百分比、数据传输速率和下载该包的剩余时间。
图 A-18。从谷歌 Android 服务器仓库下载 SDK 平台 Android 4 . 1 . 2 API Level 16 revision 3
还要注意在图 A-18 中,在最右上角有一个图标,该图标打开一个 Android SDK 管理器日志窗口,该窗口实际上实时显示了正在进行的更详细的操作。
图 A-19。Android SDK 管理器日志窗口(通过图 A-18 最右侧显示的日志窗口图标打开)
一旦你的 Android 4.1 API 安装完毕,你就可以返回去安装你需要为你想要在 Android 市场上支持的设备开发的任何其他 API 级别。例如,最初的 Kindle Fire 使用 API 级别 10(目前为 2.3.7),新的 Kindle Fire HD 使用 API 级别 15(目前为 4.0.4)。根据所销售的支持 API 的设备数量,这是两个更受欢迎的 API 级别。
请注意,您选择安装的每一个其他(以前的)API 级别都需要一些时间来下载,并且会在您的硬盘驱动器上安装数百兆字节的额外 Android 开发文件,因此在这样做之前,请绝对确保您需要为这些旧设备进行开发。
Eclipse 的 Android 开发工具插件
构建可靠的 Android 开发环境的工作流程的下一步是向 Eclipse 展示 Android SDK 素材所在的位置,并开始巩固 Eclipse 的 IDE 功能和 Android 的软件开发功能之间的“桥梁”。这是在 Eclipse 中通过您可能熟悉的插件完成的,插件是几十年前由软件开发人员发明的,他们希望让第三方开发人员在他们的软件中添加他们没有时间或资源自己编码的功能。插件在数字成像软件包中尤其普遍,如 Photoshop 和 GIMP,并允许将软件开发功能添加到 Eclipse 中,如能够将 Eclipse 从 Java 开发环境转变为 Android 开发环境。让我们通过任务栏启动 Eclipse,并插入 Android SDK 功能。
一旦 Eclipse 启动,接受 Users 文件夹中的默认工作区位置,然后单击 IDE 右上角的 Help 菜单,并选择菜单底部附近的“Install New Software”选项。在出现的安装可用软件对话框中,点击右上角的“Add…”按钮,向 Eclipse 添加一个新的软件库网址;在这种情况下,它将是谷歌的 Android 仓库之一。在出现的添加存储库对话框中,在第一个数据输入字段中输入:Google Plug-In for Eclipse 4.2,并在第二个数据输入字段中输入以下确切的 URL 位置:
[`dl.google.com/eclipse.plugin/4.2`](http://dl.google.com/eclipse.plugin/4.2)
接下来,点击确定按钮。如果你想知道,google.com
前的 dl 代表下载。
现在,您将看到 Eclipse 转到 Google 软件存储库,获取 Eclipse 中用于 Android 开发的插件,并用它们填充 Install Available Software 对话框的中心。选择开发者工具复选框和Eclipse Google 插件复选框,然后点击下一步按钮继续安装。一个安装细节对话框将很快出现,它将详细概述 Android 开发者工具(ADT)插件集合的内容,以及 Eclipse 4.2 的 Google 插件,该插件将新的 Eclipse 4.2 IDE 与 ADT 插件套件联系起来。
再次单击下一个按钮,出现一个对话框,允许您查看并接受与您正在使用的所有软件包相关的软件许可协议。选择“我接受许可协议条款”单选按钮选项,点击完成按钮进入安装软件(进度条)对话框,安装通过 Eclipse 将 Android SDK 连接到 Java SDK 的插件软件。
安装过程完成后,会弹出一个对话框,要求您重启 Eclipse,以便将新的更改(Android 插件)安装到内存中。重新启动的原因是,尽管软件已经安装到您硬盘上的 Eclipse 中,但是软件需要重新启动,以便将这些更改实际加载到您的系统内存中,这样它们就可以在运行时在 Eclipse IDE 中使用。
重启后进入 Eclipse,使用文件新建
项目。。。菜单顺序查看您是否已经通过刚刚安装的 ADT 插件将 Android 开发功能正确安装到 Eclipse 中。
如果您在新项目对话框中看到一个名为“Android”的文件夹,并且它有用于创建 Android 应用、示例和测试项目的子文件夹,那么您已经成功地将 Eclipse 变成了一个可工作的 Android 软件开发环境!这里是命令链,可以这么说,关于什么与什么对话(连接或插入),从 Java 到 Android,在这一点上:
Java Eclipse
用于 Eclipse 4.2 的谷歌插件
Android 开发者工具
Android SDK
Android App
因此,您的 Android 应用使用来自 Android SDK 的函数,它将这些函数传递给 Android Developer Tools 插件,后者通过 Eclipse 4.2 的 Google 插件在 Eclipse 中实现这些函数,所有这些函数都运行在 Java SDK (Java 语言函数)和 JRE (Eclipse 运行在 Java 运行时环境之上,以便运行/运行)之上。咻!难怪它不是在公园散步设置和配置!
配置 Android 开发环境
最后,在我们进入本书其余部分的应用开发之前,让我们确保我们的 Android 开发环境得到了优化配置。这是在 Eclipse 中通过窗口首选项菜单序列完成的,它会打开 Eclipse 首选项对话框。在对话框的左窗格中,您会看到一个左侧带有箭头的 Android 部分,它打开了一个子部分,显示所有 Android 首选项配置区域。选择顶部的主窗口(单词“Android”),你将得到主要的 Android 首选项设置区域,在顶部显示 SDK 位置,以及下面所有安装的 Android SDK API 级别。
您可能还会遇到感谢使用 Android SDK 对话框,要求您在 Eclipse 中与 Google 共享您的 Android 使用统计数据,以便 Google 可以改进他们的 ADT 插件产品。为了开发人员社区的利益,我选择允许这样做,因为正如 Enterprise First Officer Spock 曾经说过的那样:“许多人的需求超过了一个人的需求。”
确保 Eclipse 找到了 Android SDK 并将其设置到正确的安装位置,并且您安装的所有 API 级别都存在,然后单击底部的 OK 按钮。只是为了绝对确保你正在想尽一切办法,进入 Eclipse Help 菜单,并选择 Check for Updates 菜单项。我们这样做是为了确保您已经安装的所有软件都是最新版本。如果 Eclipse 发现了您最近安装的几十个包的更新,请不要惊讶,每当我在新的开发工作站上安装新的 Android IDE 环境时,它总是会发现更新的版本。
如果 Eclipse 确实找到了 Eclipse、Android 开发工具、Eclipse 4.2 的 Google 插件或任何 Android API 级别的包的任何可用更新,它会给你一个可用更新对话框。如果您看到此对话框,通过勾选每个更新旁边的框来选择要安装的可用更新,然后单击下一个按钮继续。
然后你会看到一个更新细节对话框,它会给你关于每个软件更新的更多细节。同样在该对话框中点击下一步,然后查看并接受最后一个查看许可对话框中显示的软件许可,并点击完成按钮开始安装过程。当软件更新下载时,您应该会看到一个更新软件进度条对话框,然后当软件下载并准备安装时,会出现一个安全警告对话框,告诉您将要安装未签名的内容。单击 OK 安装更新,然后当要求您重启 Eclipse 时,单击 Yes ,以便将更改加载到系统内存中,这样您现在就可以运行 Eclipse 的最新更新版本。咻!我们现在必须完成这个过程,对吗?!差不多!
配置您的 Android 虚拟设备
为本书的其余部分准备好您的 Android 开发环境的过程的最后一步是设置 Android 虚拟设备(AVD ),这是我们在本附录中预先做的,这样我们以后就不必再担心它了。AVD 是一个仿真器,允许您使用模拟 Android 智能手机或平板电脑的软件设备仿真器在工作站上测试您的 Android 应用。
这是可取的,因为将你的应用上传到实际的物理 Android 设备(硬件)上的工作过程比右键单击你的项目并选择运行要繁琐得多,我们将在第二章中看到这一点。这并不是说你不应该偶尔在真正的 Android 硬件设备上测试你的应用,而是 AVD 允许你以更高的速度和频率进行测试。
因为你刚刚最后一次重启 Eclipse,进入窗口菜单,选择底部附近的 AVD 管理器选项。这将打开 Android 虚拟设备管理器对话框,其中列出了您的 AVD,目前没有 AVD 设置,因此在对话框的中心区域显示:没有可用的 AVD。要向 Eclipse 环境添加新的 AVD,单击对话框右上角的 New… 按钮,打开Create New Android Virtual Device对话框。
首先要做的是命名模拟器,因此在名称字段中输入 API_16_Emulator 。然后为仿真器选择目标,这是仿真器的 API 级别,是 Android 4.1.2 或者 API 级别 16。从下拉选择菜单中选择:Android 4.1–API Level 16。接下来设置 SD 卡为普通 512MB SD 卡配置,勾选快照启用选项。接下来,设置默认的内置皮肤 WVGA 800x480 ,因为大多数智能手机和平板电脑至少使用这个分辨率。
最后,你已经准备好创建你的第一个 Android 4.1 模拟器了,点击 Create AVD 按钮。如果您正在为其他一些流行的 API 级别进行开发,如果您需要测试您的应用在这些硬件平台上的交付,请利用相同的工作流程,并为 Kindle Fire (Android 2.3.7 API 级别 10)或 Kindle Fire HD (Android 4.0.4 API 级别 15)添加其他模拟器。