Android5-高级教程-一-

134 阅读1小时+

Android5 高级教程(一)

原文:Pro Android 5

协议:CC BY-NC-SA 4.0

零、前言

早在 2008 年,我得到了我的第一台 Android 设备。这就是梦,也被称为 G1,我立即开始修补它。毕竟,这是一部有数千种应用前景的智能手机,谁知道会有多少种可能的手机。那说明当时我懂的有多深!我真的应该考虑数百万个应用和数万个设备,因为这是 Android 今天的发展方向。

亲爱的读者,无论是传统的手机、平板电脑、汽车、飞机上的娱乐系统、机器人,还是其他各种各样的 Android 设备,让它们变得伟大的是像你这样的人编写的应用!每天,Android 开发人员都在推动应用和 Android 能够做什么的可能性,正是这种能量将我吸引到社区,并以我自己的方式帮助像 Pro Android 这样的书籍。

我所听到的关于技术和创新的最好的观察之一是,当你创造一些东西并与另一个人分享时,创新就发生了,然后他们以完全意想不到的方式适应和使用这些东西。所以让我本着这种精神向你推荐这本书。享受 Pro Android 为您提供的一切,并利用它来创造完全意想不到的东西!不管是什么,我们都会第一个去尝试。

—格兰特·艾伦 纽约【2015 年 5 月

介绍

欢迎来到安卓的奇妙世界。在这个世界里,只需一点知识和努力,你也可以编写 Android 应用。然而,要编写好的应用,你需要更深入地挖掘,了解 Android 架构的基础,了解应用如何协同工作,了解移动应用与所有以前的编程形式有何不同。Android 上的在线文档还不错,但还不够。您可以阅读源代码,但这一点也不容易。

这本书是关于 Android 的七年研究、开发、测试、提炼和写作的高潮。我们已经阅读了所有的在线文档,搜索了源代码,探索了互联网的广阔领域,并编写了这本书。我们已经填补了空白,预计到了您的问题,并提供了答案。一路走来,我们看到 API 来了又去,被修订。我们已经看到了应用构造方式的重大变化。起初我们都使用活动,但是当平板电脑出现时,我们开始使用片段。我们把我们所学的一切都用在了这本书上,用实用的指导来使用最新的 Android APIs 来编写有趣的应用。

你仍然可以找到开始主题的覆盖范围,以帮助新的学习者开始开发 Android。您还会发现对更高级主题的覆盖,例如 Google Maps Android API v2,它与 v1 有很大不同。我们已经用可用 API 的最新信息更新了这个版本。您将发现对意图、服务、广播接收器、通信、片段、小部件、传感器、动画、安全、Google Cloud 消息、音频和视频等的深入报道。对于每个主题,都有示例程序以有意义的方式说明每个 API。所有源代码都是可下载的,所以您可以将其复制并粘贴到您的应用中,以获得良好的开端。

一、你好安卓

欢迎来到这本书,欢迎来到 Android 开发的世界。在不到十年的时间里,Android 帮助改变了现代移动计算和电话的面貌,并在应用的开发方式和开发人员方面发起了一场革命。有了这本书在你的手中,你现在是伟大的 Android 爆炸的一部分!我们假设你想直接使用 Android,所以我们不会用关于 Android 的历史、主要人物、赞誉或任何其他散文的炉边聊天来烦你。我们要直奔主题了!

在本章中,您将首先了解使用 Android 软件开发工具包(SDK) 构建应用所需的东西,并设置您选择的开发环境。接下来,你走过一个“你好,世界!”应用。然后本章解释了 Android 应用的生命周期,最后讨论了如何在 Android 虚拟设备(avd)和真实设备上运行应用。所以让我们开始吧。

Android 开发的先决条件

要为 Android 构建应用,您需要 Java SE 开发工具包(JDK) 、Android SDK 和一个开发环境。严格地说,您可以使用简单的文本编辑器和一些像 Ant 这样的命令行工具来开发您的应用。出于本书的目的,我们将使用普遍可用的 Eclipse IDE,尽管您可以自由采用 Android Studio 及其 IntelliJ 基础——我们甚至将为那些没有见过它的人介绍 Android Studio。除了几个附加工具之外,我们在书中分享的例子在这两种 ide 之间同样适用。

Android SDK 需要 JDK 6 或 7(完整的 JDK,而不仅仅是 Java 运行时环境[JRE])和可选的支持 IDE。目前,Google 直接支持两种可选的 ide,提供了一些选择。历史上,Eclipse 是 Google 支持的第一个用于 Android 开发的 IDE,为 Android 4.4 KitKat 或 5.0 Lollipop 开发需要 Eclipse 3.6.2 或更高版本(本书使用 Eclipse 4.2 或 4.4,也分别称为 Juno 和 Luna,以及其他版本)。Google 为 Android 发布并支持的替代环境现在被称为 Android Studio。这是 IDEA IntelliJ 的打包版本,内置 Android SDK 和开发者工具。

注意在撰写本文时,Java 8 已经推出,但 Android SDK 尚不支持。在以前版本的 Android SDK 中,也支持 Java 5,但现在情况不同了。最新版本的 Eclipse (4.4,也就是 Juno)也可以使用,但 Android 在最新版本的 Eclipse 上一直不太可靠。在这里查看系统需求找到最新:【developer.android.com/sdk/index.h…

Android SDK 兼容 Windows (Windows XP、Windows Vista 和 Windows 7)、Mac OS X(仅限英特尔)和 Linux(仅限英特尔)。硬件方面,你需要一台 Intel 的机器,越强大越好。

为了让您的生活更轻松,如果您选择 Eclipse 作为您的 IDE,您将希望使用 Android 开发工具(ADT) 。ADT 是一个 Eclipse 插件,支持使用 Eclipse IDE 构建 Android 应用。

Android SDK 由两个主要部分组成:工具和包。当你第一次安装 SDK 时,你得到的只是基础工具。这些是帮助您开发应用的可执行文件和支持文件。这些包是特定于特定版本的 Android(称为平台)或特定平台插件的文件。平台包括 Android 1.5 到 4.4.2。这些附加软件包括谷歌地图应用编程接口、市场许可证验证器,甚至还有供应商提供的软件,如三星的 Galaxy Tab 附加软件。安装 SDK 后,您可以使用其中一个工具来下载并设置平台和附加组件。

请记住,您只需要设置和配置 Eclipse 或 Android Studio 中的一个。如果你愿意,你可以两者都用,但这肯定不是必须的。我们开始吧!

设置您的 Eclipse 环境

在本节中,您将逐步下载 JDK 6 、Eclipse IDE、Android SDK(工具和软件包)以及 ADT。您还可以配置 Eclipse 来构建 Android 应用。谷歌提供了一个页面来描述安装过程(【developer.android.com/sdk/install…

下载 JDK

你首先需要的是 JDK。Android SDK 要求 JDK 6 或更高版本;我们使用 JDK 6 和 7 开发了我们的示例,这取决于所使用的 Eclipse 或 Android Studio 的版本。对于 Windows 和 Mac OS X,从甲骨文网站(downloads/index . html)下载 JDK 7 并安装。你只需要 JDK,不需要捆绑。要安装 Linux 版 JDK,请打开一个终端窗口,并指示您的软件包管理员安装它。例如,在 Debian 或 Ubuntu 中,尝试以下操作:

sudo apt-get install sun-java7-jdk

这将安装 JDK 以及任何依赖项,比如 JRE。如果没有,这可能意味着您需要添加一个新的软件源,然后再次尝试该命令。网页help.ubuntu.com/community/Repositories/Ubuntu解释软件来源以及如何添加到第三方软件的连接。根据您使用的 Linux 版本的不同,这个过程会有所不同。完成后,重试该命令。

随着 Ubuntu 10.04 (Lucid Lynx)的推出,Ubuntu 建议使用 OpenJDK,而不是 Oracle/Sun JDK。要安装 OpenJDK,请尝试以下操作:

sudo apt-get install openjdk-7-jdk

如果找不到,请按照前面所述设置第三方软件,然后再次运行该命令。JDK 依赖的所有包都会自动添加。可以同时安装 OpenJDK 和 Oracle/Sun JDK。要在 Ubuntu 上安装的 Java 版本之间切换活动 Java,请在 shell 提示符下运行以下命令

sudo update-alternatives --config java

然后选择您想要的默认 Java。

现在您已经安装了 Java JDK,是时候设置 JAVA_HOME 环境变量指向 JDK 安装文件夹了。要在 Windows XP 机器上执行此操作,请选择开始image我的电脑,右键单击,选择属性,选择高级选项卡,然后单击环境变量。单击“新建”添加变量,如果变量已经存在,则单击“编辑”进行修改。 JAVA_HOME 的值类似于 C:\ Program Files \ JAVA \ JDK 1 . 7 . 0 _ 79。

对于 Windows Vista 和 Windows 7,进入环境变量屏幕的步骤略有不同。选择开始image计算机,右键单击,选择属性,单击高级系统设置的链接,然后单击环境变量。之后,按照与 Windows XP 相同的说明更改 JAVA_HOME 环境变量。

对于 Mac OS X,你在中设置 JAVA_HOME 。bashrc 文件在您的主目录中。编辑或创建。bashrc 文件,并添加如下所示的一行

export JAVA_HOME=path_to_JDK_directory

其中路径 _ 到 _ JDK _ 目录大概是/库/Java/Home 。对于 Linux,编辑你的。bashrc 文件并添加一行类似于 Mac OS X 的代码,除了您的 Java 路径可能是类似于 /usr/lib/jvm/java-6-sun 或/usr/lib/JVM/Java-6-open JDK 的代码。

下载 Eclipse

安装 JDK 后,您可以下载面向 Java 开发人员的 Eclipse IDE。(不需要 Java EE 的版本;它可以工作,但是它要大得多,并且包含了你在这本书里不需要的东西。)本书中的例子使用的是 Eclipse 4.2 或 4.4 (在 Linux 和 Windows 环境下均可)。你可以从 www.eclipse.org/downloads/下… Eclipse。

注意除了这里介绍的各个步骤,您还可以从 Android 开发者网站下载 ADT 捆绑包。这包括带有内置开发工具的 Eclipse 和一个软件包中的 Android SDK。这是一个快速入门的好方法,但是如果您有一个现有的环境,或者只是想知道所有的组件是如何连接在一起的,那么遵循一步一步的说明是正确的方法。

Eclipse 发行版是一个。可以在任何地方提取的 zip 文件。在 Windows 上,最简单的解压位置是 C:\ ,这会产生一个 C:\eclipse 文件夹,在那里可以找到 eclipse.exe。根据您的安全配置,从 C:\运行时,Windows 可能会坚持实施 UAC。对于 Mac OS X,你可以解压到应用。对于 Linux,您可以解压到您的主目录,或者让您的管理员将 Eclipse 放在一个您可以访问它的公共位置。对于所有平台,Eclipse 可执行文件都在 eclipse 文件夹中。您还可以使用 Linux 的软件中心找到并安装 Eclipse,以添加新的应用,尽管这可能不会为您提供最新的版本。

当您第一次启动 Eclipse 时,它会询问您工作区的位置。为了简单起见,您可以选择一个简单的位置,如 C:\android 或您的主目录下的一个目录。如果您与其他人共享计算机,您应该将工作区文件夹放在您的个人目录下的某个位置。

下载 Android SDK

要为 Android 构建应用,您需要 Android SDK。如前所述,SDK 附带了基础工具;然后您下载您需要和/或想要使用的包部件。SDK 的工具部分包括一个模拟器,所以你不需要一个装有 Android 操作系统的移动设备来开发 Android 应用。它还有一个安装工具,允许您安装想要下载的软件包。

可以从developer.android.com/sdk下载 Android SDK。它以的名字发货。zip 文件,类似于 Eclipse 的分发方式,所以您需要将其解压缩到一个适当的位置。对于 Windows,将文件解压缩到一个方便的位置(我们使用了 C: 驱动器),之后你应该有一个类似于 C:\android-sdk-windows 的文件夹,其中包含如图图 1-1 所示的文件。对于 Mac OS X 和 Linux,您可以将文件解压缩到您的主目录。请注意,Mac OS X 和 Linux 没有 SDK 管理器可执行文件;Mac OS X 和 Linux 中的 SDK 管理器相当于运行工具/android 程序。

9781430246800_Fig01-01.jpg

图 1-1 。Android SDK 的基本内容

另一种方法(仅适用于 Windows)是下载安装程序 EXE 而不是 zip 文件,然后运行安装程序可执行文件。这个可执行文件检查 Java JDK,为您解包嵌入的文件,并运行 SDK 管理程序来帮助您设置其余的下载。

无论是通过使用 Windows installer 还是通过执行 SDK 管理器,接下来都应该安装一些软件包。当您第一次安装 Android SDK 时,它没有任何平台版本(即 Android 版本)。安装平台非常容易。在你启动了 SDK 管理器之后,你会看到已经安装了什么和可以安装什么,如图 1-2 所示。您必须添加 Android SDK 工具和平台工具,以便您的环境能够工作。因为你用的时间短,所以至少添加 Android 1.6 SDK 平台,以及你的安装程序中显示的最新平台。

9781430246800_Fig01-02.jpg

图 1-2 。向 Android SDK 添加软件包

单击安装按钮。您需要为正在安装的每个项目点按“接受”(或“全部接受”),然后点按“安装”。Android 然后下载你的包和平台让你可以使用。Google APIs 是使用 Google Maps 开发应用的附加组件。你可以随时回来添加更多的软件包。

更新 PATH 环境变量

Android SDK 附带了一个工具目录,您希望它位于您的路径中。在您的路径中还需要刚刚安装的平台工具目录。让我们现在添加它们,或者,如果您正在升级,请确保它们是正确的。当你在那里时,你也可以添加一个 JDK bin 目录,这将使以后的生活更容易。

对于 Windows,返回到环境变量窗口。编辑路径变量并添加分号(;)放在最后,后面是 Android SDK tools 文件夹的路径,再后面是一个分号,再后面是 Android SDK platform-tools 文件夹的路径,再后面是一个分号,然后是 %JAVA_HOME%\bin 。完成后,点按“好”。对于 Mac OS X 和 Linux,编辑你的。bashrc 文件,并将 Android SDK 工具目录路径添加到您的 path 变量,以及 Android SDK 平台工具目录和 $JAVA_HOME/bin 目录。类似下面的内容适用于 Linux:

export PATH=$PATH:$HOME/android-sdk-linux_x86/tools:$HOME/android-sdk-linux_x86/platform-tools:$JAVA_HOME/bin

只要确保指向 Android SDK 工具目录的 PATH 组件对于您的特定设置是正确的。

工具窗口

在本书的后面,有时您需要执行命令行工具。这些程序是 JDK 或 Android SDK 的一部分。通过将这些目录放在您的路径中,您不需要指定完整的路径名来执行它们,但是您需要启动一个工具窗口 来运行它们(后面的章节参考这个工具窗口)。在 Windows 中创建工具窗口最简单的方法是选择开始image运行,键入 cmd ,点击确定。对于 Mac OS X,从 Finder 中的应用文件夹中选择“终端”,或者从 Dock 中选择“终端”(如果有)。对于 Linux,运行您最喜欢的终端。

稍后您可能需要知道工作站的 IP 地址。要在 Windows 中找到它,启动工具窗口并输入命令 ipconfig 。结果包含一个 IPv4(或类似的东西)条目,旁边列出了您的 IP 地址。IP 地址看起来像这样:192.168.1.25。对于 Mac OS X 和 Linux,启动工具窗口并使用命令 ifconfig 。你可以在标签 inet addr 旁找到你的 IP 地址。

您可能会看到名为 localhost 或 lo 的网络连接;此网络连接的 IP 地址是 127.0.0.1。这是操作系统使用的特殊网络连接,与您工作站的 IP 地址不同。为您的工作站 IP 地址寻找一个不同的数字。

安装 ADT

现在你需要安装 ADT(最近更名为 GDT,谷歌开发者工具),这是一个帮助你构建 Android 应用的 Eclipse 插件。具体来说,ADT 与 Eclipse 集成,为您创建、测试和调试 Android 应用提供了便利。您需要使用 Eclipse 中的 Install New Software 工具来执行安装。(本节稍后将介绍升级 ADT 的说明。)要开始,启动 Eclipse IDE 并遵循以下步骤:

  1. 选择帮助image安装新软件。

  2. Select the Work With field, type in

    [`dl-ssl.google.com/android/eclipse/`](https://dl-ssl.google.com/android/eclipse/),
    

    然后按回车键。Eclipse 联系站点并填充列表,如图 1-3 所示。

    9781430246800_Fig01-03.jpg

    图 1-3 。使用 Eclipse 中的安装新软件特性安装 ADT

  3. 您应该看到一个名为 Developer Tools 的条目,它有四个子节点:Android DDMS、Android 开发工具、Android Hierarchy Viewer 和 Android Traceview。就在出版这本书之前,谷歌更新了 ADT,使其成为更通用的 Eclipse 或 GDT 谷歌开发者工具插件的一部分。在 GDT 寻找同样的选择。选择父节点 Developer Tools,确保也选择了子节点,然后单击 Next 按钮。你看到的版本可能比这些新,没关系。您可能还会看到其他工具。这些工具将在第十一章中进一步解释。

  4. Eclipse 要求您验证要安装的工具。单击下一步。

  5. 您需要查看 ADT 以及安装 ADT 所需工具的许可证。查看许可证,单击“我接受”,然后单击“完成”按钮。

Eclipse 下载开发人员工具并安装它们。您需要重新启动 Eclipse,新的插件才会出现在 IDE 中。

如果您在 Eclipse 中已经有了一个旧版本的 ADT,请转到 Eclipse Help 菜单并选择 Check for Updates。您应该看到 ADT 的新版本,并能够按照安装说明进行操作,从步骤 3 开始。

注意如果您正在升级 ADT,您可能在要升级的工具列表中看不到这些工具。如果你没有看到它们,那么在你升级了 ADT 的其余部分之后,去安装新的软件,并从 Works With 菜单中选择【https://dl-ssl.google.com/android/eclipse/】。中间的窗口应该显示可以安装的其他工具。

让 ADT 在 Eclipse 中发挥作用的最后一步是将它指向 Android SDK。在 Eclipse 中,选择窗口image首选项。(在 Mac OS X 上,偏好设置位于 Eclipse 菜单下。)在首选项对话框中,选择 Android 节点,并将 SDK 位置字段设置为 Android SDK 的路径(参见图 1-4 ),然后单击应用按钮。请注意,您可能会看到一个对话框,询问您是否要向 Google 发送有关 Android SDK 的使用统计数据;这个决定取决于你。

9781430246800_Fig01-04.jpg

图 1-4 。将 ADT 指向 Android SDK

您可能想在 Android image构建页面上再做一次偏好更改。如果你想让你的文件保存得更快,跳过打包选项应该被选中。默认情况下,ADT 会在每次构建应用时为启动做好准备。通过选中此选项,只有在真正需要时才会进行打包和索引。

从 Eclipse 中,您可以启动 SDK 管理器。为此,选择窗口image Android SDK 管理器。您应该会看到与图 1-2 中相同的窗口。

如果您已经选择 Eclipse 作为您的 IDE,那么您几乎已经为您的第一个 Android 应用做好了准备——您可以跳过 Android Studio 的以下部分,直接进入“学习 Android 的基本组件”部分。

设置您的 Android Studio 环境

2013 年,谷歌推出了第二个支持的开发环境,称为 Android Studio (或推出时的 Android Developer Studio)。这是基于一个流行的 Java IDE: IDEA IntelliJ。关于 Android Studio 最重要的一点是,它仍然是一个正在进行中的工作。截至本书写作时,最新版本是 1.2。任何熟悉版本号变幻莫测的人都知道,从低版本号开始通常意味着“小心!”

第二个需要记住的最重要的事情是,Android Studio 目前假设的是 64 位开发环境。这意味着像 Java 这样的依赖也需要是 64 位的。

接下来的章节简要介绍了 Android Studio 的设置,供那些有兴趣或者有足够热情的人使用。请注意,本书的其余部分主要展示了使用 Eclipse 的示例和选项。

Android Studio 的 Java 需求

像 Eclipse 一样,Android Studio 依赖于一个有效的 Java 安装。Android Studio 会在安装过程中尝试自动发现您的 Java 环境,因此安装和配置 Java 是值得的。

对于 Java 安装,记住 Android Studio 是 64 位的。在所有其他方面,你可以遵循前面题为“下载 JDK”的部分——为了节省一些树木,我们不会在这里逐字重复。确保您遵循了那里的所有说明,包括设置 JAVA_HOME 环境变量,因为这是 Android Studio 安装程序用来查找您的 JAVA 安装的主要指示器。

下载和安装 Android Studio

谷歌在主要的 Android 开发网站上提供 Android Studio,目前位于 URL【http://developer.android.com/sdk/installing/studio.html】。这可能会随时改变,但在 developer.android.com 网站上快速搜索一下应该就能找到。Android Studio 被打包成一个整体包,几乎包含了你需要的所有组件。Java SDK 是个例外——我们很快会谈到这一点。从前面的 URL 下载的软件包将被命名为类似于 android-studio-bundle-132.893413-windows.exe 的名称(对于 windows ),或者类似的名称(对于 OS X 和 Linux 具有不同的扩展名),包括以下内容:

  • IntelliJ IDEA 的 Android Studio 捆绑包的当前最新版本
  • 内置 Android SDK
  • 所有相关的 Android 构建工具
  • Android 虚拟设备映像

我们将在后面的章节中详细讨论这些组件。对于 Windows 安装,运行可执行文件,按照提示选择安装路径,并决定 Android Studio 是对 Windows 机器上的所有用户开放,还是只对当前用户开放。对于 OS X,打开。dmg 文件,并将 Android Studio 条目复制到您的应用文件夹。在 Linux 下,提取的内容。tgz 文件到你想要的位置。

安装完成后,可以从提示时选择的开始菜单文件夹启动 Windows 下的 Android Studio 在应用文件夹的 OS X 下;而在 Linux 下通过运行。安装目录下的/Android-studio/bin/studio . sh 文件。不管是什么操作系统,你都应该看到 Android Studio 主屏幕,如图图 1-5 所示。

9781430246800_Fig01-05.jpg

图 1-5 。 Android Studio 首次推出时

学习 Android 的基本组件

每个应用框架都有一些关键组件,开发人员在开始编写基于框架的应用之前需要了解这些组件。例如,为了编写 Java 2 平台企业版(J2EE)应用,您需要理解 JavaServer Pages (JSP)和 servlets。类似地,当您为 Android 构建应用时,您需要理解视图、活动、片段、意图、内容提供者、服务和 AndroidManifest.xml 文件。您将在此简要介绍这些基本概念,并在整本书中更详细地探讨它们。

视角

视图 是构成用户界面基本构件的用户界面(UI)元素。视图可以是按钮、标签、文本字段或许多其他 UI 元素。如果你熟悉 J2EE 和 Swing 中的视图,那么你就会理解 Android 中的视图。视图也被用作视图的容器,这意味着 UI 中通常有视图的层次结构。最后,你看到的一切都是一种风景。

活动

一个活动 是一个 UI 概念,通常代表应用中的一个屏幕。它通常包含一个或多个视图,但这不是必须的。活动听起来很像——帮助用户做一件事的东西,可以是查看数据、创建数据或编辑数据。大多数 Android 应用内部都有几个活动。

片段

当屏幕很大时,很难在单个活动中管理它的所有功能。片段 就像子活动,一个活动可以同时在屏幕上显示一个或多个片段。当屏幕很小时,一个活动很可能只包含一个片段,这个片段可以是在更大的屏幕中使用的同一个片段。

目的

意图 一般定义做一些工作的“意图”。意图包含了几个概念,所以理解它们的最好方法是查看它们的使用示例。您可以使用意图来执行以下任务:

  • 广播消息
  • 启动服务
  • 发起一项活动
  • 显示网页或联系人列表
  • 拨打电话号码或接听电话

意图并不总是由你的应用发起——它们也被系统用来通知你的应用特定的事件(比如一个文本消息的到达)。

意图可以是明确的,也可以是隐含的。如果你只是简单地说你想显示一个 URL,系统会决定用什么组件来实现这个意图。您还可以提供关于应该如何处理意图的具体信息。意图松散地耦合动作和动作处理器。

内容供应器

设备上的移动应用之间的数据共享很常见。因此,Android 为应用定义了一个标准机制来共享数据(如联系人列表),而不暴露底层存储、结构和实现。通过内容供应器,您可以公开您的数据,并让您的应用使用来自其他应用的数据。

服务

Android 中的服务 类似于你在 Windows 或其他平台中看到的服务——它们是后台进程,可能会运行很长时间。Android 定义了两种类型的服务:本地服务和远程服务。本地服务是只能由托管服务的应用访问的组件。相反,远程服务是指设备上运行的其他应用可以远程访问的服务。

电子邮件应用用来轮询新邮件的组件就是服务的一个例子。如果设备上运行的其他应用不使用这种服务,则这种服务可以是本地服务。如果几个应用使用该服务,那么它就被实现为一个远程服务。

AndroidManifest.xml

AndroidManifest.xml ,类似于 J2EE 世界的 web.xml 文件,定义了你的应用的内容和行为。例如,它列出了应用的活动和服务,以及应用运行所需的权限和功能。

AVD

AVD 允许开发者测试他们的应用,而无需连接实际的 Android 设备(通常是手机或平板电脑)。avd 可以以各种配置创建,以模拟不同类型的真实设备。

你好世界!

现在,您已经准备好构建您的第一个 Android 应用了。您首先构建一个简单的“Hello World!”程序。按照以下步骤创建应用的框架:

  1. 启动 Eclipse,并选择 File image New image Project。在新建项目对话框中,选择 Android 应用项目,然后单击下一步。你看到新的 Android 项目对话框,如图图 1-6 所示。(Eclipse 可能在新菜单中添加了 Android Project,如果有就可以用。)工具栏上还有一个新的 Android 项目按钮。

    9781430246800_Fig01-06.jpg

    图 1-6 .使用新建项目向导创建机器人应用

  2. 如图 1-6 所示,输入 HelloAndroid 作为项目名称。您需要将这个项目与您在 Eclipse 中创建的其他项目区分开来,所以当您在 Eclipse 环境中查看所有项目时,选择一个对您有意义的名称。您还将看到可用的构建目标。选择安卓 2.2。这是您用作应用基础的 Android 版本。你可以在更高版本的 Android 上运行你的应用,比如 4.3 和 4.4;但是 Android 2.2 有这个例子需要的所有功能,所以选择它作为你的目标。一般来说,最好尽可能选择最低的版本号,因为这样可以最大限度地增加运行应用的设备数量。

  3. 让项目名称根据您的应用名称自动完成。

  4. 使用 com.androidbook.hello 作为包名。像所有 Java 应用一样,您的应用必须有一个基本包名,就是这个。此包名将用作应用的标识符,并且在所有应用中必须是唯一的。因此,最好以您自己的域名作为包名的开头。如果您没有自己的包名,请创造性地确保您的包名不会被其他任何人使用。单击下一步。

  5. 下一个窗口提供了客户启动器图标的选项、存储源代码和其他文件的工作区的实际目录,以及其他几个选项。将所有这些都保留为默认值,然后单击 Next。

  6. 下一个窗口显示配置启动器图标选项和设置,如图图 1-7 所示。尽管您所做的任何更改都是修饰性的,会影响应用部署时启动器图标的外观,而不是它的实际逻辑,但是您可以随意使用这里的选项。准备就绪后,单击下一步。

    9781430246800_Fig01-07.jpg

    图 1-7 .新机器人项目的机器人启动器配置选项

  7. 接下来,您将看到创建活动屏幕。选择“空白活动”作为活动类型,然后单击“下一步”移至向导的最后一个屏幕。

  8. 新 Android 应用向导的最后一个屏幕将是空白的活动详情页面。键入 HelloActivity 作为活动名称。你告诉 Android 这个活动是当你的应用启动时启动的。您的应用中可能还有其他活动,但这是用户看到的第一个活动。允许布局名称自动填充值 activity_hello 。

  9. 单击 Finish 按钮,这会告诉 ADT 为您生成项目框架。现在,打开 src 文件夹下的 HelloActivity.java 文件,修改 onCreate() 方法如下:

    /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            /** create a TextView and write Hello World! */
            TextView tv = new TextView(this);
            tv.setText("Hello World!");
            /** set the content view to the TextView */
            setContentView(tv);
        }
    

您将需要添加一个导入 Android . widget . textview;用其他导入文件顶部的语句来摆脱 Eclipse 报告的错误。保存 HelloActivity.java 的文件。

要运行这个应用,您需要创建一个 Eclipse 启动配置,并且您需要一个运行它的虚拟设备。我们将快速浏览这些步骤,稍后再回来讨论 AVDs 的更多细节。按照以下步骤创建 Eclipse 启动配置:

  1. 选择运行image运行配置。

  2. 在运行配置对话框中,双击左窗格中的 Android 应用。向导将插入一个名为“新配置”的新配置。

  3. 重命名配置 RunHelloWorld

  4. 单击 Browse 按钮,并选择 HelloAndroid 项目。

  5. 将启动操作设置为启动默认活动。该对话框应如图 1-8 所示。

    9781430246800_Fig01-08.jpg

    图 1-8 .配置一个黯然失色运行配置来运行“你好世界!”应用

  6. 单击应用,然后单击运行。你就快到了!Eclipse 已经准备好运行您的应用,但是它需要一个运行它的设备。如图 1-9 所示,警告您没有找到兼容的目标,并询问您是否要创建一个。单击是。

    9781430246800_Fig01-09.jpg

    图 1-9 .警告目标并要求新主动脉瓣疾病的错误信息

  7. 你会看到一个显示现有 avd 的窗口(见图 1-10 )。您需要添加一个适合您新应用的 AVD。单击新建按钮。

    9781430246800_Fig01-10.jpg

    图 1-10 .现有的 avd

  8. 填写如图图 1-11 所示的创建 AVD 表格。将 Name 设置为 KitKat,选择 Android 4.4 - API Level 19(或其他版本)作为目标,将 SD 卡大小设置为 64MB),并选择其他值,如图所示。单击创建 AVD。经理可能会确认您的 AVD 创建成功。通过单击右上角的 X 关闭 AVD 管理器窗口。

    9781430246800_Fig01-11.jpg

    图 1-11 .配置 AVD

    T12T14注意你正在为你的主动脉瓣疾病选择一个较新版本的 SDK,但是你的应用也可以在一个旧版本上运行。这是可以的,因为具有较新软件开发工具包(Software Development Kit)的主动脉瓣疾病可以运行需要较旧软件开发工具包(Software Development Kit)的应用。当然,相反的情况是不正确的:一个需要新软件开发工具包(Software Development Kit)特性的应用不能在一个有旧软件开发工具包(Software Development Kit)的主动脉瓣疾病上运行.

  9. 从底部列表中选择您的新 AVD。请注意,您可能需要单击刷新按钮,以使任何新的 avd 显示在列表中。单击确定按钮。

  10. Eclipse 用您的第一个 Android 应用启动模拟器(见图 1-12 )!

