AndroidStudio3 和 Kotlin 学习手册(三)
八、Android Studio 简介和设置
我们将介绍的内容:
-
Android 概述
-
历史
-
工具作业
-
设置
对于不同的人来说,Android 可能意味着很多事情,但是既然你拿着这本书,我想你会对 Android 中适合开发者的部分感兴趣。Android 是一个由操作系统、软件库、应用框架、软件开发工具包、预建应用和参考设计组成的平台。平台及其发展生态系统都随着时间的推移而演变。
在这一章,我们将看看 Android 的历史和架构。我们还将讨论 Android Studio 以及如何设置它。
历史
2003 年的某个时候,安迪·鲁宾创立了一家名为 Android Inc .的公司,Android 由此诞生。当时,谷歌已经在支持安卓公司,但还没有拥有它。谷歌在 2005 年的某个时候收购了安卓公司;然后在 2007 年,开放手机联盟诞生了,Android OS 正式开源。在这段时间里,Android 还没有达到 1.0 版本,还远未成为主流。Android 在 2008 年达到了 1.0 版本——当时甜点的名字还没有成为文化的一部分,但用不了多久它们就会成为文化的一部分。
接下来的两年,从 2009 年到 2010 年,见证了一股快速发行的洪流:纸杯蛋糕、甜甜圈、甜甜圈、圣克莱尔和姜饼版本在此期间发行。
2011 年是一个重要的里程碑,因为在那之前,Android 操作系统仍然局限于手机。Honeycomb 是 Gingerbread 的继任者,是第一个安装在平板电脑上的 Android 版本。Honeycomb 引起了一些争议,因为谷歌没有立即开放源代码。
表 8-1 显示了 Android 历史的简要总结。
表 8-1
安卓的历史
| **2003 年** | 由安迪·鲁宾创建并得到谷歌支持的安卓公司诞生了 | | **2005 年** | 谷歌收购了安卓公司。 | | **2007 年** | Android 是官方开源的。谷歌将其所有权移交给了开放手机联盟(OHA) | | **2008 年** | 发布了 1.0 版本 | | **2009 年** | 发布了版本 1.1、1.5(纸杯蛋糕)、1.6(甜甜圈)和 2.0(艾克蕾尔) | | **2010 年** | 版本 2.2 (Froyo)和 2.3 (Gingerbread)已经发布 | | **2011 年** | 发布了 3.0 版(蜂巢)和 4.0 版(冰淇淋三明治) | | **2012 年** | 版本 4.1 (Jellybean)发布了 | | **2013 年** | 发布了 4.4 版(KitKat) | | **2014 年** | 5.0-5.1 版本(棒棒糖)发布;Android 变成了 64 位 | | **2015 年** | 6.0 版本(棉花糖)发布 | | **2016 年** | 发布了 7.0-7.1.2 版本(牛轧糖) | | **2017 年** | 版本 8(奥利奥)发布 | | **2018** | 版本 9 (Android P,beta)发布 |体系结构
Android 最明显的部分,至少对开发者来说,是它的操作系统。操作系统是一个复杂的东西,但在大多数情况下,它是用户和硬件之间的桥梁。这太简单了,但对我们的目的来说已经足够了。我所说的“用户”并不是指最终用户或个人。我的意思是应用,程序员创造的一段代码,就像文字处理器或电子邮件客户端。
以电子邮件应用为例:当你在键盘上键入每个字符时,该应用需要与硬件通信,以便消息到达你的屏幕和硬盘,并最终通过你的网络发送到云端。这是一个比我在这里描述的更复杂的过程,但这是基本的想法。最简单地说,操作系统做三件事:
-
代表应用管理硬件
-
为网络、安全、内存管理等应用提供服务。
-
管理应用的执行;这是允许我们几乎同时运行多个应用的部分
图 8-1 展示了 Android 平台的逻辑架构。
图 8-1
Android 的逻辑架构
图的最底层是 Linux 内核。它负责与硬件接口,以及其他事情。它还负责各种服务,如内存管理和进程执行。
Linux 是一个非常稳定的 OS,并且相当普遍;你会发现这个操作系统被广泛使用。它可以在小到手表,大到服务器农场的东西上运行。Android 内部有一个嵌入式 Linux,处理硬件接口和其他一些内核功能。
在 Linux 内核之上是低级库,如 SQLite、OpenGL 等。这些不是 Linux 内核的一部分,但仍然是低级的,因此,大部分是用 C/C++ 编写的。在同一层面上,你会发现 android 运行时(android 类库+ dalvik 虚拟机),这是 Android 应用运行的地方。
接下来是应用框架层。它位于底层库和 android 运行时之上,因为它需要这两者。这是我们作为应用开发人员将与之交互的层,因为它包含了我们编写应用所需的所有库。
最后,最上面是应用层。这是我们所有应用的所在地,包括我们编写的应用和预构建的应用。应该指出的是,与我们将要编写的应用相比,设备自带的预构建应用没有任何特权。如果你不喜欢手机的电子邮件应用,你可以自己编写并替换它。安卓就是这样民主的。
Android Studio IDE
为 Android 开发应用并不总是像今天这样方便。当 Android 1.0 在 2008 年发布时,开发人员通过开发工具包获得的只是一堆命令行工具和 Ant 构建脚本。如果你已经习惯了使用 Vim、Ant 和其他命令行工具来构建应用,这并没有那么糟糕,但是很多开发人员并不习惯这样。缺少代码提示、项目设置和集成调试等 IDE 功能在某种程度上是入门的障碍。
令人欣慰的是,Eclipse IDE 的 android 开发工具(ADT)也是在 2008 年发布的。Eclipse 过去是,现在仍然是许多 Java 开发人员最喜欢的 IDE 选择。很自然地,它也将成为 Android 开发者的首选 IDE。
从 2009 年到 2012 年,Eclipse 一直是开发 IDE 的选择。android SDK 在结构和范围上也经历了重大和渐进的变化。2009 年,SDK 管理器发布;我们用它来下载工具、单个 SDK 版本和可以用于模拟器的 android 图像。2010 年,发布了针对 ARM 处理器和 X86 CPUs 的附加映像。
2012 年是重要的一年,因为 Eclipse 和 ADT 最终被捆绑在一起,这是一件大事,因为在那之前,开发人员必须分别安装 Eclipse 和 ADT,安装过程并不总是顺利的。因此,将两者捆绑在一起使得开始 Android 开发变得更加容易。2012 年也值得纪念,因为它标志着 Eclipse 成为 android 主流 IDE 的最后一年。
2013 年 Android Studio 发布;可以肯定的是,它仍然处于测试阶段,但是不祥之兆已经很明显了。它将成为 Android 开发的官方 IDE。Android Studio 基于 JetBrains 的 IntelliJ。IntelliJ 是一个商业 Java IDE,也有一个社区(非付费)版本。这个版本将作为 Android Studio 的基础。
有相当多的 JVM 语言,但 Java 一直是 Android 开发的首选语言——直到 2017 年,谷歌 I/O 宣布 Android 将对 Kotlin 提供一流的支持。Android Studio 3 (AS3)自动支持 Kotlin。
设置
JDK 是 Android Studio 的必备软件,但是因为我们已经在第一章中介绍了 JDK 的安装,我们将继续安装 AS3。安装程序可用于 macOS、Windows 和 Linux 下载页面位于http://bit.ly/getas3—该页面应该能够检测到您正在使用的操作系统,并将为您显示合适的安装程序。您将被要求同意一些条款和条件,然后才能继续下载。阅读它,理解它,并同意它,这样你就可以继续下去。之后,AS3 安装程序将以压缩文件的形式下载。
对于 macOS,您需要执行以下操作:
-
解压缩安装程序的压缩文件。
-
将应用文件拖到应用文件夹中。
-
推出 AS3。
-
AS3 会提示你导入一些设置,如果你有以前的安装。您可以导入它,这是默认选项。
注意
如果您已经安装了 Android Studio,您可以继续使用该版本,并安装预览版。AS3 可以和你现有版本的 Android Studio 共存;其设置将保存在不同的目录中。
对于 Windows,您需要执行以下操作:
-
解压缩安装程序文件。
-
将解压后的目录移动到您选择的位置,例如C:\ Users \ my name \ AndroidStudio
-
向下钻取到 AndroidStudio 文件夹;在里面,你会发现 studio64.exe 的*。这是您需要启动的文件。为这个文件创建一个快捷方式是个好主意——如果你右击 studio64.exe 并选择“*固定到开始菜单”,你可以从 Windows 开始菜单中使用 AS3。或者,你也可以把它钉在任务栏上。
Linux 安装需要做的工作比简单地双击并遵循安装程序提示要多一些。在 Ubuntu 及其衍生产品的未来版本中,这可能会发生变化,变得像 Windows 和 macOS 一样简单和无摩擦,但现在,我们需要做一些调整。Linux 上的额外活动主要是因为 AS3 需要一些 32 位库和硬件加速。
注意
本节中的安装说明适用于 64 位 Ubuntu 和其他 Ubuntu 衍生产品(例如,Linux Mint、Lubuntu、Xubuntu、Ubuntu MATE 等)。).我选择这个发行版是因为我认为它是一个非常常见的 Linux 版本;因此,本书的读者将会使用该发行版。
如果你运行的是 64 位版本的 Ubuntu,你需要安装一些 32 位的库,这样 AS 才能正常工作。
要开始获取 Linux 的 32 位库,请在终端窗口上运行以下命令:
sudo apt-get update && sudo apt-get upgrade -y
sudo dpkg --add-architecture i386
sudo apt-get install libncurses5:i386 libstdc++6:i386 zlib1g:i386
所有准备工作完成后,您需要执行以下操作:
-
解压下载的安装文件。您可以使用命令行工具或 GUI 工具来解包文件。例如,您可以右键单击该文件,然后选择“*在此解包”*选项,如果您的文件管理器有该选项的话。
-
解压文件后,将文件夹重命名为 AndroidStudio。
-
将文件夹移动到您拥有读取、写入和执行权限的位置。或者,你也可以把它移到 /usr/local/AndroidStudio。
-
打开一个终端窗口,进入 AndroidStudio /bin 文件夹,然后运行*。/studio.sh.*
-
第一次启动时,AS3 会问你是否要导入一些设置。如果您已经安装了以前版本的 Android Studio,您可能需要导入这些设置。
Android Studio 配置
如果这是您第一次安装 AS3,您可能希望在开始编码工作之前先配置一些东西。在这一部分,我将带您了解以下内容:
-
获得更多我们需要的软件,以便创建针对特定版本 Android 的程序
-
确保我们拥有所有需要的 SDK 工具;并且可选地
-
改变我们获取 AS3 更新的方式
如果你还没有启动 AS3,那么点击“配置”,如图 8-2 所示。从下拉列表中选择“首选项”。
图 8-2
从 AS3 开始屏幕转到首选项
您将看到“首选项”窗口,如图 8-3 所示。在窗口的左侧,单击“Android SDK”
图 8-3
SDK 平台
当您进入 SDK 窗口时,启用“显示包细节”选项,这样您就可以看到每个 API 级别的更详细的视图。我们不需要下载 SDK 窗口中的所有内容。我们将只得到我们需要的物品。
SDK 级别或平台号是 Android 的特定版本。Android 8 或“奥利奥”是 API 等级 26 和 27,牛轧糖是 API 等级 24 和 25。你不需要记住平台号,至少不再需要,因为 AS3 用相应的 Android 昵称显示平台号。
如果你愿意,你可以下载“牛轧糖”和“奥利奥”;这些是 API 等级 24、25、26 和 27。出于我们的目的,请下载“棉花糖”——它是 API 等级 23。这是我们在整本书中最常使用的版本。请确保在下载平台的同时,您还将下载“Google APIs 英特尔 x86 Atom_64 系统映像”当我们测试运行我们的应用时,我们将需要这些。
选择一个 API 级别现在可能没什么大不了的,因为在这一点上,我们只是在练习应用。当您计划向公众发布您的应用时,您可能不会轻易做出这个选择。为你的应用选择一个最低的 SDK 或 API 级别将决定有多少人能够使用你的应用。在撰写本文时,所有安卓设备中有 25%在使用“棉花糖”,22%在使用“牛轧糖”,4%在使用“奥利奥”。这些统计数据来自开发者的仪表板页面。安卓。com 。不时检查这些统计数据是个好主意;你可以在这里找到 http://bit.ly/droiddashboard 。
回到我们的配置,当您对您的选择满意时,启用您想要下载的 API 和图像的复选框,然后单击“SDK 工具”——它就在“SDK 平台”按钮的旁边,如图 8-4 所示。
图 8-4
SDK 工具
你通常不需要改变这个窗口上的任何东西,但是检查一下你是否有工具也无妨,如表 8-2 所示,标记为“已安装”
表 8-2
SDK 工具
|工具
|
描述
| | --- | --- | | Android SDK 构建工具 | 这包含了像 adb 这样的重要工具,它将帮助我们进行诊断和调试;sqlite3,我们在创建使用数据库的应用时可以使用它;加上一些其他工具。 | | Android SDK 平台工具 | 这包含了像 adb 这样的重要工具,它将帮助我们进行诊断和调试;sqlite3,我们在创建使用数据库的应用时可以使用它;加上一些其他工具。 | | Android SDK 工具 | 这包括基本的 Android 工具,如 ProGuard。您不需要深入研究这些工具的细节(目前)。只要确保这个框被选中,我们就可以开始了。 | | 安卓模拟器 | 你肯定会用这个。这是一个设备仿真工具。我们将使用它在虚拟设备中测试我们的应用。 | | 支持知识库 | 如果你想写针对 Android Wear,Android TV,或者 Google Cast 的代码,你要下载这个。它还包含本地 Maven 资源库以支持库。支持库还允许您在旧版本的 Android 上使用新功能。 | | HAXM 安装程序 | 如果你使用的是 macOS,或者是搭载英特尔处理器的 PC,你可以使用这个。它是 Android 模拟器的加速器。 |
注意
如果你在 Linux 平台上,你不能使用 HAXM,即使你有一个 Intel 处理器。KVM 将用于 Linux,而不是 HAXM。
一旦你对你的选择满意,点击“确定”按钮开始下载软件包。
我们要做的最后一项配置检查是“更新通道”它在同一个“偏好设置”窗口中。点击右侧的“更新”项,显示“更新”设置,如图 8-5 所示。
图 8-5
更新
AS3,就像任何 Android Studio 安装一样,默认配置为从您最初下载安装程序的渠道获取更新。因为我们从稳定的渠道下载了安装程序,所以默认情况下它会从那个渠道获得更新。您可以将频道切换到以下四个频道之一:
-
金丝雀频道:这是最新版本,每周都会更新。您不希望将它用于生产代码。
-
开发频道:就像金丝雀频道,但更稳定一些。你还是不想用这个做生产。
-
**Beta 通道:**这包含发布候选。在反馈到稳定的渠道之前,开发人员基本上都在等待反馈。
-
**稳定渠道:**这是官方稳定发布,适合生产工作。
硬件加速
在你编写应用的时候,不时地测试和运行它是很有用的,这样可以得到即时的反馈,并发现它是否像预期的那样运行,或者它是否在运行。为此,您将使用物理或虚拟设备。每个选项都有其利弊,你不必选择一个而不是另一个。事实上,你最终将不得不使用这两个选项。
Android 虚拟设备或 AVD 是一个仿真器,你可以在其中运行你的应用。在模拟器上运行有时会很慢——这就是谷歌和英特尔开发 HAXM 的原因。这是一个模拟器加速工具,让测试你的应用变得更容易忍受。这对开发者来说绝对是福音。也就是说,如果您使用的是支持虚拟化的英特尔处理器,并且您没有使用 Linux。但是,如果你不够幸运,没有落到馅饼的那一部分,也不用担心;在 Linux 中有很多方法可以实现模拟器加速,我们将在后面看到。
macOS 用户可能最容易拥有,因为 HAXM 是自动随 AS3 安装的。他们不需要做任何事情就可以得到它 AS3 安装程序会为他们做好准备。
Windows 用户可以通过以下方式获得 HAXM:
-
从
https://software.intel.com/en-us/android下载。像安装其他 Windows 软件一样安装它,双击它,然后按照提示进行操作。 -
或者,你可以通过 AS3 的 SDK 管理器获得 HAXM 这是推荐的方法。
对于 Linux 用户,推荐的软件是 KVM。KVM(基于内核的虚拟机)是一个用于 Linux 的虚拟化解决方案。它包含虚拟化扩展(英特尔 VT 或 AMD-V)。
要获得 KVM,我们需要从回购中提取一些软件。但是在做其他事情之前,你需要做两件事:
-
确保在 BIOS 或 UEFI 设置中启用了虚拟化。关于如何获得这些设置,请查阅您的硬件手册。它通常包括关闭电脑,重新启动电脑,并在听到系统扬声器的声音时按下中断键,如 F2 或 DEL,但正如我所说的,请查阅您的硬件手册。
-
一旦您完成了更改,并重新启动到 Linux,请检查您的系统是否可以运行虚拟化。这可以通过从终端
egrep –c '(vmx|svm)' /proc/cpuinfo运行以下命令来完成。如果结果是一个大于零的数字,这意味着您可以继续安装。
要安装 KVM,在终端窗口上键入命令,如清单 8-1 所示。
sudo apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
sudo adduser your_user_name kvm
sudo adduser your_user_name libvirtd
Listing 8-1Commands to Install KVM
您可能需要重新启动系统才能完成安装。
章节总结
-
Android 是完整的开发平台。它包括操作系统、应用框架、应用、软件开发套件、预构建的应用和参考设计
-
Android 的发布周期大约是 12 个月;我们每年都有新版本。
-
AS3 自动包含对 Kotlin 的支持。
-
仿真器的硬件加速是你可能想要研究的东西。它将在开发和测试过程中节省大量的等待时间。
下面是下一章的内容:
-
一个安卓应用里面有什么?我们将探索应用的组成部分;Android 称之为组件,有好几个。我们将逐一查看。
-
我们将创建我们的第一个项目。我们将逐步介绍如何在 Android Studio 中启动并运行一个简单的项目。
-
我们将构建一个仿真器——它是用来测试应用的。Android devs 称之为 AVD,是 Android 虚拟设备的简称。
-
我们将看看 Android Studio IDE 的某些部分。了解你的工具的各个角落总是好的。
九、入门指南
我们将介绍的内容:
-
Android 组件
-
创建项目
-
创建 android 虚拟设备
-
Android Studio IDE
android 中的应用与为桌面编写的应用不太一样。就外表而言,它们可能有一些惊人的相似之处,但在结构上却有很大的不同。EXE 文件包含应用需要的所有例程和子例程。有时它可能依赖于一些动态加载的库,但是可执行文件几乎是自包含的。Android 应用不完全是这样,它们由松散耦合的组件组成,这些组件使用 Android 平台特有的消息传递机制相互通信。
在这一章中,我们将仔细看看 Android 应用的内部。我们还将通过创建和运行一个示例应用来熟悉 Android Studio 3。最后,我们将简单浏览一下 Android Studio 3 IDE。
应用中有什么
android 应用不像 Windows 中的 EXE 文件那样是一个单一的包。它是一个松散组装的组件和其他资源的捆绑包,它们一起保存在一个 Android 包文件或 APK 中。图 9-1 显示了一个假想应用的逻辑结构。
图 9-1
什么构成了一个应用
图 9-1 中描述的应用是一个大型应用——它拥有一切,包括厨房水槽。你的应用不需要包括所有这些东西,就像我们这里假设的应用;但你的肯定会包括其中一些。
活动、服务、广播接收者和内容提供者被称为 Android 组件。它们是应用的关键组成部分。它们是对有用事物的高级抽象,如向用户显示屏幕、在后台运行任务、广播事件以便感兴趣的应用可以响应它们等。组件是具有非常特定行为的预编码或预构建的类,我们通过扩展它们在应用中使用它们,以便我们可以添加我们的应用特有的行为。
构建一个 Android 应用很像建造一座房子。有些人用传统方式建造房屋——他们组装横梁、支柱、地板等。他们像工匠一样,用原材质手工制作门和其他配件。如果我们以这种方式构建 android 应用,可能会花费我们很长时间,而且可能会非常困难。对于一些程序员来说,从头构建应用所需的技能可能遥不可及。在 Android 中,应用是使用组件构建的。把它想象成房子的预制构件。零件是预先制造好的,只需要组装就可以了。
一个活动是我们把用户可以看到的东西放在一起的地方。是用户可以做的专注的事情。例如,可以有目的地使用户能够查看单封电子邮件或填写表单。它是用户界面元素粘合在一起的地方。如图 9-1 所示,活动内部有视图和片段。视图是用于将内容绘制到屏幕中的类;视图对象的一些例子是按钮和文本视图。片段类似于活动,因为它也是一个组合单元,但是更小。像活动一样,它们也可以持有视图对象。大多数现代应用使用片段来解决在多种外形上部署应用的问题。片段可以根据可用的屏幕空间和/或方向打开或关闭。
服务是允许我们在不冻结用户界面的情况下运行程序逻辑的类。服务是在后台运行的代码;当你的应用需要从网上下载文件或者播放音乐时,它们会非常有用。
BroadcastReceivers 允许我们的应用监听来自 Android 系统或其他应用的特定消息——是的,我们的应用可以发送消息并在系统范围内广播。例如,如果您希望在电池电量下降到 10%以下时显示警告消息,您可能希望使用 BroadcastReceivers。
ContentProviders 允许我们创建能够与其他应用共享数据的应用。它管理对某种中央数据存储库的访问。有些内容提供商有自己的用户界面,但有些没有。您使用该组件的主要原因是允许其他应用访问您的应用的数据,而无需通过一些 SQL 技巧。数据库访问的细节对他们是完全隐藏的(客户端应用)。Android 中的“联系人”应用就是一个作为内容提供者的预建应用的例子。
您的应用可能需要一些视觉或听觉素材;这些就是我们在图 9-1 中所说的“资源”。
AndroidManifest 顾名思义——它是一个清单,并且是 XML 格式的。它声明了关于应用的一些事情,比如
-
应用的名称
-
当用户启动应用时,哪个活动将首先显示
-
app 里有什么样的组件。如果它有活动,清单会声明它们——类名和所有的名称。如果应用有服务,它们的类名也将在 manifest 中声明。
-
这个应用可以做哪些事情?它的权限是什么?允许上网还是相机?它能记录 GPS 位置吗?等等。
-
它使用外部库吗?
-
此应用将在哪个(些)版本的 Android 上运行?
如你所见,舱单是一个繁忙的地方,有很多事情要留意。不过这个文件不用太担心。AS3 的创建向导会自动处理这里的大部分条目。为数不多的几个与它交互的场合之一可能是当你需要给你的应用添加权限的时候。
组件激活
Android 热衷于松散耦合。应用只是由清单文件保存在一起的组件的集合。这些组件中的每一个都可以通过向其发送消息来激活。这种程序交互性的方法非常独特,因为它非常以用户为中心。它赋予用户很大的权力来选择如何操作和创建数据。
让我们举一个 Android 设备的常见使用场景。例如,用户打开“联系人”应用并选择 John Doe 的联系人详细资料。比方说,这个联系人可能有一个电子邮件地址、一部手机和一个 twitter 用户名。用户可以点击 John 的每一个联系点,每次,Android 都会启动一个不同的应用;默认的电子邮件客户端、拨号器和下载的 Twitter 应用。用户可能不关心启动了哪个应用或者当前打开了多少个应用;他只是想传达一个信息。如果这位用户不喜欢电子邮件应用或默认的 twitter 客户端,他可以删除这些应用,并用其他东西替换它们,他应该会回到业务中。
为了实现这种程序交互,Android 需要设计平台,重点关注松散耦合和可插拔性。像联系人应用这样的组件不应该知道当电子邮件地址或手机号码被窃听时应该使用什么应用的任何具体细节。用于特定类型数据的应用的解决方案不应硬连线到联系人应用中;否则,用户将无法在发送电子邮件或推文时选择使用哪个应用。
这就是意图的来源。当一个组件拥有超出其服务能力的数据或信息时,它可以使用 Intents 访问 Android 平台,并询问是否有任何应用能够(或想要)满足请求。有两种意图:隐含的和明确的。我们在电子邮件和 twitter 示例中讨论的意图被称为显式意图。我们将在后面的章节中更深入地探讨这一点。
Android Intents 是一种组件激活机制。它们是一种消息传递机制,如果您想要激活任何 Android 组件,无论是活动、服务、内容提供者还是广播接收者,都可以使用。要激活任何组件,您需要创建一个意图,并将其传递给想要激活的组件。在具有多个活动的应用中,意图用于将控制或焦点从一个活动切换到另一个活动。
创建项目
现在,我们对 Android 应用内部有了一些工作思路,让我们尝试创建一个示例项目并试用 ide。如果 AS3 还没有打开,就启动它。图 9-2 为 Android Studio 3 的欢迎画面。
点击“开始一个新的 Android Studio 项目”,如图 9-2 所示。检查一下你是否有互联网连接可能是个好主意。AS3 使用 Gradle 构建工具;当创建向导完成时,Gradle 将从 internet 存储库中提取几个文件。图 9-3 显示下一个屏幕。
图 9-2
AS3 欢迎屏幕
如图 9-3 所示,您需要填写一些关于项目的信息(例如,应用名称、公司域名和项目位置)。应用名称的默认值是“我的应用”;您可以保留默认值。
图 9-3
创建新项目
我填了公司域名;如果你愿意,你也可以。一般是你公司的网站。该信息将在项目中使用,并将成为反向 DNS 表示法中的包名。因此,我们的类将存储在名为 com.example.ted 的包中。
项目位置是 AS3 将存储项目的文件夹的位置。您也可以保留默认值。
启用“包含 Kotlin 支持”复选框很重要,因为我们将使用 Kotlin 作为编程语言。点击“下一步”
图 9-4 显示了下一个屏幕。在这里,您将被要求选择您的应用预期运行的 Android 版本。只勾选“手机和平板”,选择 API 23。
图 9-4
目标 Android 设备
图 9-5 显示下一个画面;可能会出现一个小弹出窗口,提醒您需要安装“即时应用”现在单击“否”。“即时应用”是 Google Play 的一项功能,允许用户在不安装应用的情况下使用或试用应用。如果用户喜欢这个应用,那么如果有必要,他们可以从应用商店购买。我们暂时完全忽略这一点。点击“下一步”
图 9-5
即时应用
在下一个屏幕上,如图 9-6 所示,我们被要求向应用添加一个活动。您有几个选择,但是对于我们的目的,选择“空活动”点击“下一步”
图 9-6
选择一项活动
项目创建向导的最后一个屏幕如图 9-7 所示。我们被要求填写活动名称和布局名称。我们将保留所有内容的默认值。点击“下一步”
图 9-7
配置活动
图 9-8 显示了我们在 AS3 主窗口中新创建的项目。在点击图 9-7 中的“下一步”按钮后,需要一段时间事情才会尘埃落定,因为 Gradle 工具会构建项目,当它试图这么做时,它会从存储库中提取相当多的文件。
图 9-8
带有打开项目的主 AS3
我们现在不会试图改变这个项目中的任何东西。我们的目标是简单地试用 AS3,熟悉项目创建的各个步骤。项目创建向导已经生成了一个带有几个视图的活动。我们测试的下一步是在模拟器中运行项目。为此,单击工具栏中的运行图标(图 9-8 中的圆圈)。
当您点击运行图标时,将出现“选择部署目标”屏幕,如图 9-9 所示。该屏幕显示所有运行的 Android 虚拟设备(avd)。它还显示了所有连接的物理 Android 设备,如果你插入了任何设备。
图 9-9
选择部署目标
如您所见,我已经创建了几个虚拟设备。在您的情况下,您可能看不到“可用虚拟设备”下的任何内容,因为您有一个全新的安装。单击“创建新的虚拟设备”
在图 9-10 中,您可以选择虚拟设备的外形。我选择了 Nexus 5x。点击“下一步”
图 9-10
选择硬件
图 9-11 显示了系统映像的选项。系统映像是我们可以在模拟器上运行的 Android 操作系统的副本。我们的项目是用 API 23(“棉花糖”)的目标 SDK 值创建的。选择高于 API 23 的系统映像是可以的,但是出于我们的目的,让我们实际下载 API 23 系统映像。
图 9-11
系统映像
点击中间标签“x86 Images”,如图 9-11 所示,使用 Google APIs 查找 API level 23,x86_64。单击“下载”链接。
图 9-12 显示了组件安装程序窗口,它显示了下载的进度。完成后,单击“完成”关闭窗口。
图 9-12
组件安装程序
我们再次回到系统图像窗口,如图 9-13 所示。您会注意到“棉花糖”标签旁边的“下载”链接不再可见,该行现在是可选的。当棉花糖行被选中时,点击“下一步”
图 9-13
系统映像
图 9-14 显示了创建 AVD 的最终配置屏幕。我将保留所有的默认值,包括 AVD 名称。点击“完成”
图 9-14
Android 虚拟设备
我们回到了“选择部署目标”屏幕(图 9-15 ),但这一次,我们新创建的 AVD (Nexus 5X API 23)显示在“可用虚拟设备”中选择我们刚刚创建的 AVD,然后单击“OK”
图 9-15
选择部署目标
AS3 可能会提示您安装“即时运行”,如图 9-16 所示。我们想安装这个,因为它将加快我们的开发时间。即时运行允许我们将代码更改推送到 AVD,而无需构建新的 APK。那会节省我们的时间。点按“安装并继续”
图 9-16
瞬间奔跑
AS3 将为应用创建 APK,并在之后立即将其推送到 AVD。完成后,您应该能够看到应用在 AVD 中运行,如图 9-17 所示。
图 9-17
Android 虚拟设备
IDE
让我们花点时间熟悉一下 IDE。在深入研究编码之前,最好先了解一些情况。Android Studio 基于 IntelliJ,我们在前面的章节中使用 IntelliJ 进行 Kotlin 研究,所以 AS3 应该看起来很熟悉。图 9-18 显示了一个打开项目的 AS3 IDE。
图 9-18
带有已打开项目的 AS3 IDE
编辑器窗口是最突出的窗口,拥有最多的屏幕空间。在编辑器窗口中,您可以创建和修改项目文件。它会根据您正在编辑的内容改变外观。如果您正在处理程序源文件,此窗口将只显示源文件。编辑布局文件时,您可能会看到原始 XML 文件或布局的可视化渲染。
Android Studio 中的每个项目都包含一个或多个带有源代码文件和资源文件的模块。模块的类型包括 Android 应用模块、库模块,有时还包括 Google 应用模块。AS3 默认在 Android 视图中显示项目文件,如图 9-18 所示。Android 视图由模块组成,提供对项目最相关文件的快速访问。您可以通过点击项目窗口顶部的向下箭头来改变查看项目文件的方式,如图 9-19 所示。
图 9-19
如何在项目窗口中切换视图
导航栏允许您浏览项目文件。这只是“项目文件”窗口的一个更紧凑的视图。这是一个水平排列的人字形集合,类似于一些网站上可以找到的面包屑导航。您可以通过导航栏或项目工具窗口打开项目文件。
工具栏允许您执行各种操作(例如,保存文件、运行应用、打开 AVD 管理器、打开 SDK 管理器、撤销、重做操作等)。).
工具窗口让您可以访问非常具体的任务(例如,查看项目文件、查看所有待办事项注释、查看 logcat 窗口、访问 profiler 等。).每个工具窗口都是可展开和可折叠的。你可以在需要的时候把它们打开,用完后把它们藏起来。
工具窗口栏沿着 IDE 窗口的周边运行。它包含激活特定工具窗口所需的各个按钮。
状态栏是 IDE 的一部分,显示你的项目和 AS3 本身的进展。它显示上下文相关的消息,如错误消息、正在运行的流程、存储库消息等。
炙单
Android Studio 提供了许多导航 IDE 的方式,但是主要的导航方式是主菜单。图 9-20 显示 AS3 主菜单;它位于 IDE 的顶部,提供了最完整的导航方式。它包含用于打开、创建项目、重构代码、运行和调试应用、将文件置于版本控制之下等等的命令。
图 9-20
Android Studio 的主菜单
快捷键
随着应用的增长,您可能想尝试一种更快的方式来导航 AS3。这里有一些键盘快捷键让你开始。
表 9-1
一些键盘快捷键
|工作
|
Linux 和 Windows
|
苹果
|
| --- | --- | --- |
| 在文件中搜索 | CTRL + F | ≤??] |
| 到处搜索 | CTRL + Shift + F | + F``+ F |
| 全部保存 | CTLR + S | ≤??] |
| 覆盖方法 | CTRL + O | CTRL + O |
| 实现方法 | CTRL + I | CTRL + I |
| 基本代码完成 | CTRL + Space | CTRL + Space |
| 建设 | CTRL + F9 | ≤??] |
| 构建并运行 | Shift + F10 | CTRL + R |
| 应用更改(通过即时运行) | CTRL + F10 | + R``+ R |
表 9-1 中显示的快捷键列表明显不完整。Android 开发者网站维护着一个页面,上面有 Android Studio 键盘快捷键的完整列表;你可以在这里找到 http://bit.ly/androidstudiokbshortcuts 。
AS3 主菜单中的某些操作或选项没有默认的键盘映射(例如,进入全屏视图)。在这种情况下,您可以将自己选择的键盘快捷键映射到菜单操作。您可以在 AS3 的键图设置中完成此操作。
要打开键图设置,从主菜单中选择文件 ➤ 设置(在 macOS 上, Android Studio ➤ 首选项,导航到键图窗格,如图 9-21 所示。
图 9-21
设置键盘映射
-
按键映射下拉菜单让您选择所需的按键映射,它在预设的按键映射之间切换。
-
动作列表。右键单击一个操作来修改它。您可以为动作添加附加的键盘快捷键,添加鼠标快捷键以将动作与鼠标单击相关联,或者移除当前快捷键。如果您正在使用预置的键映射,修改动作的快捷键将自动创建键映射的副本,并将您的修改添加到副本中。
-
您可以使用搜索框来搜索使用动作名称的快捷键。
-
通过快捷方式搜索。您可以在此搜索窗口中键入键盘快捷键来查找操作名称。
自定义代码样式
在同一个设置(MAC OS 中的首选项)窗口中,您还可以自定义编码风格和许多其他设置,如编辑器字体和配色方案等。
若要自定编码样式,请打开“偏好设置”窗口(如果它尚未打开)。在主菜单上点击文件 ➤ 设置(在 macOS 上, Android Studio ➤ 首选项)。代码样式窗口位于首选项窗口右侧的编辑器菜单下,如图 9-22 所示。
图 9-22
代码风格
现在,您可以随意调整编辑器。这些设置是不言自明的,只需根据你的喜好进行调整即可——或者,如果你在一个团队中工作,根据发布的编码风格指南来调整设置。
章节总结
-
Android 应用由松散组装的组件组成,这些组件由 AndroidManifest.xml 组合在一起。
-
您可以在 Android 清单文件中设置应用权限。
-
一个应用可能包含活动、服务、广播接收者和内容提供者等组件的组合。
-
组件使用意图相互通信。
在下一章,我们将开始研究如何用活动和布局来构建用户界面。我们将了解 Android 如何使用 XML 作为布局资源,以及这些 XML 资源如何在运行时使用名为 inflation 的过程转换并呈现为对象,等等。
十、活动和布局
我们将介绍的内容:
-
活动和布局
-
查看和查看组对象
-
活动生命周期
-
Kotlin Android 扩展
大多数程序需要一个入口点或一个开始例程,所有的执行都从那里开始。即使是前面例子中简单的“Hello World”也需要一个主函数作为入口点。安卓程序也一样,它也需要自己版本的“功能主”但是 Android 程序的入口点不仅仅是一个名为“main”的函数——它比这个函数要复杂一些。在这一章中,我们将探索一个基本应用的结构。我们将看看如何建立一个用户界面,并发现是什么让他们滴答作响。
应用入口点
一个简单的向用户显示屏幕的应用至少需要三样东西。它需要(1)一个充当主程序文件的活动类;(2)包含所有 UI 定义的布局文件;以及(3)清单文件,它将项目的所有内容联系在一起。如果你还记得使用 JavaBean 的 manifest 文件,Android manifest 有点像。它描述了项目的内容。
当应用启动时,Android 运行时会创建一个 Intent 对象并检查清单文件。它在寻找intent-filter节点的特定值;运行时试图查看应用是否有定义的入口点,类似于“主函数”清单 10-1 显示了清单文件的摘录。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Listing 10-1Excerpt from AndroidManifest.xml
清单 10-1 显示了一个活动的声明。如果应用有多个活动,你会看到几个定义,如清单 10-1——每个活动一个定义。定义的第一行有一个名为android:name的属性。该属性指向活动的类名。在这个例子中,类的名称是“MainActivity”
第二行声明了意图过滤器;当您在 intent-filter 节点上看到类似于android.intent.action.MAIN的内容时,这意味着该活动是应用的入口点。当应用启动时,这是将与用户交互的活动。
活动类别
主活动类负责与用户的初始交互。这是一个 Kotlin 类,在这个类中,我们可以并且经常做以下事情:
-
选择要使用的 UI 文件。当我们从活动内部调用
setLayout(xml:file)函数时,它会将活动绑定到 xml:file。这被称为“布局绑定”当活动绑定到布局时,屏幕上将充满用户可以触摸或滑动的用户界面元素。 -
获取视图对象的引用。视图对象也称为小部件或控件。当我们有一个对视图对象的编程引用时,我们可以操作它们,改变它们的属性,或者将它们与一个事件相关联。这被称为视图绑定。
Activity 类以某种方式继承了 android.app.Activity。在我们的例子中,它们继承自 AppCompatActivity 这是 FragmentActivity 的子元素,而后者又是 android.app.Activity 的子元素。我们使用 appcompactivity 类,这样我们就可以将工具栏等现代 UI 元素放在我们的项目中,并且仍然可以在旧版本的 android 上运行它们,否则工具栏将不受支持——因此,appcompactivity 的名称中有“Compat”。
当运行时启动一个最终会启动一个活动的应用时,它会创建并跟踪该活动发生了什么。每个活动都有一个非常完整的生命周期,每个生命周期事件都有一个关联的函数,我们可以用它来定制应用的行为。
图 10-1 显示了活动生命周期的各个阶段。每个方框显示了特定存在阶段的活动状态。函数调用的名称嵌入在连接阶段的方向箭头中。
图 10-1
活动生命周期
当运行时启动应用时,它调用主活动的onCreate()函数,这将使活动的状态变为“已创建”。您可以使用此函数执行初始化例程,如准备事件处理代码等。
活动将进行到下一个状态,即“已开始”;此时,用户可以看到活动,但是还不能进行交互。下一个状态是“恢复”;这是应用与用户交互的状态。
如果用户单击任何可能启动另一个活动的东西,运行时将暂停当前活动,并进入“暂停”状态。从那里,如果用户返回到活动,调用onResume()函数,活动再次运行。另一方面,如果用户决定打开一个不同的应用,运行时可能会“停止”并最终“破坏”该应用。
布局文件
布局文件包含以 XML 层次结构排列的视图对象。像按钮或文本字段这样的用户界面元素是在 XML 文件中编写的。一些人可能会害怕只使用 XML 编辑器手工编写 UI。但是你不必担心,因为 AS3 使得用户界面的构建变得很容易。我们可以在文本模式(手工编辑 XML)下使用布局文件,也可以在设计模式(WYSIWYG)下使用它。
图 10-2 显示了以两种可能模式显示的布局文件:文本模式和设计模式。您可以通过点击主编辑器窗口左下方的“文本”或“设计”选项卡来切换模式。当您通过编辑 XML 来更改元素时,AS3 会自动更新设计视图的呈现。同样,当您在设计视图中进行更改时,XML 文件也会更新。
图 10-2
以文本和设计模式显示的布局文件
清单 10-2 显示了一个典型的布局文件。如果您选择创建一个“空”活动,那么这就是项目创建向导将产生的结果。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Listing 10-2activity_main.xml
一个简单的布局文件通常有两个部分:一个容器的声明和其中每个 UI 元素的声明。在清单 10-2 中,第二行(也是 XML 文档的根)是容器的声明。TextView 元素被声明为容器的子节点。这就是容器和 UI 元素在布局文件中的排列方式。
查看和查看组对象
视图对象是一个组合单元。通过将一个或多个视图对象并排排列,或者有时相互嵌入,可以构建一个 UI。Android 库定义了两种视图,一个“视图”和一个“视图组”。按钮或文本字段就是视图对象的一个例子。这些对象旨在与其他视图一起组成,但它们并不包含子视图,而是独立存在的。另一方面,视图组可以包含子视图——这就是它们有时被称为容器的原因。
图 10-3 显示了一些更常见的 UI 元素的类层次结构。用户界面中的每一项都是 android.view.View 类的子类。我们可以使用 Android SDK 中预先构建的用户界面元素,如 TextView、Button、ProgressBars 等。或者,如果需要,我们可以构造自定义控件(窗口小部件或视图有时被称为“控件”),方法是:( 1)对现有元素(如 TextViews)进行子类化;(2)子类化视图类本身,完全从零开始绘制一个自定义小部件;或者(3)细分视图组并在其中嵌入其他小部件——这就是所谓的复合视图(图 10-3 中的单选按钮组就是一个例子)。
图 10-3
视图组类层次结构
每个视图对象最终都会在运行时变成一个 Java 对象,但是我们在设计时将它们作为 XML 元素来处理。我们不必担心 Android 如何将 XML 膨胀成 Java 对象,因为这个过程对我们来说是不可见的——它发生在幕后。图 10-4 显示了 Android 编译过程的逻辑表示。
图 10-4
Android 编译过程
Kotlin 编译器将程序源文件转换成 Java 字节码。产生的字节码与 Kotlin 标准库结合形成一个 DEX 文件。DEX 文件是 Dalvik 可执行文件——它是 Android 运行时(ART)理解的可执行文件格式。在 dex 文件和其他资源被打包到 Android 包(APK)之前,它还产生了一个名为“R.class”的特殊文件。我们使用 R.class 来获取对我们在布局文件中定义的 UI 元素的程序引用。
容器
除了创建复合视图,ViewGroup 类还有另一个用途。它们是布局管理器的基础。布局管理器是一个容器,负责控制子视图在屏幕上相对于容器和彼此的位置。Android 自带了几个预置的布局管理器。表 10-1 向我们展示了其中的一些。
表 10-1
布局经理
|布局管理器
|
描述
| | --- | --- | | 线形布局 | 根据选定的方向,将小工具定位在单行或单列中。可以为每个微件分配一个权重值,该权重值决定了该微件相对于其他微件所占用的空间量。 | | 表格布局 | 以行和列的网格格式排列小部件 | | 框架布局 | 将子视图堆叠在一起。XML 布局文件的最后一个条目是堆栈顶部的条目。 | | 相对布局 | 通过在每个视图上指定对齐方式和边距,可以相对于其他视图和容器定位视图。 | | 约束布局 | ConstraintLayout 是最新布局。它还相对于彼此和容器定位小部件(如 RelativeLayout)。但是它不仅仅使用对齐和边距来完成布局管理。它引入了“约束”对象的概念,将小部件锚定到目标上。这个目标可以是另一个小部件或容器;或者另一个定位点。这是我们将在本书中的大多数例子中使用的布局。 |
现在我们已经有了一些关于活动和布局的工作知识,让我们在下一节中在代码级别探索它们。
你好世界
让我们创建一个包含空活动的新应用。如果你想继续进行代码示例,项目信息如表 10-2 所示。
表 10-2
Hello 应用的项目信息
|项目明细
|
值
| | --- | --- | | 应用名称 | 你好 | | 公司域 | 使用您的网站名称 | | Kotlin 支架 | 是 | | 波形因数 | 仅限手机和平板电脑 | | 最低 SDK | API 23 棉花糖 | | 活动类型 | 空的 | | 活动名称 | 主要活动 | | 布局名称 | 活动 _ 主要 |
创建项目时,您会在项目窗口中看到一堆文件,但我们只对三个感兴趣。图 10-5 显示了(1)主程序文件的位置;(二)载货清单;以及(3)项目文件窗口中的主布局文件。
图 10-5
CH10Hello 项目
名为 activity_main.xml 的主布局文件位于 app ➤ res ➤布局文件夹中。所有用户界面元素都写在布局文件中。
主程序文件 MainActivity.kt 位于 app ➤ java ➤包名文件夹中。这是 Kotlin 文件,包含扩展 Android 活动的类。如果你想对用户生成的事件做出反应,这里就是我们编写程序逻辑的地方。不要被“java”文件夹所迷惑,所有的源文件,不管是 java 还是 Kotlin,都存储在“Java”文件夹中。没有“Kotlin”文件夹。
清单文件向 Android 构建工具描述了关于应用的基本信息:Android OS 和 Google play。看图 10-5 ,好像清单文件在 app ➤清单➤ AndroidManifest.xml 里。你需要记住,我们看到的是项目窗口的“Android 视图”。它是项目文件的逻辑表示,而不是文件相对于项目根文件夹的文字排列。如果想查看项目文件的实际位置,切换到“项目视图”,如图 10-6 所示。
图 10-6
CH10Hello,在项目视图中
项目视图显示了所有项目文件的实际位置。它看起来比“Android 视图”要忙得多,但是如果你需要定位项目下的任何文件,这个视图会很有用。现在我们可以回到“Android 视图”,这是我们将在本书的大部分内容中使用的。
让我们仔细看看生成的布局和 MainActivity 文件。代码分别显示在清单 10-3 和 10-4 中。
| -什么 | 布局文件的根节点,它也声明哪种布局管理器是有效的。在这种情况下,我们使用约束布局管理器 | | ➋ | TextView 对象的声明。它是布局管理器的子节点。 | | ➌ | 定义 TextView 对象的约束之一。它说,在文本视图的底部有一个锚点,它锚定在容器的底部。 |<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ➊
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView ➋
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent" ➌
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Listing 10-3activity_main.xml
package com.example.ted.ch10hello
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { ➊
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) ➋
}
Listing 10-4MainActivity.Kt
}
既然我们知道了项目向导给了我们什么,让我们对应用进行更改。
修改 Hello World
我们将对布局文件和活动进行一些小的修改。我们将执行以下操作:
-
更改当前 TextView 控件中的文本。
-
在屏幕上添加一个按钮,我们将把按钮放在文本视图的正下方。
-
向活动添加一个功能。该函数将递增 TextView 的当前值。
-
我们将把我们的新函数与按钮关联起来,这样我们每次单击按钮,TextView 的值都会增加 1。
图 10-7 显示了我们项目在 AS3 内部的总体布局。目前,我们正在设计模式下查看 activity_main.xml。在这种模式下,我们可以看到视图面板、设计图面和蓝图面。
图 10-7
设计视图中显示的 CH10Hello
要添加一个按钮控件,请将按钮从视图面板拖放到设计图面,如图 10-8 所示——您也可以将其拖放到蓝图图面中,这样也可以。
图 10-8
从视图选项板拖放控件
按钮控件还没有任何约束,因为我们没有把任何。向设计图面添加控件时,不会自动添加约束。TextView 有约束,因为它是在我们创建项目时由向导生成的。图 10-9 显示了我们当前项目的运行时和设计时再现。
图 10-9
无约束按钮
Hello TextView 很好地位于屏幕中央,因为它有四个锚点(约束)。该按钮在设计时出现在 Hello 文本的正下方,但在运行时,它位于屏幕的位置 0,0(左上角),这是控件在运行时没有约束时的定位方式。
让我们重新开始。移除设计图面中所有现有的约束。您可以通过选择所有控件并单击“清除约束”按钮来完成此操作,如图 10-10 所示。
图 10-10
清除约束
移除所有约束后,按照您希望控件在运行时出现的方式在设计图面上重新定位控件。接下来,再次选择所有控件,方法是在控件周围单击并拖动鼠标。
要“神奇地”为我们的控件添加所有约束,单击“推断约束”,如图 10-11 所示。AS3 将尽力猜测控件所需的约束,这些约束将与您在设计图面中的排列相匹配。
图 10-11
推断约束
可以在“属性”窗口中设置控件的属性。我们需要改变文本视图和按钮控件的一些属性。当在设计界面中选择一个对象时,该对象的属性会出现在属性窗口中,如图 10-12 所示。
图 10-12
属性窗口
“属性”窗口包含选定视图对象的所有属性,但默认情况下不会显示所有属性。它只显示我们常用的属性。要查看所有属性,点击“查看所有属性”按钮,如图 10-12 所示。
将 TextView 的“ID”属性改为“textHello”,如图 10-12 所示。接下来,将“textApperance”更改为“Material”。大”——您必须在属性窗口中向下滚动一点,这样才能看到“textApperance”属性。
视图对象的 ID 属性很重要,因为它使视图对象可以从我们的代码(Activity 类)中访问。
我们需要更改的下一个属性是按钮的 onClick 属性。选择按钮,然后找到“onClick”属性。您可能需要显示按钮的所有属性并向下滚动,直到看到 onClick 属性。
图 10-13
按钮的 onClick 属性
在按钮的 onClick 属性中键入“addNumber”,如图 10-13 所示。这个动作会将按钮的 click 事件关联到 MainActivity 类中的addNumber()函数。当然,我们还没有写函数,但是没关系,因为我们很快就会实现它。
我们已经完成了布局文件中的工作。现在我们可以在 MainActivity 类上工作了。打开主活动。Kt 并进行如下修改,如清单 10-5 所示。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.textHello).text = "1"
}
}
Listing 10-5MainActivity.Kt
这里没有惊喜。onCreate()函数中的最后一条语句获取对 textHello 对象的引用,并将 text 属性设置为“1”这已经是很大的进步了。请记住,在 Java 中,这条语句看起来像清单 10-6 。
TextView helloText = (TextView) findViewById(R.id.textHello);
helloText.setText("1")
Listing 10-6How to Set a Property During Runtime, in Java
在 Kotlin 中,我们得到了很好的 getter 和 setter 语法糖。但是我们仍然可以削减一些锅炉板代码。AS3 自动带有 Kotlin Android Extensions 插件,每当创建一个新项目时,它已经在模块级的“build.gradle”文件中声明。图 10-14 显示了 build.gradle 文件及其内容。
图 10-14
build.gradle,模块级
Gradle 已经取代 Apache Ant 成为构建工具。你通常不需要改变 gradle 文件中的任何内容,因为默认内容在大多数情况下都很好。
回到代码,清单 10-7 显示了 MainActivity 的完整程序。Kt,它实现了每当单击按钮时递增 textHello 值的逻辑。
| -什么 | 该语句导入了 Kotlin Android 扩展。您可能不需要自己输入——AS3 会在尝试使用视图对象的 ID 进行视图绑定时自动添加它。 | | ➋ | 我们不用再用`findViewById()`了;我们甚至不必使用 R.class 来限定视图对象的 ID。Android Kotlin 扩展将视图暴露给我们的代码,少了很多仪式。这将产生更加清晰的代码。还要注意,我们得到了 Kotlin 添加的良好的 getter 和 setter 语法。 | | ➌ | `addNumber()`功能与按钮控件的 *onClick* 事件相关联。该函数是一个*事件处理程序*——当点击按钮时,该函数将被调用。它需要接受一个`View`对象作为参数,因为这是事件处理程序的要求。该函数需要能够访问引发事件的视图对象。 | | -你好 | `textHello.text`以 CharSequence 类型返回 textHello 的当前值。`toString()`将其转换为字符串类型,我们可以使用`toInt()`函数将其转换为 Int 类型。我们需要 Int 形式的值,因为我们将在数学运算中使用它。 | | ➎ | 该语句将 textHello 的 text 属性设置为一个新值。 |import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.* ➊
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textHello.text = "1" ➋
}
fun addNumber(v: View) { ➌
val currVal = textHello.text.toString().toInt() ➍
val nextVal = currVal + 1
textHello.text = nextVal.toString() ➎
}
}
Listing 10-7MainActivity.Kt
完成编辑后,在 AVD 上运行应用。图 10-15 显示了在仿真器上运行的项目。
图 10-15
运行在模拟器上的 CH10Hello
章节总结
-
Android 应用的入口点需要三个文件:清单文件、布局文件和活动类
-
AndroidManifest 文件声明了 Android 项目的所有内容。清单可以指定一个活动类作为应用的入口点。
-
布局文件描述了屏幕的 UI 结构。每个元素都被描述为一个 XML 节点,但是 XML 文件在运行时是膨胀的。膨胀过程产生 UI 元素的 Java 对象表示。
-
所有 UI 元素都继承自 android.view.View 类。
-
复合视图可以通过从 ViewGroup 类继承来构造。
-
布局管理器提供了在屏幕上排列 UI 元素的方法。Android SDK 有大量预置的管理器,我们可以开箱即用。
-
Kotlin Android 扩展允许我们通过公开视图元素的属性和功能来简化视图绑定代码。我们不再需要使用 findViewById。
在下一章中,我们将学习如何:
-
使用一些基本的视图元素,如按钮和吐司
-
使用 Kotlin 的 Android 扩展获取视图对象的引用;它取代了黄油刀
-
处理点击和长点击;我们将使用对象表达式的完整语法来完成长格式,并使用 lambda 表达式来完成捷径
十一、事件处理
我们将介绍的内容:
-
监听器对象
-
匿名内部对象
-
在事件处理程序中使用 lambdas
在上一章中,我们已经做了一些事件处理。我们编写了一个函数,它会在每次单击按钮时增加文本视图的值,这部分练习是关于声明性事件处理的。要将函数名绑定到点击事件,我们只需将视图的 android:onClick 属性设置为函数名。这是一种简单的处理事件的方式,但是仅限于“click”事件。当你需要处理像长点击或手势这样的事件时,你需要使用事件监听器——这是本章的主题。
事件处理简介
用户通过触摸、点击、滑动或输入东西来与你的应用进行交互。Android 框架捕获、存储、处理这些动作,并将其作为事件对象发送给你的应用。我们可以通过编写专门用于处理这些事件的函数来响应这些事件。处理事件的函数写在监听器对象里面——而且有相当多的这样的函数。图 11-1 显示了 Android 框架和你的应用如何处理用户动作的简化模型。
图 11-1
简化的事件处理模型
当用户用你的应用做一些事情时,比如点击一个按钮,Android 框架捕捉这个动作,并把它变成一个事件对象。事件对象包含关于用户动作的数据(例如,点击了哪个按钮,按钮被点击时的位置,等等)。)Android 将此事件对象发送到您的应用,并调用与用户动作相对应的特定函数。如果用户点击了按钮,Android 将调用按钮对象上的onClick()函数,如果用户点击了同一个按钮,但按住时间稍长,则调用onLongClick()函数。像按钮一样,视图对象可以响应一系列事件,如点击、按键、触摸或滑动等。表 11-1 列出了一些常见事件及其对应的事件处理程序。
表 11-1
通用监听器对象
|连接
|
功能
|
描述
|
| --- | --- | --- |
| View.OnClickListener | onClick() | 当用户触摸并按住控件时(在触摸模式下),或者用导航键聚焦在项目上然后按 ENTER 键时,调用此函数 |
| View.OnLongClickListener | onLongClick() | 几乎和点击一样,但时间更长 |
| View.OnFocusChangeListener | onFocusChange() | 当用户导航到控件上或离开控件时 |
| View.OnTouchListener | onTouch() | 几乎和点击动作一样,但是这个处理程序让你发现用户是向上还是向下滑动。你可以用这个来回应手势 |
| View.OnCreateContextMenuListener | onCreateContextMenu() | 当一个上下文菜单被构建时,Android 调用这个,作为一个持续长时间点击的结果 |
为了设置一个监听器,View 对象可以设置或者更恰当地说,注册一个监听器对象。注册一个监听器意味着当用户与视图对象交互时,你告诉 Android 框架调用哪个函数。图 11-2 显示了注册处理程序的注释代码。
图 11-2
带注释的事件注册和处理代码
setOnClickListener 是 android.view.view 类的成员函数,这意味着 View 的每个子类都有它。这个函数需要一个 OnClickListener 对象作为参数——这个对象成为按钮控件的监听器。点击按钮时,运行 onClick 函数内的代码。
我们通过创建一个从视图继承的对象表达式来创建监听器对象。OnClickListener 。该类型在视图类中被声明为嵌套接口。对象表达式是 Java 的匿名内部类的 Kotlin 等价物。在 Java 中,我们编写了类似清单 11-1 中 thseat 的代码。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.out.println("Hello click");
}
}});
Listing 11-1onClick Listener in Java
在 Kotlin 中,使用对象表达式创建一个匿名内部类,如清单 11-2 所示。
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
println("Hello click")
}
})
Listing 11-2onClick Listener in Kotlin
清单 11-2 实际上是一种编写对象表达式的冗长方式。Kotlin 对 lambdas 的支持可以简化我们现有的代码,如清单 11-3 所示。
button.setOnClickListener {
println("Hello")
}
Listing 11-3onClick Listener Using lambdas
现在我们已经有了足够的关于事件的工作知识,让我们通过创建一个新项目来进一步探索它们。表 11-2 显示了项目详情。
表 11-2
CH11EventAnonymousclass 类的项目信息
|项目详细信息
|
价值
| | --- | --- | | 应用名称 | ch11 事件匿名类 | | 公司域 | 使用您的网站名称 | | Kotlin 支架 | 是 | | 波形因数 | 仅限手机和平板电脑 | | 最低 SDK | API 23 棉花糖 | | 活动类型 | 空的 | | 活动名称 | 主要活动 | | 布局名称 | 活动 _ 主要 | | 向后兼容性 | 是的。应用兼容性 |
这个项目将只包含两个控件:一个是当我们使用向导时项目附带的 TextView,另一个是我们还没有添加的 Button view。这个按钮将使用一个匿名的内部对象拦截点击和长时间点击事件。
在主编辑器中打开 activity_main.xml 文件,如果它还没有打开的话。你可以在 app > res > layout 文件夹下的项目浏览器窗口中找到。
向设计图面添加一个按钮,并向其添加一些约束。您可以通过将按钮控件从组件面板中拖动到设计图面上来将它添加到布局中,如图 11-3 所示。
图 11-3
向设计图面添加一个 button 控件
当按钮控件被选中时,点击约束工具栏上的“推断约束”(如图 11-3 )。
您可能会注意到在布局编辑器的右上角有一个黄色的警告三角形(如图 11-4 所示)。单击警告框。
图 11-4
显示警告和错误按钮
图 11-5 显示消息工具窗口。它包含了一些关于为什么我们会得到警告的解释,以及一个建议修复的按钮提示。
图 11-5
建议的修复
AS3 抱怨是因为新添加的按钮在其文本属性中有一个硬编码的值。清单 11-4 显示了“修复”之前的 activity_main.xml(的一个片段)现在, android:text 属性有一个值“Button”,一个字符串文字。
<Button
android:id="@+id/button"
android:text="Button"
/>
Listing 11-4activity_main.xml, Button Element, Before the Fix
Androids 更喜欢我们在资源文件中编写属性值,比如按钮的文本属性,而不是硬编码。单击“修复”按钮,这样 AS3 可以自动提取字符串资源。该操作打开提取资源窗口(参见图 11-6 )。
图 11-6
提取资源
我们的项目在app/RES/values/strings . XML中有一个字符串资源文件。它为应用提供文本资源值。Android 希望我们在这个资源文件中存储所有的字符串文字,而不是像你在清单 11-4 中看到的那样硬编码它们。
“资源名称”成为新创建的字符串资源的“名称”属性,“资源值”成为字符串资源的值。该值将显示在按钮的文本中。单击“确定”完成操作。
清单 11-5 显示了修复后 activity_main.xml 的内容。android:text 的值现在设置为“@string/button。”@符号意味着我们不应该直接使用这个字符串的值,而应该在字符串资源文件中查找一个名为“button”的资源。
<Button
android:id="@+id/button"
android:text="@string/button"
/>
Listing 11-5activity_main.xml, Button Element, After the Fix
我们需要在布局文件上做的最后一件事是给布局容器分配一个 id 属性。默认情况下,布局容器没有 id 属性。我们需要给它分配一个 id,因为我们将在后面的代码中引用它。切换到设计模式,点击布局容器内的某处(如图 11-7 )。在属性面板中,编辑 id 属性。在这个例子中,布局容器的 id 是“root_layout”
图 11-7
更改布局容器的 id 属性
清单 11-6 显示了我们布局文件的修改内容。
| -什么 | 布局容器的 android:id 现在被设置为`+@id/root_layout`。在后面的代码中,我们可以将这个控件称为`root_layout .` | | ➋ | android:text 属性现在有一个值`@string/button;`,它不再是硬编码的。它现在从 strings.xml 资源文件中获取其值。 |<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout" ➊
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.353" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="36dp"
android:text="@string/button" ➋
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</android.support.constraint.ConstraintLayout>
Listing 11-6Complete Listing for activity_main.xml
现在我们可以处理程序文件了。打开主活动。Kt 在主编辑器中。您可以通过双击文件 app/Java/com . example…/main activity 来启动它。项目窗口中的 Kt。
我们希望按钮对点击和长时间点击都有反应。为此,我们需要为同一个按钮设置两个独立的侦听器——我们可以创建两个按钮,并为每个按钮分配一个侦听器,但是我觉得如果我们将两个侦听器绑定到同一个按钮,这个练习会更有指导意义。
在我们设置侦听器之前,活动不需要对用户可见;它只需要处于“已创建”状态。这就是为什么我们要在onCreate()回调函数中设置监听器。让我们先处理点击事件,然后我们将处理长时间点击。清单 11-7 显示了 OnClickListener 的代码。
button.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
}
})
Listing 11-7
OnClickListener
顺便说一下,当您键入这些代码时,您可能会看到一些错误或警告,如图 11-8 所示。
图 11-8
AS3 提示
在图 11-8 中,AS3 警告了一个未解析的“按钮”引用。要修复这个错误,我们可以手动键入所需的导入语句,或者使用 AS3 的“快速修复”功能。要使用快速修复,单击未解析引用中的任意位置——在我们的例子中是“按钮”标识符——然后按下键 OPTION + ENTER (如果你在 macOS 上);如果你在 Windows 或 Linux 上,按 ALT + ENTER 。
如果有多种解决问题的方法,AS3 将提供一些选项。您可以滚动选项并选择您想要使用的选项。
图 11-9 显示了如何修复未解决的参考误差的选项。我们将选择最后一个选项—这个导入语句是 Kotlin Android Extensions (KAE)。KAE 的神奇之处在于,它将布局中所有视图元素的 id 作为 Activity 类的扩展属性公开。因此,如果您在 activity_main.xml 中有一个 ID 为“Button”的按钮视图,您可以像使用常规变量一样在 activity 类中简单地使用该 ID——您不再需要使用 findViewById() 。
图 11-9
AS3 暗示导入
一旦你输入了清单 11-7 和图 11-10 中所示的事件处理程序,你会注意到 AS3 在提示我们将 listener 对象转换成 lambda 表达式。
图 11-10
转换为 lambda 提示
要使用快速修复,单击“OnClickListener”中的任意位置,如图 11-11 所示,按 OPTION + ENTER 或 ALT + ENTER,然后选择“转换为 lambda”
图 11-11
转换为 lambda 快速修复
lambda 简化版删除了我们的一些代码 setOnClickListener 的括号、对象表达式和被覆盖的 onClick 函数都被删除了,留给我们的只有下面的代码:
button.setOnClickListener { }
接下来要做的事情是在 onClick 处理程序中放置一个 Toast 消息。清单 11-8 显示了点击处理程序中的一个简单的 Toast 消息。祝酒词是一个小的弹出消息,一段时间后会自动消失。您可以使用它向用户发送小的反馈消息。清单 11-8 展示了如何在 OnClickListener 中构造一个 Toast 消息。
button.setOnClickListener {
Toast.makeText(this, "Hello World", Toast.LENGTH_LONG).show()
}
Listing 11-8
Toast Message
显示祝酒词的过程分为两步。第一步是使用 makeText() 函数创建一条 Toast 消息。它有三个参数:(1)应用的上下文,在我们的例子中是 MainActivity 的实例;(2)消息显示;以及(3)显示消息多长时间。第二步是通过调用。**显示()**功能。
让我们转到长点击监听器。这个监听器的代码如清单 11-9 所示。
button.setOnLongClickListener(object: View.OnLongClickListener {
override fun onLongClick(v: View?): Boolean {
return true
}
})
Listing 11-9
OnLongClickListener
将清单 11-9 中的代码简化为它的 lambda 版本,得到如下代码:
button.setOnLongClickListener { true }
为了测试长点击处理程序,让我们使用 SnackBar 而不是 Toast。SnackBar 类似于 Toast,但它出现在屏幕的底部。你也可以让它在超时后消失,比如祝酒,或者你可以让用户滑动它。SnackBar 比 Toast 功能更强大,因为您可以在消息中包含一些操作,比如一个小对话框。
在项目中使用 SnackBar 之前,需要修改项目的 build.gradle 文件。请参见清单 11-10 了解您需要做出的更改。
| -什么 | 在使用 SnackBar 之前,您需要将它添加到项目的 **build.gradle** 文件(应用级别)中。 |dependencies {
implementation 'com.android.support:design:27.1.1' ➊
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
Listing 11-10/app/build.gradle
之后,您需要“同步”gradle 文件。主编辑器的上部会出现一个黄色条,右上角会有一个“同步”文件的链接。点击它,如图 11-12 所示。
图 11-12
同步 build.gradle 文件
之后,您现在可以使用 SnackBar 元素了。清单 11-11 展示了如何在长点击处理程序中构建一个 SnackBar。
button.setOnLongClickListener {
Snackbar.make(root_layout, "Long click", Snackbar.LENGTH_LONG).show()
true
}
Listing 11-11SnackBar Message Inside OnLongClickListener
SnackBar 的 make 函数需要三个参数:(1)一个父视图;root_layout 是我们的布局容器的 ID;(2)要显示的消息;以及(3)显示消息多长时间。
OnLongClickListener 中的最后一行实际上是一个 return 语句,但是我们省略了“return ”,因为处理程序是 lambda 形式的——在这种形式中,返回块上的最后一个表达式。
onLongClick()回调函数有一个布尔签名—它返回 true 或 false。在我们的例子中,我们返回了 true,这告诉 Android 运行时事件已经被消费了,不需要其他事件处理程序(如 onClick)再次处理它。如果我们返回 false,onClick 处理程序会在 onLongClick 之后立即启动。清单 11-12 显示了 MainActivity 的完整代码。
| -什么 | 我们项目的包声明。这来自项目创建期间的“公司域”条目。 | | ➋ | Kotlin Android 扩展(KAE)的导入声明。KAE 将 activity_main.xml 中的所有视图元素转换为扩展属性。因此,我们可以只使用 ID 来引用任何视图元素。 | | ➌ | 我们从 AppCompatActivity 扩展而来,因此我们可以使用 SnackBar 等现代元素,同时仍然可以在早期版本的 Android 上运行该应用。 | | -你好 | 该语句将 MainActivity 绑定到我们的布局文件 activity_main.xml。 |package com.example.ted.ch11_event_anonymous_class ➊
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.test.ViewAsserts
import android.view.View
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.* ➋
class MainActivity : AppCompatActivity() { ➌
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) ➍
button.setOnClickListener {
Toast.makeText(this, "Hello World", Toast.LENGTH_LONG).show()
}
button.setOnLongClickListener {
Snackbar.make(root_layout, "Long click", Snackbar.LENGTH_LONG).show()
true
}
}
}
Listing 11-12MainActivity.Kt, Annotated
如果你在模拟器上运行应用,你会看到类似图 11-13 的东西。
图 11-13
在模拟器中运行的已完成项目
章节总结
-
如果想处理简单的点击事件,可以将 android:onClick 属性设置为一个函数名。
-
如果您想要拦截某些事件,监听器对象必须注册到 Android 运行时。
-
侦听器对象有很多种,它们在 View 类中被列为嵌套接口。
-
使用 Kotlin Android 扩展简化了我们的编码。它将布局文件中所有视图的 Id 作为 MainActivity 的扩展属性公开——我们不再需要使用 findViewById() 了。
-
Lambdas 清理我们的事件处理代码。
在下一章,我们将看看 Android 最重要的部分之一:意图。Android 作为一个架构,没有它就无法存在。它是将 Android 中所有松散耦合的组件结合在一起的粘合剂。
十二、意图
我们将介绍的内容:
-
意向概述
-
显性和隐性意图
-
在活动之间传递数据
-
从意图返回结果
Android 的架构在构建应用的方式上非常独特。它有组件的概念,而不仅仅是简单的对象。而安卓让这些组件交互的方式,是只有安卓平台才有的。Android 使用意图作为其组件通信的方式——它使用意图在组件之间传递消息。在这一章中,我们将看看意图:它们是什么以及我们如何使用它们。
意图是什么
意图是“对要执行的操作的抽象描述”。 1 “这是 Android 独有的概念,因为没有其他平台使用相同的东西作为组件激活的手段。在前几章中,我们看了 Android 应用的内部。你可能记得一个应用只是一堆松散地组合在一起的“组件”(见图 12-1 ),每个组件都在一个清单文件中声明。
图 12-1
一个 安卓应用的逻辑表示
如果您需要您的组件相互通信(例如,启动另一个活动),该怎么办?你认为我们应该如何处理?如果你有桌面编程的经验,你可以做一些类似清单 12-1 中的代码。
class MainActivity : AppCompatActivity {
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
SecondActivity(). // Won't work
}
})
}
class SecondActivity : AppCompatActivity {}
Listing 12-1Wrong Way to Activate Another Activity
清单 12-1 可能看起来是启动另一个活动的简单而直接的方法,但不幸的是,它是错误的,不会起作用。活动不是一个简单的对象,而是一个组件。不能仅仅通过实例化来激活组件。要启动一个活动,您需要创建一个 Intent 对象,并使用 startActivity()函数启动它。代码如清单 12-2 所示。
| -什么 | **this @ main activity**Intent 构造函数的第一个参数是一个`Context`对象。我们传递了`this@MainActivity`,因为 Activity 类是`Context`的子类,所以我们可以使用它。或者,我们也可以使用`getApplicationContext()` `;`,应用上下文也可以被接受。 | | ➋ | **第二个活动::class.java** 。第二个参数是一个`Class`对象。它是我们想要向其传递消息的组件的类。这是一个*反射*语法。你可能已经知道,*反射*允许我们在运行时检查程序的结构。如果 SecondActivity 是一个 Kotlin 类(KClass ),它将引用 second activity 的运行时引用,但它不是。SecondActivity 是一个 Java 类(Android 库仍然是 Java 的),因此我们称它为`SecondActivity::class.java` | | ➌ | 我们通过调用`startActivity()`并向其传递 intent 对象来启动活动。 |button.setOnClickListener {
val intent = Intent(this@MainActivity, SecondActivity::class.java) ➊ ➋
startActivity(intent) ➌
}
Listing 12-2How to Activate Another Activity
Android 平台对松散耦合非常狂热,组件激活在它的架构中受到了影响。应用只是由清单文件保存在一起的组件的集合,每个组件都可以通过向其发送消息来激活。基本思想是,组件之间不直接对话。如果一个组件,比如一个活动,想要与另一个组件对话,它需要向 Android 运行时发送一个请求,并让运行时解析这个请求。您可以将意图视为 Android 中的消息传递机制:它将组件粘合在一起。
松耦合
你可能会认为 Android 的架构过于复杂,因为为什么要花这么大力气去推出另一个屏幕呢?为什么我们不能只创建一个对象的实例就完事了呢——这已经是众所周知的编程习惯了。为什么我们要用组件激活来代替它呢?
嗯,Android 的程序交互性方法非常独特,因为它非常以用户为中心。它赋予用户很大的权力来选择如何操作和创建数据。移动用户一般以任务为中心,而不是以 app 为中心;他们并不真正关心哪个应用做什么,只要它能完成。
让我们举一个 Android 设备的常见使用场景。例如,用户打开“联系人”应用并选择 Ted Hagos 的联系人详细资料。比方说,这个联系人可能有一个电子邮件地址、一部手机和一个 twitter 用户名。用户可以点击 Ted 的每个联系点,每一次,Android 都会启动一个不同的应用;默认的电子邮件客户端、拨号器和下载的 Twitter 应用。用户可能不关心启动了哪个应用或者当前打开了多少个应用;他只是想传达一个信息。如果这位用户不喜欢电子邮件应用或默认的 twitter 客户端,他可以删除这些应用,并用其他东西替换它们,他应该会回到业务中。图 12-2 显示了一个使用联系人应用的简单故事板。
图 12-2
用户如何与“通讯录”应用交互
为了实现这种程序交互,Android 需要设计平台,重点关注松散耦合和可插拔性。像联系人应用这样的组件,不应该知道当电子邮件地址或手机号码被点击时应该使用什么应用的任何具体细节。用于特定类型数据的应用的解决方案不应硬连线到联系人应用中;否则,用户将无法在发送电子邮件或推文时选择使用哪个应用。
这就是意图的来源。当一个组件需要完成一项超出其服务能力的任务时,它可以使用 Intents 访问 Android 平台,并询问是否有任何应用可以(或想要)满足该请求。
两种意图
有两种意图:隐含的和明确的。打个比方可能有助于说明这两种意图之间的区别。比方说,我们将请人买些糖。如果我们给出一个指令,比如“你能买些糖吗”,没有进一步的细节,这就相当于一个隐含的意图,因为那个人可以在任何地方买糖。另一方面,如果我们给出类似“请你去第三街的 ABC 商店买些糖”的指示,这就相当于一个明确的意图。清单 12-2 中的代码示例是一个显式意图的例子。
隐式意图非常强大,因为它们允许您的应用利用其他应用。你的应用可以获得不是你自己编写的功能。例如,您可以创建一个打开相机、拍摄和保存照片的意图,而无需编写任何特定于相机的代码。
意图可以携带数据
意图可以做的远不止发起其他活动;你也可以用它发送和接收数据。假设我们有两个名为 MainActivity 和 SecondActivity 的活动,当在 MainActivity 中单击一个按钮视图对象时,我们希望启动并向 SecondActivity 发送一些数据。要向 SecondActivity 发送数据,您需要:
-
创建一个意图——在我们的例子中,它是一个明确的意图。
-
使用 putExtra 方法将数据添加到意图中。
-
通过调用 startActivity 方法启动另一个活动;此时,Android 运行时将启动 SecondActivity。
-
在 SecondActivity 的 onCreate 方法中,我们可以通过使用 getExtra 方法从意图中提取数据。
图 12-3 显示了这一切是如何工作的简单序列图。
图 12-3
如何向另一个活动发送数据
注意
Android 中的大多数函数调用像 startActivity 、 onCreate 等。是异步的——这就是为什么序列图中使用的箭头是半杆箭头。图 12-3 (以及其他序列图中)所示的调用顺序只是近似值,它们实际上可能不会按照那个顺序发生。
为了用代码表示这些步骤,它可能看起来像清单 12-3 。
button.setOnClickListener {
val intent = Intent(this@MainActivity, SecondActivity::class.java)
intent.putExtra("main_activity_data", editText.text.toString())
startActivity(intent)
}
Listing 12-3Code Snippet from MainActivity
putExtra 方法的参数是一个键值对;第一个参数是键或名称,第二个参数是值。name 参数将总是字符串类型,但第二个参数(值)可能不总是字符串类型。 putExtra 方法是重载的,它可以接受第二个参数的类型范围。如果你在 Android Studio 中输入得足够慢,你可能会在输入 putExtra 方法时看到代码提示中显示的选项;见图 12-4 。
图 12-4
AS3 中的代码提示显示了重载的 putExtra()
在清单 12-3 中,我们在 **putExtra 的第二个参数中放了一个字符串;**我们也可以使用其他类型(例如, Int、Byte、Char、Float、Short、等基本类型。).我们也可以使用包、Parcelables、或序列化。
在调用了意图上的 putExtra 方法之后,下一步是调用 startActivity 。这将触发 Android 运行时的意图解析机制,并最终启动第二个活动。
现在我们继续进行第二项活动。很自然,你想从 *MainActivity 中提取我们发送的数据。*要做到这一点,你需要做两件事。您需要:
-
获取对意图对象的引用;和
-
从意图中调用 getExtra 函数。该代码可能如下所示:
val myintent = getIntent()
val data = myintent.getStringExtra("main_activity_data")
但是由于 Kotlin 的 getters 和 setters 的魔力,getIntent()函数变成了 intent 属性。所以,我们可以这样重写:
val data = intent.getStringExtra("main_activity_data")
从另一个活动中获取结果
在上一节中,我们设法启动了第二个活动并向其发送数据。在这一节中,我们将基于前面的例子,但是这一次,我们也将一些数据发送回 MainActivity 。为此,我们需要:
-
创建一个明确的意图。
-
使用 putExtra 方法将数据添加到意图中。
-
通过调用 startActivityForResult 方法启动另一个活动。像 startActivity 方法一样,我们需要将一个 Intent 对象作为参数传递给这个方法。此外,我们还需要传递一个请求代码给它。一个请求代码充当某种令牌。当我们开始一个活动并期望返回一些结果时,任何其他活动都可以返回任何结果。如果我们在一个项目中有几个活动,当我们得到结果时,可能会感到困惑。我们需要一种方法来跟踪谁发送回这些结果,而请求代码将帮助我们做到这一点。一旦我们调用 startActivityForResult,第二个活动就会启动。
-
在 SecondActivity 的 onCreate 方法中,我们可以通过使用 getExtra 方法从意图中提取数据。
-
我们可以在 SecondActivity 中做一些计算。当我们准备好发回数据时,我们将执行以下操作:
-
获取对意图对象的引用。
-
使用 putExtra 方法将数据添加到意图中。
-
调用 SecondActivity 的 setResult 方法。在这里我们需要做两件事情:(1)设置意向呼叫的状态,如果没有错误,您可以将其设置为活动。结果 _ OK;以及(2)将包含额外内容的意图对象作为第二个参数传递。
-
从 SecondActivity 内部调用 finish() 。这将停止 SecondActivity,并有效地将意图发送给名为 SecondActivity 的组件,即 MainActivity
-
-
回到 MainActivity,无论我们期望从 second Activity——或者任何其他活动——返回什么结果,都可以从 onActivityResult 回调中接收。这个方法的参数中有三样东西:它有请求代码、结果代码和由 SecondActivity 发回的 Intent 对象。
图 12-5 显示了如何从另一个活动发送和获取结果的序列图。
图 12-5
从另一个活动获取结果的序列图
当您向另一个活动发送数据并希望获得一些数据时,您需要使用 startActivityForResult 而不是 startActivity 。这样做的代码如下所示:
startActivityForResult(intent, SECOND_ACTIVITY)
和 startActivity 一样,你将 Intent 对象传递给 startActivityForResult ,除了 Intent 对象,你还需要传递一个请求代码 ( SECOND_ACTIVITY)。这个请求代码对于 MainActivity 非常重要,因为我们将使用它来跟踪从谁那里获取数据。请求代码是一个您需要定义的 Int 。只要您有多个请求代码,每个代码都是不同的,那么您将使用什么号码并不重要。如果您发送数据并期望从几个活动返回数据,您将使用请求代码来跟踪哪些其他活动正在向您发送数据。这样,当结果出来的时候,我们就可以知道我们最初想做什么。
在 SecondActivity 中,当我们准备好发回数据时,我们需要创建另一个 Intent 对象,并使用 putExtra 方法加载数据。之后,我们调用 SecondActivity 的 setResult 方法。setResult 方法有两个参数:一个结果代码和 Intent 对象。如果应用一切正常,使用活动。结果 _ OK;否则使用活动。结果 _ 取消。RESULT_OK 实际上是-1,RESULT_CANCELLED 是 0,但是请不要使用 Int 文字,总是使用提供的类常量。
当您在 SecondActivity 上调用 finish 方法时,它将进入停止状态,MainActivity 将再次出现在前台—因此,它将重启并且恢复。无论 SecondActivity 发送回什么数据,我们都应该能够在 MainActivity 的 onActivityResult 回调中获得它。清单 12-4 显示了一个典型的被覆盖的 onActivityResult 回调。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if((requestCode == SECOND_ACTIVITY) and (resultCode == Activity.RESULT_OK)) {
// extract data here
}
}
Listing 12-4
onActivityResult
注意
您如何知道何时应该覆盖 onActivityResult 回调?如果您使用 startActivityForResult 启动另一个活动,您应该覆盖 onActivityResult 回调——在这里您可以获得发送给您的任何数据。
隐含的意图
我们在前面几节中看到的都是明确意图的例子。明确的意图告诉 Android 运行时精确地激活哪个组件。回到我们的类比,这就像告诉某人去 3 rd 街的杂货店买些糖。另一方面,一个隐含的意图只是简单地给出了“得到一些糖”的指令——在哪里或如何得到糖并不重要。隐含的意图只规定了行动。
当你使用一个隐含的意图时,一般的想法是你想使用一个不存在于你的应用中的功能——如果它存在于你的应用中,你会首先使用一个明确的意图;—因此,您要求 Android 运行时在设备上的某个地方找到一个应用来满足您的请求。
从前面的例子中我们知道,意图可以携带数据;我们用临时演员做了这件事。额外的东西是一个意图可以拥有的四样东西之一;另外三个是动作、数据、和类别。动作是您想要做的操作(例如,查看、拨号、应答、呼叫等。).数据与操作必须处理的信息类型有关(是 URI、电话号码、图片等。),而类别与哪些组件有资格处理此意图有关。有时,运行时需要类别来过滤或选择那些能够响应我们意图的组件。您可以向活动、广播接收者和服务发送意图,但是在本章中,我们将只处理活动。
通常你需要做四件事来获得一个隐含的意图。您需要:
-
创建意图对象
-
设置其动作(例如,“查看地图”、“拨打电话”、“拍照”等)。)
-
设置其数据;和
-
启动意图
清单 12-5 向我们展示了这一切在代码中的样子。
| -什么 | 使用无参数构造函数创建意图。 | | ➋ | 设置意图动作。在这个例子中,我们想要查看一些东西;它可以是联系人、网页、地图、某处的图片等。此时,Android 运行时还不知道您想要查看什么。ACTION_VIEW 是您可以使用的许多意图操作之一。你可以在安卓官方网站找到其他种类的动作([`bit . ly/androidcommonint ents`](http://bit.ly/androidcommonintents))。 | | ➌ | 设置其数据。在这一点上,Android 运行时已经很清楚你在做什么了。在这个例子中,Uri 是一个网页。Android 很聪明地判断出我们想要浏览网页。 | | -你好 | Android 将搜索设备上最符合这一要求的每个应用。如果它发现不止一个应用,它会让用户选择哪一个。如果只找到一个,它将简单地启动该应用。 |val m_intent = Intent() ➊
m_intent = setAction(Intent.ACTION_VIEW) ➋
m_intent = setData(Uri.parse("https://workingdev.net")) ➌
startActivity (m_intent) ➍
Listing 12-5Example Intent to Launch a Web Browser
我们可以将清单 12-16 中的代码简化成这样
m_intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://workingdev.net"))
startActivity(m_intent)
动作和数据可以作为参数传递给 Intent 的构造函数。
任何符合我们意图的组件都不需要为了接收意图而运行。请记住,所有应用都需要一个清单文件。每个应用在清单文件中声明它的能力,特别是通过<intent-filter>部分。Android 的包管理器拥有设备上安装的所有应用的所有信息。Android 的运行时只需要清单文件上的信息来查看哪些应用有能力和/或有资格响应意图。
在接下来的部分中,我们将更详细地探讨隐含和明确的意图。我们将设置示例项目,以便您可以在上面进行练习。
演示 1:发起一项活动
我们不会在这个项目上做任何花哨的东西。我们将简单地创建两个活动:MainActivity 和 SecondActivity。当单击按钮时,我们将从 MainActivity 启动 SecondActivity。项目详情见表 12-1 。
表 12-1
演示应用的项目详情
|项目详细信息
|
价值
| | --- | --- | | 应用名称 | ch12 launchanothersactivity | | 公司域 | 您的网站名称 | | Kotlin 支架 | 是 | | 波形因数 | 仅限手机和平板电脑 | | 最低 SDK | API 23 棉花糖 | | 活动类型 | 空的 | | 活动名称 | 主要活动 | | 布局名称 | 活动 _ 主要 | | 向后兼容性 | 是的。应用兼容性 |
当项目在主窗口中打开时,创建第二个活动。方法之一是选择“app”项目工具窗口,如图 12-6 所示,然后从主工具栏点击文件 ➤ 新建 ➤ 活动 ➤ 空活动。
图 12-6
在 项目工具窗口中选择“app”
让我们把它命名为“SecondActivity”,如图 12-7 所示。
图 12-7
新的安卓活动
接下来,转到 activity_main.xml(设计视图)。移除 TextView 元素并用按钮视图替换它。将按钮定位在布局的中心附近,然后使用“推断约束”按钮,如图 12-8 所示
接下来,同样在设计视图中打开 activity_second.xml,然后添加一个按钮视图并将其置于布局的中心,就像您在 activity_main 中所做的那样..
图 12-8
在布局上居中按钮视图
此时,您应该可以使用以下视图元素和类:
-
主要活动。Kt 及其关联的 activity_main.xml ,这来自项目创建向导
-
SecondActivity.Kt .及其关联的 activity_second.xml ,这来自活动创建向导
-
activity_main 中的按钮视图对象,其 id 为“Button”——这是项目中第一个按钮元素的默认 id
-
activity_second 中的另一个按钮视图对象,其 id 为“Button 2”——这是项目中第二个按钮元素的默认 id
清单 12-6 和 12-7 分别显示 activity_main 和 activity_second 的代码;如果您尝试自己构建项目,您可以将它们用作参考或比较。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="88dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Listing 12-7/app/res/layout/-->activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Listing 12-6/app/res/layout/activity_main.xml
清单 12-8 和 12-9 显示了主活动的注释代码。Kt 和 SecondActivity。Kt。
| -什么 | 我们正在定义一个简单的日志对象。我们本可以使用 **android.util.Log** 类,但是我认为大多数阅读本书的人都有 Java 背景,所以这看起来应该很熟悉。参数`MainActivity::` [`class.name`](http://class.name) 大致相当于 Java 的`getClass().getName()`。或者,您也可以将任何字符串传递给`getLogger()`方法——例如`getLogger("My Project")`——但是通常的做法是使用 Logger 对象的类名。 | | ➋ | 我们只是创建一个日志条目,说明我们正在进行 MainActivity 的“onCreate”回调。 | | ➌ | 这是按钮的点击监听器的基本设置;你已经做过了。 | | -你好 | 这条线创建一个意图对象。意图对象的第一个参数是上下文对象;您可以在这里使用应用上下文,但是在我们的例子中,我们使用了活动上下文。`this@MainActivity`是对 MainActivity 的上下文的引用。第二个参数是意图的目标对象。这是给 Android 运行时的一个特定指令,我们想要激活这个对象。第二个参数的类型应该是**类**。MainActivity 的类对象的符号是`MainActivity::class.java`。 | | ➎ | 我们启动意图。 |import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.util.logging.Logger
class MainActivity : AppCompatActivity() {
val Log = Logger.getLogger(MainActivity::class.java.name) ➊
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.info("onCreate") ➋
button.setOnClickListener { ➌
val m_intent = Intent(this@MainActivity, SecondActivity::class.java) ➍
startActivity(m_intent) ➎
}
}
override fun onPause() {
super.onPause()
Log.info("onPause")
}
override fun onRestart() {
super.onRestart()
Log.info("onRestart")
}
override fun onResume() {
super.onResume()
Log.info("onResume")
}
}
Listing 12-8Full Listing and Annotated Code of -->MainActivity.Kt
| -什么 | 当我们调用它时,SecondActivity 将处于“停止”状态。 |
| ➋ | 当 SecondActivity 进入 **onStart** 回调时,它将对用户可见。无论什么活动在前台,现在都将被移到后台;主活动将进入“暂停”状态。 |
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_second.*
import java.util.logging.Logger
class SecondActivity : AppCompatActivity() {
val Log = Logger.getLogger(SecondActivity::class.java.name)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.info("onCreate")
button2.setOnClickListener {
finish() ➊
}
}
override fun onStart() {
super.onStart() ➋
Log.info("onStart")
}
override fun onStop() {
super.onStop()
Log.info("onStop")
}
}
Listing 12-9
-->SecondActivity.Kt
当您从 MainActivity 调用 startActivity 时,运行时将激活 SecondActivity。当 SecondActivity 对用户可见时,这应该发生在 SecondActivity 的 onStart 期间,MainActivity 将进入“暂停”状态。
当你从 SecondActivity 调用finish()时,它会进入“停止”状态。MainActivity 将被带到前台,因此它将重新进入“resume”和“restart”状态。这种相互作用如图 12-9 所示。
图 12-9
MainActivity、SecondActivity 和运行时的序列图
我已经覆盖了 MainActivity 和 SecondActivity 的一些生命周期回调。您可以检查日志来查看调用生命周期方法的时间和顺序。您可以使用 Logcat 工具窗口来检查应用和系统日志,如图 12-10 所示。
图 12-10
Logcat 工具窗口
演示 2:向活动发送数据
在这个项目中,我们将继续探索显式意图的基本机制。然而,我们并不只是启动另一个活动,我们还会向它发送一些数据。我们将详细讨论如何在意图中加入“额外”以及如何提取它。同样,如果你想编码,项目的细节如表 12-2 所示。
表 12-2
项目详情
|项目详细信息
|
价值
| | --- | --- | | 应用名称 | ch12senddatatoanothersctivity | | 公司域 | 您的网站名称 | | Kotlin 支架 | 是 | | 波形因数 | 仅限手机和平板电脑 | | 最低 SDK | API 23 棉花糖 | | 活动类型 | 空的 | | 活动名称 | 主要活动 | | 布局名称 | 活动 _ 主要 | | 向后兼容性 | 是的。应用兼容性 |
与上一节一样,我们还需要创建另一个活动。创建另一个活动,并将其命名为“SecondActivity”
返回 activity_main 并在设计视图中打开它。从布局中删除“Hello”TextView,然后添加一个 EditText 和一个 Button 视图,如图 12-11 所示。对齐元素,在布局中居中,并使用“推断约束”,就像我们在之前的演示项目中所做的那样。
图 12-11
activity_main.xml,设计视图
接下来,在设计视图中打开 activity_second,然后向其中添加一个 TextView 元素。使用“推断约束”(像往常一样)并调整一些属性,如文本大小和文本对齐,如图 12-12 所示。
图 12-12
activity_second.xml,设计模式
到目前为止,您应该已经拥有了以下视图元素和类:
-
主要活动。Kt 及其关联的activity _ main . XML;这来自项目创建向导。
-
SecondActivity.Kt .及其关联的activity _ second . XML;这来自活动创建向导。
-
activity_main 中的 editText 和 button 视图对象,它们的 id 分别是“EditText”和“Button”。editText 是项目中第一个明文元素的默认 id。
-
activity_second 中的 textView 对象,其 id 为“TextView”,这是项目中第一个 TextView 元素的默认 id。
清单 12-10 和 12-11 分别显示了 activity_main.xml 和 activity_two.xml 的代码。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<TextView
android:id="@+id/textView"
android:layout_width="324dp"
android:layout_height="wrap_content"
android:text="TextView"
android:textAlignment="center"
android:textSize="36sp"
tools:layout_editor_absoluteX="35dp"
tools:layout_editor_absoluteY="78dp" />
</android.support.constraint.ConstraintLayout>
Listing 12-11/app/res/layout/activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="31dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="@+id/editText"
app:layout_constraintStart_toStartOf="@+id/editText"
app:layout_constraintTop_toBottomOf="@+id/editText" />
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="49dp"
android:ems="10"
android:inputType="textPersonName"
android:text="Name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Listing 12-10/app/res/layout/activity_main.xml
清单 12-12 和 12-13 分别显示了 MainActivity 和 SecondActivity 的注释代码。
| -什么 | 我们正在获取用户在 EditText 对象中输入的任何内容的值。这样做的语法实际上是`editText.getText().toString()`但是 Kotlin 用 getters 和 setters 的语法糖使我们的生活变得更容易。我们可以使用属性“ **text** ”来设置或获取 EditText 视图的运行时值。我们必须调用`toString()`函数,因为`EditText.getText()`的返回类型是**可编辑的**或**字符序列**。我需要它是字符串类型的,因为 **putExtra** 既不接受 Editable 也不接受 CharSequence 它收绳子。 | | ➋ | 我们正在创建一个明确的意图,其目标是 **SecondActivity。** | | ➌ | 现在我们要放一些数据在意图上。putExtra 的两个参数看起来像一个*键-值*对;他们确实是。*键*是第一个参数,“main_activity_data”,而*值*是 EditText 的运行时内容——当然是转换成字符串。 | | -你好 | 我们正在发送意向对象。 |import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val m_data = editText.text.toString() ➊
val m_intent = Intent(this@MainActivity, SecondActivity::class.java) ➋
m_intent.putExtra("main_activity_data", m_data) ➌
startActivity(m_intent) ➍
}
}
}
Listing 12-12
MainActivity
| -什么 | 我们正在获取对与 SecondActivity 相关联的 Intent 对象的引用,我们在这里没有创建新的 Intent 对象。语法实际上是`getIntent()`,但是因为 Kotlin 的魔法酱,我们把它简单地称为**意图** |
| ➋ | Intent 对象的 **getStringExtra** 方法正在做您认为它在做的事情。它使用地图习惯用法从意图对象中提取一些数据;你给它一个键,你会得到一个值。在本例中,我们给它指定了键“main _ activity _ data——这是我们在 MainActivity 中使用的同一个键。我们使用了 **getStringExtra** 方法,因为我们知道它包含一个字符串。*的取放器*应与*的放放器*对应。如果你放入*字节、数组或包*,那么你应该分别得到 **getByteExtra** 、 **getArrayExtra、**和 **getBundleExtra** 。 |
| ➌ | 我们正在改变文本视图的运行时值。我们把它设置成我们从意图号外得到的任何东西。 |
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_second.*
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val m_data = intent.getStringExtra("main_activity_data") ➊ ➋
textView.setText(m_data) ➌
}
}
Listing 12-13
SecondActivity
运行程序并尝试在编辑文本上键入。当您单击按钮时,SecondActivity 上的 TextView 应该显示您键入的任何内容。
演示 3:向活动发送数据并从中获取数据
在这个项目中,我们将要求用户输入他的体重和身高,然后我们将计算他的身体质量指数(身体质量指数)。该项目有两个活动:主活动和次活动。
我们将要求用户在 MainActivity 上输入他的身高和体重。我们将通过一个意向把数据发送给 SecondActivity。在 SecondActivity 中,我们将从 MainActivity 发送给我们的意图中提取数据。我们将使用身高和体重数据来计算身体质量指数,然后将其发送回 MainActivity。
如果你想继续,我已经在表 12-3 中列出了项目细节。
表 12-3
演示应用的项目详情
|项目详细信息
|
价值
| | --- | --- | | 应用名称 | ch12 sendanddgetdatabackfrom activity | | 公司域 | 您的网站名称 | | Kotlin 支架 | 是 | | 波形因数 | 仅限手机和平板电脑 | | 最低 SDK | API 23 棉花糖 | | 活动类型 | 空的 | | 活动名称 | 主要活动 | | 布局名称 | 活动 _ 主要 | | 向后兼容性 | 是的。应用兼容性 |
和前面的演示一样,这个项目也有两个活动,但是它有更多的视图元素。像在前面的演示中那样创建两个活动。
MainActivity 有两个视图元素:两个用于用户输入的 EditTexts、一个按钮和一个 TextView,我们将使用它来显示身体质量指数。您可以在清单 12-14 中找到视图对象的详细信息,如 id、和文本大小;这是 activity_main.xml 的完整代码。
我给了视图一个非常简单的安排——我简单地将它们垂直打包并居中。我也没有太在意布局的限制。在目测了一个我认为不那么令人反感的布局后,我使用了“推断约束”按钮来自动修复所有的布局约束,就像我们在之前的演示中所做的那样。图 12-13 说明了如何管理 activity_main 的布局。
图 12-13
【activity _ main 的基本布局
示例代码不会特意以编程方式验证输入,所以我们将在编辑文本上放置一些验证机制。体重和身高输入字段应该只接受数字,特别是浮点数;我们可以通过设置 EditText 视图的 inputType 属性来实现这一点。以下是如何做到这一点:
-
在设计视图上编辑 activity_main 时,选择一个编辑文本视图。
-
在属性工具窗口中,单击“输入类型”
-
选择“数字十进制”
-
对其他编辑文本重复步骤 1-3。
图 12-14 说明了这一过程。
图 12-14
对编辑文本施加验证约束
这应该可以处理 MainActivity 的 UI。清单 12-14 显示了 activity_main.xml 的完整代码
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/input_weight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="68dp"
android:ems="10"
android:inputType="numberDecimal"
android:text="Name"
app:layout_constraintEnd_toEndOf="@+id/input_height"
app:layout_constraintStart_toStartOf="@+id/input_height"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/input_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="23dp"
android:ems="10"
android:inputType="textPersonName"
android:text="Name"
app:layout_constraintEnd_toEndOf="@+id/btn_send_data"
app:layout_constraintStart_toStartOf="@+id/btn_send_data"
app:layout_constraintTop_toBottomOf="@+id/input_weight" />
<Button
android:id="@+id/btn_send_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="21dp"
android:text="calculate BMI"
app:layout_constraintEnd_toEndOf="@+id/txt_bmi"
app:layout_constraintStart_toStartOf="@+id/txt_bmi"
app:layout_constraintTop_toBottomOf="@+id/input_height" />
<TextView
android:id="@+id/txt_bmi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="33dp"
android:text="TextView"
android:textSize="36sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_send_data" />
</android.support.constraint.ConstraintLayout>
Listing 12-14/app/res/layout/activity_main.xml
您可以使用 AS3 中的上下文菜单来创建 SecondActivity。右键点击项目文件夹中的“app”,然后新建 ➤ 活动 ➤ 清空活动,如图 12-15 所示。
图 12-15
创建一个新的空活动
填写新活动的详细信息,如图 12-16 所示。确保新活动的名称是 SecondActivity ,并且您正在与 MainActivity 相同的包中创建它。
图 12-16
创建第二个活动
SecondActivity 有两个视图元素:一个显示传递给它的意图内容的 TextView 和一个触发身体质量指数计算的按钮。图 12-17 显示了 SecondActivity 的用户界面。将布局中的元素居中,并使用“推断约束”将元素锚定到位。你也可以根据自己的喜好调整文本视图的文本对齐和文本大小属性。
图 12-17
activity_second.xml
清单 12-15 显示了 activity_second.xml 的完整代码
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<TextView
android:id="@+id/txt_intentdata"
android:layout_width="346dp"
android:layout_height="wrap_content"
android:layout_marginTop="109dp"
android:text="TextView"
android:textAlignment="center"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_calculate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="29dp"
android:text="calc bmi"
app:layout_constraintEnd_toEndOf="@+id/txt_intentdata"
app:layout_constraintStart_toStartOf="@+id/txt_intentdata"
app:layout_constraintTop_toBottomOf="@+id/txt_intentdata" />
</android.support.constraint.ConstraintLayout>
Listing 12-15/app/res/layout/activity_second.xml
让我们放大一下 MainActivity 的 onCreate 方法。应用一打开,EditText 就会等待用户输入。一旦用户点击按钮,我们的应用将收集输入并有目的地发送出去。
清单 12-16 显示了 MainActivity 的注释片段,其中包含了单击按钮时的事件处理代码。
| -什么 | 我们声明并定义了一个属性,它将作为某种常量。这就是我们稍后在代码中用作*请求代码*的内容。 | | ➋ | 我们正在设置纯文本视图的*提示*属性。提示显示为文本的灰色占位符。如果您在 HTML 5 中使用了占位符属性,提示属性与此类似。您可以使用提示来代替标签。 | | ➌ | 我们正在定义一个显式意图,`this@MainActivity`是上下文,意图目标是一个类对象(`SecondActivity::class.java`)。 | | -你好 | 我们需要向 SecondActivity 发送两个数据点,当需要发送多对键值对时,最好使用 Bundles。 | | ➎ | 像 Intent 一样, **Bundle** 对象也允许我们以几种方式向它添加数据。我在这个例子中使用了`putFloat()`,因为我想处理浮点数。如果需要处理字符串,字节,字符,整型等。,只需使用适当的 **putXXX** 方法即可。 | | ➏ | 我们正在加载以捆绑到意图对象。使用有意图的包允许我们处理更复杂的数据结构。 | | -好的 | 我们正在发送活动,但是这一次,我们告诉运行时我们期望一些数据返回——这就是为什么我们使用 **startActivityForResult** 。每当其他活动调用 MainActivity 的`finish()`方法时,这就通知运行时调用 main activity 的 **onActivityResult** 回调。startActivityForResult 的第二个参数是请求代码。当我们收到返回的结果时,请求代码将帮助我们路由程序逻辑。在这个调用中,我们使用了类常量 **SECOND_ACTIVITY** 作为启动 SecondActivity 的请求代码,这意味着当 SecondActivity 调用它的`finish()`方法时,这个请求代码也会被发送回 MainActivity。 |val SECOND_ACTIVITY = 1000 ➊
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
input_weight.setHint("weight (lbs)") ➋
input_height.setHint("height (inches)")
btn_send_data.setOnClickListener {
val m_intent = Intent(this@MainActivity, SecondActivity::class.java) ➌
val m_bundle = Bundle() ➍
m_bundle.putFloat("weight", input_weight.text.toString().toFloat()) ➎
m_bundle.putFloat("height", input_height.text.toString().toFloat())
m_intent.putExtra("main_activity_data", m_bundle) ➏
startActivityForResult(m_intent, SECOND_ACTIVITY) ➐
}
}
Listing 12-16onCreate Method of MainActivity
练习的下一个阶段发生在 SecondActivity 的 onCreate 回调上。在我们将身高和体重数据发送给接收活动之后,我们必须提取并处理这些数据。清单 12-17 显示了该代码的注释片段。
| -什么 | 我们需要获得对与 SecondActivity 关联的 Intent 对象的引用。这是我们从 MainActivity 中启动的同一个 Intent 对象。这也是激活 SecondActivity 的相同意图。为了获得关联的 Intent 对象,我们应该调用`getIntent()`,但是因为我们使用的是 Kotlin,而不是方法`getIntent()`,我们简单地称它为**Intent**——属性而不是方法。请记住,我们在这里并不是创建一个新的意图,我们只是获得一个与 SecondActivity 相关的意图的引用。我们在 MainActivity 中发送了一个包,所以我们应该使用 **getBundleExtra** 来获取数据。 | | ➋ | 既然我们已经获得了包,我们需要开始从包中获取更多的数据。我们使用 **putFloat** 将数据放入包中,因此,我们需要使用 **getFloat** 将数据取出。 | | ➌ | 我们将文本视图的*文本*属性设置为串联的*高度*和*重量*字符串。 | | -你好 | 在这一行中,我们正在创建一个新的意图对象。这个活动将把一些数据发送回 MainActivity。我们需要一个新的意图来做这件事。 | | ➎ | 这是一种计算身体质量指数的简单方法,但应该可行。 | | ➏ | 现在我们已经计算了身体质量指数,让我们加载到我们新创建的意图对象。 | | -好的 | **setResult** 方法接受两个参数:a.**结果代码**。这不是 0 就是-1。一般来说,如果出错了,你应该返回-1,或者如果一切顺利,你应该返回 0。但是在 Activity 类中使用类常量是个好主意。**活动。结果 _OK** 为-1,**活动。RESULT_CANCELLED** 为 0。b.**意图**。这是包含计算身体质量指数的目的对象。 | | -好的 | 最后,为了将计算结果返回给 MainActivity,我们需要调用`finish().` |override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val bundle = intent.getBundleExtra("main_activity_data") ➊
val height = bundle.getFloat("height") ➋
val weight = bundle.getFloat("weight")
txt_intentdata.text = "Height: $height | Weight: $weight" ➌
btn_calculate.setOnClickListener {
val m_intent = Intent() ➍
val m_bmi = 703 * (weight / (height * height)) ➎
m_intent.putExtra("second_activity_data", m_bmi) ➏
setResult(Activity.RESULT_OK, m_intent) ➐
finish() ➑
}
}
Listing 12-17onCreate Method of SecondActivity
意图之旅的下一部分是回到主活动。在 SecondActivity 调用完成后,运行时将调用 MainActivity 上的 onActivityResult 回调——在这个回调中,我们有机会处理 SecondActivity 发送给我们的任何数据。清单 12-18 向我们展示了 MainActivity 的 onActivityResult 的注释片段。
| -什么 | 该表达式中有两个测试:1.`requestCode == SECOND_ACTIVITY`。我们在问数据是否来自 SecondActivity。2.`Activity.RESULT_OK`。我们正在尝试查看 SecondActivity 是否调用了 setResult 并实际调用了 finish。 | | ➋ | 既然我们知道数据来自 SecondActivity,并且一切顺利,我们就可以从意图中提取数据了。我们使用了 **getFloatExtra** ,因为我们知道它包含了一个浮点——毕竟我们把它放在了那里。我们不得不在`data?.getFloatExtra()`中使用*安全调用*(问号),因为传递给 onActivityResult 的 Intent 对象的签名是可空类型。 | | ➌ | 我们可以显示计算出的身体质量指数值。 |override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if((requestCode == SECOND_ACTIVITY) and (resultCode == Activity.RESULT_OK)) { ➊
val bmi = data?.getFloatExtra("second_activity_data", 1.0F) ➋
txt_bmi.setText(bmi.toString()) ➌
}
}
Listing 12-18Annotated onActivityResult of MainActivity
如果您正在编写代码,那么现在您应该能够将整个应用拼凑起来。
清单 12-19 显示了 MainActivity 的完整代码。您可能会注意到这个完整列表与列表 12-16 和 12-18 之间的一些差异。为了简洁明了,我省略了清单 12-16 和 12-18 中的一些其他细节。在清单 12-19 中,我放回了所有省略的部分,并且对它们进行了注释,这样您可以更容易地发现它们。
| -什么 | 让我们清除输入字段。我们将这个调用放在 **onResume** 回调中,这样每次活动对用户可见时,输入字段都是清晰的。您可能还记得,在活动的生命周期中,可以多次调用 **onResume** 生命周期方法。当应用启动时,它将被第一次调用。当 SecondActivity 调用 **finish** 时会被第二次调用,MainActivity 会从后台堆栈中弹出,以此类推。 | | ➋ | 我没有使用返回可空类型的`data?.getExtra(),`,而是使用了返回不可空类型的`data!!.getExtra(),`。我这样做是为了简化我们在 **gerBMIDescription** 函数中的代码,它需要一个不可空的类型。我们本可以在 **getBMIDescription** 中处理可空类型,但是我选择使用更简单的方法处理不可空类型。 | | ➌ | 该函数接受一个身体质量指数浮点值,并返回一个权重描述。 | | -你好 | `initializeInputs()`的实现。我们只是将 EditTexts 的文本属性*设置为一个空字符串。* |import android.app.Activity
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
val SECOND_ACTIVITY = 1000
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
input_weight.setHint("weight (lbs)")
input_height.setHint("height (inches)")
btn_send_data.setOnClickListener {
val m_intent = Intent(this@MainActivity, SecondActivity::class.java)
val m_bundle = Bundle()
m_bundle.putFloat("weight", input_weight.text.toString().toFloat())
m_bundle.putFloat("height", input_height.text.toString().toFloat())
m_intent.putExtra("main_activity_data", m_bundle)
startActivityForResult(m_intent, SECOND_ACTIVITY)
}
}
override fun onResume() {
super.onResume()
clearInputs() ➊
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if((requestCode == SECOND_ACTIVITY) and (resultCode == Activity.RESULT_OK)) {
val bmi = data!!.getFloatExtra("second_activity_data", 1.0F) ➋
val bmiString = "%.2f".format(bmi)
input_height.setText("")
input_weight.setText("")
txt_bmi.setText("BMI : $bmiString ${getBMIDescription(bmi)}")
}
}
private fun getBMIDescription(bmi: Float) : String { ➌
return when (bmi) {
in 1.0..18.5 -> "Underweight"
in 18.6..24.9 -> "Normal weight"
in 25.0..29.9 -> "Overweight"
else -> "Obese"
}
}
private fun clearInputs() { // ➍
input_weight.setText("")
input_height.setText("")
}
}
Listing 12-19Full Code Listing for MainActivity
演示 4:隐含的意图
我们最后一个演示应用的特点是隐含的意图。在本节中,我们将处理三种类型的数据:web URI、地理坐标和电话号码。希望这三个例子能给你足够的洞察力和立足点,让你继续探索隐含的意图。像往常一样,如果你想编码,项目细节如表 12-4 所示。
表 12-4
演示应用的项目详情
|项目详细信息
|
价值
| | --- | --- | | 应用名称 | CH12ImplicitIntents | | 公司域 | 使用您的网站名称 | | Kotlin 支架 | 是 | | 波形因数 | 仅限手机和平板电脑 | | 最低 SDK | API 23 棉花糖 | | 活动类型 | 空的 | | 活动名称 | 主要活动 | | 布局名称 | 活动 _ 主要 | | 向后兼容性 | 是的。应用兼容性 |
该应用有一个简单的设置,我在 activity_main.xml 上做的唯一一件事就是删除“Hello World”TextView。我使用 Options 菜单来帮助用户选择启动三个示例意图。选项菜单在动作栏上,如图 12-18 所示。
图 12-18
MainActivity 菜单
在 UI 部分没有什么要做的,所以没有必要显示 activity_main 的 XML 列表。我们需要做的一切都在 MainActivity 内部完成。
在前面的章节中,我们使用 XML 资源构建了菜单;在这个例子中,我构建的菜单有点不同。我没有使用 XML 资源,而是动态构建了所有的菜单项。清单 12-20 显示了 MainActivity 的完整和带注释的代码。
| -什么 | 在调用了 **onCreate** 方法之后,将会调用 **onCreateOptionsMenu** 回调。在 API 11 (Honeycomb)之前, **onCreateOptionsMenu** 只有在用户点击手机的*选项*按钮时才会被调用,但从 Honeycomb 开始,现在被称为 onCreate。这种行为变化的主要原因是因为 ActionBar 是从 API 11 开始引入的。因为我们使用的是 API 23,所以我们可以利用这个行为来构建一个简单的菜单。 | | ➋ | 我们将动态添加一个菜单项。 | | ➌ | 每当用户点击其中一个菜单项,就会调用 **onOptionsItemSelected** 。这是我们处理菜单点击的地方。 | | -你好 | **项**参数可以告诉我们点击了哪个菜单项。我们将它转换成字符串,这样我们就可以用它在 **when** 表达式中路由我们的程序逻辑。 | | ➎ | 这是创建意图的简化版本。 |import android.content.Intent
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean { ➊
menu?.add("Web") ➋
menu?.add("Map")
menu?.add("Phone number")
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean { ➌
var m_uri: Uri
var m_intent: Intent = Intent()
when (item?.toString()) { ➍
"Web" -> {
m_uri = Uri.parse("https://www.apress.com")
m_intent = Intent(Intent.ACTION_VIEW, m_uri) ➎
}
"Map" -> {
m_uri = Uri.parse("geo:40.7113399,-74.0263469")
// This would have worked as well
// m_uri = Uri.parse("https://maps.google.com/maps?q=40.7113399,-74.0263469")
m_intent = Intent(Intent.ACTION_VIEW, m_uri)
}
"Phone number" -> {
m_uri = Uri.parse("tel:639285083333")
m_intent = Intent(Intent.ACTION_DIAL, m_uri)
}
startActivity(m_intent)
return true
}
}
Listing 12-20
MainActivity
图 12-19 显示了我们的应用的运行时快照。
图 12-19
隐含意图,运行
章节总结
-
意图用于组件激活。
-
有两种意图:隐性的和显性的。
-
明确的意图让我们可以处理多种活动。您可以使用明确的意图来激活特定的活动。
-
隐式意图扩展了应用的功能。它让你的应用做一些超出应用功能的事情。
-
你可以通过意图发送和接收数据。
在下一章中,我们将:
-
查看并简要了解材质设计(不多)。
-
了解如何在我们的应用中创建和应用样式和主题。
-
了解如何在动作栏中添加菜单。
开发者。安卓。com/reference/Android/content/Intent