9781430246800_Fig01-12.jpg

图 1-12 。 HelloAndroidApp 在模拟器中运行

注意仿真器可能需要一段时间来仿真设备启动过程。一旦启动过程完成,您通常会看到一个锁定的屏幕。单击菜单按钮或拖动解锁图像来解锁 AVD。解锁后,你应该会看到 HelloAndroidApp 在模拟器中运行,如图图 1-11 所示。请注意,模拟器在启动过程中会在后台启动其他应用,因此您可能会不时看到警告或错误消息。如果这样做了,通常可以关闭它,让模拟器进入启动过程的下一步。例如,如果您运行模拟器并看到类似“应用 abc 没有响应”的消息,您可以等待应用启动,或者直接要求模拟器强制关闭应用。通常,您应该等待,让模拟器干净地启动。

现在您知道了如何创建一个新的 Android 应用,并在模拟器中运行它。接下来,我们将更深入地了解 AVDs,以及如何部署到真实设备上。

AVD

AVD 代表一个设备及其配置。例如,你可以用一个 AVD 来代表一个运行 1.5 版本 SDK 和 32MB SD 卡的非常旧的 Android 设备。这个想法是你创建你要支持的 avd,然后在开发和测试你的应用时,将仿真器指向这些 avd 中的一个。指定(和更改)使用哪种 AVD 非常容易,并且可以轻松测试各种配置。前面,您看到了如何使用 Eclipse 创建 AVD。你可以通过选择窗口image Android 虚拟设备管理器在 Eclipse 中制作更多的 avd。您还可以使用命令行创建 avd,在工具目录下使用名为 android 的工具(例如 c:\ Android-SDK-windows \ tools )。 android 允许你创建一个新的 AVD 并管理现有的 AVD。例如,您可以通过使用“avd”选项调用 android 来查看现有的 avd、移动 avd 等等。运行 android -help 可以看到使用 android 的可用选项。现在,让我们创建一个 AVD。

在真实设备上运行

测试 Android 应用的最佳方式是在真实设备上运行它。任何商业 Android 设备在连接到您的工作站时都应该可以工作,但您可能需要做一点工作来设置它。如果你有一台 Mac,除了用 USB 线把它插上,你不需要做任何事情。然后,在设备本身上,选择设置image应用image开发(尽管这可能因手机和版本而异)并启用 USB 调试。在 Linux 上,你可能需要创建或者修改这个文件:/etc/udev/rules . d/51-Android . rules。我们将此文件的副本与项目文件一起放在我们的网站上;将其复制到正确的目录,并根据您的计算机修改相应的用户名和组值。然后,当你插入一个 Android 设备时,它会被识别。接下来,在设备上启用 USB 调试。

对于 Windows,你必须处理 USB 驱动程序。谷歌提供了一些 Android 包,这些包放在 Android SDK 目录的 usb_driver 子目录下。其他设备供应商为您提供了驱动程序,所以请在他们的网站上查找。你也可以访问 forum.xda-developers.com 的 XDA 论坛,那里讨论了为各种手机和设备寻找和配置驱动程序的建议。当您设置好驱动程序后,在设备上启用 USB 调试,您就准备好了。

现在,您的设备已连接到您的工作站,当您尝试启动应用时,它会直接在设备上启动,或者(如果您运行了仿真器或连接了其他设备)会打开一个窗口,您可以在其中选择要启动的设备或仿真器。如果没有,请尝试编辑您的运行配置来手动选择目标。

探索 Android 应用的结构

虽然 Android 应用的大小和复杂性可能有很大的不同,但它们的结构是相似的。图 1-13 显示了“Hello World!”您刚刚构建的应用。

9781430246800_Fig01-13.jpg

图 1-13 。“你好,世界!”的结构应用

Android 应用有些工件是必需的,有些是可选的。表 1-1 总结了一个 Android 应用的元素。

表 1-1 。安卓应用的神器

|

假象

|

描述

|

必需的?

| | --- | --- | --- | | AndroidManifest.xml | Android 应用描述符文件。这个文件定义了应用的活动、内容提供者、服务和意图接收者。您还可以使用该文件以声明方式定义应用所需的权限,以及检测和测试选项。 | 是 | | src | 包含应用所有源代码的文件夹。 | 是 | | 资产 | 文件夹和文件的任意集合。 | 不 | | 无 | 包含应用资源的文件夹。这是可绘制、动画师、布局、菜单、值、 xml 和 raw 的父文件夹。 | 是 | | 可拉伸 | 包含应用使用的图像或图像描述符文件的文件夹。 | 不 | | 动画师 | 包含 XML 描述符文件的文件夹,这些文件描述了应用使用的动画。 | 不 | | 布局 | 包含应用视图的文件夹。 | 不 | | 菜单 | 包含应用中菜单的 XML 描述符文件的文件夹。 | 不 | | 值 | 包含应用使用的其他资源的文件夹。该文件夹中的资源示例包括字符串、数组、样式和颜色。 | 不 | | xml | 包含应用使用的附加 XML 文件的文件夹。 | 不 | | 生 | 包含应用所需的附加数据(可能是非 XML 数据)的文件夹。 | 不 |

正如你从表 1-1 中看到的,一个 Android 应用主要由三部分组成:应用描述符、各种资源的集合和应用的源代码。如果你暂时抛开 AndroidManifest.xml 文件,你可以用这种简单的方式来看待一个 Android 应用:你有一些用代码实现的业务逻辑,其他的都是资源。

Android 也采用了通过 XML 标记定义视图的方法。您从这种方法中受益,因为您不必对应用的视图进行硬编码;您可以通过编辑标记来修改应用的外观。

还值得注意的是资源方面的一些限制。首先,Android 只支持在 res 下的预定义文件夹中的单级文件列表。例如,资产文件夹和 res 下的 raw 文件夹有一些相似之处。两个文件夹都可以包含原始文件,但是在 raw 中的文件被认为是资源,而在 assets 中的文件不是。所以 raw 中的文件是本地化的,可以通过资源 id 访问,等等。但是 assets 文件夹的内容被认为是通用内容,可以在没有资源限制和支持的情况下使用。请注意,因为 assets 文件夹的内容不被视为资源,所以您可以在该文件夹中放置任意层次的文件夹和文件。(第三章讲了很多关于资源的东西。)

注意你可能已经注意到 XML 在 Android 中被大量使用。您知道 XML 可能是一种臃肿的数据格式,那么当您知道您的目标是资源有限的设备时,依赖 XML 有意义吗?事实证明,您在开发过程中创建的 XML 实际上是使用 Android 资产打包工具(AAPT)编译成二进制的。因此,当您的应用安装在设备上时,设备上的文件以二进制形式存储。当运行时需要该文件时,该文件以二进制形式读取,而不是转换回 XML。这给了您两个世界的好处——您可以使用 XML,而不必担心占用设备上的宝贵资源。

检查应用的生命周期

Android 应用的生命周期由系统根据用户需求、可用资源等进行严格管理。例如,用户可能想要启动 web 浏览器,但是系统最终决定是否启动该应用。尽管系统是最终的管理者,但它遵循一些定义好的逻辑准则来决定应用是否可以被加载、暂停或停止。如果用户当前正在进行某项活动,系统会给予该应用较高的优先级。相反,如果某个活动不可见,并且系统确定某个应用必须关闭以释放资源,则它会关闭优先级较低的应用。

应用生命周期的概念是合乎逻辑的,但是 Android 应用的一个基本方面使事情变得复杂。具体来说,Android 应用架构是面向组件和集成的。这允许丰富的用户体验、无缝的重用和简单的应用集成,但是给应用生命周期管理人员带来了复杂的任务。

让我们考虑一个典型的场景。用户正在通过电话与某人交谈,需要打开一封电子邮件来回答问题。用户转到主屏幕,打开邮件应用,打开电子邮件消息,单击电子邮件中的链接,并通过阅读网页上的股票报价来回答朋友的问题。这个场景需要四个应用:家庭应用、通话应用、电子邮件应用和浏览器应用。当用户从一个应用导航到下一个应用时,体验是无缝的。然而,在后台,系统正在保存并恢复应用状态。例如,当用户单击电子邮件消息中的链接时,系统在启动浏览器应用活动以启动 URL 之前保存正在运行的电子邮件消息活动的元数据。事实上,系统在开始另一个活动之前保存任何活动的元数据,以便它可以返回到该活动(例如,当用户回溯时)。如果内存成为问题,系统必须关闭运行活动的进程,并在必要时恢复它。

Android 对应用及其组件的生命周期非常敏感。因此,您需要理解和处理生命周期事件,以便构建一个稳定的应用。运行 Android 应用及其组件的进程会经历各种生命周期事件,Android 提供了回调,您可以实现这些回调来处理状态变化。首先,你应该熟悉一个活动的各种生命周期回调(见清单 1-1 )。

清单 1-1 。 一种活动的生命周期方法

protected void onCreate(Bundle savedInstanceState);
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();

清单 1-1 显示了 Android 在活动生命周期中调用的生命周期方法列表。为了确保实现一个稳定的应用,理解系统何时调用每个方法是很重要的。请注意,您不需要对所有这些方法做出反应。但是,如果你这样做了,一定要调用超类版本。图 1-14 显示了状态之间的转换。

9781430246800_Fig01-14.jpg

图 1-14 。活动的状态转换

系统可以根据正在发生的事情来启动和停止您的活动。Android 在刚创建活动时调用 onCreate() 方法。 onCreate() 后面总是跟随着对 onStart() 的调用,但是 onStart() 前面并不总是跟随着对 onCreate() 的调用,因为如果您的应用被停止,就可以调用 onStart() 。当调用 onStart() 时,用户看不到您的活动,但很快就会看到了。 onResume() 在 onStart() 之后调用,正好是活动在前台,用户可以访问的时候。此时,用户可以与您的活动进行交互。

当用户决定移动到另一个活动时,系统调用您的活动的 onPause() 方法。从 onPause() 中,您可以期待调用 onResume() 或 onStop() 。 onResume() 被调用,比如用户把你的活动带回前台。如果用户看不到您的活动,将调用 onStop() 。如果您的活动在调用 onStop() 后被带回到前台,那么 onRestart() 将被调用。如果您的活动位于活动堆栈上,但对用户不可见,并且系统决定终止您的活动,则调用 onDestroy() 。

作为一名开发人员,您不必处理每一种可能的情况;你主要处理 onCreate() 、 onResume() 、 onPause() 。您处理 onCreate() 来为您的活动创建用户界面。在这种方法中,您将数据绑定到小部件,并为 UI 组件连接任何事件处理器。在 onPause() 中,您希望将关键数据保存到应用的数据存储中:这是在系统终止应用之前调用的最后一个安全方法。 onStop() 和 onDestroy() 不保证被调用,所以不要依赖这些方法进行临界逻辑。

这场讨论的要点是什么?系统管理您的应用,它可以随时启动、停止或恢复应用组件。尽管系统控制着你的组件,但是它们并不完全独立于你的应用运行。换句话说,如果系统在您的应用中启动一个活动,您可以在您的活动中依赖应用上下文。

简单调试

Android SDK 包括许多工具,您可以使用它们进行调试。这些工具与 Eclipse IDE 集成在一起(参见图 1-15 中的一个小例子)。

9781430246800_Fig01-15.jpg

图 1-15 。调试工具,你可以在构建 Android 应用时使用

你在 Android 开发中使用的工具之一是 LogCat。该工具显示您使用 android.util.Log 、异常、 System.out.println 等发出的日志消息。虽然 System.out.println 工作正常,并且消息出现在 LogCat 窗口中,但是要记录来自应用的消息,您应该使用 android.util.Log 类。该类定义了常见的信息、警告和错误方法,您可以在 LogCat 窗口中过滤这些方法,以查看您想要查看的内容。下面是一个示例 Log 命令:

Log.v("string TAG", "This is my verbose message to write to the log");

这个例子展示了日志类的静态 v() 方法,但是还有其他针对不同严重性级别的方法。最好对您想要记录的消息使用适当的调用级别,通常在您想要部署到生产的应用中留下冗长的调用并不是一个好主意。请记住,日志记录会占用内存和 CPU 资源。

LogCat 的特别之处在于,当您在模拟器中运行应用时,您可以查看日志消息,但是当您将真实设备连接到工作站并处于调试模式时,您也可以查看日志消息。事实上,日志消息是这样存储的,您甚至可以从记录日志消息时断开连接的设备中检索最新的消息。当您将设备连接到工作站并打开 LogCat 视图时,您会看到最后几百条消息。

启动模拟器

前面您已经看到了如何在 Eclipse 中从项目启动模拟器。在大多数情况下,您希望首先启动模拟器,然后在运行的模拟器中部署和测试您的应用。要在任何时候启动模拟器,首先从 Android SDK 的工具目录或从 Eclipse 的窗口菜单运行带有 avd 选项的 Android 程序进入 AVD 管理器。在管理器中,从列表中选择所需的 AVD,然后单击开始。

当你点击开始按钮时,启动选项对话框打开(见图 1-16 )。这允许您缩放模拟器窗口的大小以适合您的显示器,并更改启动和关闭选项。缩放结果有时会出乎意料地大或小,因此请根据您的屏幕尺寸和屏幕密度选择适合您的值。

9781430246800_Fig01-16.jpg

图 1-16 。启动选项对话框

您也可以在启动选项对话框中使用快照。当您退出模拟器时,保存到快照会导致较长的延迟。顾名思义,您正在将模拟器的当前状态写到一个快照映像文件中,下次启动时可以使用它来避免经历整个 Android 启动序列。如果有快照,启动速度会快得多,因此节省时间的延迟是值得的—您基本上是从您停止的地方开始。

如果你想从头开始,你可以选择清除用户数据。您也可以取消选择从快照启动以保留用户数据并完成启动序列。或者,您可以创建您喜欢的快照,并仅启用从快照启动选项;这样可以反复使用快照,所以启动和关闭都很快,因为每次退出时不会创建新的快照映像文件。快照图像文件存储在与其余 AVD 图像文件相同的目录中。如果在创建 AVD 时没有启用快照,您可以随时编辑 AVD 并在那里启用它们。

参考

以下是一些对您可能希望进一步探索的主题有帮助的参考:

摘要

本章涵盖了以下主题,帮助您为 Android 开发做好准备:

  • 下载并安装 JDK、Eclipse 或 Android Studio 以及 Android SDK
  • 如何修改路径变量并启动工具窗口
  • 安装和升级视图、活动、片段、意图、内容提供者、服务和 AndroidManifest.xml 文件的 ADT 基本概念
  • Android 虚拟设备(AVDs),当你没有设备(或者你想要测试的特定设备)时,它可以用来测试应用
  • 建立一个“你好的世界!”应用并将其部署到仿真器
  • 初始化任何应用的基本要求(项目名称、Android 目标、应用名称、包名称、主要活动、最低 SDK 版本)
  • 运行配置在哪里以及如何更改它们
  • 将真实设备连接到您的工作站,并在其上运行您的新应用
  • Android 应用的内部结构,以及活动的生命周期
  • LogCat,以及在哪里寻找来自应用的内部消息
  • 启动模拟器时可用的选项,如快照和调整屏幕显示大小

二、Android 应用架构介绍

第一章介绍了开发 Android 应用所需的环境和工具。本章将对 Android 的应用架构进行广泛的介绍。我们将通过做三件事来做到这一点。首先,我们将通过构建一个 Android 应用来展示其架构。然后,我们将介绍 Android 架构的基本组件,即活动、资源、意图、活动生命周期和保存状态。我们将以一个学习路线图来结束这一章,学习如何使用本书的其余部分来创建简单到复杂的移动应用。

在本章的第一节中,一个一页的计算器应用将让你鸟瞰使用 Android SDK 编写应用。创建这个应用将演示如何创建 UI,编写 Java 代码来控制 UI,以及构建和部署应用。

除了演示用户界面,这个计算器应用将向您介绍活动、资源和意图。这些概念是 Android 应用架构的核心。我们将在本章的第二节中详细介绍这些主题,以便为您理解 Android SDK 的其余部分打下坚实的基础。我们还将讨论活动的生命周期,并简要概述应用的持久性选项。

第三部分中,我们将给出本书剩余部分的路线图,解决构建 Android 应用的基本和高级方面。这最后一节将这些章节分成一组学习轨道。这一节是对整套 Android APIs 的广泛介绍。

此外,在这一章中你将会找到以下问题的答案:我怎样才能创建一个拥有丰富控件的 UI?如何持久存储状态?如何读取应用输入的静态文件?我怎样才能接触到网络,从网络上阅读或向网络上写东西?Android 还提供了哪些 API 让我的应用功能丰富?

事不宜迟,让我们进入简单的计算器应用,打开 Android 的世界。

探索简单的 Android 应用

本章我们想要演示的计算器应用如图 2-1 中的所示。

9781430246800_Fig02-01.jpg

图 2-1 。一款计算器应用

图 2-1 中的显示在 Android 中称为活动。这个活动在顶部有两个编辑控件,代表两个数字。您可以在这些编辑框中输入数字,并使用图形底部的运算符按钮来执行算术运算。操作的结果将显示在顶部的编辑控件中。这两个编辑框标记为操作数 1 和操作数 2。要使用 Android SDK 创建这种类型的计算器应用,您需要执行以下步骤:

  1. 在文本/xml 文件(Android 中称为布局或布局文件)中创建用户界面(UI) 定义。
  2. 在 Java 文件中编写编程逻辑(通常在扩展基本活动类的类中)。
  3. 创建一个描述您的应用的配置文件(这个文件总是被称为 AndroidManifest )。xml)。
  4. 创建一个项目和一个目录结构来放置步骤 1、2 和 3 中的文件。
  5. 使用步骤 4 中的项目构建一个可部署的包(它被称为)。apk 文件)。

通过浏览这些步骤的细节,你会对 Android 应用的制作有所了解。我们现在将经历这些步骤。

通过布局文件定义用户界面

Android 应用在许多方面类似于 web 应用。在 web 应用中,UI 就是你的网页。网页的用户界面是通过 HTML 定义的。一个 HTML 网页是一系列的控件,如段落、分区、表格、按钮等。Android 中的 UI 构造类似。Android 中的布局文件就像一个 HTML 页面,尽管控件来自 Android SDK 而不是 HTML。在 Android 中,这个文件被称为布局文件。清单 2-1 显示了产生图 2-1 的用户界面的布局文件。

清单 2-1 。 定义活动 UI 的 Android 布局文件

<?xml version="1.0" encoding="utf-8"?>
<!--
*********************************************
* calculator_layout.xml
* corresponding activity: CalculatorMainActivity.java
* prefix: cl_ (Used for prefixing unique identifiers)
*
* Use:
*    Demonstrate a simple calculator
*    Demonstrate text views, edit text, buttons, business logic
*********************************************
-->
<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:layout_margin="5dp" android:padding="5dp"
    android:background="@android:color/darker_gray"
    >
    <!--  Operand 1 -->
    <TextView android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Operand 1, (And Result)"
        />
    <EditText android:layout_width="match_parent" android:layout_height="wrap_content"
        android:id="@+id/editText1"  android:text="0"
        android:inputType="numberDecimal"/>
    <!--  Operand 2 -->
    <TextView android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Operand 2"
        android:layout_marginTop="10dp"
        />
    <EditText android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="0"
       android:id="@+id/editText2"
       android:inputType="numberDecimal">
    </EditText>
    <!--  Buttons for Various Operators -->
    <TextView android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Operand1 = Operand1 Operator Operand2"
        android:layout_marginTop="10dp"
        />
<LinearLayout
    android:orientation="horizontal"
    android:layout_marginTop="10dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <Button android:text="+" android:id="@+id/plusButton"
       android:layout_weight="1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
    </Button>
    <Button android:text="-" android:id="@+id/minusButton"
       android:layout_weight="1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
    </Button>
    <Button android:text="*" android:id="@+id/multiplyButton"
       android:layout_weight="1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
    </Button>
    <Button android:text="/" android:id="@+id/divideButton"
       android:layout_weight="1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
    </Button>
</LinearLayout>
</LinearLayout>

让我们逐行检查清单 2-1 的计算器 XML 布局文件。与图 2-1 中的相比,这个文件看起来很复杂。是的,它很冗长,但是您很快就会看到它的架构很简单。

在布局文件中指定注释

作为一个好的实践,清单 2-1 中的布局 XML 文件顶部的注释指出了这个文件名是什么,什么 UI 活动将被用来显示这个文件,这个文件的目的是什么,以及这个布局文件中有什么简单的控件。

在布局文件中添加视图和视图组

布局文件中的每个 XML 节点表示一个 UI 控件。这些控件可以是视图,也可以是其他视图的容器。其他视图的容器称为视图组 。例如,按钮就是一个视图。清单 2-1 中的 LinearLayout 是一个视图组,它将所有子视图垂直向下或水平交叉放置。因此, LinearLayout 就像一个 HTML div 一样,它可以横向或纵向布局其子元素。

在布局文件中指定控件属性

计算器布局文件中的 UI 控件有 LinearLayout 、 TextView 、 EditText 和一个按钮。当在屏幕上绘制时,这些控件中的每一个都代表一个 Java 对象。作为一个对象,每个控件都有属性。如果控件属于核心 Android SDK,它们的属性会以 "android:" 作为前缀,如 "android:orientation" 用于 LinearLayout 控件。你通常在应用中使用的大部分(如果不是全部)控件都来自核心的 Android SDK。当您编写自己的控件时,它们被称为自定义控件。这些自定义控件允许您定义自定义属性。有关自定义控件的更多信息,请参见本章的“路线图”部分。

指示视图组属性

一些控件属性被标记为“Android:layout _”、如 android:layout_width 。虽然在给定的 XML 节点中提到了这些属性,比如一个按钮,但是它们被父节点读取并使用,比如 LinearLayout 来放置子节点。父节点是视图组,如 LinearLayout 。你可以在清单 2-1 的布局文件中的第一个 LinearLayout 节点如何定义填充和边距中看到这种差异。在本例中,属性 padding 属于最顶端的 LinearLayout 对象,而同一个最顶端的 LinearLayout 的边距属性 layout_margin 属性属于 LinearLayout 的父对象,后者是 Android 框架提供的隐式视图组。所以对于填充,你说 android:padding ,对于边距,你说 android:layout_margin 。注意有无“布局 _”前缀。如果您想知道一个对象(或控件)支持什么属性,您可以使用 eclipse 中的 Ctrl-Space 来查看该对象属性的一组建议。根据您的开发环境,您可以很容易地找到一组等效的组合键来做同样的事情。

控制控件的宽度和高度

控件的两个常用属性是其布局宽度和布局高度。控件的布局父级管理这些值。这些属性的值通常是匹配 _ 父项和包装 _ 内容。如果您说您的 TextView 的宽度设置为 match_parent ,控件的宽度与父宽度匹配。当一个 TextView 被设置为它的高度 wrap_content 时,那么它的高度将刚好足以包含它在垂直方向上的所有文本。当然,这两个属性可用于布局的所有子控件,而不仅仅是文本控件。这两个布局控件属性 match_parent 和 wrap_content 也适用于控件的高度。

介绍资源和背景

虽然我们正在解释布局文件中的控件,但这是介绍资源的好地方。布局文件是资源,并且由资源组成。在计算器布局文件中,我们通过在根 LinearLayout 控件上设置背景来设置整个视图的背景。该指令如下所示:

android:background="@android:color/darker_gray"

Android 中的每个视图或控件都支持背景属性。背景通常被认为是资源。在这个例子中,背景指向一个资源,来自 Android 包,类型为颜色,引用值为深灰色。

在 Android 中,应用的许多输入被表示为资源。一些示例资源是图像文件、整个布局文件、颜色、字符串、XML 文件、菜单和 Android SDK 中列出的许多其他东西。例如,我们讨论的整个计算器布局文件本身就是一个资源。

从计算器布局文件中可以看出,资源有不同的类型。在 Android 中,它们被进一步宽泛地分为“基于价值”或“基于文件”作为值的资源的例子有字符串和颜色。文件资源的例子有图像或布局文件。清单 2-2 展示了一个创建基于价值的资源的例子,这些资源是字符串和颜色。

清单 2-2 。 基于价值的资源的例子

<?xml version="1.0" encoding="utf-8"?>
<!-- this file will be in /res/values subdirectory  -->
<resources>
    <string name="hello">Hello World, CalculatorMainActivity!</string>
    <string name="app_name">A Demo Calculator</string>
    <color name="red">#FF0000</color>
    <color name="blue">#0000FF</color>
</resources>

您可以拥有任意数量的基于值的文件,只要它们都在 /res/values 子目录下。每个文件将以资源根节点开始。您可以使用 Ctrl-Space 来发现其他可能的基于价值的可用资源。

转到基于文件的资源,清单 2-3 显示了一个将许多基于文件的资源放在它们各自的资源子目录下的例子。

清单 2-3 。 基于文件的资源示例

/res/layout/page1_layout.xml (A layout file for say page 1)
/res/drawable/page1_background.jpg (An example image file)
/res/drawable-hdpi/page1_background.jpg (Same image file for a different density)
/res/xml/some_preferences.xml (example of an input file for your app)

任何这些资源,无论是基于文件还是基于值,都可以使用“ @ ”资源引用语法在布局文件中引用。例如,在清单 2-1 中的计算器布局文件中,背景可以被设置为引号之间的颜色值,如 "#FFFFFF ,"或指向一个资源引用(由一个已经被定义为颜色资源的开始@ color/red】指示)(如清单 2-2 中的所示)。在以“@”开头的语法中,引用资源的类型是“颜色”其他类型资源的一些关键字是 string (用于字符串)、d rawable (用于图像)等。

在清单 2-1 中,读取 LinearLayout 的背景属性的值,即@ android:color/darker _ gray 的方法如下:使用 Android 核心框架中标识为 darker_gray 的资源的值,该资源的资源类型为 color 。有了这些资源引用语法的知识,再看一下计算器布局文件清单,您将能够阅读它,其中每个控件都有属性,每个属性都有一个直接指定的值,或者引用资源文件中其他地方定义的资源。

定义为资源引用的控件属性值的间接性具有优势。资源可以针对语言、设备密度变化和各种因素进行定制,而无需更改编译后的 Java 源代码。例如,当你提供背景图片时,你可以把这些图片放在不同的目录中,并用 Android 指定的惯例来命名它们。然后 Android 知道如何根据你的应用运行的设备来定位正确的图片。

在布局文件中使用文本控件

在计算器布局示例中,我们使用了两个基于文本的控件。一个是 TextView 控件,作为标签使用;另一个是 EditText 控件,用于获取输入文本。我们已经向您展示了如何使用以“ layout_ ”开头的属性来设置任何视图的宽度和高度。每个基于文本的控件也有一个名为 text 的属性。在我们的示例中,我们已经直接将文本指定为该属性的值。建议使用资源引用。例如:

android:text="Literal text"  //what we did for clarity
or
android:text="@string/LiteralTextId" //doing it properly

后一个资源 ID, LiteralTextId ,可以在 /res/values 子目录下的文件中定义,非常类似于清单 2-2 中的内容。

计算器布局中的 EditText 控件有一个属性 inputType 来提供必要的约束和验证,这些约束和验证需要在数据被键入可编辑字段时发生。请参考文档以查看可编辑字段可用的大量约束。或者,您可以使用 eclipse ADT 在编码期间动态地发现可用的输入类型。

为控件使用自动生成的 id

为了操作清单 2-1 的计算器布局中的控件,我们需要一种方法将它们转换成 Java 对象。这是通过使用活动的当前加载的布局文件中的唯一 ID 来定位这些控件来实现的。让我们看看布局文件中的一个例子,其中一个编辑文本控件被赋予一个 ID 编辑文本 2 ,如下所示:

android:id="@+id/editText2"

这种格式告诉 Android 这个 EditText 控件的 ID 是一个类型为 ID 的资源,它的整数值在 Java 中应该被称为 editText2 。+便于为 editText2 分配一个新的唯一整数。如果您没有 + 符号,那么 Android 会寻找一个用名为 editText2 的 id 定义的整数值资源。借助 + 的便利,我们可以避免先单独定义一个资源,然后再使用它。在某些情况下,您可能需要一个由多段代码共享的众所周知的 ID,在这种情况下,您将删除 + ,并采取多个步骤,首先定义 ID,然后在多个地方使用它的名称。您将在编程逻辑部分(稍后)看到如何使用这些控件 id 来定位和操作控件。

实现编程逻辑

要查看设备屏幕上的计算器布局,您需要一个从 Android SDKs 类 activity 派生的 Java 类。这样一个活动代表了移动应用中的一个窗口。所以你需要通过扩展 Android 基础的一个活动类来创建一个计算器活动,如清单 2-4 所示。

清单 2-4 。 编程逻辑:实现一个活动类

/**
 * Activity name: CalculatorMainActivity
 * Layout file: calculator_layout.xml
 * Layout shortcut prefix for ids: cl_
 * Menu file: none
 * Purpose and Logic
 * ******************
 * 1\. Demonstrate business logic for a simple calculator
 * 2\. Load the calculator_layout.xml as layout
 * 3\. Setup button callbacks
 * 4\. Respond to button clicks
 * 5\. Read values from edit text controls
 * 6\. Perform operation and update result edit control
 */
public class CalculatorMainActivity extends Activity
implements OnClickListener
{
    private EditText number1EditText;
    private EditText number2EditText;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.calculator_layout);
        gatherControls();
        setupButtons();
    }
    private void gatherControls()   {
        number1EditText = (EditText)this.findViewById(R.id.editText1);
        number2EditText = (EditText)this.findViewById(R.id.editText2);
        number2EditText.requestFocus();
    }
    private void setupButtons()    {
        Button b = (Button)this.findViewById(R.id.plusButton);
        b.setOnClickListener(this);

        b = (Button)this.findViewById(R.id.minusButton);
        b.setOnClickListener(this);

        b = (Button)this.findViewById(R.id.multiplyButton);
        b.setOnClickListener(this);

        b = (Button)this.findViewById(R.id.divideButton);
        b.setOnClickListener(this);
    }
    @Override
    public void onClick(View v)     {
        String sNum1 = number1EditText.getText().toString();
        String sNum2 = number2EditText.getText().toString();
        double num1 = getDouble(sNum1);
        double num2 = getDouble(sNum2);
        Button b = (Button)v;

        double value = 0;
        if (b.getId() == R.id.plusButton)   {
            value = plus(num1, num2);
        }
        else if (b.getId() == R.id.minusButton)   {
            value = minus(num1, num2);
        }
        else if (b.getId() == R.id.multiplyButton)   {
            value = multiply(num1, num2);
        }
        else if (b.getId() == R.id.divideButton)   {
            value = divide(num1, num2);
        }
        number1EditText.setText(Double.toString(value));
    }

    private double plus(double n1, double n2)    {
        return n1 + n2;
    }
    private double minus(double n1, double n2)    {
        return n1 - n2;
    }
    private double multiply(double n1, double n2)    {
        return n1 * n2;
    }
    private double divide(double n1, double n2)    {
        if (n2 == 0)    {
            return 0;
        }
        return n1 / n2;
    }
    private double getDouble(String s)    {
        if (validString(s))        {
            return Double.parseDouble(s);
        }
        return 0;
    }
    private boolean invalidString(String s)    {
        return !validString(s);
    }
    private boolean validString(String s)    {
        if (s == null)     {
            return false;
        }
        if (s.trim().equalsIgnoreCase(""))    {
            return false;
        }
        return true;
    }
}

在这个清单中,计算器活动被称为 CalculatorMainActivity 。一旦你有了这个活动,你可以加载计算器布局到其中,以便看到图 2-1 的计算器屏幕。

让我们了解一下 Android 中的一项活动。程序员不需要直接实例化一个活动。Android 框架可以基于用户的动作实例化一个活动。从这个意义上说,活动是由 Android 管理的一个“托管组件”。

当另一个具有更高优先级的 UI 位于某个活动之上时,该活动可以部分隐藏或完全隐藏(例如,由于一个电话)。或者,由于内存限制,后台中的活动可以被临时移除。在这些情况下,当用户再次访问应用时,活动可以自动恢复。

将布局文件装入活动

因为活动是事件驱动的,所以活动依赖于回调。第一个重要的回调是 onCreate() 回调。在清单 2-4 中给出的计算器活动中,你可以很容易地找到这个方法。这是我们将计算器布局加载到计算器活动中的地方。这是通过方法 setContentView() 完成的。该方法的输入是计算器布局文件的标识符。

Android 的一个很好的特性是它对包括布局文件在内的各种资源的处理。它自动生成一个名为 R.java 的 java 类,在这里它为所有资源定义整数 id,不管它们是基于值的还是基于文件的。在清单 2-4 给出的活动中,变量 r . layout . calculator _ layout 指向计算器布局文件(它本身在清单 2-1 中)。

当你尝试 Android 框架时,另一个神秘的东西是的。由于 Android 框架可能会停止和重启(甚至重新创建)活动,它需要一种方法将活动的最后状态传递给 onCreate() 方法。这就是保存实例捆绑包的功能。它是保存活动先前状态的键值对的集合。你将在本章后面更详细地了解状态管理的这一方面,也将在第九章中了解,在那里我们将介绍当设备旋转时会发生什么。对于计算器示例的实现,我们简单地调用超类的方法来传递状态包。

收集控件

接下来的两个方法, gatherControls() 和 setupButtons() ,为计算器建立交互模型。在 gatherecontrols()方法中,您获取需要操作(读取或写入)的编辑控件的 java 引用,并将它们本地保存在 calculator 活动类中。您可以通过在基本活动类上使用 findViewById() 方法来实现这一点。 findViewById() 方法将布局文件中控件的 Id 作为输入。这里,Android 也自动生成这些 id,并将它们放入 R.java 类。在您的 eclipse 项目中,您可以在/ gen 子目录中看到这个文件。清单 2-5 显示了为这个计算器项目生成的 R.java 文件。(如果您自己尝试这个项目,这些 id 可能会有所不同。所以使用这个清单主要是为了理解概念。)

清单 2-5 。自动生成的资源标识:R.java

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int background=0x7f020000;
        public static final int icon=0x7f020001;
    }
    public static final class id {
        public static final int divideButton=0x7f050005;
        public static final int editText1=0x7f050000;
        public static final int editText2=0x7f050001;
        public static final int minusButton=0x7f050003;
        public static final int multiplyButton=0x7f050004;
        public static final int plusButton=0x7f050002;
    }
    public static final class layout {
        public static final int calculator_layout=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040001;
        public static final int hello=0x7f040000;
    }
}

注意 R.java 如何为每种资源类型使用不同的类前缀。这允许 eclipse 中的程序员根据 id 的类型快速地将它们分开。所以,比如所有布局文件的 id 都以 R.layout 为前缀,所有图像 id 都以 R.drawable 为前缀,所有字符串都以 R.string 为前缀,等等。然而,在使用这些 id 时有一个注意事项。即使您有十个布局文件,所有控件的 id 都生成到一个名称空间中,如 R.id.*(其中“id”是一个资源类型的示例)。因此,您可能希望养成在布局文件中用一些前缀来命名控件的习惯,以表明它们属于哪个布局文件。

设置按钮

清单 2-1 的计算器布局中的一些控件是计算器按钮。分别是代表操作员的按钮: +、- 、 x 、 / 。我们需要在按下这些按钮时调用代码。方法是在按钮控件上注册一个回调对象。这些回调对象必须实现视图。OnClickListener 接口。计算器活动除了扩展活动类之外,还实现了视图。OnClickListener 接口,允许我们将活动注册为每个按钮被按下时需要被回调的活动。正如您在活动代码中看到的(清单 2-4 ),这是通过调用每个按钮上的 setOnClickListener 来完成的。

对按钮点击的响应:将所有这些联系在一起

当点击任何操作按钮时,清单 2-4 中的给出的计算器活动中的 onClick() 方法被调用。在这个方法中,我们将调查回调的视图的 ID。这个调用视图应该是按钮之一。在这个方法中,我们将从两个编辑文本控件中读取值(操作数值),然后调用一个特定于每个操作符的方法。运算符方法将计算结果并更新标记为 result 的编辑文本。

更新机器人清单。可扩展置标语言

到目前为止,我们已经有了 UI(根据布局文件)和根据计算器活动的业务逻辑。每个 Android 应用都必须有自己的配置文件。这个文件叫做 AndroidManifest.xml 。这可以在项目的根目录中找到。清单 2-6 显示了该项目的 AndroidManifest.xml 。

清单 2-6 。?? 应用配置文件:AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
      package="com.androidbook.calculator"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="14" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".CalculatorMainActivity"
            android:theme="@android:style/Theme.Light"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

这个清单文件的 package 属性遵循类似于 java 名称空间的命名结构。在计算器 app 中,包设置为 com . androidbook . calculator。这就像给你的应用一个名字和一个唯一的标识符。一旦您签署了此应用并将其安装在谷歌 Play 商店等应用发布者上,只有您才能更新它或发布它的后续版本。 uses-sdk 指令表示该应用向后兼容的 API。应用节点有许多属性,包括它的标签和一个将显示在 Android 设备应用菜单中的图标。在应用节点内部,我们需要定义组成这个应用的所有活动。每个活动由其各自的 java 类名来标识。如果活动类名不是完全限定的,那么 java 包就被认为与所标识的应用包相同。活动的主题表示属于该活动的视图将继承的一组属性。这就像在 HTML UI 上设置 CSS 样式一样。Android 有一些默认的风格。选择浅色主题有利于截图时的对比(如图图 2-1 )。第七章专门讨论在你的应用中使用风格和主题。

在 Android 应用清单文件中,活动可以指定一系列意图过滤器。意图是 Android 独有的编程概念。Android 非常依赖这些意图。Android 使用意图对象来调用包括活动在内的应用组件。一个意图对象可以包含一个显式活动类名,这样当您调用该意图时,您最终会调用该活动。或者,除了有一个显式的类名之外,intent 还可以指示一个通用的动作,比如查看网页的 VIEW。当你用一个普通的动作调用这样一个意图时,Android 将会呈现所有可能的活动来满足这个动作。活动通过 manifest 文件向 Android 注册,它们可以通过意图过滤器来响应一些动作。清单 2-7 展示了如何通过一个意图对象来调用一个活动。

清单 2-7 。 使用意图对象调用活动

//currentActivity refers to the activity in which this code runs
Intent i = new Intent(currentActivity,SomeTargetActivity.class);
currentActivity.startActivity(i); //start the target activity

尽管我们使用 currentActivity 作为第一个参数的值来创建一个 intent,但它所需要的只是一个名为 Context 的基类引用。ccontext 引用代表了应用上下文,像活动这样的组件在其中运行。回到 intent 对象,它有许多标志和额外的数据元素,可以用来控制 intent 正在调用的目标活动的行为。清单 2-8 显示了一个例子。

清单 2-8 。 在一个意向对象上使用临时演员

//currentActivity refers to the activity in which this code runs
Intent intent = new Intent(currentActivity,SomeTargetActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("some-key", "some-value");
currentActivity.startActivity(intent);

在本例中,我们希望将目标活动放在窗口或活动堆栈的顶部,并关闭之前在它上面的任何其他活动。当一个人从其他活动中调用活动时,这些活动相互重叠。这个堆栈允许后退按钮导航回堆栈中的上一个活动。当您返回时,当前顶层活动结束,前一个活动显示在前台。清单 2-8 中的代码就像回到目标活动的最后一个位置,使它成为顶层实例,并删除/完成它上面所有最近的活动。 Extras on intent 是一组键值对,您可以将它们从源活动传递给目标活动。各种活动相互之间相当孤立。它们之间不共享局部变量。相反,它们应该通过可以序列化和反序列化的对象来传递数据。Android 使用了一个类似于 Serializable 的界面,叫做 Parcelable ,它允许更大的灵活性和效率。

最终,每个活动几乎总是由意图对象启动。您可以通过调用 getIntent() 在目标活动中的任何地方获取 intent 对象。一旦获得了 intent 对象,就可以获得它的额外内容,并查看是否有您需要的相关数据。

意图及其变体的完整研究是一个大课题。在结束对计算器应用的讨论后,我们将在本章的后面继续讨论更多的意图。我们还在这一章的末尾提供了一个链接,可以找到我们之前版本中关于意图的免费章节。

将文件放置在 Android 项目中

让我们回到我们的主线,计算器 app。至此,您已经拥有了创建计算器应用所需的三个文件。使用你在第一章中学到的知识创建一个空的 Android 项目,并调整该项目来放置这三个文件。清单 2-9 中给出了这些文件及其父目录。

清单 2-9 。计算器应用文件的 位置

/res/layout/calculator_layout.xml
/src/com/androidbook/calculator/CalculatorActivity.java
/AndroidManifest.xml

图 2-2 显示了你的 Android 项目在 eclipse 中的结构。你可以看到清单 2-9 中文件的相对位置。

9781430246800_Fig02-02.jpg

图 2-2 。一款计算器 app 目录结构

图 2-1 中的目录结构也显示了其他资源如图像和字符串的位置。您还可以在图 2-2 中看到设备相关图像文件的目录结构。这就是 Android 如何通过使用不同的资源子目录后缀来解决本地化多语言支持。当你阅读这本书的时候,你会学到一个 Android 项目的其他子目录。

在真实设备上测试计算器应用

现在剩下的就是构建 APK 文件,签署它,并准备好部署。测试您的项目的最简单的方法是让 eclipse 将 APK 部署到模拟器并测试它。在设备上测试此文件(签名后)的最简单方法是通过电子邮件将其发送给您自己,然后在您的设备上打开电子邮件。设备上有一个安全设置,允许来自未验证来源的 apk。只要这是允许的,你就可以安装 APK 文件并在你的设备上运行。或者您也可以将设备连接到 USB 端口,让 eclipse 将 APK 直接部署到设备上。您甚至可以通过 eclipse 在设备上调试它。您也可以将 APK 文件从您的 PC 或 Mac 复制到设备 SD 卡,并从那里进行安装。

这就结束了我们关于计算器应用的部分,它展示了 Android 应用的本质。现在我们将转到本章的第二部分,在这里我们将更深入地讨论活动,并重新审视资源、意图和保存状态。先说活动。

Android 活动生命周期

Android 活动是 Android 应用的独立组件,可以根据各种事件(包括用户发起的和系统发起的事件)来启动、停止、暂停、重启或回收。因此,通过查看活动的所有回调来审查活动生命周期的架构是非常重要的。图 2-3 通过记录回调的顺序和执行回调的环境,展示了活动的生命周期。让我们逐一考虑这些回调方法。

9781430246800_Fig02-03.jpg

图 2-3 。注释安卓活动生命周期

void onCreate (捆绑储蓄罐)

活动的生命周期从这个方法开始。在这种方法中,您应该通过将布局加载到活动的内容视图中来加载视图层次结构。您还可以初始化任何可能在活动的生命周期中使用的活动级别变量。像许多回调一样,你也首先调用父类的 onCreate() 方法。

当调用 onCreate 时,活动可能处于三种状态之一。该活动可能是第一次开始其生命的全新活动。或者它可以是由于配置改变而自动重启的活动,例如设备从一个方向旋转到另一个方向。或者,它是一个在上次由于内存不足而关闭进程后重新启动的活动,并且在后台运行。在 onCreate 回调中,如果您在每个场景中需要做的事情不同,您应该考虑这些场景。

现在我们可以理解涉及 savedInstanceBundle 的这个方法的参数了。您可以使用这个包来查看活动的先前状态。这个包最初可能用于保存配置更改期间的活动状态,或者当活动及其流程由于内存不足而关闭时。保存到这个 bundle 参数中的状态通常被称为活动的实例状态。实例状态本质上是临时的;具体来说,在这个调用过程中,它被绑定到应用的这个实例。这种类型的状态不会像文件一样被写入永久存储。当应用恢复时,如果这种状态恢复到初始状态,用户将不会太不安。在回调中我们将很快解释叫做【on pause()你可以把必须持久化的状态保存到长期存储器中。如果发生这种情况,您可以使用 onCreate() 方法来加载该状态以及启动的一部分。

这种方法还需要考虑另一个因素。当由于方向改变而重新启动或重新创建一个活动时,旧的活动被销毁,并在其位置创建一个新的活动。这意味着新的活动在内存中有了新的引用。旧的活动参考不再有效。让一个外部线程或全局对象抓住旧的活动不放是错误的。因此,当活动被重新创建时,需要有一种机制来告诉外部对象有一个新的活动引用。为此,重新创建的活动需要知道外部对象的引用。这个外部对象引用被称为“非配置实例引用”有一个回调方法叫做 onRetainNonConfigurationInstance()可以返回对这个外部对象的引用;我们很快会谈到这一点。Android SDK 随后保留这个引用,并通过一个名为 getlastonconfigurationinstance()的方法使其可用于重新创建的活动。请注意,在第八章中,我们将向您展示如何通过所谓的无头保留片段更好地做到这一点。我们将在关于 AsyncTask 的第十五章中回到这个话题。

onCreate 方法还有另一个细微差别。你可能想要确保在布局中你有正确的视图和片段(你将在第八章中学习)来匹配状态被保存的时间。因为随后的 onRestoreInstanceState()(在 onStart() 之后调用)假设所有的视图和片段层次结构都存在以恢复它们各自的状态,仅仅存在先前的状态不会重新创建视图。因此,由这个方法来加载要显示的正确布局。如果您在与活动交互的过程中没有删除或添加视图,这通常不是问题。

void onStart()

创建后,此方法将活动推入可见状态。换句话说,这个方法启动了活动的“可见生命周期”。这个方法在 onCreate() 之后被调用。这个方法假设视图层次结构已经从 onCreate() 加载并可用。你通常不需要重写这个方法,如果你这样做了,确保你首先调用父节点的 onStart() 。在图 2-2 中,注意这个方法也可以从另一个叫做 onRestart 的回调中调用。

你必须知道 onRestoreInstanceState 方法是在这个方法之后调用的。因此,您不应该对这种方法中的视图状态做出假设。所以尽量不要在这个方法中操纵视图的状态。在随后的 on restorestancestate 或 onResume 方法中进行细化。因为这是 onStop() 的对应物,所以如果您已经在 onStop() 或 onPause() 中停止了某个东西,请执行相反的操作。如果你看到一些事情正在用这种方法来做,要谨慎地看待它,确保它是你想要的。还要知道,在活动的整个当前周期中,开始和停止周期可能会发生多次。

当活动先隐藏后显示时,也可以调用此方法,因为另一个活动已经到了可见性堆栈的顶部。在那些情况下,这个方法在 onRestart() 之后被调用,它本身在 onStop() 之后被触发。所以这个方法有两条路径:要么是 onCreate() 要么是 onRestart() 。在这两种情况下,视图层次结构都应该在回调之前建立并可用。

参见 onrestoreinstallationstate(bundle savedinstancestate)

如果用户合法地关闭一个活动,那么用户愿意放弃的状态就是实例状态。例如,当用户选择一个后退按钮时,他/她就在通知 Android 他/她对这个活动不再感兴趣,这个活动可以被关闭,放弃所有尚未保存的状态。因此,这种状态是短暂的,并且只在活动存在于内存中时才有效,这就是实例状态。

如果系统选择关闭活动,因为方向发生了变化,那么当活动重新开始时,用户将会期望返回到临时(实例)状态。为了方便起见,Android 调用这个 onRestoreInstanceState 方法,其中包含保存的实例状态。(参见 onSavedInstanceState 方法解释。)

与实例状态相反,活动的持久状态是用户希望看到的,即使在活动结束并且不再运行之后。这种持久性状态可能是在活动过程中创建的,甚至可能是在活动创建之前就存在了。这种类型的状态,尤其是在活动的帮助下创建的状态,必须像文件一样显式保存到外部持久性存储中。如果活动没有使用显式的“保存”按钮来满足这种需求,那么就需要使用“ onPause 方法来保存这种隐式的持久状态。这是因为在内存不足的情况下,不能保证调用之后的任何方法。如果信息太重要而不能丢失,就不应该依赖实例状态。

参见 onResume()

Resume 上的回调方法是活动完全可见的前身。这也是活动前台周期的开始。在这个前台循环中,活动可以在 onResume() 和 onPause() 之间移动多次,因为其他更紧急的活动、通知或对话会优先进行。

当这个方法被调用时,我们可以期待视图和它们的状态被完全恢复。您可以借此机会调整最终状态更改。由于这个方法没有捆绑包,如果需要的话,你需要依靠来自 onCreate 或 onRestoreInstanceState 方法的信息来微调状态。

如果您在期间因为停止了任何计数器或动画,您可以在此重新启动它们。您还可以跟踪视图是否真的被破坏的情况,方法是遵循前面的回调方法(无论 onResume 是由 onCreate 、 onRestart 还是 onPause )并尽可能最小地调整视图状态。通常情况下,您不会在这里进行状态管理,而只是那些需要根据可见性打开或关闭的任务。

参见 on case()

该回调表示活动即将进入后台。当活动完全可见时,您应该停止任何正在运行的计数器或动画。活动可能会在结束时进行到或者在结束时进行到。前往的结果将把活动带到前台。转到 onStop 将使活动进入后台状态。

根据 SDK,它也是在活动和流程被完全回收之前保证被调用的最后一个方法。所以这是开发人员将任何非实例和持久数据保存到文件中的最后机会。

Android SDK 也在使前台活动完全活动之前等待该方法返回。所以你想在这个方法中简洁。还要注意,这个方法没有传递任何包。这表明该方法用于存储持久数据,并且也存储在诸如文件或网络的外部存储介质中。

您还可以使用此方法停止后台任务的任何计数器、动画或状态显示。您可以在 onResume 中继续。

void onStop()

回调方法 onStop() 将活动从部分可见状态转移到后台状态,同时保持所有视图层次结构不变。这是 onStart 的翻版。通过调用 onStart 可以将活动带回到可视循环。在同一个活动生命周期中,从 onStop 到 onStart 的状态转换通过 onRestart() 方法完成。

调用后,活动不再可见。但是请记住,在低内存条件下,这可能不会在之后调用,因为。由于这种不确定性,不要使用此方法来启动或停止此进程之外的服务。因为而在做那件事,结果在继续。但是,您可以使用此方法来控制流程内部的服务或工作。这是因为,只要进程是活动的,这个方法就会被调用。如果整个过程都停止了,那么那些相关的任务或全局变量无论如何都会消失。

void onSaveInstanceState(Bundle saveStateBundle)

如果进程仍在内存中,则控制转到 onDestroy() 从 onStop 出来。然而,如果 Android 意识到活动在没有用户预期的情况下被关闭,那么它会在调用 onDestroy() 之前调用 onSaveInstanceState() 。方向改变是一个非常具体的例子。SDK 警告说 onSaveInstanceState() 的时序是在 onStop() 之前还是之后是不可预测的。

此方法的默认实现已经保存了视图的状态。但是,如果有一些视图不知道的显式状态,您需要将它保存在 bundle 对象中,并在 onRestoreInstanceState 方法中检索它。您确实需要首先调用父视图的 onSaveInstanceState() 方法,以便视图有机会自己保存它们的状态。视图保存状态有一些限制和规则。关于 UI 控件的章节(第三章、第四章和第五章)和配置更改的章节(第九章)更详细地介绍了这个主题。

参见 onRestart()

当活动从后台状态过渡到部分可见状态时,即从 onStop 到 onStart 时,调用该方法。如果你想基于是重新开始还是重启来优化代码,你可以在 onStart 中使用这些知识。当它重新启动时,视图和它们的状态相当完整。

你可以用这种方法做一些在 onStart 中已经完成的事情,但是当活动不可见时进行优化,但是在 resume 中重复做太昂贵了。

对象 on retain no configuration instance()??]

这个回调方法用于处理由于配置更改而导致的活动重新创建。该方法返回进程内存中的一个对象引用,该对象引用需要在活动重新创建后重新分配给活动。我们之前在描述 onCreate 方法时已经详细解释过了。

当重新创建活动时,从该方法返回的对象通过方法 getlastonconfigurationinstance()变得可用。现在在 onCreate() 中,新的活动可以使用先前建立的资源和对象引用。重要的是,如果那些先前的资源保持旧的活动引用,那么资源可以被告知使用新的。

这种困境的存在是因为在方向改变时,Android 不会终止进程,而是丢弃旧的活动,在新的方向上重新创建活动,并期望程序员提供新的布局,等等。,以适应新的配置。所以工作对象仍然在那里保持一个旧的活动。这是与它的“get”对应物相关联的克服这一障碍的方法。

当你阅读第八章时,你将会了解到这种方法已被废弃,你将会在它的位置使用所谓的无头保留片段。这些无头的保留片段的额外好处是能够跟踪活动的生命周期,而不仅仅是对活动的引用。

void ondstroy()

onDestroy() 是 onCreate() 的对应物。活动将在晚会结束后结束。一个活动可能因为两个主要原因而结束。

一个是明确的结束。当用户通过单击用于指示用户已完成的按钮,或者通过使用后退按钮离开活动转到上一个活动,而明确地使活动完成时,就会发生这种情况。在这种情况下,除非用户再次选择该活动,否则系统不会恢复该活动。在这个场景中,活动生命周期以 onDestroy 方法结束。

活动可以结束的第二个原因是非自愿的。当设备的方向改变时,Android SDK 将强制关闭活动并调用 onDestroy 方法,然后重新创建活动并再次调用 onCreate 。

当一个活动在后台时,如果系统需要内存,Android 可能会关闭该进程,并且可能没有机会调用 onDestroy 方法。由于这种不确定性,就像 onStop 一样,不要使用这种方法来控制活动运行的流程之外的任务或服务。但是,如果进程仍然在内存中,onDestroy 将作为生命周期的一部分被调用,只要代码属于该进程,您就可以将清理代码放在 onDestroy 中。

活动回访的一般说明

使用图 2-3 来指导您了解这些回调的顺序以及如何最好地使用它们。如果您要覆盖回调,您需要回调父方法。SDK 文档明确指出需要哪些派生方法来回调它们的父对等方法。还可以参考 SDK 文档来了解在哪些回调过程中,系统不会因为内存不足而终止进程。还要注意,只有少数回调带有实例状态包。

更多关于资源的信息

我们想告诉你更多关于 Android 应用如何使用资源的信息。在计算器布局文件中,您已经看到了一些使用的资源,如字符串、图像、id 等。

其他不太明显的资源包括维度、drawables、字符串数组、复数语言术语、xml 文件和所有类型的输入文件。在 Android 中,某些东西被视为资源 a)如果它是程序的输入,并且是 apk 文件的一部分,b)如果输入的值或内容可以基于语言、地区或设备的方向而具有不同的值,通常称为配置更改。

资源的目录结构

Android 中的所有资源都放在你的应用包根目录的 /res 子目录下。清单 2-10 展示了一个 /res 可能的样子:

**清单 2-10 。Android 资源和资产目录结构

/res/values/strings.xml
           /colors.xml
           /dimens.xml
           /attrs.xml
           /styles.xml
    /drawable/*.png
             /*.jpg
             /*.gif
             /*.9.png
             /*.xml
    /anim/*.xml`
    /layout/*.xml
    /raw/*.*
    /xml/*.xml
/assets/*.*/*.*

我们将在第七章的中介绍 attrs.xml 和 styles.xml 。 anim 子目录中的 xml 文件定义了可应用于各种视图的动画。我们将在动画章节(第十八章)中介绍这些动画相关的资源。xml 子目录中的 xml 文件被编译成二进制文件,可以使用它们的资源 id 来读取它们。我们将很快展示一个这样的例子。/ raw 子目录保存被放置的文件,它们没有被转换成任何二进制格式。

/assets 目录是 /res 的同级,它不是资源层次结构的一部分。这意味着该子目录中的文件不会因语言或区域设置而改变。Android 不会为这些文件生成任何 id。这个目录更像是任何用作输入的文件的静态本地存储,比如应用的配置文件。

除了资产目录之外, /res 子目录中的所有其他工件最终都会在 R.* 名称空间中生成一个 ID,就像您之前看到的那样。每个不同的资源类型在 R.* 下都有自己的名称空间,如 R.id 、 R.string 或 R.drawable 等。

从 Java 代码中读取资源

在布局文件中,正如您在计算器布局中看到的,一个资源可以引用其他资源。例如,计算器布局资源文件引用了字符串和颜色引用。这种方法很常见。或者,您也可以使用 Java API 通过方法 activity . get resources()来检索资源值。该方法返回对 Android SDK java 类资源的引用。您可以使用这个类上的方法来获取本地 R.* 名称空间中标识的每个资源的值。清单 2-11 展示了这种方法的一个例子:

**清单 2-11 。读取 Java 代码中的资源值

Resources res = activity.getResources();
//Retrieving a color resource
int somecolor  = res.getColor(R.color.main_back_ground_color);
// Using a drawable resource
ColorDrawable redDrawable=(ColorDrawable)res.getDrawable(R.drawable.red_rectangle);

可绘制资源的运行时行为

drawable 目录是一个有趣的案例,值得报道,以展示 Android 架构的流畅性。如前所述,这个目录可以包含可以设置为背景的图像。这个目录还允许 XML 文件知道如何被转换成可绘制的 java 对象,这些对象可以在运行时被用作背景。清单 2-12 显示了一个这样的例子:

清单 2-12 。 一个形状可绘制的 XML 资源文件的例子

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:shape="rectangle">
    <solid android:color="#f0600000"/>
    <stroke android:width="3dp" android:color="#ffff8080"/>
    <corners android:radius="13dp" />
    <padding android:left="10dp" android:top="10dp"
        android:right="10dp" android:bottom="10dp" />
</shape>

如果将这样的文件放在 drawable 子目录中,并将其命名为 background1.xml ,就会产生一个名为 R.drawable.background1 的 ID。然后,您可以使用该 ID,就像它是用矩形边框绘制的任何视图的背景图像一样。其他可能的形状有椭圆形、直线形和环形。

与 shape xml 文件类似,drawing able 目录中的每个允许的 xml 文件都定义了一个 drawing able,它定义了一种特定的绘制方式。这些可绘制项的示例包括可以用某些行为来装饰的位图、或者可以从一个图像过渡到另一个图像的图像、作为其他可绘制项的集合的分层可绘制项、可以基于输入参数来选择的可绘制项、可以通过显示多个图像来响应进度的可绘制项、可以剪辑其他可绘制项的可绘制项等...有关使用这些运行时可绘制对象可以执行的许多复杂操作,请参见以下 URL:

[`androidbook.com/item/4236`](http://androidbook.com/item/4236)

使用任意 XML 文件作为资源

Android 还允许将任意 XML 文件用作资源,然后可以针对每个设备进行本地化或调整。清单 2-13 是从 /res/xml 子目录中读取和处理基于 XML 的资源文件的例子。

**清单 2-13 。读取 XML 资源文件

private String readAnXMLFile(Activity activity) throws XmlPullParserException, IOException {
   StringBuffer sb = new StringBuffer();
   Resources res = activity.getResources();
   XmlResourceParser xpp = res.getXml(R.xml.test);

   xpp.next();
   int eventType = xpp.getEventType();
    while (eventType != XmlPullParser.END_DOCUMENT) {
        if(eventType == XmlPullParser.START_DOCUMENT) {
           sb.append("******Start document");
        }
        else if(eventType == XmlPullParser.START_TAG)  {
           sb.append("\nStart tag "+xpp.getName());
        }
        else if(eventType == XmlPullParser.END_TAG) {
           sb.append("\nEnd tag "+xpp.getName());
        }
        else if(eventType == XmlPullParser.TEXT) {
           sb.append("\nText "+xpp.getText());
        }
        eventType = xpp.next();
    }//eof-while
    sb.append("\n******End document");
    return sb.toString();
}//eof-function

使用原始资源文件

Android 也允许任何类型的非编译文件作为资源。清单 2-14 是一个读取放置在 /res/raw 子目录中的文件的例子。作为一种资源,甚至这个目录中的原始文件也可以针对语言或设备配置进行定制。Android 也为这些文件自动生成 id,因为它们和其他资源一样是资源。

清单 2-14 。?? 读取原始资源文件

String getStringFromRawFile(Activity activity) throws IOException {
      Resources r = activity.getResources();
      InputStream is = r.openRawResource(R.raw.test);
      //assuming you have a function to convert a stream to a string
      String myText = convertStreamToString(is);
      is.close(); //take care of exceptions etc.
      return myText;
}

从资产目录中读取文件

尽管通常与资源放在一起, /assets 目录有点不同。这个目录不在 /res 路径下,所以这个目录中的文件的行为不像资源文件。Android 不会在 R.* 名称空间中为这些文件生成资源 id。这些文件不能基于区域设置或设备配置进行自定义。清单 2-15 显示了一个读取放置在 /assets 子目录中的文件的例子。

清单 2-15 。?? 从资产目录中读取文件

String getStringFromAssetFile(Activity activity) {
    AssetManager am = activity.getAssets();
    InputStream is = am.open("test.txt");
    String s = convertStreamToString(is);
    is.close();
    return s;
}

到目前为止,我们已经使用了一个活动引用来获取资源或一个资产管理器对象,如清单 2-15 所示。实际上,我们需要的只是活动的基类,即上下文对象。

阅读没有活动参考的资源和资产

有时,您可能需要从源代码内部读取 XML 资源文件或资产文件,在这种情况下传递活动引用会造成干扰。对于这些情况,您可以使用下面的方法来获取应用上下文,然后使用该引用来获取资产和资源。

当 Android 加载您的应用时(为了调用它的任何组件),它实例化并调用一个应用对象来通知应用可以初始化自己。这个应用类名在 Android 清单文件中指定。如果 MyApplication.java 是你的应用 java 类,那么它可以在 Android 清单文件中指定,如清单 2-16 所示。

清单 2-16 。 在清单文件中指定应用类

<application android:name=".MyApplication"
        android:icon="@drawable/icon" .../>

清单 2-17 展示了我们如何编写我的应用,也展示了我们如何在一个全局变量中捕获应用上下文。

清单 2-17 。 捕获应用上下文的应用示例代码

public class MyApplication extends Application {
   //Make sure to check for null for this variable
   public static volatile Context s_appContext = null;

   @Override
   public void onConfigurationChanged(Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
   }
   @Override
   public void onCreate() {
      super.onCreate();
      MyApplication.s_appContext = this.getApplicationContext();
   }
   @Override
   public void onLowMemory() {
      super.onLowMemory();
   }
   @Override
   public void onTerminate() {
      super.onTerminate();
   }
}

有了在全局变量中捕获的应用上下文,我们现在可以访问资产管理器来读取我们的资产,如清单 2-18 中的所示。

清单 2-18 。 使用应用对象来获取应用资产文件

AssetManager am = MyApplication.s_appContext.getAssets();
InputStream is = am.open(filename);

了解资源目录、语言和区域设置

让我们总结一下 Android resources 的概念,指出资源目录是如何根据语言、地区或设备的配置变化(比如方位)来加载资源的。查看清单 2-19 中的如何将一个同名的布局文件放置在多个布局目录中,这些布局目录以相同的布局前缀开始,但具有不同的限定符,如用于纵向的“端口”和用于横向的“陆地”。SDK 文档中有大量这样的限定符。我们还将在第九章中讨论这些方面(配置变更)。清单 2-19 显示了一个如何通过纵向或横向配置排列布局文件的例子:

清单 2-19 。 演示资源限定符

\res\layout\main_layout.xml
\res\layout-port\main_layout.xml
\res\layout-land\main_layout.xml

更多关于意图

我们已经讨论了如何使用意图来调用活动。我们现在想涵盖更多的基本方面的意图。清单 2-20 显示了 intents 是如何被用来调用一些预先构建的 Google 应用的。

清单 2-20 。 示例代码使用意图

public class IntentsUtils {
    public static void invokeWebBrowser(Activity activity)    {
       Intent intent = new Intent(Intent.ACTION_VIEW);
       intent.setData(Uri.parse("[`www.google.com`](http://www.google.com)"));
       activity.startActivity(intent);
    }
    public static void invokeWebSearch(Activity activity)    {
       Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
       intent.setData(Uri.parse("[`www.google.com`](http://www.google.com)"));
       activity.startActivity(intent);
    }
    public static void dial(Activity activity)    {
       Intent intent = new Intent(Intent.ACTION_DIAL);
       activity.startActivity(intent);
    }
    public static void call(Activity activity)    {
       Intent intent = new Intent(Intent.ACTION_CALL);
       intent.setData(Uri.parse("tel:555–555–5555"));
       activity.startActivity(intent);
    }
    public static void showMapAtLatLong(Activity activity)     {
       Intent intent = new Intent(Intent.ACTION_VIEW);
       //geo:lat,long?z=zoomlevel&q=question-string
       intent.setData(Uri.parse("geo:0,0?z=4&q=business+near+city"));
       activity.startActivity(intent);
    }
}

请注意,这些意图不是通过类名调用特定的活动,而是使用合适活动的目标质量。例如,调用浏览器来查看网页,意图简单地说动作是 ACTION_VIEW ,并且意图的数据部分被设置为网址。Android 然后环顾四周,查看所有知道如何在数据属性中显示所请求的数据类型的活动。然后,它将为用户提供一个选项,用户希望选择哪个活动来打开 URL。这些不指定要调用的组件的类名的意图被称为隐式意图。我们稍后会对此进行更详细的介绍。

开始结果活动

清单 2-21 显示了一个活动的例子,其中它的一个方法正在调用一个目标活动,以便在该目标活动完成时获得一个结果。这是通过清单 2-21 中的 invokePick() 方法完成的。

清单 2-21 。 利用意图从活动中获得结果

public class SomeActivity extends Activity {
.....
//Call this method to start a target activity that knows how to pick a note
//Use a data URI that tells the target activity which list of notes to show
public static void invokePick(Activity activity) {
  Intent pickIntent = new Intent(Intent.ACTION_PICK);
  int requestCode = 1;
  pickIntent.setData(Uri.parse(
     "content://com.google.provider.NotePad/notes"));
  activity.startActivityForResult(pickIntent, requestCode);
}

//the following method will be called when the target activity finishes
//Notice the outputIntent object that is passed back which could
//contain additional information

@Override
protected void
onActivityResult(int requestCode,int resultCode, Intent outputIntent) {
   super.onActivityResult(requestCode, resultCode, outputIntent);
   parseResult(this, requestCode, resultCode, outputIntent);
}
public static void parseResult(Activity activity
    , int requestCode, int resultCode , Intent outputIntent)
{
    if (requestCode != 1)  {
     Log.d("Test", "Someone else called this. not us");
     return;
    }
    if (resultCode != Activity.RESULT_OK)  {
      Log.d("Test", "Result code is not ok:" + resultCode);
               return;
    }
    Log.d("Test", "Result code is ok:" + resultCode);
    Uri selectedUri = outputIntent.getData();
    Log.d("Test", "The output uri:" + selectedUri.toString());

    //Proceed to display the note
    outputIntent.setAction(Intent.ACTION_VIEW);
    startActivity(outputIntent);
}

常量 RESULT_OK 、RESULT _ cancelled 和 RESULT_FIRST_USER 都在 activity 类中定义。常量 RESULT_FIRST_USER 用作用户定义的“活动结果”的起始编号。这些常数的数值如列表 2-22 所示:

清单 2-22 。 返回活动的结果值

RESULT_OK = -1;
RESULT_CANCELED = 0;
RESULT_FIRST_USER = 1;

为了使 PICK 功能起作用,正在响应的实现或目标活动应该有明确满足 ACTION_PICK 需求的代码。让我们看一个例子,看看在 Google sample NotePad 应用中是如何做到这一点的。(参见参考资料部分,在那里可以找到这个应用。)当项目列表中的项目被选中时,调用目标活动的意图被检查以查看它是否是一个 ACTION_PICK 意图。如果是,则所选注释项的数据 URI 被设置为新的 intent,并通过 setResult() 返回,如清单 2-23 所示。然后,调用活动可以调查返回的意图,看看其中有什么数据。参见清单 2-21 中的方法 parseResult() 。

清单 2-23 。 目标活动通过数据 URI 返回结果

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
    Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);

    String action = getIntent().getAction();
    if (Intent.ACTION_PICK.equals(action) ||
              Intent.ACTION_GET_CONTENT.equals(action))    {
        // The caller is waiting for us to return a note selected by
        // the user.  They have clicked on one, so return it now.
        setResult(RESULT_OK, new Intent().setData(uri));
        finish();
    }
    ...other ways of how this activity may have been invoked
}

执行 GET_CONTENT 操作

ACTION_GET_CONTENT 类似于 ACTION_PICK 。在 ACTION_PICK 的情况下,您指定了一个数据 URI,它指向一个项目集合,就像一个类似记事本的应用中的笔记列表。您将期望 intent 操作选取其中一个音符并将其返回给调用者。在 ACTION_GET_CONTENT 的情况下,你向 Android 表明你需要一个特定 MIME 类型的项目。Android 搜索可以创建这些项目之一的活动,或者从满足该类型的现有项目集中进行选择的活动。

使用 ACTION_GET_CONTENT ,您可以使用清单 2-24 中所示的代码从记事本应用支持的笔记集合中选择一个笔记:

清单 2-24 。 调用活动创建内容

public static void invokeGetContent(Activity activity) {
      Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);
      int requestCode = 2;
      pickIntent.setType("vnd.android.cursor.item/vnd.google.note");
      activity.startActivityForResult(pickIntent, requestCode);
}

注意意图类型是如何被设置为单个注释的 MIME 类型的。与此形成对比的是 ACTION_PICK 代码,它明确指出了一个指向笔记集合的 URL(就像一个可以检索一页数据的 web URL)。

对于响应 ACTION_GET_CONTENT 的活动,该活动必须注册一个意图过滤器,表明该活动可以提供一个 MIME 类型的项目。清单 2-25 展示了 SDK 的记事本应用是如何完成的:

清单 2-25 。 活动过滤器获取内容

<activity android:name="NotesList" android:label="@string/title_notes_list">
......
<intent-filter>
    <action android:name="android.intent.action.GET_CONTENT" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
      </intent-filter>
......
</activity>

响应 onActivityResult() 的其余代码与前面的 ACTION_PICK 示例相同。如果有多个活动可以返回相同的 MIME 类型,Android 会显示选择器对话框让你选择一个活动。

相关意图和活动

intent 不仅用于启动活动,还用于启动其他组件,如服务或广播接收器。这些组件将在后面的章节中介绍。您可以看到这些组件具有某些属性。组件的一个属性可以是该组件所属的类别。另一个属性可以是该组件可以查看、编辑、更新或删除什么类型的数据。另一个属性可以是组件可以响应什么类型的动作。如果您将这些组件视为数据库中的实体,那么它们的属性可以被视为列。那么意图可以被看作是一个 where 子句,它指定了选择一个组件(如要启动的活动)的所有或部分特征。清单 2-26 是一个展示如何查询所有被归类为 CATEGORY_LAUNCHER 的活动的例子。

清单 2-26 。 查询符合意向的活动

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);

PackageManager 是一个关键类,它允许您在不调用活动的情况下发现符合特定意图的活动。基于 ResolveInfo API,您可以遍历收到的活动,并在您认为合适的时候调用它们。清单 2-27 是前面代码的扩展,它遍历活动列表,如果匹配某个名称就调用其中一个活动。在代码中,我们使用了一个任意的名称来测试它:

清单 2-27 。 遍历匹配的活动列表,寻找意向

for(ResolveInfo ri: list) {
    //ri.activityInfo.
    Log.d("test",ri.toString());
    String packagename = ri.activityInfo.packageName;
    String classname = ri.activityInfo.name;
    Log.d("test", packagename + ":" + classname);
    if (classname.equals("com.ai.androidbook.resources.TestActivity")) {
        Intent ni = new Intent();
        ni.setClassName(packagename,classname);
        activity.startActivity(ni);
    }
}

理解显性和隐性意图

当您在一个意图中指定一个显式的活动名称(或一个组件名称,如服务或广播接收器)时,这样的意图被称为显式意图。当这个意图用于启动一个活动时,该活动被调用,而不管该意图中还有什么,比如它的类别或数据。

正如您所看到的,一个意图不一定要有一个明确指定的活动来调用它。意图可以依赖于活动的动作属性、类别属性或数据属性。这些省略了显式活动或组件类的意图被称为隐式意图。当您使用隐式意图来调用活动时,活动必须将 CATEGORY_DEFAULT 作为其类别之一,这一点非常重要。如果您希望您的活动明确地由一个意图开始,那么您根本不需要为该活动指定任何类别。清单 2-28 展示了一个在 Android 清单文件中最小化注册一个活动的例子,这样它就可以被一个明确的意图调用。

清单 2-28 。 最小活动定义

<activity android:name="com.androidbook.asynctask.TestProgressBarDriverActivity"
      android:label="Test Progress bars"/>

如果你想通过一个隐含的意图来调用这个活动,而不指定它的类名,比如通过一个动作,那么你需要添加下面的意图过滤器,一个用于动作,一个用于所需的默认强制类别,如清单 2-29 所示。

清单 2-29 。 一个带有过滤器的活动定义

<activity android:name="com.androidbook.asynctask.TestProgressBarDriverActivity"
       android:label="Test Progress bars">
      <intent-filter>
           <action android:name="com.androidbook.intent.action.ME" />
           <category android:name="android.intent.category.DEFAULT" />
     </intent-filter>
</activity>

在 Android 中保存状态

当您查看计算器应用时,您的下一个需求可能是如何存储 Android 应用的数据。让我们简单介绍一下可用的选项。Android 中存储数据的方式有五种:1)共享首选项,2)内部文件,3)外部文件,4) SQLlite ,5)云端网络存储。

Shared preferences API 是 Android SDK 中一个复杂的 API,用于保存、显示和操作应用的首选项。尽管这个特性是为偏好设计和定制的,但它也可以用来保存应用的任意状态。共享偏好设置是应用和设备内部的。Android 不会将这些数据提供给其他应用。用户不希望通过安装到 USB 端口上来直接操作这些数据。当应用被删除时,该数据被自动删除。这些共享偏好在第十一章中有详细介绍。

虽然共享首选项数据是结构化的键/值对数据,并遵循一些其他强加的语义,但内部文件是独立的文件,您可以在没有预定义结构的情况下写入。我们还没有发现使用内部文件优于共享首选项或其他方式的令人信服的优势,特别是对于中小型国家。因此,对于大多数应用,你可以选择其中之一。

与存储在设备内部存储器上的内部文件不同,外部文件存储在 SD 卡上。这些成为公共文件,其他应用包括用户可以在你的应用环境之外看到。外部文件可用于存储即使在应用之外也有意义的数据,如图像文件或视频文件。对于严格意义上的应用内部状态,内部文件是更好的选择。

如果状态非常大,达到几十兆字节,外部文件也是一个选项。通常,当这种情况发生时,您无论如何都不希望将状态保存为一个整体文件,而是选择更细粒度的存储,如 SQLlite 这样的关系数据库。

我们将在第二十五章中给出一个关于如何使用偏好、内部文件和外部文件来存储你的应用状态的快速概述和简短代码示例。技巧之一是直接使用 JSON 和 GSOn 持久化 java 对象树,同时考虑这种粒度级别是否合适。如果您不熟悉 JSON,它是基于 JavaScript 的对象的对象传输和存储格式。它通常也适用于任何对象结构,包括 java 对象,最近经常这样使用。GSON 是一个 Google 库,它将 Java 对象与 JSON 字符串相互转换。

SQLlite 是一个非常好的选项,推荐用来存储应用的状态。缺点是保存和读取数据的逻辑变得冗长和麻烦。您也许可以使用 O/R 映射库来克服 java 对象及其关系表示之间的这种不匹配。SQLlite 还经常用于存储需要由多个应用通过称为内容提供者的概念共享的数据。这是第二十五章的中心话题。

最后,基于云的网络存储也开始崭露头角。例如,parse.com 等许多 MBAAS(移动后端即服务)平台支持将移动数据直接存储在云中,供在线和离线使用。随着你开始在多个设备上为同一个用户提供应用,或者能够与其他用户合作,这种模式将变得越来越重要。这个话题在我们的伙伴书《Android 专家》中有详细的介绍。

很多时候,对于你的应用来说,GSON 选项将应用状态存储在一个内部文件中确实是最快、最实用的方法。当然,您确实想分析解决方案的粒度,看看这种更简单的方法是否不会成为计算能力或电池寿命的负担。如果你的应用很受欢迎,你可能想通过优化存储速度来使用 SQLlite 的第二个版本,或者使用云存储,如果这更适合那个版本的话。

学习 Android 的路线图和本书的其余部分

让我们快速回顾一下到目前为止我们已经学过的内容。在这个单页应用中,您已经看到了 UI 是如何组装的,业务逻辑是如何用 Java 编写的,以及如何使用 Android manifest 文件将应用定义到 Android sdk。我们解释了什么是资源,它们如何相互引用,它们如何在布局文件中被引用,甚至如何将输入文件作为资源读取。我们已经向您展示了什么是意图,它们的复杂性,以及如何使用它们来调用或发现活动。我们已经涵盖了活动的生命周期,这对理解 Android 架构非常重要。我们还简要介绍了如何保存应用的状态。这是计划和编写简单应用的良好基础。

现在,我们希望通过成为 Android 平台上的专业应用开发人员的路线图来跟进这个 Android 应用的鸟瞰图。该路线图将本书的章节分为以下六个主要学习方向:

  • 途径 Android 应用的用户界面基础
  • 路线 2:保存状态
  • 路线 3:准备/将您的应用带到 Google Play
  • 路线 4:使您的应用健壮
  • 路线 5:为你的应用带来技巧
  • 路线 6:与其他设备和云集成

在这六个方面中,前三个是你必须了解的基本方面,以编写对你和更大的社区有用的 Android 应用。路线 4、5 和 6 旨在使您的应用变得更好,并在后续版本中提供丰富的功能。我们将讨论每个专题由哪些章节组成,以及您期望从该专题中获得什么。

途径 Android 应用的 UI 要点

Android 有许多现成的 UI 控件和布局来编写功能丰富的应用。例如按钮、各种文本视图、编辑文本控件、复选框、单选按钮、日期和时间控件、列表控件、显示模拟和数字时钟的控件、显示图像和视频的控件、选择数字的控件等。我们将在第三章 的 中讨论其中的一些。在那一章中,我们还将介绍从这些控件中组合 UI 所需的基本布局。

一旦你能够使用基本的控件来构建你的 UI,你的应用中绝对需要的一个控件就是列表控件。我们没有把列表控件作为一个基本控件来介绍,因为它有点复杂。此外,Android 有许多功能和方法来处理基于列表的应用。因此,我们为列表控件和填充这些列表控件所必需的数据适配器专门写了一章。这些方面都涵盖在 第四章 中。

一旦你掌握了基本的控件、基本的布局和列表控件,你将开始寻找更复杂的布局,比如网格和表格布局。这些将在“使用高级布局”下的第五章 中介绍

菜单见第六章**。Android 的菜单基础设施包括上下文菜单、弹出菜单、动作栏中的选项图标等。**

你的移动应用如果不通过样式化来提炼就不是真正完整的,就像 CSS 一样。 第七章 讲述了 Android 中的风格和主题是如何工作的。

对话框在任何用户界面中都是必不可少的。Android 中的对话框有点复杂。要理解 Android 中的对话框,你必须首先理解片段的概念。对话的架构只是片段的一个方面。片段现在是 Android UI 的核心。 第八章 解释了什么是片段并在 第十章 我们盖对话框,盖在第八章之上。

在移动应用中,如果不了解设备方向改变时应用会发生什么变化,就无法编写应用。在 Android 中,为改变方向而正确编程并不容易。如何为方向和其他器件配置更改编程在第九章的和章节中有所介绍。

对于任何合理有用的应用,您可能需要了解所有这些 UI 要素。所以轨道 1 是一个重要的轨道。

路线 2:保存状态

一旦你知道了如何构造你的应用的 UI,下一步你需要做的就是保存你的应用的状态。请参考前面关于保存状态的部分,以了解哪些选项可用以及在哪些章节中介绍了这些选项。路线 2 也是一个重要的路线,因为你应该知道如何保存状态。

路线 3:准备/将您的应用推向市场

通过完成途径 1 和 2,您可以构建一个非常合理的应用,并将其部署到市场上。 第三十章 向你展示如何将你的应用带到 Google Play 商店。

路线 4:让您的应用变得健壮

Track 4 是深入 Android 内部的高级课程。你需要浏览本专题的章节,巩固你对 Android 工作原理的理解。我们从兼容性库上的第十二章 的 开始这个曲目。本章讲述了如何让你的应用在旧版本上运行良好,同时使用仅在新平台上可用的功能。

Android 允许你在你的应用中运行代码,即使你并没有在前台使用这个应用。它可能是你在后台播放的音乐,也可能是你把图片备份到云端,等等。这种类型的代码在 Android 中被称为服务。第十三章 中的 介绍了服务工作。这些服务可以通过直接的用户动作或通过警报或广播事件来触发。警报管理器包含在第十七章 中。****

当您使用意图来调用组件(如活动或服务)时,您的目标是单个组件。Android 还支持发布-订阅协议,在该协议中,可以使用一个意图来调用同时注册的多个组件。这些组件被称为接收器或广播接收器。广播接收器是您的应用中的一段代码,即使您的应用在事件发生时尚未启动或处于休眠状态,它也会执行以响应广播事件。如何使用广播接收器在第十六章 的 中介绍。

随着你开始使用 Android 越来越多的功能,如服务、广播接收器和内容供应器,你需要了解 Android 如何使用单个主线程来运行这些组件中的代码。这个线程模型在第十四章 的 中有详细介绍。了解这一点将有助于您编写健壮的代码。在本期节目中,你还将了解到非常有用的 AsyncTask ,它用于简化从主线程卸载工作。这个 API 经常在 UI 中使用,用来从 web 上读取消息或检查电子邮件等。 AsyncTask 包含在第十五章** 中。**

路线 5:为你的应用带来技巧

为了让你的应用看起来吸引人,你可以做的第一件事就是添加一点或很多动画。这在 第十八章 中有所涉及。基于触摸的界面现在是标准。通过拖放操作你的环境更加自然。你想利用传感器来编写与外部世界更好地集成的应用。这些触摸屏、拖放和传感器分别在 第二十二章、第二十三章和第二十四章 中介绍。

主屏幕部件是提取应用片段并使其在您选择的任何主屏幕上可用的绝佳方式。这种个性化功能,当以创新的方式使用时,会使与设备的交互变得简单而愉快。第二十一章 中的 小部件。

基于地图和位置的应用是为移动设备开发的。这个话题在 第十九章 中有所涉及。

你可以非常容易地将音频和视频整合到你的 Android 应用中。这个 API 包含在第二十章的 中。

路线 6:与其他设备和云集成

你可以使用谷歌云消息来联系你的移动应用的用户。谷歌云消息在 第二十九章 中有所涉及。通过 Android 中的 NFC 和蓝牙功能,您可以开始在应用中与物理环境进行交互。我们希望在这本书的在线伙伴上发布一些关于这些主题的材料。

最终曲目:获得专家机器人的帮助

现在,我们要谈论几个本书没有涉及的话题。如果您发现这些主题与您的需求相关,您可以考虑一下。其中大部分都是基于我们在 2014 年初通过 Apress 出版的关于 Android 专家的书的研究。

Android 有一个公共 API 来编写自定义组件,这些组件可以以不同于开箱即用的方式工作和行为。您可以编写自定义视图,在其中您可以控制绘制什么和如何绘制,然后可以与其他现成的控件(如按钮或文本控件)共存。您还可以将多个现有控件组合成一个复合控件,该复合控件可以像独立控件一样工作。您还可以设计符合您的显示需求的新布局。要很好地创建这些定制组件,需要很多技巧。你必须了解核心的 Android 视图架构。这一材料在来自 Apress 的专家 Android 书中有三章 100 页的内容。

如果你的应用是基于表单的,你需要写很多代码来验证表单输入。你真的需要一个框架来处理这个问题。专家 Android 有一章是关于创建一个小的表单处理框架的,这个框架非常有用,可以减少错误和你需要写的代码量。

MBAAS,移动后端即服务,是移动应用所需要的技术,现在已经非常普及。MBAAS 提供的功能包括用户登录、社交登录、用户管理、代表云中的用户保存数据、与用户通信、用户之间的协作等。在专家 Android 中,我们有多个章节致力于一个名为 Parse 的 MBAAS 平台。

OpenGL 在 Android 上已经取得了很大的进步,现在已经对新一代可编程 GPU 提供了实质性的支持。Android 支持 ES 2.0 已经有一段时间了。在专家 Android 中,我们有超过 100 页关于 OpenGL 的内容。我们从头开始解释所有的概念,而不需要参考外部书籍,尽管我们给出了关于 OpenGL 的大量参考书目。我们很好地介绍了 ES 2.0,并提供了结合 OpenGL 和常规视图的指导,为 3D 组件铺平了道路。

Android 的联合搜索协议非常强大,因为你可以用很多富有想象力的方式来使用它。专家 Android 全面探索了它的基本原理以及一些优化使用它的替代方法。

Android 为调试提供了越来越多的功能。这些话题在专家 Android 中有所涉及。手机最终是一个通话设备,尽管它的使用频率越来越低。我们也有一章是关于在专家 Android 中使用电话 API 的。

现在我们把这本书的其余部分留给你

最后,你可能想知道为什么你应该成为一名移动开发者。我们可以举出两个强有力的论据,其中一个以前从未有过。最常见的一种方式是成为 IT 组织的一员,从事移动编程工作。IT 机会正在增加,但还没有完全实现,这与 Web 编程范例出现时的情况不同。然而,我们预计这种需求会逐渐增加。

另一方面,迫在眉睫的令人兴奋的机会是你成为一个独立的应用发行商。你写的应用有销售渠道,这在软件行业是独一无二的。不是我们每个人都会成为 IT 组织中的明日之星。独立开发者之路为你提供了一条按照你自己的速度和方向成长的途径。运气和耐心甚至会让你变得富有。至少你可以在满足自己需求的同时给社会增加价值。

如果你决定进入 Android 移动编程领域,你应该准备好合适的硬件,让这种体验变得可以忍受。如果你要买一台 Windows 笔记本电脑,看看你能否买到一台至少 8G 内存、固态硬盘和相当快的处理器的笔记本电脑。预计花费约 1000 至 1500 美元。如果你买的是 Mac 笔记本电脑,类似的配置可能要花 2500 美元左右。一个好的快速配置对 Android 开发很重要。如果你是一个经验丰富的 Java 程序员,考虑到这笔投资和手中的这本书,如果你遵循这里列出的轨道,你可以在大约六个月内成为一个称职的移动 Android 应用开发人员。

参考

以下是本章所讨论主题的附加资源。

摘要

本章列出了使用 Android SDK 创建移动应用所需了解的一切。您已经看到了 UI 是如何构造的。你知道什么是活动。你知道活动生命周期的复杂性。你了解资源和意图。你知道如何拯救国家。最后,通过阅读总结本书其余部分的学习资料,您可以看到 Android SDK 的广度。我们希望前两章给你一个 Android SDK 开发工作的开端。

三、构建基本用户界面和使用控件

前一章向您介绍了 Android 中一些可用的 UI 元素的速成课程,以及如何快速地将它们组合起来创建计算器应用。虽然这很有趣,但我们希望你开始思考除了第二章中介绍的文本视图、编辑文本和按钮控件之外,Android 中还有哪些 UI 小部件。

在这一章中,我们将详细讨论用户界面和控件。我们将从讨论 Android 中 UI 开发的一般原理开始,然后我们将描述 Android SDK 附带的许多 UI 控件。这些是您将创建的界面的构建块。在接下来的章节中,我们还将讨论视图适配器和布局管理器,您将看到它们是如何建立在我们在本章中介绍的基本控件之上的。

到本章结束时,你将对 Android 工具集中的许多 UI 控件有一个坚实的理解,以及如何将 UI 控件布局到屏幕上并用数据填充它们。

Android 中的用户界面开发

Android 中的 UI 开发很有趣。好玩是因为相对容易。有了 Android,我们有了一个简单易懂的框架和有限的开箱即用控件。可用的屏幕区域通常是有限的——如果不是在平板电脑上,也是在手机上——这指导了 Android 控件的“简单电源”的基本哲学。Android 也承担了许多通常与设计和构建高质量 ui 相关的繁重工作。这一点,再加上用户通常想做一个特定的动作,让我们可以轻松地构建一个好的 UI 来交付一个好的用户体验。

Android SDK 附带了许多控件,您可以使用它们来为您的应用构建 ui。与其他 SDK 类似,Android SDK 提供了文本字段、按钮、列表、网格等等。此外,Android 提供了一组适用于移动设备的控件。

常见控件的核心是两个类: android.view.View 和 Android . view . view group。正如第一个类的名字所暗示的那样,视图类代表一个通用的视图对象。Android 中的常用控件最终扩展了视图类。视图组也是一个视图,但是它也包含其他视图。视图组是布局类列表的基类。Android 和 Swing 一样,使用布局的概念来管理控件在容器视图中的布局。正如我们将要看到的,使用布局可以让我们很容易地控制 ui 中控件的位置和方向。

你可以从多种方法中选择在 Android 中构建 ui。您可以完全用代码来构造 ui。也可以用 XML 定义 ui。您甚至可以将两者结合起来——用 XML 定义 UI,然后在代码中引用它并修改它。为了证明这一点,本章我们将使用这三种方法中的每一种来构建一个简单的 UI。

在我们开始之前,让我们定义一些术语。在本书和其他 Android 文献中,你会在关于 UI 开发的讨论中发现术语视图控件小部件容器布局。如果您是 Android 编程或 UI 开发的新手,您可能不熟悉这些术语。在我们开始之前,我们将简要描述它们(见表 3-1 )。

表 3-1 。 UI 术语表

|

学期

|

描述

| | --- | --- | | 视图、小部件、控件 | 每一个都代表一个 UI 元素。示例包括按钮、网格、列表、窗口、对话框等。术语视图小部件控件在本章中可以互换使用。 | | 容器 | 这是一个用于包含其他视图的视图。例如,网格可以被视为一个容器,因为它包含单元格,每个单元格都是一个视图。 | | 布局 | 这是容器和视图的可视化排列,可以包括其他布局。我们将在本章中讨论布局,并在第五章中全面探讨 Android 的布局特性。 |

图 3-1 显示了我们将要构建的应用的屏幕截图。屏幕截图旁边是应用中控件和容器的布局层次结构。

9781430246800_Fig03-01.jpg

图 3-1 。活动的用户界面和布局

在讨论示例程序时,我们将参考这个布局层次结构。现在,知道应用有一个活动。活动的 UI 由三个容器组成:包含人名的容器、包含地址的容器以及子容器的外部父容器。

完全用代码构建 UI

第一个例子,清单 3-1 ,演示了如何完全用代码构建 UI。为了尝试这一点,使用项目名控件,包名 com.androidbook.controls ,以及名为 MainActivity 的活动,创建一个新的 Android 应用项目,然后将清单 3-1 中的代码复制到您的 MainActivity 类中。

注意我们会在本章末尾给你一个 URL,你可以用它来下载本章的项目。这将允许您将这些项目直接导入 Eclipse,而不是复制和粘贴代码。

清单 3-1 。 完全用代码创建一个简单的用户界面

package com.androidbook.controls;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MainActivity extends Activity
{
    private LinearLayout nameContainer;

    private LinearLayout addressContainer;

    private LinearLayout parentContainer;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        createNameContainer();

        createAddressContainer();

        createParentContainer();

        setContentView(parentContainer);
    }

    private void createNameContainer()
    {
        nameContainer = new LinearLayout(this);

        nameContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT));
        nameContainer.setOrientation(LinearLayout.HORIZONTAL);

        TextView nameLbl = new TextView(this);
        nameLbl.setText("Name: ");

        TextView nameValue = new TextView(this);
        nameValue.setText("John Doe");

        nameContainer.addView(nameLbl);
        nameContainer.addView(nameValue);
    }

    private void createAddressContainer()
    {
        addressContainer = new LinearLayout(this);

        addressContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT));
        addressContainer.setOrientation(LinearLayout.VERTICAL);

        TextView addrLbl = new TextView(this);
        addrLbl.setText("Address:");

        TextView addrValue = new TextView(this);
        addrValue.setText("911 Hollywood Blvd");

        addressContainer.addView(addrLbl);
        addressContainer.addView(addrValue);
    }

    private void createParentContainer()
    {
        parentContainer = new LinearLayout(this);

        parentContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT));
        parentContainer.setOrientation(LinearLayout.VERTICAL);

        parentContainer.addView(nameContainer);
        parentContainer.addView(addressContainer);
    }
}

如清单 3-1 所示,该活动包含三个 LinearLayout 对象。我们将在第五章中更深入地讨论布局,但是有一个小的先有鸡还是先有蛋的问题,需要知道一点关于布局的知识,这样你就可以学习许多基本的控制。现在,知道布局对象包含在屏幕的一部分中定位对象的逻辑就足够了。例如, LinearLayout 知道如何垂直或水平布局控件。布局对象可以包含任何类型的视图,甚至是其他布局。

nameContainer 对象包含两个 TextView 控件:一个用于标签名称:,另一个用于保存名称的实际文本(如 John Doe)。 addressContainer 还包含两个 TextView 控件。这两个容器的区别在于名称容器是水平布局的,而地址容器是垂直布局的。这两个容器都位于 parentContainer 中,这是活动的根视图。构建完容器后,活动通过调用 setContentView(parent container)将视图的内容设置为根视图。当需要呈现活动的 UI 时,根视图被调用来呈现自身。然后,根视图调用其子视图来呈现它们自己,子控件调用它们的子控件,依此类推,直到呈现整个 UI。

如清单 3-1 所示,我们有几个线性布局控件。其中两个垂直布置,一个水平布置。名称容器横向布局。这意味着两个 TextView 控件水平并排出现。 addressContainer 垂直布局,这意味着两个 TextView 控件一个叠在另一个上面。 parentContainer 也是垂直布局的,这就是为什么 nameContainer 出现在 addressContainer 的上方。请注意两个垂直布局的容器之间的细微差别, addressContainer 和 parentContainer。parentContainer 被设置为占据整个屏幕的宽度和高度:

parentContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
        LayoutParams.FILL_PARENT));

并且 addressContainer 垂直包装其内容:

addressContainer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
        LayoutParams.WRAP_CONTENT));

换句话说, WRAP_CONTENT 意味着视图应该只占用它在那个维度中需要的空间,不要超过包含它的视图所允许的空间。对于 addressContainer ,这意味着容器将垂直占据两行,因为这是我们提供的虚拟地址所需要的。

完全用 XML 构建用户界面

现在让我们用 XML 构建相同的 UI(参见清单 3-2 )。XML 布局文件存储在资源( /res/ )目录下一个名为布局的文件夹中。要尝试这个例子,在 Eclipse 中创建一个新的 Android 项目。默认情况下,您将获得一个名为 activity_main.xml 的 XML 布局文件,它位于 res/layout 文件夹下。双击 activity_main.xml 查看内容。Eclipse 将为您的布局文件显示一个可视化编辑器。您可能在视图的顶部有一个字符串,写着“Hello World,MainActivity!”或者类似的东西。单击视图底部的 activity_main.xml 选项卡,查看 activity_main.xml 文件的 xml。这显示了一个 LinearLayout 和一个 TextView 控件。使用布局或 activity_main.xml 选项卡,或同时使用两者,在 activity_main.xml 文件中重新创建清单 3-2 。省省吧。

清单 3-2 。 完全用 XML 创建用户界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <!-- NAME CONTAINER -->
    <LinearLayout        android:orientation="horizontal" android:layout_width="fill_parent"
        android:layout_height="wrap_content">

            <TextView  android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:text="Name:" />

            <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:text="John Doe" />

    </LinearLayout>

    <!-- ADDRESS CONTAINER -->
    <LinearLayout        android:orientation="vertical" android:layout_width="fill_parent"
        android:layout_height="wrap_content">

            <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:text="Address:" />

            <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:text="911 Hollywood Blvd" />
    </LinearLayout>

</LinearLayout>

在您的新项目的 src 目录下,有一个。包含一个活动类定义的 java 文件。双击该文件以查看其内容。注意语句 setContentView(r . layout . activity _ main)。清单 3-2 中显示的 XML 片段,结合对 setContentView(r . layout . activity _ main)的调用,将呈现与之前完全用代码生成时相同的 UI。XML 文件是不言自明的,但是请注意,我们定义了三个容器视图。第一个 LinearLayout 相当于我们的父容器。这个容器通过如下设置相应的属性将其方向设置为垂直:Android:orientation = " vertical "。父容器包含两个 LinearLayout 容器,分别代表 nameContainer 和 addressContainer 。

运行这个应用将产生与我们前面的示例应用相同的 UI。标签和值将如图 3-1 所示显示。

用代码构建 XML 格式的用户界面

清单 3-2 是一个人为的例子。在 XML 布局中硬编码 TextView 控件的值没有任何意义。理想情况下,我们应该用 XML 设计 ui,然后从代码中引用控件。这种方法使我们能够将动态数据绑定到设计时定义的控件。事实上,这是推荐的方法。用 XML 构建布局并使用代码填充动态数据是相当容易的。

清单 3-3 显示了相同的用户界面,但 XML 略有不同。这个 XML 将 id 分配给 TextView 控件,这样我们就可以在代码中引用它们。

清单 3-3 。 用带有标识的 XML 创建用户界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <!-- NAME CONTAINER -->
    <LinearLayout        android:orientation="horizontal" android:layout_width="fill_parent"
        android:layout_height="wrap_content">

            <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content" android:text="@string/name_text" />

            <TextView android:id="@+id/nameValue"
        android:layout_width="wrap_content" android:layout_height="wrap_content" />

    </LinearLayout>

    <!-- ADDRESS CONTAINER -->
    <LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
        android:orientation="vertical" android:layout_width="fill_parent"
        android:layout_height="wrap_content">

            <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content" android:text="@string/addr_text" />

            <TextView android:id="@+id/addrValue"
        android:layout_width="fill_parent" android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

除了将 id 添加到我们希望从代码中填充的 TextView 控件之外,我们还有标签 TextView 控件,我们正在用字符串资源文件中的文本填充这些控件。这些是没有 id 的文本视图有一个 android:text 属性。这些 TextView 的实际字符串将来自于 /res/values 文件夹中的 strings.xml 文件。清单 3-4 展示了我们的 strings.xml 文件可能的样子。

清单 3-4 。 strings.xml 文件为清单 3-3

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Common Controls</string>
    <string name="name_text">Name:</string>
    <string name="addr_text">Address:</string>
</resources>

清单 3-5 中的代码演示了如何获取 XML 中定义的控件的引用来设置它们的属性。您可以将它放入活动的 onCreate() 方法中。

清单 3-5 。 泛指在运行时控制资源

setContentView(R.layout.activity_main);
TextView nameValue = (TextView)findViewById(R.id.nameValue);
nameValue.setText("John Doe");
TextView addrValue = (TextView)findViewById(R.id.addrValue);
addrValue.setText("911 Hollywood Blvd.");

清单 3-5 中的代码很简单,但是请注意,在调用 findViewById() 之前,我们通过调用 setContentView(r . layout . activity _ main)来加载资源——如果视图还没有被加载,我们就不能获取对视图的引用。

Android 的开发者已经做了很好的工作,使得控件的每个方面都可以通过 XML 或代码来设置。在 XML 布局文件中设置控件的属性通常比使用代码更好。但是,有很多时候需要使用代码,比如设置要显示给用户的值。

FILL_PARENT vs. MATCH_PARENT

常量 FILL_PARENT 在 Android 2.2 中被弃用,取而代之的是 MATCH_PARENT 。不过,严格来说,这是一次更名。该常量的值仍然是–1。类似地,对于 XML 布局, fill_parent 被替换为 match_parent 。那么你用什么值呢?代替 FILL_PARENT 或 MATCH_PARENT ,您可以简单地使用值–1,这样就可以了。然而,这不是很容易阅读,并且您没有一个等价的未命名值用于您的 XML 布局。有更好的方法。

根据您需要在应用中使用的 Android APIs,您可以根据 2.2 之前的 Android 版本构建您的应用并依靠向前兼容性,或者根据 2.2 或更高版本的 Android 构建您的应用并将 minSdkVersion 设置为您的应用将运行的最低版本的 Android。例如,如果你只需要 Android 1.6 中已经存在的 API,那么在 Android 1.6 上构建并使用 FILL_PARENT 和 fill_parent 。您的应用在 Android 的所有更高版本(包括 2.2 及更高版本)中运行应该没有问题。如果您需要 Android 2.2 或更高版本的 API,请继续构建该版本的 Android,使用 MATCH_PARENT 和 match_parent ,并将 minSdkVersion 设置为更老的版本:例如, 4 (对于 Android 1.6)。您仍然可以将 Android 2.2 中构建的 Android 应用部署到旧版本的 Android 上,但是您必须小心早期版本的 Android SDK 中没有的类和/或方法。有一些方法可以解决这个问题,比如使用反射或创建包装类来处理 Android 版本之间的差异。我们将在后面的章节中讨论这些高级主题。

了解 Android 的常用控件

我们现在将开始讨论 Android SDK 中的常见控件。我们将从文本控件开始,然后是按钮、复选框、单选按钮、列表、网格、日期和时间控件以及一个地图视图控件。这些将与我们将在第四章中介绍的布局控件一起出现。

文本控件

文本控件可能是你在 Android 中使用的第一种控件。Android 有一套完整但并不强大的文本控件。在本节中,我们将讨论文本视图、编辑文本、自动完成文本视图和多自动完成文本视图控件。图 3-2 显示了操作中的控制。

9781430246800_Fig03-02.jpg

图 3-2 。Android 中的文本控件

文本视图

你已经看到了清单 3-3 中文本视图控件的简单 XML 规范,以及如何在清单 3-4 中的代码中处理文本视图 s。注意我们如何在 XML 中指定文本的 ID、宽度、高度和值,以及如何使用 setText() 方法设置值。 TextView 控件知道如何显示文本,但不允许编辑。这可能会让您认为该控件本质上是一个虚拟标签。不是真的。 TextView 控件有一些有趣的属性,使它非常方便。例如,如果您知道 TextView 的内容将包含一个 web URL 或电子邮件地址,您可以将 autoLink 属性设置为 email|web ,该控件将查找并高亮显示任何电子邮件地址和 URL。此外,当用户单击这些突出显示的项目之一时,系统将负责启动带有电子邮件地址的电子邮件应用或带有 URL 的浏览器。在 XML 中,这个属性应该在文本视图标签中,看起来像这样:

<TextView   ...     android:autoLink="email|web"    ...    />

您指定一组由管道分隔的值,包括 web 、 email 、 phone 或 map ,或者使用 none (默认)或 all 。如果要在代码中设置自动链接行为,而不是使用 XML,对应的方法调用是 setAutoLinkMask() 。你可以给它传递一个类似于之前的表示值组合的 int,比如 Linkify。电子邮件地址|Linkify。WEB _ URLS。为了实现这个功能, TextView 使用了 Android . text . util . link ify 类。清单 3-6 显示了一个代码自动链接的例子。

清单 3-6 。 在文本视图中的文本上使用 Linkify

TextView tv =(TextView)this.findViewById(R.id.tv);
tv.setAutoLinkMask(Linkify.ALL);
tv.setText("Please visit my website, [`www.androidbook.com`](http://www.androidbook.com)
or email me at davemac327@gmail.com.");

请注意,在设置文本之前,我们在 TextView 上设置了自动链接选项。这很重要,因为在设置文本后设置自动链接选项不会影响现有文本。因为我们使用代码将超链接添加到我们的文本中,所以我们为清单 3-6 中的文本视图编写的 XML 不需要任何特殊属性,看起来就像这样简单:

<TextView android:id="@+id/tv" android:layout_width="wrap_content"
  android:layout_height="wrap_content"/>

如果你愿意,你可以调用 Linkify 类的静态 addLinks() 方法,按需查找并添加到任何 TextView 或任何 Spannable 的内容的链接。不使用 setAutoLinkMask() ,我们可以在设置文本后执行以下*:*

Linkify.addLinks(tv, Linkify.ALL);

单击一个链接将导致调用该动作的默认意图。例如,单击一个 web URL 将启动带有该 URL 的浏览器。点击一个电话号码将启动电话拨号器,等等。 Linkify 类可以立即执行这项工作。

Linkify 还可以检测你想要寻找的自定义模式,决定它们是否与你认为需要点击的东西相匹配,并设置如何激发一个意图,使点击变成某种行动。这里不赘述那些细节,但是知道这些事情是可以做到的。

从字体属性到最小线和最大线等等,还有更多文本视图的特性可以探索。这些都是显而易见的,我们鼓励您进行实验,看看如何使用它们。尽管你应该记住 TextView 类中的一些功能不适用于只读字段,但是 TextView 的子类也有这些功能,其中一个我们将在下面介绍。

编辑文本

编辑文本 控件是文本视图的子类。顾名思义, EditText 控件允许文本编辑。 EditText 不如你在互联网上找到的文本编辑控件强大,但基于 Android 设备的用户可能不会直接在 EditText 控件中键入文档——他们最多会键入几个段落,或者使用功能更全的基于 HTML 的页面。因此,该类具有有限但适当的功能,甚至可能会让您感到惊讶。例如,编辑文本最重要的属性之一是输入类型。您可以将 inputType 属性设置为 textAutoCorrect 以使控件更正常见的拼写错误。您可以将其设置为 textCapWords 以使控件将单词大写。其他选项只需要电话号码或密码。

指定大写、多行文字和其他功能的方法比较古老,但现在已被废弃。如果在没有 inputType 属性的情况下指定它们,它们可以被读取;但是如果指定了输入类型,这些旧的属性将被忽略。

EditText 控件的旧默认行为是在一行上显示文本,并根据需要扩展。换句话说,如果用户键入的内容超过了第一行,就会出现另一行,依此类推。然而,您可以通过将 singleLine 属性设置为 true 来强制用户使用一行。在这种情况下,用户将不得不继续在同一行上键入。对于 inputType ,如果不指定 textMultiLine ,则 EditText 将默认为单行。因此,如果您想要旧的多行输入的默认行为,您需要用 textMultiLine 指定 inputType 。

EditText 的一个很好的特性是你可以指定提示文本。一旦用户开始键入文本,该文本将显示为轻微褪色并消失。该提示的目的是让用户知道该字段中的内容,而无需用户选择和删除默认文本。在 XML 中,这个属性是 Android:hint = " your hint text here "或 Android:hint = " @ string/your _ hint _ name ",其中 your_hint_name 是在 /res/values/strings.xml 中找到的字符串的资源名。在代码中,您可以用一个 CharSequence 或一个资源 ID 来调用 setHint() 方法。

自动完成文本视图

AutoCompleteTextView 控件是一个具有自动完成功能的 TextView 。换句话说,当用户在文本视图中输入时,控件可以显示选择建议。清单 3-7 展示了带有 XML 和相应代码的 AutoCompleteTextView 控件。

清单 3-7 。 使用 AutoCompleteTextView 控件

<AutoCompleteTextView android:id="@+id/actv"
    android:layout_width="fill_parent"  android:layout_height="wrap_content" />
AutoCompleteTextView actv = (AutoCompleteTextView) this.findViewById(R.id.actv);

ArrayAdapter<String> aa = new ArrayAdapter<String>(this,
                android.R.layout.simple_dropdown_item_1line,
                new String[] {"English", "Hebrew", "Hindi", "Spanish",
                "German", "Greek" });

actv.setAdapter(aa);

在清单 3-7 中显示的 AutoCompleteTextView 控件向用户建议一种语言。例如,如果用户键入 en ,控件会提示英语。如果用户键入 gr ,控件推荐希腊语,依此类推。

如果您使用过建议控件或类似的自动完成控件,您会知道像这样的控件有两个部分:文本视图控件和显示建议的控件。这是总的概念。要像这样使用控件,您必须创建控件,创建建议列表,告诉控件建议列表,并可能告诉控件如何显示建议。或者,您可以为建议创建第二个控件,然后将这两个控件关联起来。

Android 让这变得简单了,从清单 3-7 中可以明显看出。要使用 AutoCompleteTextView ,可以在布局文件中定义控件,并在活动中引用它。然后创建一个保存建议的适配器类,并定义将显示建议的控件的 ID(在本例中,是一个简单的列表项)。在清单 3-7 中, ArrayAdapter 的第二个参数告诉适配器使用一个简单的列表项来显示建议。最后一步是将适配器与 AutoCompleteTextView 相关联,这可以使用 setAdapter() 方法来完成。暂时不要担心适配器;我们将在本章的后面讨论这些。

多自动完成文本视图

如果您玩过 AutoCompleteTextView 控件,您会知道该控件只为文本视图中的整个文本提供建议。换句话说,如果你键入一个句子,你不会得到每个单词的建议。这就是多自动完成文本视图的用武之地。您可以使用 MultiAutoCompleteTextView 在用户键入时提供建议。例如,图 3-2 显示用户键入单词英文后跟逗号,然后是 Ge ,此时控件提示德文。如果用户继续,控件将提供额外的建议。

使用多自动完成文本视图就像使用自动完成文本视图。不同的是,你必须告诉控件从哪里开始再次建议。例如,在图 3-2 中,你可以看到控件可以在句首和逗号后提供建议。MultiAutoCompleteTextView 控件要求您给它一个标记器,它可以解析句子并告诉它是否再次开始建议。清单 3-8 演示了如何使用 MultiAutoCompleteTextView 控件和 XML 以及 Java 代码。

清单 3-8 。 使用 MultiAutoCompleteTextView 控件

<MultiAutoCompleteTextView android:id="@+id/mactv"
    android:layout_width="fill_parent"  android:layout_height="wrap_content" />

MultiAutoCompleteTextView mactv = (MultiAutoCompleteTextView) this
                .findViewById(R.id.mactv);
ArrayAdapter<String> aa2 = new ArrayAdapter<String>(this,
                android.R.layout.simple_dropdown_item_1line,
new String[] {"English", "Hebrew", "Hindi", "Spanish", "German", "Greek" });

mactv.setAdapter(aa2);

mactv.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());

清单 3-7 和清单 3-8 之间唯一显著的区别是使用了多自动完成文本视图和对 setTokenizer() 方法的调用。由于本例中的 CommaTokenizer ,在 EditText 字段中键入逗号后,该字段将再次使用字符串数组提出建议。键入的任何其他字符都不会触发该字段提出建议。因此,即使您键入法语 Spani ,部分单词 Spani 也不会触发建议,因为它后面没有逗号。Android 为电子邮件地址提供了另一个标记器,名为 Rfc822Tokenizer 。如果你愿意,你可以创建你自己的记号赋予器。

按钮控件

按钮在任何 widget 工具包中都很常见,Android 也不例外。Android 提供了一组典型的按钮以及一些额外的功能。在这一节中,我们将讨论三种类型的按钮控件:基本按钮、图像按钮和切换按钮。图 3-3 显示了带有这些控件的用户界面。最上面的按钮是基本按钮,中间的按钮是图像按钮,最后一个是切换按钮。

9781430246800_Fig03-03.jpg

图 3-3 。安卓按钮控件

让我们从基本按钮开始。

按钮控件

Android 中的基本按钮类是 android.widget.Button 。除了如何使用它来处理点击事件之外,这种类型的按钮没什么特别的。清单 3-9 显示了按钮控件的 XML 布局片段,加上一些我们可能在活动的 onCreate() 方法中设置的 Java。我们的基本按钮看起来像图 3-3 中的顶部按钮。

清单 3-9 。 处理按钮上的点击事件

<Button android:id="@+id/button1"
    android:text="@string/basicBtnLabel"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />

Button button1 = (Button)this.findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener()
{
     public void onClick(View v)
     {
        Intent intent = new Intent(Intent.ACTION_VIEW,
                                Uri.parse("[`www.androidbook.com`](http://www.androidbook.com)"));
        startActivity(intent);
     }
});

清单 3-9 展示了如何注册一个按钮点击事件。通过用一个 OnClickListener 调用 setOnClickListener() 方法来注册点击事件。在清单 3-9 中,动态创建了一个匿名监听器来处理按钮 1 的点击事件。当点击按钮时,监听器的 onClick() 方法被调用,在这种情况下,启动浏览器到我们的网站。

从 Android SDK 1.6 开始,有一种更简单的方法来为你的按钮设置一个点击处理器。清单 3-10 显示了一个按钮的 XML,其中你为处理器指定了一个属性,加上作为点击处理器的 Java 代码。

清单 3-10 。 为按钮设置点击处理器

<Button   ...    android:onClick="myClickHandler"    ...  />
    public void myClickHandler(View target) {
        switch(target.getId()) {
        case R.id.button1:
        ...

将调用 handler 方法,将 target 设置为代表被单击按钮的视图对象。注意点击处理器方法中的开关语句如何使用按钮的资源 id 来选择要运行的逻辑。使用这种方法意味着您不必在代码中显式创建每个按钮对象,并且您可以跨多个按钮重用同一方法。这使得事情更容易理解和维护。这也适用于其他按钮类型。

ImageButton 控件

Android 通过 Android . widget . imagebutton 提供一个图片按钮。使用图像按钮类似于使用基本按钮(见清单 3-11 )。我们的图像按钮看起来像图 3-3 中的中间按钮。

清单 3-11 。?? 使用 ImageButton

<ImageButton android:id="@+id/imageButton2"
    android:layout_width="wrap_content" android:layout_height="wrap_content"
    android:onClick="myClickHandler"
    android:src="@drawable/icon"  />

ImageButton imageButton2 = (ImageButton)this.findViewById(R.id.imageButton2);
imageButton2.setImageResource(R.drawable.icon);

在这里,我们用 XML 创建了图像按钮,并从一个 drawable 资源中设置了按钮的图像。按钮的图像文件必须存在于 /res/drawable 下。在我们的例子中,我们只是简单地重用了按钮的 Android 图标。我们还在清单 3-11 中展示了如何通过调用按钮上的 setImageResource() 方法并传递给它一个资源 ID 来动态设置按钮的图像。请注意,您只需要做其中一项。您不需要在 XML 文件和代码中都指定按钮图像。

图像按钮的一个很好的特性是你可以为按钮指定一个透明的背景。结果将是一个可点击的图像,它的行为就像一个按钮,但可以是你想要的任何样子。只需为图片按钮设置 Android:background = " @ null "即可。

因为您的图像可能与标准按钮非常不同,所以您可以自定义按钮在 UI 中使用时的其他两种状态。除了正常显示外,按钮可以有焦点,并且可以被按下。拥有焦点仅仅意味着按钮当前是事件将要发生的地方。例如,您可以使用键盘或 D-pad 上的箭头键将焦点指向某个按钮。按下表示按钮的外观在被按下时但在用户放开之前发生变化。为了告诉 Android 我们按钮的三个图像是什么,哪一个是哪一个,我们设置了一个选择器。这是一个简单的 XML 文件, imagebuttonselector ,它位于我们项目的 /res/drawable 文件夹中。这有点违反直觉,因为这是一个 XML 文件,而不是图像文件,然而选择器文件必须放在那里。选择器文件的内容将类似于清单 3-12 中的。

清单 3-12 。 使用带有 ImageButton 的选择器

<?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)">
     <item android:state_pressed="true"
           android:drawable="@drawable/button_pressed" /> <!-- pressed -->
     <item android:state_focused="true"
           android:drawable="@drawable/button_focused" /> <!-- focused -->
     <item android:drawable="@drawable/icon" /> <!-- default -->
    </selector>

关于选择器文件,有几点需要注意。首先,不要像在 values XML 文件中那样指定一个 < resources > 标记。其次,按钮图像的顺序很重要。Android 将依次测试选择器中的每一项,看是否匹配。因此,您希望将正常图像放在最后,以便仅在按钮未被按下且按钮没有焦点时使用。如果首先列出的是普通图像,那么即使按钮被按下或有焦点,它也总是匹配并被选中。当然,你所指的 drawables 必须存在于 /res/drawables 文件夹中。在布局 XML 文件中按钮的定义中,您希望将选择器 XML 文件的 android:src 属性设置为常规的 drawable,如下所示:

<Button   ...    android:src="@drawable/imagebuttonselector"    ...   />

ToggleButton 控件

与复选框或单选按钮一样, ToggleButton 控件是一个双态按钮。该按钮可以处于开或关状态。如图图 3-3 所示,切换按钮的默认行为是在打开状态时显示一个彩色条,在关闭状态时显示一个灰条。此外,默认行为还会在按钮处于打开状态时将按钮文本设置为打开,在按钮处于关闭状态时将按钮文本设置为关闭。如果开/关不适合您的应用,您可以修改切换按钮的文本。例如,如果您有一个想要通过 ToggleButton 启动和停止的后台进程,您可以通过使用 android:textOn 和 android:textOff 属性将按钮的文本设置为停止和运行。

清单 3-13 显示了一个例子。我们的切换按钮是图 3-3 中最底部的按钮,它处于 on 位置,所以按钮上的标签写着 Stop。

清单 3-13 。?? 安卓切换按钮

<ToggleButton android:id="@+id/cctglBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Toggle Button"
        android:textOn="Stop"
        android:textOff="Run"/>

因为 ToggleButton s 有 on 和 off 文本作为单独的属性,所以 ToggleButton 的 android:text 属性并没有真正被使用。它是可用的,因为它已经被继承了(从 TextView ),但是在这种情况下,你不需要使用它。

复选框控件

复选框控件是另一个双态按钮,允许用户切换其状态。不同之处在于,在许多情况下,用户不会将它视为一个调用即时操作的按钮。然而,从 Android 的角度来看,它是一个按钮,你可以用复选框做任何你可以用按钮做的事情。

在 Android 中,可以通过创建 android.widget.CheckBox 的实例来创建复选框。参见清单 3-14 和图 3-4 。

清单 3-14 。 创建复选框

<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

<CheckBox android:id="@+id/chickenCB"
    android:text="Chicken"
    android:checked="true"
    android:layout_width=""wrap_content"
    android:layout_height="wrap_content" />

<CheckBox android:id="@+id/fishCB"
    android:text="Fish"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<CheckBox android:id="@+id/steakCB"
    android:text="Steak"
    android:checked="true"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

</LinearLayout>

9781430246800_Fig03-04.jpg

图 3-4 。使用复选框 控制

您可以通过调用 setChecked() 或 toggle() 来管理复选框的状态。可以通过调用 isChecked() 获得状态。

如果您需要在复选框被选中或取消选中时实现特定的逻辑,您可以通过调用 setOnCheckedChangeListener()和 CompoundButton 的实现来注册 on-checked 事件。OnCheckedChangeListener 接口。然后,您必须实现 onCheckedChanged() 方法,该方法将在复选框被选中或取消选中时被调用。清单 3-15 显示了一些处理复选框的代码。

清单 3-15 。 在代码中使用复选框

public class CheckBoxActivity extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.checkbox);

            CheckBox fishCB = (CheckBox)findViewById(R.id.fishCB);

            if(fishCB.isChecked())
                fishCB.toggle();     // flips the checkbox to unchecked if it was checked

            fishCB.setOnCheckedChangeListener(
                        new CompoundButton.OnCheckedChangeListener() {

                @Override
                public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
                    Log.v("CheckBoxActivity", "The fish checkbox is now "
                            + (isChecked?"checked":"not checked"));
                }});
        }
}

设置 OnCheckedChangeListener 的好处在于,您被传递了复选框按钮的新状态。你可以使用 OnClickListener 技术,就像我们使用基本按钮一样。当调用 onClick() 方法时,您需要确定按钮的新状态,方法是对其进行适当的转换,然后对其调用 isChecked() 。清单 3-16 显示了如果我们将 Android:onClick = " myClickHandler "添加到我们的复选框按钮的 XML 定义中,这段代码可能会是什么样子。

清单 3-16 。 在代码中使用复选框 android:onClick

public void myClickHandler(View view) {
    switch(view.getId()) {
    case R.id.steakCB:
        Log.v("CheckBoxActivity", "The steak checkbox is now " +
                (((CheckBox)view).isChecked()?"checked":"not checked"));
    }
}

开关控制

在 Android 4.0 中引入了开关小部件,它提供了与复选框非常相似的行为。事实上,这两个小部件是如此的相似,当你回顾一个开关对象的代码时,你几乎肯定会有一种似曾相识的感觉。许多人(包括本书的一些作者)认为引入开关更多的是出于审美原因。在过去几年中,UI 设计的趋势是朝着看起来像真实世界事物的微件的扭曲理想发展,并且开关是真实世界中的具体选择器——毕竟没有多少厨房电器有复选框。

开关与复选框控件的相似之处扩展到检查和更改状态的常用方法。这种方法的模仿包括 setChecked() 打开开关, isChecked() 测试当前状态,等等。由 Switch 小部件提供的一个美学差异是能够在状态之间改变相关的文本。还可以使用其他方法来控制此文本:

  • getTextOn() :返回开关打开时显示的文本。
  • getTextOff() :返回开关关闭时显示的文本。
  • setTextOn() :设置开关打开时显示的文本。虽然好的设计通常意味着不会更改文本,但在某些情况下,实时更新交换机文本中的某些指标会有所帮助。
  • setTextOff() :设置开关关闭时显示的文本。

在清单 3-17 中显示了包括开关的示例布局。

清单 3-17 。 使用开关创建布局

<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <Switch
        android:id="@+id/switchdemo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This switch is: off" />

</LinearLayout>

注意记住“switch”是 Java 中的保留字。所以我们用一个不冲突的 ID。

在清单 3-18 中,我们的示例开关的代码应该会激起我们提到的关于复选框控件的似曾相识的感觉。

清单 3-18 。 用代码控制开关行为

public class SwitchDemo extends Activity
  implements CompoundButton.OnCheckedChangeListener {
  Switch sw;

  @Override
  public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);

    sw=(Switch)findViewById(R.id.switchdemo);
    sw.setOnCheckedChangeListener(this);
  }

  public void onCheckedChanged(CompoundButton buttonView,
                                 boolean isChecked) {
    if (isChecked) {
      sw.setTextOn("This switch is: on");
    }
    else {
      sw.setTextOff("This switch is: off");
    }
  }
}

我们切换工作的结果显示在图 3-5 中。

9781430246800_Fig03-05.jpg

图 3-5 。使用开关 控制

单选按钮控件

RadioButton 控件是任何 UI 工具包不可或缺的一部分。单选按钮给用户几个选择,并强迫他们选择一个项目。为了实施这种单一选择模型,单选按钮通常属于一个组,每个组一次只能选择一个项目。

要在 Android 中创建一组单选按钮,首先创建一个 RadioGroup ,然后用单选按钮填充该组。清单 3-19 和图 3-6 显示了一个例子。

清单 3-19 。 使用安卓单选按钮 小工具

<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
    android:orientation="vertical"
   android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <RadioGroup
        android:id="@+id/rBtnGrp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <RadioButton
            android:id="@+id/chRBtn"
            android:text="Chicken"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <RadioButton
            android:id="@+id/fishRBtn"
            android:text="Fish"
            android:checked="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <RadioButton
            android:id="@+id/stkRBtn"
            android:text="Steak"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </RadioGroup>

</LinearLayout>

9781430246800_Fig03-06.jpg

图 3-6 。使用单选按钮

在 Android 中,使用 Android . widget . radio group 实现一个单选按钮组,使用 Android . widget . radio button 实现一个单选按钮。

请注意,单选按钮组中的单选按钮默认情况下是未选中的,尽管您可以在 XML 定义中设置一个为选中,就像我们在清单 3-19 中对 Fish 所做的那样。要以编程方式将其中一个单选按钮设置为选中状态,可以获取对该单选按钮的引用并调用 setChecked() :

RadioButton steakBtn = (RadioButton)this.findViewById(R.id.stkRBtn);
steakBtn.setChecked(true);

您还可以使用 toggle() 方法来切换单选按钮的状态。与复选框控件一样,如果您使用 OnCheckedChangeListener 接口的实现调用 setOnCheckedChangeListener(),您将被通知已选中或未选中事件。不过,这里有一点小小的不同。这是一个与以前不同的班级。这一次,技术上来说是 RadioGroup。OnCheckedChangeListener 类代表 RadioGroup,而之前它是 CompoundButton。OnCheckedChangeListener 类。

单选按钮组也可以包含除单选按钮之外的视图。例如,清单 3-20 在最后一个单选按钮后添加了一个文本视图。还要注意,第一个单选按钮( anotherRadBtn )位于单选按钮组之外。

清单 3-20 。?? 一个单选按钮组而不仅仅是单选按钮

<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
        android:orientation="vertical"  android:layout_width="fill_parent"
        android:layout_height="fill_parent">

    <RadioButton android:id="@+id/anotherRadBtn"
        android:text="Outside"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <RadioGroup android:id="@+id/radGrp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <RadioButton android:id="@+id/chRBtn"
            android:text="Chicken"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

          <RadioButton android:id="@+id/fishRBtn"
            android:text="Fish"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <RadioButton android:id="@+id/stkRBtn"
            android:text="Steak"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <TextView android:text="My Favorite"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </RadioGroup>
</LinearLayout>

清单 3-20 展示了在一个单选按钮组中可以有非单选按钮控件。您还应该知道,单选按钮组只能在它自己的容器中的单选按钮上执行单选操作。也就是说,ID 为 anotherRadBtn 的单选按钮不会受到清单 3-20 中所示单选按钮组的影响,因为它不是该组的子组之一。

您可以通过编程来操作单选按钮组。例如,您可以获取对单选按钮组的引用,并添加单选按钮(或其他类型的控件)。清单 3-21 展示了这个概念。

清单 3-21 。?? 在代码中添加一个单选按钮到一个单选按钮组

RadioGroup radGrp = (RadioGroup)findViewById(R.id.radGrp);
RadioButton newRadioBtn = new RadioButton(this);
newRadioBtn.setText("Pork");
radGrp.addView(newRadioBtn);

一旦用户选中了单选按钮组中的单选按钮,用户就不能通过再次单击来取消选中它。清除单选按钮组中所有单选按钮的唯一方法是以编程方式调用单选按钮组上的 clearCheck() 方法。

当然,你想用 RadioGroup 做一些有趣的事情。你可能不想轮询每个单选按钮来确定它是否被选中。幸运的是,电台组有几种方法可以帮你。我们用清单 3-22 中的来演示这些。这段代码的 XML 在清单 3-20 中。

清单 3-22 。 以编程方式使用单选按钮组

public class RadioGroupActivity extends Activity {
        protected static final String TAG = "RadioGroupActivity";

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.radiogroup);

            RadioGroup radGrp = (RadioGroup)findViewById(R.id.radGrp);

            int checkedRadioButtonId = radGrp.getCheckedRadioButtonId();

            radGrp.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup arg0, int id) {
                    switch(id) {
                    case -1:
                        Log.v(TAG, "Choices cleared!");
                        break;
                    case R.id.chRBtn:
                        Log.v(TAG, "Chose Chicken");
                        break;
                    case R.id.fishRBtn:
                        Log.v(TAG, "Chose Fish");
                        break;
                    case R.id.stkRBtn:
                        Log.v(TAG, "Chose Steak");
                        break;
                    default:
                        Log.v(TAG, "Huh?");
                        break;
                    }
                }});
        }
}

我们总是可以使用 getCheckedRadioButtonId()来获取当前选中的 RadioButton ,这将返回选中项的资源 Id,或者如果没有选中任何内容,则返回–1(如果没有默认设置,并且用户还没有选择选项,则有可能)。我们之前在我们的 onCreate() 方法中展示了这一点,但是实际上,您可能希望在适当的时候使用它来读取用户当前的选择。我们还可以设置一个监听器,当用户选择其中一个单选按钮时,监听器会立即得到通知。请注意, onCheckedChanged() 方法采用了一个 RadioGroup 参数,允许您对多个 RadioGroup 使用相同的 OnCheckedChangeListener。您可能已经注意到了–1 的开关选项。如果使用 clearCheck() 通过代码清除了 RadioGroup ,也会发生这种情况。

ImageView 控件

我们还没有涉及的一个基本控件是 ImageView 控件。这用于显示图像,其中图像可以来自文件、内容提供者或资源(如 drawable)。你甚至可以指定一种颜色,然后 ImageView 就会显示这种颜色。清单 3-23 显示了一些 ImageView s 的 XML 示例,后面是一些显示如何创建 ImageView 的代码。

***清单 3-23 。***XML 和代码中的 ImageView

<ImageView android:id="@+id/image1"
  android:layout_width="wrap_content"  android:layout_height="wrap_content"
  android:src="@drawable/icon" />

<ImageView android:id="@+id/image2"
  android:layout_width="125dip"  android:layout_height="25dip"
  android:src="#555555" />

<ImageView android:id="@+id/image3"
  android:layout_width="wrap_content"  android:layout_height="wrap_content" />

<ImageView android:id="@+id/image4"
  android:layout_width="wrap_content"  android:layout_height="wrap_content"
  android:src="@drawable/manatee02"
  android:scaleType="centerInside"
  android:maxWidth="35dip"  android:maxHeight="50dip"
  />

  ImageView imgView = (ImageView)findViewById(R.id.image3);

  imgView.setImageResource( R.drawable.icon );

  imgView.setImageBitmap(BitmapFactory.decodeResource(
              this.getResources(), R.drawable.manatee14) );

  imgView.setImageDrawable(
              Drawable.createFromPath("/mnt/sdcard/dave2.jpg") );

  imgView.setImageURI(Uri.parse("file://mnt/sdcard/dave2.jpg"));

在这个例子中,我们用 XML 定义了四个图像。第一个只是我们应用的图标。第二个是一个比它高还宽的灰色条。第三个定义没有在 XML 中指定图像源,但是我们将一个 ID 与这个 ID(image3)相关联,我们可以在代码中使用它来设置图像。第四个图像是我们的另一个可绘制图像文件,我们不仅指定图像文件的来源,还设置图像在屏幕上的最大尺寸,并定义如果图像大于我们的最大尺寸该怎么办。在这种情况下,我们告诉 ImageView 居中并缩放图像,使其符合我们指定的大小。

在清单 3-23 的 Java 代码中,我们展示了几种设置图片 3 的方法。当然,我们首先必须通过使用资源 ID 找到对 ImageView 的引用。第一个 setter 方法, setImageResource() ,简单地使用图像的资源 ID 来定位图像文件,以便为我们的 ImageView 提供图像。第二个 setter 使用 BitmapFactory 将图像资源读入一个位图对象,然后将 ImageView 设置为那个位图。注意,在将位图应用到我们的 ImageView 之前,我们可以对位图做一些修改,但是在我们的例子中,我们按原样使用它。此外, BitmapFactory 有几种创建位图的方法,包括从一个字节数组和一个 InputStream 。您可以使用 InputStream 方法从 web 服务器读取图像,创建位图图像,然后从那里设置 ImageView 。

第三个设置使用一个 Drawable 作为我们的图像源。在本例中,我们显示的是来自 SD 卡的图像来源。你需要在 SD 卡上放一些有合适名字的图像文件。类似于 bitmap factory,Drawable 类有几种不同的方法来构造 Drawable s,包括从 XML 流中构造。

最后一个 setter 方法获取图像文件的 URI,并将其用作图像源。对于这最后一个调用,不要认为你可以使用任何图像 URI 作为来源。这个方法实际上只适用于设备上的本地图像,而不适用于通过 HTTP 找到的图像。要使用基于互联网的图像作为你的 ImageView 的来源,你最有可能使用 BitmapFactory 和 InputStream 。

日期和时间控件

日期和时间控件在许多小部件工具包中很常见。Android 提供了几个基于日期和时间的控件,其中一些我们将在本节讨论。具体来说,我们将介绍日期选择器、时间选择器、数字时钟和模拟时钟控件。

日期选择器和时间选择器控件

顾名思义,使用日期选择器控件选择日期,使用时间选择器控件选择时间。清单 3-24 和图 3-7 显示了这些控件的例子。

***清单 3-24 。***XML 中的日期选择器和时间选择器控件

<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

  <TextView android:id="@+id/dateDefault"
    android:layout_width="fill_parent" android:layout_height="wrap_content" />

  <DatePicker android:id="@+id/datePicker"
    android:layout_width="wrap_content" android:layout_height="wrap_content" />

  <TextView android:id="@+id/timeDefault"
    android:layout_width="fill_parent" android:layout_height="wrap_content" />

  <TimePicker android:id="@+id/timePicker"
    android:layout_width="wrap_content" android:layout_height="wrap_content" />

</LinearLayout>

9781430246800_Fig03-07.jpg

图 3-7 。日期选择器和时间选择器 ui

如果查看 XML 布局,您会发现定义这些控件很容易。与 Android toolkit 中的任何其他控件一样,您可以通过编程方式访问这些控件来初始化它们或从中检索数据。例如,你可以初始化这些控件,如清单 3-23 所示。

清单 3-25 。 分别用日期和时间初始化日期选择器和时间选择器

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.datetimepicker);

    TextView dateDefault = (TextView)findViewById(R.id.dateDefault);
    TextView timeDefault = (TextView)findViewById(R.id.timeDefault);

    DatePicker dp = (DatePicker)this.findViewById(R.id.datePicker);
    // The month, and just the month, is zero-based. Add 1 for display.
    dateDefault.setText("Date defaulted to " + (dp.getMonth() + 1) + "/" +
            dp.getDayOfMonth() + "/" + dp.getYear());
    // And here, subtract 1 from December (12) to set it to December
    dp.init(2008, 11, 10, null);

    TimePicker tp = (TimePicker)this.findViewById(R.id.timePicker);

    java.util.Formatter timeF = new java.util.Formatter();
    timeF.format("Time defaulted to %d:%02d", tp.getCurrentHour(),
                    tp.getCurrentMinute());
    timeDefault.setText(timeF.toString());

    tp.setIs24HourView(true);
    tp.setCurrentHour(new Integer(10));
    tp.setCurrentMinute(new Integer(10));
}

清单 3-25 将日期选择器上的日期设置为 2008 年 12 月 10 日。请注意,对于月份,内部值是从零开始的,这意味着一月是 0,十二月是 11。对于时间选择器,小时数和分钟数设置为 10。另请注意,该控件支持 24 小时视图。如果不设置这些控件的值,默认值将是设备已知的当前日期和时间。

最后,注意 Android 提供了这些控件的模态窗口版本,比如 DatePickerDialog 和 TimePickerDialog 。如果要向用户显示控件并强制用户做出选择,这些控件非常有用。我们将在第八章的中更详细地讨论对话。

textblock 和 analogclock 控件

Android 还提供文本时钟和模拟时钟控件(见图 3-8 )。

9781430246800_Fig03-08.jpg

图 3-8 。使用模拟时钟和数字时钟

如图所示,除了小时和分钟之外,文本时钟还支持秒。Android 中的模拟时钟是一个双手时钟,一只手用于小时指示器,另一只手用于分钟指示器。要将这些添加到你的布局中,使用如清单 3-26 所示的 XML。

清单 3-26 。?? 在 XML 中添加数字时钟或模拟时钟

<TextClock
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:format12Hour="hh:mm:ss aa" android:format24Hour="kk:mm:ss" />

<AnalogClock
  android:layout_width="wrap_content" android:layout_height="wrap_content" />

这两个控件实际上只是用于显示当前时间,因为它们不允许您修改日期或时间。换句话说,它们是唯一能够显示当前时间的控件。因此,如果你想改变日期或时间,你需要坚持使用日期选择器 / 时间选择器或日期选择器对话框 / 时间选择器对话框。不过,这两个时钟的好处在于,它们会自动更新,而无需你做任何事情。也就是说,秒针在文本时钟中滴答走,指针在模拟时钟上移动,而不需要我们做任何额外的事情。

MapView 控件

随着 Google Play 服务的推出,Android 显示地图数据的方式发生了一些变化。然而,绝大多数开发人员仍然喜欢原始的 MapView 控件,原因有很多——向后兼容性、简单性等等。顾名思义,com . Google . Android . maps . mapview 控件可以显示地图。您可以通过 XML 布局或代码实例化这个控件,但是使用它的活动必须扩展 MapActivity。MapActivity 负责加载地图、执行缓存等多线程请求。

注意严格来说,MapView 是 Google API 的一部分,而不是 Android API 的一部分。以便测试代码等。对于 MapView,请确保您的仿真器是根据包含 Google APIs 的 SDK 版本创建的。

清单 3-27 显示了一个 MapView 的实例化。

清单 3-27 。 通过 XML 布局创建 MapView 控件

<LinearLayout xmlns:android="[`schemas.android.com/apk/res/android`](http://schemas.android.com/apk/res/android)"
        android:orientation="vertical" android:layout_width="fill_parent"
        android:layout_height="fill_parent">

    <com.google.android.maps.MapView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:enabled="true"
        android:clickable="true"
        android:apiKey="myAPIKey"
        />

</LinearLayout>

我们将在第十九章中详细讨论基于位置的服务。这也是您学习如何获得自己的映射 API 密钥的地方。

参考

以下是一些对您可能希望进一步探索的主题有帮助的参考:

  • 【http://www.androidbook.com/proandroid5/projects】:与本书相关的可下载项目列表。在本章中,寻找一个名为 pro Android 5 _ Ch03 _ controls . ZIP 的 ZIP 文件。这个 ZIP 文件包含本章中的所有项目,列在单独的根目录中。还有一个自述。TXT 文件,它准确地描述了如何从这些 ZIP 文件之一将项目导入 Eclipse。
  • :几篇“布局招数”——非常值得一读的类型化技术文章。他们开始研究在 Android 中设计和构建 ui 的性能方面。在这个列表中寻找其他与构建 ui 相关的文章。

摘要

让我们通过快速列举你所学到的关于构建用户界面的知识来结束这一章:

  • XML 资源如何定义 UI 外观,以及代码如何填充数据
  • Android 中所有的基本用户界面控件
  • 第四章中的列表视图和第五章中的布局的提示。