Android开发从入门到精通

——Android经典教程

目录

目录 1

第一章 什么是Android 1

什么是Android-嵌入式设备编程的历史-第一章(1) 1

开放手机联盟和Android-(2) 4

介绍Android第一章(3) 5

Android示例-第四章(4) 6

Android的几个示例-第四章(5) 7

第二章下载和安装Eclipse总则 8

下载和安装Eclipse总则-第二章(1) 8

下载和安装JRE-第二章(2) 9

下载和安装Eclipse-第二章(3) 11

第三章下载和安装AndroidSDK 12

下载和安装AndroidSDK-第三章(1) 12

下载AndroidSDK-第三章(2) 13

为Eclipse配置AndroidPlugin-第三章(3) 14

第四章浏览AndroidSDK 17

浏览AndroidSDK-第四章(1) 17

AndroidSDK是什么-第四章(2) 17

Android文档-第四章(3) 18

Android示例-第四章(4) 19

Android的几个示例-第四章(5) 19

Android工具-第四章(6) 20

AndroidAPIs-第四章(7) 22

应用程序生命周期-第四章(8) 22

第五章Android程序:HelloWorld! 24

Android程序:HelloWorld!-第五章(1) 24

仔细查看Android创建的文件-第五章(2) 26

引用库和目录-第五章(3) 27

HelloWorld!自动产生文件的详解-第五章(4) 29

HellowWorld!再来一次-第五章(5) 32

HelloWorld!使用一个图形-第五章(6) 34

HelloWorld!代码为基的UI-第五章(7) 35

HelloWorld!XML为基的UI-第五章(8) 36

第六章使用命令行工具和Android模拟器 38

使用命令行工具和Android模拟器-第六章(1) 38

利用WindowsCLI创建一个壳活动-第六章(2) 39

运行ActivityCreator.bat-第六章(3) 39

项目结构-第六章(4) 42

在WindowsCLI下创建HelloWorld!活动-第六章(5) 47

增加JAVA_HOME第六章(6) 47

编译并安装应用程序第六章(7) 48

如果运行ANT时出错该怎么办?第六章(8) 48

用adb安装你的应用程序第六章(9) 52

运行应用程序产生了一个错误怎么办-第六章(10) 53

卸载一个较早的活动-第六章(11) 53

重新安装并启动应用程序-第六章(12) 54

Linux上的HelloWorld!第六章(13) 54

在CLI中创建一个图片基础的HelloWorld!第六章(14) 56

第七章使用Intents和电话拨号盘 57

使用Intents和电话拨号盘第七章(1) 57

Intents是什么?第七章(2) 58

使用拨号盘第七章(3) 63

从你的活动中打出电话第七章(4) 66

编辑活动许可第七章(5) 68

修改AndroidPhoneDialer第七章(6) 70

执行一个EditTextView第七章(7) 75

试试这个:修改AndoridPhoneDialer项目第七章(8) 78

第八章列表,菜单和其它Views 81

列表,菜单和其它Views第八章(1) 81

修改AndroidManifest.xml文件第八章(2) 83

使用菜单第八章(3) 86

为AutoComplete创建一个活动第八章(4) 90

按钮第八章(5) 97

CheckBox第八章(6) 101

EditText第八章(7) 106

RadioGroup第八章(8) 110

Spinner第八章(9) 115

试试这个:修改更多的View属性第八章(10) 121

第九章使用手机的GPS功能 121

使用手机的GPS功能第九章(1) 121

什么是轨迹文件第九章(2) 124

使用Android位置基础API读取GPS第九章(3) 125

书写代码来允许活动第九章(4) 129

传递坐标到Google地图第九章(5) 131

增加缩放控制第九章(6) 135

试试这个:在MapView之间转换第九章(7) 140

第十章使用GoogleAPI的Gtalk 145

使用GoogleAPI的GTalk第十章(1) 145

在Android中执行GTalk第十章(2) 146

编译并运行GoogleAPI第十章(3) 154

试试这个:为GoogleAPI活动增加设置特性第十章(4) 155

第十一章应用程序:找一个朋友 156

应用程序:找一个朋友第十一章(1) 156

创建一个SQLite数据库第十一章(2) 157

创建一个定制的ContentProvider第十一章(3) 158

创建ContentProvider第十一章(4) 161

创建FindAFriend活动第十一章(5) 171

创建NameEditor活动第十一章(6) 173

创建LocationEditor活动第十一章(7) 177

创建FriendsMap活动第十一章(8) 186

创建FindAFriend活动第十一章(9) 192

运行FindAFriend活动第十一章(10) 196

AndroidSDK工具参考第十二章(完) 196

AndroidSDK工具参考第十二章(完) 196

AndroidSDK1.5-包装索引 错误!未定义书签。

第一章什么是Android

什么是Android-嵌入式设备编程的历史-第一章(1

暂时可以这样说,传统的桌面应用程序开发者已经被惯坏了。这个不是说桌面应用程序开发比其他开发很简单。总之作为桌面应用程序开发者,我们已经有能力按照我们的想法创造出各种应用程序。包括我自己,因为我也是从做桌面程序开始的。一方面,我们已经使得桌面程序更容易的与桌面操作系统来进行交互,并且和任何底部的硬件很自由的交互。这种类型独立自主的程序编制其实对于很小的开发者团体来说是不敢贸然趟手机开发这趟浑水的。

注意:

在本部分讨论中,我提到两种不同的开发者:传统的桌面应用程序开发,他们能使用任何的编程语言,而且最终的产品和程序是用来运行“桌面”操作系统的;还有就是Android的程序开发者,为Android平台开发程序的JAVA程序员。我不是想说谁更好或者其它的意图。区别仅仅在于想说明并比较桌面操作系统环境的开发风格,工具。

有很长一段时间,手机的开发者由大的著名开发组中的少数人组成,作为嵌入式设备的开发者。相对于桌面开发或者后续的网络开发,被视作更少“魅力”,而且嵌入式设备的开发通常因为硬件和操作系统而处于劣势。因为嵌入式设备的制造商们太小气,他们要保护他们硬件方面的秘密,所以他们给开发者们非常有限的库来运行。

嵌入设备与桌面系统显著不同的一部分是嵌入设备是个“芯片上的电脑”。例如:说起你的标准电话遥控。这个并不是一个非常强大并且复杂性的技术。当任何的按钮被按下去,一个芯片解释一个信号以一种方式已经被编程进了设备。这个允许设备知道什么是从输入设备(键盘)来的需要。并且如何的响应这些命令(比如,打开电视机)。这个是一个简单的嵌入式设备的编程。总之,不管你相不相信,像这样的简单设备绝对的和早期的手机和开发有着紧密的联系。

大多数的嵌入式设备运行(有些还在运行)在私有的操作系统。原因是选择并创建一个私有的操作系统而不同定制的系统是产品必然选择。简单的设备不需要非常健全和优化的操作系统。

作为一个产品的演化,更多复杂的嵌入式设备,如早期的PDA,家庭安全系统和GPS等。5年前某种程度上都转移标准的操作系统平台上。小的操作系统如Linux,或者一个微软的嵌入式平台,已经在嵌入设备上变得普遍了。设备演变的那些时间里,手机已自己的路径开始分支出去。这个分支是显而易见的。

差不多开始的时候,手机作为一个外围设备并且运行私有软件,而这些软件被制造商们所拥有和控制,而且几乎可以被认为是一个“关闭”的系统。习惯使用私有操作系统主要是制造商自己开发硬件,或者至少定义了开发的目的只是用来运行手机。最终的结果就是使开放成为不可能。现有的软件包或者解决方案会可靠的和他们的硬件交互。而且,制造商想要保护他们硬件的商业秘密。以防允许进入而发现设备软件的水准。所以风尚就是,而且大多数仍然是使用完全私有并且关闭的软件来运行他们的设备。任何人想为手机开发程序必须需要详尽的私有环境来运行软件的知识。而解决方案就是直接从制造商那里购买昂贵的开发工具。这就孤立了很多的“自制软件”的开发者。

注意:

一个关于自制软件开发的文化包含了手机程序的开发。“自制软件”是指开发者通常不是工作在手机开发公司内,通常利用自己的时间在他们的设备上生产小的,一次性的产品。

另外,使手机开发无法出手的是硬件制造商对于“内存和需要”左右为难的解决方案。直到最近,手机才能执行比打出和接听电话,查找联系人,发送和接收短消息。不是今天“瑞士军刀”的技术。及时在2002年,在消费者的手上,带照相机的手机还是不多见。在1997年,小的应用程序如计算器和游戏爬进了手机内,但是强大的功能仍然是手机的拨号盘本身。手机还不想今天一样是一个多用途,多功能工具。没有人预见互联网浏览的需求,MP3播放,或者更多的是我们今天定制的功能。在1997年,手机制造商们没有预见消费者需要的是一个一体化的设备。但是,即使这个需求展现出来,设备内存和存储容量还是一个需要克服的大的障碍。更多的人可能想要他们的设备是一个多功能一体化的工具,但是制造商们不许跨越他们的障碍。

让问题变得简单,就要在任何的设备让内存来存储并运行程序,包括手机。手机作为一个设备,直到最近还没有足够多内存来执行“额外”的程序。在最近的两年里,内存的价格已经达到了非常低的水平。设备制造商们有足够的能力压低价格来包含更多的内存。很多的现在的手机标准内存已经超过了90年代中期电脑内存。于是,现在我们有需求,而且有内存。我们可以直接跳到为手机开发酷的应用程序了,对吗?不完全是这样。设备的制造商们仍然紧密的保护他们的操作系统。有一些在手机上开放JAVA为基础的小运行环境。更多的是不允许。即使允许运行JAVA应用程序但还是不允许进入核心的系统。而这些是桌面开发者习惯于拥有的。

开放手机联盟和Android-2)

这个对于应用程序开发的障碍开始在2007年的11月份被打破,当Google在开放手机联盟下发布Android。开放手机联盟是一个硬件和软件开发者的集合,包括谷歌,NTTDoCoMo,SprintNextel和HTC。他们的目标是创建一个更多的开放手机环境。在开放联盟第一个被发布的产品就是移动设备操作系统Android。(更多关于开放手机联盟的信息,见:www.openhandsetalliance.com)。

对于这个Android的发布,谷歌使很多开发工具和向导成为可能来帮助在新系统上可能的开发者。帮助系统,平台软件开发包(SDK),甚至一个开发者的论坛,可以在谷歌的Android的网站上找到,http://code.google.com/android.这个网站应该是你的起点,而且我极度推荐你去访问。

注意:

谷歌为了推动这个新的Android操作系统,甚至为寻找新的Android程序而设立了1000万美元的奖金。

运行Linux,Windows或者即使PalmOS的手机是很容易找到,如本文所述,没有硬件平台已经宣告可以来运行Android.HTC,LG电子,摩托罗拉和三星都是开发手机成员,在Android的发布下,我们希望在不久的将来有一些Android为基的设备。在2007年11月发布时,系统自身还仍旧是一个测试版的程序。这是个对开发者的好新闻因为它给了我们一个罕见的提前看到将来的设备和有机会来开始开发应用程序,而当硬件发布时就可以运行。

注意:

这个策略明确的给了开放手机联盟一个大的优势,超越其它手机操作系统开发者。因为当第一代设备发布时会有数不尽的可用开发程序可以运行。

介绍Android第一章(3)

Android,作为一个系统,是一个运行在Linux2.6核心上的JAVA基础的操作系统。系统是非常轻量型的而且全特性。

图显示了一个未经修改的Android桌面屏幕。

Android应用程序用JAVA开发而且很容易被放置到新的平台上。如果你没有下载JAVA或者不确定那一个版本你需要,我在第二章详细列出了开发环境的安装。其他Android的特点包括一个加速3-D图形引擎(基于硬件支持),被SQLite推动的数据库支持,和一个完整的网页浏览器。

如果你熟悉JAVA编程或者是任何种类的OOP开发者,你可能使用程序用户接口(UI)开发-那就是,UI安置是直接在程序代码中有句柄的。Android,识别并许可UI开发,而且支持新生,XML为基础的UI布局。XMLUI布局对普通桌面开发者是一个非常新的概念。我会在本书的相关章节里描述XMLUI布局和程序化UI开发。

Android另一个更令人激动和关注的特点是因为它的样式,第三方应用程序——包括“自制的”——会和系统捆绑的有着同样的优先权。这是和大多数系统不同之处,但是给了嵌入式系统程序一个比由第三方开发者创建的线性优先权大的优先执行权。而且,每一个应用程序在虚拟计算机上以一个非常轻量的方式按照自己的线路执行。

除了大量的SDK和成型的类库可以用之外,对激动人心的特性对于Android的开发者来说是我们现在可以进入到操作系统可以进入的地方。也就是说,如果你要创建一个应用程序打一个电话,你已经进入到电话的拨号盘。加入你要创建一个应用程序来使用电话内部的GPS(如果安装了),你已经进入了。对于开发者创建动态和令人好奇的程序已经敞开大门。

和上面这些可用的特点相同,谷歌已经非常迫切的奉送一些特性。Android的开发者可以将自己的应用程序和谷歌提供的如谷歌地图和无所不在的谷歌搜索绑在一起。假设你要写程序在谷歌地图上显示一个来电话者的的位置,或者你要储存一般的搜索结果到你的联系人中。在Android中,这个门已经完全打开。

第二章开始你Android的开发旅程。你会学到如何和为什么使用特定的开发环境或者综合的开发环境(IDE),而且你会下载并且安装JAVAIDEEdipse.

问专家:

Q:谷歌和开放手机联盟的区别在哪里?

A:谷歌是开放手机联盟的一个成员。谷歌在收购了Android的原开发后,在开放手机联盟发布了操作系统。

Q:Android有能力运行任何的Linux软件吗?

A:没必要。我坚信会有一种方式绕开大多数的开源系统和应用程序用AndroidSDK编译而用于Android。主要原因是Android程序执行特定的文件格式,这会在后续的章节中讨论。

Android示例-第四章(4

Android示例在SDK/SAMPLES内,包含了6个示例可以很好的描述Android的一些功能:
●APIDemos

●Hello,Activity!

●LunarLander

●NotePad

●SkeletonApp

●Snake

这些示例由谷歌提供来给你一个快速的印象,那就是如何快速的开发Android的应用程序。每一个应用程序描述Android不同功能的一块。你可以用Eclipse打开并且运行这些应用程序。下面是对于每一个示例的简要描述。

APIDemos
这个API示例应用程序说明在一个单独的Activity内如何展示多个API功能的示例。

提示:

一个Activity是一个Android的应用程序。Activities会在后续的章节中深入展开。

如下图(略)所示的,这个API示例应用程序包好了很多的,小的不同的Android功能的例子。这些例子包含3-D图形变换,列表,过程对话框和一个手指-画图示例。

运行API样本示例应用程序

使用Eclipse,装载API示例应用现场作为一个Android项目。要做到这个,在Eclipse菜单选择文件|新建|项目,一个新的Android项目向导会启动。现在不用担心向导页面上的一些选项。只是选择从现有的项目中创建项目就好了,并且浏览到API示例所在的目录,点击这个示例。当项目装载好了,选择运行,在Android模拟器中来查看。用你自己的方式去查看超过40个示例吧,使用每一个示例去熟悉这些术语和功能。

Android的几个示例-第四章(5

Hello,Activity!

Hello,Actoviry应用程序,是一个简单的HelloWorld风格的应用程序。虽然设计简单,但是它展示了平台的能力。在下一章,你会创建自己的HelloWorld!风格的程序。

LunarLander月球登陆
LunarLander是一个在Android模拟器上玩的游戏。这个游戏是2-D的游戏它在Android上工作是多么的简单。控制非常的简单,而且游戏不是非常的复杂。总之,对游戏开发来说是一个良好的开始。

月球登陆执行一个简单控制方案(上,下,左,右)。游戏同时显示相关的非固定的图形并且对平台来说,令人印象深刻。复杂游戏的理论如冲突检出是以一个简单的方式使用的。虽然本书没有包含Android平台游戏编程的内容,加入你有兴趣来做这个,你或许可以从月球登陆中获得某些启发。

NotePad写字板
NotePad,允许你打开,创建并且编辑小的笔记。写字板不是一个全功能的字符编辑器,所以不要期待是和WindowsMobileword的竞争对手。但是,作为一个演示工具,使用非常少的代码就能实现这个效果已经非常的棒了。

SkeletonApp框架应用
SkeletonApp这是一个基本的程序,展示了几个不同的应用程序功能。如字体,按钮,图形和表格。如果你想自己运行SkeletonApp,真的不应当把它排除在外,参考SkeleteApp,它会提供不少关于如何执行特定的条款。

Snake

最有一个在AndroidSDK的示例就是这个蛇了。这是一个小的SNAFU风格游戏,比月球登陆复杂。

注意:

如果你打开每一个示例应用程序的文件夹,你会看到一个文件夹命名为src。这个是给出示例源代码的文件夹。你可以为其他任何的应用程序来查看,编辑并且重新编译这些代码。利用这些源代码来学一些Android平台技巧和提示。

第二章下载和安装Eclipse总则

下载和安装Eclipse总则-第二章(1

-关键技能&概念

-选择一个开发环境

-下载Eclipse

-安装和配置Eclipse

Android应用程序是在JAVA下开发的。Android自身不是一个语言,但是是一个运行应用程序的环境。这样,理论上你可以使用任何发布或者综合开发环境(IDE)来开始你的开发。事实上,你可以选择非IDE开发。

提示:

在本章稍后,我会介绍不使用IDE或者“命令行接口”(CLI)来开发Android应用程序。这期间,我不会在书中的每一个例子都使用这种技术,你将会学到如何在CLI里开发的基础知识。
假如你对使用JAVA的IDE比较舒服,如Borland的JBuilder或者开源NetBeans,你可以尽管去使用。有了中等的水平的经验,你应当可以适应本书大部分的例子。但是,开放手机联盟和谷歌认同一个JAVA的IDE,那就是:Eclipse.
注意:

如果你选择不用Eclipse来跟从本书的例子,你需要看看你的IDE文档关于编译和测试你的Android的程序。书中的例子只给了如何在Eclipse中编译和测试程序的说明,在Eclipse中使用Android的plugin。

本章简明的描述了如何下载和安装Eclipse以及所要求的JAVARuntimeEnvironment(JRE)。很多的时候,安装向导和教材趋向于跳过简单的步骤。我已经发现跳过简单的步骤经常忽略重要的条目。因为这个原因,我在本章内包含了从下载到安装的所有步骤。

为什么是Eclipse?
为什么Eclipse是推荐的Android程序开发的IDE呢?对这个特定的认同有一些原因:

1、为了保持开发手机联盟真正开放移动开发市场的宗旨,Eclipse是有着同样显著特点的,免费的JavaIDE可以使用。Eclipse同样容易使用,最少的学习时间。这些特性让Eclipse对于固定的,开放的Java开发成为吸引人的IDE。

2、开发手机联盟已经为Eclipse发布了一个Android的plugin,允许你来创建Android-定义项目,编译它们,并且使用Android模拟器来运行和调试程序。当你开发你的第一个Android程序时,这些工具和能力将会是非常宝贵的。你还是可以用其它的IDE来创建Android程序,但是Android的plugin为Elipse创建某些元素——如,文件和编译设定。这些来自Android-plugin的帮助将缩短你宝贵的开发时间并减少学习的弯路,那就意味着你可以花费更多的时间来创建惊人的应用程序了。

注意:

Elipse同样也可用于苹果和Linux系统,有着强大的能力,在不同的操作系统,意味着几乎每个人可以在任何的电脑上开发Android的应用程序。不过,本书的例子和电脑截图觉来自与微软Windows版本的Eclipse。记住这一点,如果你使用其他的电脑操作系统。你的界面可能看上去会有轻微的不同,但是总体的功能不会改变。如果在Linux的Eclipse有一些主要的操作不同点的话,我会举例说明。我会举出一些在Linux上的列子。而主要的例子会是Linux/Android的命令行环境(CLE)。

下载和安装JRE-第二章(2

在你下载和安装Eclipse之前,你必须确保在电脑上下载并安装了JavaRuntimeEnvironment(JRE)。因为Eclipse作为一个程序是由Java写成,它要求JRE来运行。如果JRE没有安装或被检测到,如果你试着打开Eclipse,你会看见下面的错误:

如果你已经是一个Java的开发者并且已经在电脑上安装了Java,你还是要按照提示安装,确保安装了正确版本的JRE。

注意:

大多数使用过网络或者以网络为基础的应用程序的人,安装过JRE。JRE允许你运行Java基础的应用程序,但是它不允许你去创建它。要创建Java应用程序,你需要下载并安装JavaDevelopmentKit(JDK),这个包含了创建Java应用程序所需的所有工具和库。如果你不熟悉Java,记住这一点就行了。对于书中提到的例子,我会下载JDK,因为它也包含了JRE.虽然你不需要JDK来运行Eclipse,但是你还是可以在本书后续章节的开发中使用。

导航到Sun公司的下载页面,http://developers.sun.com/downloads/,如下面的插图(略)所示。正常情况你只需要JRE来运行Eclipse,但是对于本书的目的,你应当下载包含了JRE的完整的JDK,下载JDK的原因是在本书的后面,我会提到只使用JDK而非Eclipse来开发Android程序。如果你想跟从教材的话,你会需要完整的JDK。

从SUN的下载页面,导航到适当JDK的下载部分。选择并下载,如下图(略):
对于书中例子,我选择使用Java5JDKUpdata14,因为在Eclipse文档中明确说明这是个支持的版本。要下载Java5JDK,选择你要下载的平台来下载。你可能简单的跟着下载Java6JDK。但是,如果你要下载旧的JDK5,你需要点击前一个发布的链接,如图(略):

注意:

下载前,你必须同意并接受Sun公司的专利使用权转让协定。

在JavaSe以前一个发布下载页面,点击J2SE5.0下载链接,然后点击JDK5.0Updatex下载按钮,x是最后的升级号码(14是本书写的时候的号码,你下载的时候可能会有所不同)。

如果你正在下载一个到微软Windows的环境,当你见到如下图(略)所示的通知时,点击Run来开始JDK的安装。

提醒:

如果你想要保存一份JDK包的备份,点击Save而并非Run。总之,当你选择保存了JDK,确保注意保存位置。在下载结束后,你需要导航到下载位置并且手动执行安装包。

在安装期间,你会被提醒阅读协议,如下图(略)。同意之后,点击Next,然后就可以选择你的定制安装选项了。

这里只有一点你需要改变的,除非你是一个成熟使用Java的人并且需要选择特定的选项,在这种情况下,请自由的改变你需要的安装选项。下面是JavaJDK安装的定制安装图(略)。

为了保持过程的简单性,并且完全地标准化,你应当接受软件自身的安装建议——选择缺省的设定——并且点击Next来继续安装。再次强调,如果你想要订制改变,请按照你自己的方式进行。总之,如果在后面的章节你遇到麻烦,你会需要修改你的安装选项。当安装完成的页面出现,如下图:(略),点击Finish,然后你的安装就会完成。

一旦你完成JavaJDK的安装——而且根据缺省,JRE也会安装——你可以开始安装Eclipse了。

下载和安装Eclipse-第二章(3

导航到www.eclipse.org/downloads的下载页面,如下图(略)。根据开放段落申明,需要JRE运行环境(推荐Java5JRE)来开发Eclipse,而这个我们已经在上节描述过了。在这个站点下载为Java开发者准备的Eclipse的IDE。软件包比较小(79MB)并且应当下载很快。确保你不是下载了EclipseIDEforJavaEE的开发包,因为这个是有点不同的产品而且我不会介绍它的使用说明。

在你下载了Elipse以后,是时候来安装它了。导航到软件包下载的位置。写这本书的时候,最新的Eclipse软件包Windows版本的文件是eclipse-java-curopa-fall2-win32.zip.解压缩软件包并且运行Eclipse.exe。Eclipse按照缺省方式安装到以用户目录(微软Windows),但是你或许想安装到你的程序文件目录下。这样会保持你应用程序的有序而且允许你设定不同的目录作为工作空间。下图(略)显示了软件启动的欢迎画面。

注意:

如果你没有看见欢迎画面,试着重新启动电脑。如果重启后没有帮助的话,只下载并安装Java5JRE。

一旦Eclipse安装开始,你会被提醒来创建一个缺省的工作空间,或者文件夹。和其他大多数开发环境一样,项目被创建,并且保存到这个工作空间内。缺省的工作空间路径是你的用户路径,选择不同路径,点击Browse来导航。如图(略)。

我建议你同样也选中选择框来定义你所有的项目到一个工作空间。选中这个框,当创建新项目时,你就会少一个需要担心的事情,而且你总是会知道在哪个路径里能找到你的源文件。在本书内,有时你需要导航到项目文件,并且在Android开发环境的外部工作,所以知道你文件的所在位置是非常有帮助的。

选择工作空间之后,点击OK。在这里,你的开发环境被下载好和安装。虽然Eclipse的安装似乎很快,你仍然需要在创建你的第一个Android项目前配置Eclipse。很多的配置工作都是和AndroidSDK和Androidplugin有关。

下一步你需要下载并安装AndroidSDK,并且为Eclipse下载并安装Androidplugin。然后配置Eclipse设定。在第三章的结尾,你会有一个可以开发应用程序的完整的开发环境。然后你会浏览AndroidSDK并且在第五章创建你的第一个HelloWorld!应用程序。

问专家

Q:Eclipse是用来开发Java的,但是Android能运行其他语言所写的程序吗?A:写这本书时,没有SDK或者模拟器可以让Android来运行Java以外的程序。

Q:能使用Eclipse(和AndroidSDK)和JRE非5的版本一起工作吗?

A:技术上说你可以使用Eclipse和版本5或者更新的版本一起工作,但是最新版本的Eclipse仅仅在Java5JRE上进行过测试。

第三章下载和安装AndroidSDK

下载和安装AndroidSDK-第三章(1

关键技能和概念

-下载AndroidSDK

-使用Eclipse的可升级特性

-为Eclipse下载,安装并配置AndroidPlugin

-检查PATH声明

在前面的章节中,你下载并安装了主要的开发环境,Eclipse。现在,你的原始开发环境已经建立了,使用Eclipse作为你的JavaIDE,你可以用它来开发Java的应用程序。你必须以某种方式来配置它,以减轻Android的开发。

因为Eclipse是Java开发环境,你可以很简单的创建并编辑Java项目。但是,如果没有可以理解的库,规定Android应用程序应当如何工作,你就无法开发任何应用可以在Android为基础的设备上运行的程序。要开始创建Android项目,你需要下载并安装AndroidSDK。然后你需要为Eclipse下载相关的Androidplugiin来使用SDK。有了这些部件的支撑,你就可以开始开发工作了。

如果你已经拥有任何的开发经验,很可能你已经熟悉使用SDK的过程。桌面程序的开发者,不管在哪一种的开发平台上开发,使用SDK来创建他们希望运行的系统上的应用程序。AndroidSDK和其它的SDK相比没有任何的不同,它包含了所有的创建运行在特有的Android平台上应用程序所需的Java代码库。SDK还包括帮助文件,文档和Android模拟器,大量的开发和调试工具。

注意:

第四章深入的阐述了AndroidSDK大多数的功能。

作为开始,你准备从谷歌Android开发网站上下载AndroidSDK,网址:http://code.google.com/android谷歌Android开发的主页上包含为Android平台开发的大量有价值的工具和文档,包括链接到Android开发者论坛。

提示:

如果你在开发的过程中遇到问题,你第一个找答案的地方应该就是Android开发者论坛。http://code.google.com/android/groups.html.这里有新手,开发者和黑客的讨论组。并且一个常规问题讨论组。考虑到Android是一个全新的平台,Android开发者论坛是较少的能找到综合,可靠信息的地方。

下载AndroidSDK-第三章(2

从谷歌的http://code.google.com/android网页可以很容易的找到AndroidSDK软件包。从开发的主页,点击下载SDK的链接开始。在你同意了AndroidSDK的软件许可协议后,你会看见AndroidSDK的下载页面。AndroidSDK软件包对于Windows版本是79MB大小,你应当能够很快的下载。根据你的操作系统选择软件包开始下载。

注意:

软件包的大小根据不同的操作系统可能不一样。

说到AndroidSDK,这里没有“setup”或者安装过程。这里,你必须跟着下面一些列的设置,在Eclipse开发环境里配置AndroidSDK。第一步是获得Androidplugin,然后配置它。

为Eclipse下载和安装AndroidPlugin,设置AndroidSDK的第一步就是为Eclipse开发环境下载和安装AndroidPlugin。Plugin的下载和安装可以同时进行,而且非常的简单。

1.打开Eclipse应用程序,你将会下载为EclipseIDE准备的AndroidPlugin。2.选择帮助|软件升级|寻找和安装。

3.在安装/升级的窗口,会允许你执行安装和下载在Eclipse任何可用的plugin,点击搜索新特性选项,然后点击下一步。
4.UpdatesitestoVisit这个窗口会列出所有可获得Eclipseplugin的网站。但是,你所需要的AndroidforEclipse没有列在这里,所以要下载这个Androidplugin你必须要告诉Eclipse到哪里去找它。所以点击NewRemoteSite这个按钮。

5.在NewRemoteSite对话框内,你要提供两个信息:网站的名称和网址。名字只是便于显示并不影响下载。我们可以输入AndroidPlugin。在URL字段。输入:https://dl-ssl.google.com/android/clipse.点击OK。

注意:

这里填写的名字只是帮助你识别。你可以输入任何你想要的名字。

6.现在新的站点AndroidPlugin应当在可用的站点列表上了。这时,Eclipse还没有开始寻找plugin,这只是个路径你告诉Eclipse。

7.选中Androidplugin的选择框然后点击完成。Eclipse开始任何可用的plugin。

8.在搜索结果页面,选择AndroidPlugin然后点击完成。

9.在特性安装的许可页面,点击接受许可协议,然后点下一步。

注意:

记住所有的plugin都安装在/eclipse/plugins的路径里。这个信息会帮助你假如你需要自己放置Androidplugin。

10.Eclipse下载Androidplugin。本书写作时,plugin的版本是0.4.0.200802081635.在最终的plugin的安装页面,是特性核实,点击安装所有来完成Androidplugin的安装。

安装完成后就是必须去配置plugin。

为Eclipse配置AndroidPlugin-第三章(3

在完成了Androidplugin的安装之后,Eclipse应当提示你重新启动应用程序。如果它没有提示你,现在就重新启动Eclipse。重启会确保安装的plugin有机会被初始化。安装下面的方式来配置是非常重要的。

配置Androidplugin的方式是从Eclipse的Preferences窗口开始的,按照下面的步骤:

1.从Eclipse的程序主窗口中|Windows|preferences.

2.再出现的窗口中,在左边选择Android菜单。在窗体的右边点击Browse,找到AndroidSDK的在硬盘的存放位置。输入到SDKLocation的字段中。Eclipse需要这个信息来进入到Android提供的工具,比如模拟器。选中AutomaticallySyneProjectstoCurrentSDK选择框,然后点击应用。
注意:
Androidpluginforwindows是以zip文件格式发布的。而且它包含了一个非常长的文件名称。android_m5-rc14-win32.重命名到一个比较容易管理的名字,这会在将来的章节中对你有帮助,特别是到命令行编程。你可能也会解压缩它到程序文件路径里。
4.AndroidSDK的最后一个设置是把它放到PATH声明内。如果你用的是微软的Windows,右击我的电脑,选择属性,然后选择高级。

5.点击环境变量。在这里可以编辑PATH声明。

6.在系统变量中,找到PATH然后双击它。

7.在编辑系统变量的对话框中增加你的AndroidSDK路径,使用分号来分别现有的系统路径。点击OK。在环境变量的窗口再次点击OK。

现在,AndroidSDK,Eclipse和Androidplugin被完全的配置好了并且准备被用来开发了。在下一章,你会浏览AndroidSDK,了解它的特性。AndroidSDK包含很多工具来帮助你来开发全功能手机应用程序,并且下一章提供一个好的概述。

问专家:
Q:AndroidSDK可以用在非Java的语言上吗?
A:不行。Android应用程序只能在Java系统上被开发。
Q:会有更新的AndroidSDK吗?
A:是的!在写本书的时候,一个SDK的升级发布了,并且解决了平台上的很多问题。我建议经常检查开发页面的更新。
Q:如果升级了,我如何更新我的SDK?
A:更新SDK是非常棘手的。当一个新的SDK发布,必须是plugin也发布。在写本书时,新的SDK和新的plugin都发布了。我试图使用“Provided(提供的)”的升级工具来改变版本。最终无果并留给了我两个的版本,都工作不正常。我最终不得不卸载了它们并且重新安装最新的一个。然后那个最新的SDK工作正常了。我建议任何面对SDK或者plugin升级的人都采用相同的过程。简单的卸载老版本,然后安装新版本。不要升级。

第四章浏览AndroidSDK

浏览AndroidSDK-第四章(1

关键技能和观念
—使用AndroidSDK文档
—使用Android工具
—使用sample应用程序
—学习Android程序的生命周期
现在,你已经建立了开发环境,准备去浏览AndroidSDK了,它包含了很多的文件和特别的工具,可以帮助你设计并开发运行在Android平台上的应用程序。这些工具设计的非常的好,而且可以帮助你制作一些难以置信的应用程序。在开始编程之前你真的需要熟悉AndroidSDK和它所带的工具。

AndroidSDK还包含了一些可以让应用程序进入Android特性的库,比如和电话功能关联的(呼出和接电话),GPS功能,和短消息。这些库组成了SDK的核心而且会是你经常会使用到的,所以,有一些时间来学习所有关于核心的库。
这一章包括了所有这些在AndroidSDK重要的条款,在本章的结尾,在你自己熟悉了AndroidSDK内容之后,你会足够舒适的开始写你的应用程序。总之,任何的事物都是这样,在你开始练习之前,你必须熟悉这些内容和指示。

注意:
我不会去介绍AndroidSDK的每一个细节,谷歌已经在SDK内做了非常好的文档。为了避开花费不必要的时间来讨论如何工作,我已经尽量少的做一些简要的说明。我只是会讨论一些重要的话题和条款,然后按照你自己的步伐去探索更深的层次。

AndroidSDK是什么-第四章(2

AndroidSDK下载后会是一个简单的ZIP文件压缩包。AndroidSDK的主体是一些文件,连续性的文档,可编程的API,工具,例子和其它。本部分详细的说明这个AndroidSDK到底有些什么。

提示:

第三章建议你解压缩AndroidSDK到程序文件的文件夹,所以容易被找到。如果你找不到SDK,因为你使用解压缩的缺省设定,应当在下面的文件夹/%downloadfolder%/android-sdk_m5-rc14_windows/android-sdk_m5-rc14_windows.(译者注:根据下载的文件名不同,这个文件夹也会不同哦).

找到解压后的AndroidSDK的文件夹,然后可以在文件夹内浏览。在根目录会有几个文件,像android.jar(一个编译过的,包含核心SDK库和api的Java应用程序)并且一些发布笔记,剩下的AndroidSDK被分成3个主要的文件夹:

●Docs包括所有的Android文档

注意:

这些文档同样也可以在Android开发网站上找到http://code.google.com/android.

●Samples可以在Eclipse内编译和测试的6个应用程序例子

●Tools包含所有在开发过程中需要的开发和调试工具

下面的部分会讨论更多关于在每一个文件夹内的内容。每一个API示例被编译过并且可插入至Android。在后续学习如何在windows和Linux中使用命令行选项创建和编译应用程序的章节中会讨论更多的工具。

Android文档-第四章(3

Android文档被放在AndroidSDK内的Docs的文件夹内。文档内提供了如何下载和安装SDK的每一个步骤,“GettingStarted”开发应用程序的快速步骤和软件包定义。文档是HTML格式并且有一个documentation.html在SDK的根目录可以进入整个文档。下面的插图(略)就是AndroidSDK文档的主页。

你可以从documentation.html上提供的链接导航到AndroidSDK内包含的文档。

注意:

当你浏览AndroidSDK时,你可能想到一些页面是一些错误的链接或者丢失了。因为当你点击某些链接时,屏幕右边可能会显示空白,不过,如果你再往下滚动页面你将会明白页面只是没有被排列好。

在这个AndroidSDK内,我已经发现有一些部分比其他的部分更重要。对于我来说最重要的AndroidSDK文档如下(它们会出现在导航条上):
●ReferenceInformation
●ClassIndex
●ListofPermissions
●ListofResourceTypes
●FAQs
●Troubleshooting
当你开始开发,Troubleshooting文档的分类部分将会特别有作用。当你深入本书并且开始开发你自己的应用程序,你会发现文档的ReferenceInformation部分会更有帮组。例如,ListofPermissions分类部分将会非常的有帮助,当你跟着本书创建更复杂的应用程序时。虽然这个现在对你用处不大。花些时间熟悉一下Android文档吧。

Android示例-第四章(4

Android示例在SDK/SAMPLES内,包含了6个示例可以很好的描述Android的一些功能:
●APIDemos
●Hello,Activity!
●LunarLander
●NotePad
●SkeletonApp
●Snake

这些示例由谷歌提供来给你一个快速的印象,那就是如何快速的开发Android的应用程序。每一个应用程序描述Android不同功能的一块。你可以用Eclipse打开并且运行这些应用程序。下面是对于每一个示例的简要描述。

APIDemos
这个API示例应用程序说明在一个单独的Activity内如何展示多个API功能的示例。

提示:

一个Activity是一个Android的应用程序。Activities会在后续的章节中深入展开。

如下图(略)所示的,这个API示例应用程序包括了很多的,小的不同的Android功能的例子。这些例子包含3-D图形变换,列表,过程对话框和一个手指-画图示例。

运行API样本示例应用程序

使用Eclipse,装载API示例应用现场作为一个Android项目。要做到这个,在Eclipse菜单选择文件|新建|项目,一个新的Android项目向导会启动。现在不用担心向导页面上的一些选项。只是选择从现有的项目中创建项目就好了,并且浏览到API示例所在的目录,点击这个示例。当项目装载好了,选择运行,在Android模拟器中来查看。用你自己的方式去查看超过40个示例吧,使用每一个示例去熟悉这些术语和功能。

Android的几个示例-第四章(5

Hello,Activity应用程序,是一个简单的HelloWorld!风格的应用程序。虽然设计简单,但是它展示了平台的能力。在下一章,你会创建自己的HelloWorld风格的程序。

LunarLander月球登陆
LunarLander,是一个在Android模拟器上玩的游戏。这个游戏一个2-D的游戏在Android上工作是多么的简单。控制非常的简单,而且游戏不是非常的复杂。总之,对游戏开发来说是一个良好的开始。

月球登陆执行一个简单控制方案(上,下,左,右)。游戏同时显示相关的非固定的图形并且对平台来说,令人印象深刻。复杂游戏的理论如冲突检出是以一个简单的方式使用的。虽然本书没有包含Android平台游戏编程的内容,加入你有兴趣来做这个,你或许可以从月球登陆中获得某些启发。

NotePad写字板
NotePad,允许你打开,创建并且编辑小的笔记。写字板不是一个全功能的字符编辑器,所以不要期待是和WindowsMobile中word的竞争对手。但是,作为一个演示工具,使用非常少的代码就能实现这个效果已经非常的棒了。

SkeletonApp框架应用
SkeletonApp,这是一个基本的程序展示了几个不同的应用程序的功能。如字体,按钮,图形和表格。如果你想自己运行SkeletonApp,真的不应当把它排除在外,参考SkeleteApp,它会提供不少关于如何执行特定的条款。

Snake蛇

最后一个在AndroidSDK的示例就是这个蛇了。这是一个小的SNAFU风格游戏,比月球登陆复杂。

注意:

如果你打开每一个示例应用程序的文件夹,你会看到一个文件夹命名为src。这个是给出示例源代码的文件夹。你可以为其他任何的应用程序来查看,编辑并且重新编译这些代码。利用这些源代码来学一些Android平台技巧和提示。

Android工具-第四章(6

AndroidSDK提供给开发者一系列功能强大并且有用的工具。在本书内,你会直接使用它们。本部分对其中的一些工具做一个快速的查看,而在后续的章节中会更加深入的进行,那就是在命令行开发中。

注意:

对于AndroidSDK中包含的更多的工具,请查看Android文档。

emulator.exe

AndroidSDk中一个最重要的工具就是这个emulator.exe。emulator.exe启动Android模拟器。Android模拟器被用来在一个假的Android环境中运行你的应用程序。在本书写作时,还没有发布Android平台可用的硬件,emulator.exe将会是唯一的方法作为测试应用程序的平台。

你可以从Eclipse或者命令行中来运行emulator.exe。在本书中,通常会使用Eclipse启动Android模拟器环境。总之,为了给你所有信息关于在Eclipse之外用AndroidSDK编程。在第六章里会介绍emulator.exe的命令行使用来创建HelloWorld应用程序。

当使用Android模拟器来测试你的应用程序,有两个选择可以导航到用户界面。第一,带按钮的模拟器。你可以使用这些导航按钮来导航Android和任何的你为这个平台开发的应用程序。

提示:

电源On/Off,声音的大小按钮被隐藏在虚拟设备的旁边。当你用鼠标移过它们时,会被自动识别。

很多的高端手机现在都包含了触摸屏,第二个输入选项就是这个模拟的触摸屏。使用你的鼠标作为一个尖笔。模拟器屏幕上的对象可以相应鼠标的动作。

adb.exe
当你使用命令行编辑器时另外一个工具会变得非常的有用,它就是Android调试桥,或者adb.exe。这个工具允许你发出命令到模拟器工具。当你在命令行环境下工作时,这个adb工具允许你做下列工作。

●开始并且停止服务
●安装和卸载应用程序
●移动文件至模拟器或者从那里移动
MKSDCARD.exe

MKSDCARD.exe是一个非常有用的工具,当你测试一个应用程序,而这个程序需要读取或者写入文件到一个插入到移动设备的SD储存卡中。MKSDCARD.exe在你的驱动器中创建一个小的驱动并且会保留测试文件。然后模拟器会把这个小的部分当成一个SD储存卡。

DX.exe
DX.exe是AndroidSDK的编译器。当运行你的Java文件,DX.exe将创建一个带有.dex后缀—Dalvik可执行格式的文件。这些会被Android设备正确的理解和运行。

注意:

Android可执行文件是叫做Dalvik可执行文件,Dalvik虚拟机器以自己脉络来运行每一个应用程序,而且程序的优先权和Android核心程序一致。

activityCreator(.bat或者.pn)

activityCreator是一个简单的命令行工具被用来设定基本的开发环境。当从命令行运行时,activityCreator将设置一个需要的基本Android应用程序所需的壳文件。有了这些壳文件是非常有用的,特别是你不使用Eclipse。当你创建一个新项目时,AndroidpluginforEclipse通过呼叫activityCreator来设置这些壳文件。依据你运行的是哪一种环境类型,你会看到不同的activityCreator的脚本文件。如果你使用Windows环境,这个就会是.bat文件,否则就是python(.pn)脚本。简单的执行这些脚本,就会依次的使用正确的参数来呼叫真正的activityCreator过程。

AndroidAPIs-第四章(7

APIs或者叫做应用程序编程接口,是AndroidSDK的核心。一个API是应用程序开发者在特定平台上创建程序的功能,方法,属性,类别和库的集合。AndroidAPI包含所有你创建与Android为基础程序交互的特定信息。

AndroidSDk同样包含2套api,—谷歌的API和可选的API.后续的章节中将重点放在这些API上,因为你将利用它们写程序。现在,让我们快速的说明一下它们包含哪些你熟悉的使用。

谷歌api

谷歌API含在AndroidSDK中并且包含编程参考允许你绑定你的程序到现有的谷歌服务中。假如你写一个应用程序允许你的用户通过你的程序进入到谷歌提供的服务中,你需要包含谷歌的API.

找到android.jar文件,谷歌的API包含在com.google.*包装中。只有很少的包含了谷歌的API.一些包装随着API一起发布包含了图形,移动性,联系人和日历等工具。总之,我们会把本书中把重点放在谷歌地图上。

使用com.google.android.maps包装,这个包含了谷歌的地图,你可以创建一个应用程序无缝的和熟悉的谷歌地图界面对接。这个包装打开了一个等待着被开发的整个有用的应用程序世界。

谷歌api还包含了一套有用的包装,来允许你利用由Jabber开放源码社区开发的最新的ExtensibleMessaging和PresenceProtocl(XMPP)。使用XMPP,应用程序可以快速知道户主在场或者是否可用(从信息和通信中)。如果你要利用电话的短信功能来创建一个聊天类的程序,这个处理XMPP的API是非常有用的。

可选的api

AndroidSDK包含了一些可选的api,它包括了一些标准Androidapi未包含的内容。说它们是可选的api意味着这些功能在手持设备上可能出现也可能不出现。也就是说一些为Android平台创建的设备可能包含升级或者一些特性而其他的没有。当利用在你的应用程序中利用这些可选的API时,包含了你的编程选项。

其中的一个可选特性(本书的后面会使用)就叫做电话基础的GPS.AndroidLBS(位置基础的服务)api需要接受并利用设备上GPS单元的信息。如果结合AndroidLBSapi和谷歌地图api,你或许有一个非常有用的应用程序会实时的显示你的位置。

其它可选的api包含利用蓝牙,Wi-Fi,播放MP3,进入并激活3-D-OpenGL硬件等。

应用程序生命周期-第四章(8)

如果你有相当好的编程经验的话,你对应用程序的生命周期这一概念应该熟悉。一个应用程序的生命周期,由一些应用程序由开始执行到终止的步骤组成。每一个应用程序,不管是哪一种语言所写,都有一定的生命周期。Android应用程序也没有例外。本部分会仔细对比ASP应用程序和Android的应用程序的生命周期。

标准ASP程序应用程序生命周期

标准ASP应用程序的生命周期和一个Android的程序生命周期非常的类似。ASP应用程序从开始到结束有5个步骤。这些步骤对所有的ASP程序是一致的。并且界定了ASP程序是什么。这些步骤按照次序如下:
1.Application_Start(程序开始)
2.Event(事件)
3.HTTPApplication.Init
4.Disposal
5.Application_End
提示:

有些ASP的参考材料考虑Disposal和Application_End在生命周期中成为一个步骤。但是,Disposal呼叫可以到达Application_End之前被打断。这个可以允许程序在真正结束之前执行特定的功能。

当应用程序被从服务器要求执行,开始呼叫Application_Start。这个过程依次的通向过程处理。当所有相关的应用程序模块被装载,HTTPApplicaation.Init被呼叫。程序执行事件,并且当用户试图去关闭它,Dispose被呼叫。Dispose然后转移过程到Application_End过程,来关闭程序。

这是一个相当标准的应用程序生命周期。大多数的程序是这个生命周期:一个应用程序被创建,装载,拥有事件,并且被关闭。下面说明和Android应用程序生命周期的对比。

Android应用程序生命周期是唯一一个系统控制多的应用程序生命周期。所有的Android应用程序,或者Actiities都运行在自有的过程中。所用的运行过程都被Android观察,并且取决于活动是如何运行的(就是说,一个前台活动,一个后台活动)Android可能选择去结束一个消耗系统资源的活动。

注意:

当决定是否关闭一个活动时,Android会考虑一些因素,如用户输入,内存使用和过程时间。一个Android或者的生命周期以一些特定的方式被称呼:

●onCreate
●onStart
●Process-specificevents(forexample:launchingactivitiesoraccessingadatabase)
●onStop
●onDestroy

与其它程序的逻辑一样,一个Android应用程序被创建,过程开始,事件被执行,过程停止,并且应用程序结束。虽然有一些不同,很多的程序开发者应该不会对这样的生命周期感到别扭。

问专家:
Q:谷歌会升级AndroidSDK吗?

A:是的。从我开始写这本书的时候,谷歌已经升级了AndroidSDK很多次了.谷歌会在Android的网站上发布最新的版本。
Q:会有任何API试用版出现在最终产品中吗?
A:或许不会。API试用版创建出来是为了炫耀产品能力的。虽然它们可能是核心解除的包含一些在API试用版里元素的应用程序,我们应该看不到月球登陆这个游戏出现在最终产品中。

第五章Android程序:HelloWorld!

Android程序:HelloWorld!-第五章(1

关键技能和概念

●创建新的Android项目

●同Views一起工作

●使用一个TextView

●修改main.xml文件

●在Android模拟器上运行应用程序

为了让你能够对在Android上编程有一个良好的印象,在第六章,你会在Windwos平台和Linux平台上使用AndroidSDK创建命令行应用程序。或者说,本章包含了在Eclipse创建程序的过程,第六章包含了使用命令行工具的创建过程。因此,在继续之前,你应当检查你的Eclipse的开发环境是否被正确的配置。再次回顾一下第三章关于AdnroidSDK的PATH声明。同时要确保JRE也是在你的PATH声明中。

提示

如果当你运行命令行示例,有任何与配置有关的问题时,请参考第二章和第三章提到的步骤,并且查看AndroidSDK文档。

在Eclipse中创建你的第一个Android项目

要开始你的第一个Android项目,打开Eclipse.当你第一次打开Eclipse,它会打开一个空开发环境,这就是你要开始的地方。你的第一个任务是设置并且命名一个工作空间。选择文件|新建|Android项目,使你能够创建一个Android特有的应用程序向导。

注意:

不要从新建菜单上选择Java项目。你的Android应用程序是在Java中写的,并且你在Java项目中进行开发,这个选项会创建一个标准的Java应用程序。选择Android项目来创建一个Android特有的应用程序。

如果你没有看到啊Android项目这个选项,这就说明在Eclipse中,Androidplugin没有被完全或者正确的安装。重新检查第三章中关于Androidplugin的安装程序来修正这个问题。

新的Android项目向导为你创建2个东西:

●一个绑住AndroidSDK的壳程序。这个将允许你使用所有Android库和包来进行编码工作,并且允许你在合适的环境中调试程序。

●新程序的第一个壳文件。这些壳文件包含一些必要的支撑你将要编写程序的文件。就如同一个在VisualStudio中,它会在你的文件中产生的一些代码。使用Eclipse中的Android项目向导产生一些初始的程序文件和一些Android创建的代码。

另外,新的Android项目向导包含一些你必须输入的选项。

在项目的名称那个字段,只是为了举例,使用HelloWorldText这个名字,这个名字非常的容易把这个HelloWorld项目从其它你将要在本章中创建的项目分别开。

在内容那个区域,保持缺省的选择:在工作区中创建一个新的项目这个选项必须被选中。并且使用缺省的位置这个选择框也应当被选中。这个将允许Eclipse在你缺省的工作区路径中创建你的项目。这样做的好处是很容易对你的项目进行排序,管理和查找。例如,如果你在工作在一个Unix基础的开发环境中,这个路径指向Home路径。如果你工作在一个Windows的环境中,工作路径将会是C:/Users/<username>/workspace。总之,有一些原因,你可能需要不选中缺省位置的选择框并且选择一个其它的路径。如果是这样的话,不管那个位置的选项,自己选一个好了。

另外一方面,如果你在Eclipse设定(在第二章的最后一节中)中没有选中“使用这个作为缺省并且不要再询问”,你可能被要求定义一个项目的位置。在Eclipse的设置中选中“所有的新项目使用缺省工作空间路径设定”(并且提供在新Android项目向导位置字段)。如果你在Eclipse设定过程中不选中这个选择框,你需要通过点击浏览按钮并导航来选择一个路径。最后三个选项是在属性区域中。这些属性定义了你的项目是如何被统一到Android环境中。在包装名称字段,你为程序包装定义,例如:android.app.Activity或者com.google.android.map.MapActivity.

注意:

包装名称遵从了标准的java命名指导方针,这个方针的建立是为了减少同名程序发布的风险。最高层的包装名称是公司的域名(如com,org和net)这个遵从了域名,如google。最后,一个为包装内容的描述性标题被提供。在本章中,HelloWrold的包装名称将省略com来识别,因为这只是一个文本程序而且不会被发布。所有在本书中将来创建的包装将是可发布的并且是用com标识符

对于这个HelloWorldText应用程序,使用_programmers_guide.HelloWorldText这个名字。这个名字识别了属于这个程序的编码而且区别开你将开发的其他应用程序。

注意:

如果你注意到你输入的这个屏幕,你会注意到当你输入程序名称,一个错误显示在本向导页面的顶端说你必须正确的填写所有的字段来继续。这个错误信息是提前并且有一些难以理解的因为你还没有填写完其他的字段。如果你看到这样的错误提示信息,忽略它并且继续完成下面两个字段的填写。

下一个属性字段,活动的名称,这个要求输入是因为它会在程序的主屏幕上被提到。想一下,活动是一个显示你应用程序的窗口。没有活动,你的应用程序将无法做更多的工作。因为Android应用程序可以被一些活动组成,新Android项目向导需要知道哪一个活动回事缺省的。活动名称的字段是要求输入并且没有缺省值的,所以如果你必须提供一个来继续下去,本例使用HelloWorldText。这个保证了程序的简单而且在这里是个描述。

最后的属性字段,应用程序名称,应用程序名称描述。这个就是安装在设备上用来管理的应用程序名称。再次说明,为了方便起见,使用HelloWorldText作为程序的名称。

提示:

程序名称和活动名称字段不一定要匹配。事实上,很多的编程者习惯于一个老的惯例,那就是程序的开始画面或者叫做主页。使用你感觉舒服的名字,为了说明的目的,本章假定你使用了建议的名字。

点击结束来结束创建过程。本向导运行一个后台程序自动产生支持一个Android应用程序所需要所要求文件和文件夹。当过程结束时,你会有你的第一个Android应用程序项目,

提示:

如果结束的按钮不可用,你可能字在属性页面的字段内犯了一个错误。确保属性页面填写正确,Eclipse不会允许任何输入错误的可能引起问题发生的信息。返回确保所有的属性字段是正确的。下一节会仔细检查自动产生的Android文件和一些为你应用程序产生的壳条目的目的。

仔细查看Android创建的文件-第五章(2)

本部分讨论Android刚刚创建的新文件。一个非常全面的结构已经为你创建好了,而且,如果你不知道要看什么的话,你最终或许会在不应该放置代码的地方放上代码。有一些Android提供的文件你需要去修改,并且有一些你不能修改。知道这些信息会避免你不得不去重新创建项目。

在你打开的项目中,首先看一下PackageExplorer,一个或者二个在主要开发区域面板的左边上的制表符。

注意:

如果你的PackageExplorer没有打开,你可以通过选择Windows|ShowView|PackageExplorer.激活它。

你应当看到一个根目录,本例中叫做HelloWorldText。根目录是你所有项目文件的“家”或者“容器”,你自己和Android创建的文件都会放在这里。从PackageExplorer很容易进入。现在会有比较少的一些项目在你的根目录里:一个AndroidManifest.xml文件,在一个参考库里的一个包装,和三个目录(res,assets和src)。我们轮流来讨论这些项目。

AndroidManifest.xml
AndroidManifest.xml文件是一个指定全局设定的地方。如果你是一个ASP.NET的开发者,你可以认为AndroidManifest.xml是Web.config和Global.asax的二合一。(如果你不是APS.NET的开发者,AndroidManifest.xml就是意味着是个存放设定的地方)。AndoridManfiest.xml将包括如程序许可,活动,和意向过滤器等的设定。标准的AndroidManifest.xml文件应当包含下面的信息:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="testPackage.HelloWorldText">
<applicationandroid:icon="@drawable/icon">
<activityclass=".HelloWorldText"android:label="@string/app_name">
<intent-filter>
<actionandroid:value="android.intent.action.MAIN"/>
<categoryandroid:value="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
</application>
</manifest>

如果你创建一个应用程序,你将要在这个文件里增加信息。注意,你提供的包装名称已经列在这里了,同样包括你的活动所需的动作。

引用库和目录-第五章(3)

一个引用库的列表也包含在根目录里了。通常,对于一个新手的项目,你应当只看一个库。扩展引用库的分支并且仔细查看当前你的应用程序项目所引用的库。由于它是一个新的Android项目,你会在项目引用里看到一个库,那就是android.jar,AndroidSDK(如果你熟悉JavaSDK,android.java是同Java的rt.java非常类似的文件。在rt.java里封装了很多java的API)。AndroidPlugin确保了这个是唯一被你应用程序引用的库。应用程序需要引用SDK来获得进入在SDK库内所有类别,比如你的Views,Controls或者甚至谷歌的API。

注意:

Eclipse可以允许你增加用户定义的外部库。但是除非你确信这些外部的引用将在Android应用程序上工作,所以增加它们之前请三思而后行。

目录(路径)

有三个目录在项目的根目录——res,assets和src。每一个都有着显著的目的。这些目录在你应用程序的运行中扮演着完整的角色。
res目录

res目录是你项目资源放置并且编译你的应用程序的地方。当你创建一个新的Android项目,res目录包含3个子目录:drawable,layout,和values。你会在很多的项目中使用drawable和layout分别放置并显示图形和布局。而values目录放置遍及程序全局的字符串。

注意:

一个引用到res目录和它内容是被包含在R.java文件中,在src目录中。我们会在本章的后面详细的讲解。drawable目录包含你程序可以使用和引用的真实图形。layout目录放置XML文件,当构造它的界面时,main.xml文件被应用程序引用。在本书的绝大多数应用程序中,你会去编辑在layout目录下的main.xml文件。这将允许你插入Views到程序的可视布局并显示它们。一个原始的main.xml文件包含下列代码:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="HelloWorld,HelloWorldText"
/>
</LinearLayout>

最后一个在res目录下的文件夹是values,放置了XML文件命名字符串。strings.xml文件是用来放置程序引用的全局字符串。

assets目录

assets目录用来放置“原料”文件的。在这个目录中可以包含为流媒体和动画准备的音频文件。因为Android模拟器的beta音频驱动没有优化,我不会在本书的应用程序中使用任何的音频文件。

src目录

src目录包含项目里所有的源文件。当项目一创立,就会包含两个文件R.java和<活动>.Java(本例中是HelloWorldText.java)

注意:

<activity>.java总是根据你的活动来命名。

HelloWorld!自动产生文件的详解-第五章(4)

R.java是一个由Androidplugin自动产生并添加到你的应用程序中的文件。这个文件包含到drawable,layout和values目录的指针(或者目录里其它的项目,本例中是字符串和图标)。你不应当必须直接修改这个文件。在你大多数的程序里会总是提到R.java.为HelloWorldText自动产生的代码如下:

/*AUTO-GENERATEDFILE.DONOTMODIFY.
*
*Thisclasswasautomaticallygeneratedbythe
*aapttoolfromtheresourcedataitfound.It
*shouldnotbemodifiedbyhand.
*/
packagetestPackage.HelloWorldText;
publicfinalclassR{
publicstaticfinalclassattr{
}
publicstaticfinalclassdrawable{
publicstaticfinalinticon=0x7f020000;
}
publicstaticfinalclasslayout{
publicstaticfinalintmain=0x7f030000;
}
publicstaticfinalclassstring{
publicstaticfinalintapp_name=0x7f040000;
}
}

注意:

R.java文件的注释部分提供了关于这个文件起源的解释。它说明文件由aapt工具创建。在第六章,当你创建命令行版本的HelloWorld时,你将用命令行工具创建所有自动产生的文件。

<activity>.java文件在src目录下,你会花费大多数时间在这个文件上。本例是HelloWorldText.java.这个是你的创建新的Android程序向导时由Androidplugin创建并与活动名称匹配来命名的。不像本部分大多数你已经看过的文件,这个文件完全可以编辑。事实上,如果你不修改代码,它会为了做一点点的事情。

Packageandroid_programmers_guide.HelloWorldText;
importandroid.app.Activity;
importandroid.os.Bundle;
publicclassHelloWorldTextextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
}

在文件上面的三行是标准预处理器指令——那就是,如大多数的编程语言,在程序处理前声明是指令到编译然后运行。在本例中,你在packageandroid_programmers_guide.HelloWorldText.有了定义和包含。

下两行通过android.java从AndroidSDK中导入特别的包装。

importandroid.app.Activity;

importandroid.os.Bundle;

这些行告诉项目去包括所有你程序里面的代码之前包括所有来自导入包装的代码。这两行对于基本的Android程序非常的重要并且不应当被移除。

提示:

如果你在项目里没有看到.android.os.Bundle的输入声明,在开发窗口展开树形。Eclipse会给出在第一个下面所有输入的声明,所以你必须展开树形结构来看其余的声明。

现在让我们关注到你的类HelloWorldText,你会看到它扩展了Activityclass.Activity被从前一行导入。所有的程序源于Activityclass,并且在Android上运行一个程序会需要这个起源。运行并在屏幕上显示某些东西必须从Activity起源。

HelloWorldText的类保持了需要创建,显示并且允许程序的代码。在HelloWorldText的类中,现在只有一个方式来定义代码onCreate().

onCreate()方法把冰柱作为一个束。那就是所有点钱状态的信息被搜集作为一个冰柱对象并且被保存在内存了。在本程序中你不能直接处理冰柱,但是你需要知道它的存在和目的。

文件中的下一行是真正可感受到的动作:

setContentView(R.layout.main);

setContentView()方法把Activity的内容设置到指定的源。在本例中,我们通过R.java文件中的指针使用layout目录里的main.xml文件。现在的main.xml文件包含了HelloWorldText的屏幕和一个TextView。TextView起源于View并且被用来在Android环境中显示文本。回头再看main.xml的内容,你会看到它包含了下面的行:

android:text="HelloWorld,HelloWorldText"

setContentView()方法被告诉去设置main.xml作为当前的View,并且main.xml包含了一个宣称“HelloWorld,HelloWorldText”的TextView。现在可能比较安全的去编译并运行HelloWorldText。要测试这个,运行你的HellowWorldText程序。选择Run|RuntoopentheRunAsdialogbox,选择一个Android应用程序,然后点击OK。

你新建立的项目包含创建HelloWorld应用程序自身的代码。总之,这个并不是太吸引人,而且也没有教你太多参与Android应用程序编程。你需要仔细研究项目本身并且看看项目是如何显示“HelloWorld!”信息的。

当你创建一个新的,由Androidplugin修改的main.xml程序的Android项目时究竟发生了什么。这是一个完美修改Android中UI的一个例子。当项目被创建时,下面的代码行由AndroidSDK增加到main.xml中:

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="HelloWorld,HelloWorldText"
/>

在我讨论xml文件中存在的TextView时,我没有说它为什么能没有任何相应的代码就可以工作。在本书的早些时候提到有两种方式来为Android设计UI:通过代码,和通过main.xml文件。在先前的代码例子中,在xml文件中创建了一个TextView并且设定文本为“HelloWorld,HelloWorldText”。编辑main.xml中的这一行,按照下面的方式:

android:text="ThisisthetextofanAndroidTextView!"

重新运行项目,并且你的运行结果应该如图所示(略)。利用一些时间并且用xml的TextView做实验。然后你可以转移到用另外一种方式来创建一个HelloWorld!应用程序了。

HellowWorld!再来一次-第五章(5)

在本部分中,你将创建另外一个HelloWorld!这次,你会使用编程代码而不是使用xml文件,并且你会自己来做大部分工作。第一步就是把main.xml里面已经有的TextView代码删除。下面就是TextView部分的代码。完全的删除它,使你的应用程序是一个空的壳。

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="HelloWorld,HelloWorldText"
/>
在移除了TextView代码以后,你的main.xml文件应该像下面这样:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</LinearLayout>

现在你有一个干净的main.xml文件了,并且一个干净的应用程序壳,你可以开始增加可以在屏幕上显示“HelloWorld!”的代码了。从打开HelloWorldText.java并移除下面的代码行开始:
setContentView(R.layout.main);

注意:

你仍旧需要为你新的应用程序来设置一个ContentView;但是你需要执行和现在的这个有一点细微的不同,所以在这里最好把完整的声明移除。

这条使用setContentView()来把main.xml显示在屏幕上。因为你不会去使用main.xml来定义你的TextView,所以你不会去设置你的view。取而代之,你会用代码来构建TextView。

下一步是从android.widget中导入TextView包装。这样你可以进入到TextView并且允许你创建自己的实例。把这些代码放置到当前HelloWorldText.java文件靠近顶部,现有导入声明的importandroid.widget.TextView的地方;

现在,创建一个TextView的实例。通过创建这个TextView实例,你可以在屏幕上显示文本而不需要直接修改main.xml文件。在onCreate()声明的后面放置下面的代码:
TextViewHelloWorldTextView=newTextView(this);
注意

TextView在当前上下文中取得一个句柄作为一个变量。传递这个到TextView并和当前的上下文相关联。如果你跟从SDK的等级,HelloWorldText扩展至Activity,而Activity扩展至ApplicationContext,而再扩展至Context。这就是你如果传递TextView的。

先前的代码行创建一个名叫HelloWorldTextView的TextView的实例,然后例示HelloWorldTextView,通过设置它到一个新的TextView。这个被上下文传递的新的TextView被完全的例示。

现在,这个TextView已经被定义好了,你可以在里面增加文本。下面的代码指定“HelloWorld!”文本到TextView:
HelloWorldTextView.setText("HelloWorld!");

这一行允许你设定你的TextView文本。setText()允许你赋值一个字符串到TextView。

你的TextView已经被创建而且现在包含了你想要显示的信息。但是,如果简单的指定“HelloWorld”到TextView中不会在屏幕上显示任何的东西。如前面所讨论的那样,你需要设置ContentView来在屏幕上显示东西。所以,你必须使用下面的代码来设置TextView到上下文并且在屏幕上显示:
setContentView(HelloWorldTextView);

仔细查看本行代码,你会发现你把setContentView到TextView。前面的三行代码是制作你的HelloWorld!应用程序。你创建一个Textview,赋值你的文本,然后显示在屏幕上。所有的事情就是这样,根本不复杂。完整的HelloWorldText.java文件应当像下面这样:

packageandroid_programmers_guide.HelloWorldText;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.TextView;
publicclassHelloWorldTextextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
/**HelloWorldJFD*/
/**BEGIN*/
/**CreateTextView*/
TextViewHelloWorldTextView=newTextView(this);
/**SettexttoHelloWorld*/
HelloWorldTextView.setText("HelloWorld!");
/**SetContentViewtoTextView*/
setContentView(HelloWorldTextView);
/**END*/
}
}
现在在Android模拟器中那个编译并且允许这个新的HelloWorld!应用程序。选择Run|Run或者按下CTRL-F11在Android模拟器中启动这个应用程序。

你刚刚创建了一个完整的Android活动。这个小的项目展示了一个常规HelloWorld!应用程序的运行。你在Android模拟器中通过设置TextView到Activity'sContentView中并且在手机上显示“HelloWorld!”信息。下一节中会用一个细微不同的方式执行一个HelloWorld!,使用一个图形。

HelloWorld!使用一个图形-第五章(6)

在本章,你会使用一个在编程中大家熟知的活动来熟悉HelloWorld!应用程序:显示图形。现在的电脑如果不显示图形就太过分了。这些图形显示的重点在于如何让它在屏幕上显示出来的能力。

大概5年以前,在手机上显示图形是非常困难的一件事。和图形一起工作是我们这些现代电脑用户认为理所应当的事情之一了。我们每天看着不同类型的窗口,甚至没有想到这个是影像被发送到屏幕的。这个版本的HelloWorld!程序将显示一个HelloWorld!的图片。

对于这个应用程序,使用NewAndroidProjectwizard(新Android项目向导)来创建一个新的项目并且命名为HelloWorldImage。

程序创建好后,找到main.xml文件并把其中的TextView代码删除,这样你就有一个干净的项目文件了。如果你没有删除这个代码,最终将再次显示文本类型的HelloWorld!程序。

在你开始写代码之前,你需要一个需要显示的图片。在你可选的图形程序内创建一个小的图片。为了方便起见,选择MicrosoftPaint,但是任何的程序都可以给你想要的图片。

为这个图片命名为helloworld.png并且把它保存到%workspace%/HelloWorldImage/
res/drawable目录下。

注意:

不要把图片的名称大小写搞混了。图片的名称只应当是小写字母。如果你插入了大写字母,当你试着在Eclipse中用这个文件时,会得到一个错误的提示。

在复制这个文件到正确的目录之后。helloworld.png这个图片应当显示在项目窗口中,在drawable目录下。

打开R.java并且看一下它的代码。Eclipse应当增加了一个指针到helloworld.png.你的R.java文件应当同下面的类似:
/*AUTO-GENERATEDFILE.DONOTMODIFY.
*
*Thisclasswasautomaticallygeneratedbythe
*aapttoolfromtheresourcedataitfound.It
*shouldnotbemodifiedbyhand.
*/
packageandroid_programmers_guide.HelloWorldImage;
publicfinalclassR{
publicstaticfinalclassattr{
}
publicstaticfinalclassdrawable{
publicstaticfinalinthelloworld=0x7f020000;
publicstaticfinalinticon=0x7f020001;
}
publicstaticfinalclasslayout{
publicstaticfinalintmain=0x7f030000;
}
publicstaticfinalclassstring{
publicstaticfinalintapp_name=0x7f040000;
}
}

有了一个干净的壳作为起点,并且一个可用的句柄到你要显示的图像,你可以开始增加你的代码了。可以以两种观点来看这个应用程序:XML基础的UI和代码为基的UI。

HelloWorld!代码为基的UI-五章(7

假定你对HelloWorldText那个部分能够理解了,这个版本的HelloWorld!会比较的熟悉了。要开始的话,你需要输入显示图片功能的包装。文本显示使用一个TextView,图片显示就要用ImageView了。因此,你必须输入ImageView包装。和TextView一样,ImageView包含在android.widgets里:

importandroid.widgets.ImageView;

注意

TextView和ImageView都是从View派生的。这样就使得两者结构非常的类似并且容易执行。

包装导入(输入)后,你可以创建你的ImageView并且在屏幕上显示它了。示例ImageView和示例ImageView是一样的。创建一个ImageVIew示例并且把它传递给上下文使用下面的代码:
ImageViewHelloWorldImageView=newImageView(this);

下面是能够看到在ImageView和TextView之间的不同之处。这一步是关于设定你想要显示的东西。在TextView例子中,你使用setText()来设定TextView的文本为“HelloWorld!”,虽然TextView和ImageVIew都是派生于View,但是它们还是不同的并且因此要求不同的方式。显然,你也不会来为ImageView使用setText()。你需要使用setImageResource()来在ImageView中显示图片。并把句柄从R.java(句柄的语法是R.drawable.helloworld)传递到helloworld.png:

HelloWorldImageView.setImageResource(R.drawable.helloworld);

最后,要把图片传输到屏幕,你必须设定ContentView。正如你在TextView做的一样,把ImageView传递到ContentView中。ContentView的工作就是把设定到对象的东西传递到屏幕上。
SetContentView(HelloWorldImageView);
YourfinalHelloWorldImage.javafileshouldlooklikethis:
packageandroid_programmers_guide.HelloWorldImage;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.ImageView;
publicclassHelloWorldImageextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
Chapter5:Application:HelloWorld!77
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
/**HelloWorldImageJFD*/
/**BEGIN*/
/**CreatetheImageView*/
ImageViewHelloWorldImageView=newImageView(this);
/**SettheImageViewtohelloworld.png*/
HelloWorldImageView.setImageResource(R.drawable.helloworld);
/**SettheContentViewtotheImageView*/
setContentView(HelloWorldImageView);
/**END*/
}
}

编译HelloWorldImage并且在Android模拟器中运行。在下一节,你将再次显示helloworld.png但是这次使用XML而不是代码。

HelloWorld!XML为基的UI-第五章(8)

本章通过比较使用XML为基的UI和代码为基的UI来给你一个比较的例子。正如你将要看到的,使用main.xml要求和代码为基的方式差不多同样多的代码来把图片发送到屏幕上。但是两个过程的句法不同。

如果在上个例子中所作使用同样的项目,从HelloWorldImage.java文件中移除TextView代码。干净的文件应该看起来像这个一样:
packageandroid_programmers_guide.HelloWorldImage;
importandroid.app.Activity;
importandroid.os.Bundle;
publicclassHelloWorldImageextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
}
}

现在你有一个清白的历史可以开始,把上面的移到main.xml中,你需要为一个ImageView增加定义。开始增加一个空的ImageView标签到你的main.xml中:
<ImageView
/>

你需要编辑ImageView的4个属性:android:id,android:layout_width,android:layout_height,和android:src。你会把这些属性添加到标签中,这些控制标签如何在屏幕上显示。

android:id属性被用来作为ImageView的识别符。android:id属性可以在ImageView代码中被提交处理。可以等一会儿在R.layout.imageview中使用@+id/<name>句法来给ImageView赋值一个识别符:
android:id="@+id/imageview"

本行插入一个以imageview命名,自动产生的ID,@+id到R.java内。

下两个你必须定义的属性是:android:layout_width和android:layout_height。这些属性控制图片如何填充屏幕。有两个可选择的选项。fill_parent值定义全部显示图片,wrap_content显示定义的图片尺寸,可能会丢失图像清晰度。本例中使用wrap_content:
android:layout_width="wrap_content"
android:layout_height="wrap_content"

最后一个需要赋值的属性是最重要的变量型的属性:android:src.这个属性指向你要显示的图片。例如,指向属性到drawable/helloworld图片:

android:src="@drawable/helloworld"
YourfullImageViewtagshouldlooklikethis:
<ImageViewandroid:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/helloworld"
/>

最后,在图像显示前,你必须把main.xml通过setContentView传递到HelloWorldImage.java中:
setContentView(R.layout.main);
编译并运行HelloWorldImage。

在本章结束前,再试一下另外一件事。回到main.xml中并且把wrap_content改成fill_parent。完成后,你的main.xml文件应当如下:
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageViewandroid:id="@+id/imageview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/helloworld"
/>
</LinearLayout>
再次运行程序来检查wrap_content和fill_parent之间的不同之处。

使用TextView和ImageView

使用一些在本章学到的技巧和技术来创建一个新的HelloWorld!应用程序。创建一个即用TextView也用ImageView的程序,图片放在屏幕上并且带有一个文本的标题。这个比在一个Activity中使用一个View多一点难度。多用Views看看你能创建什么。

下一章还会待在HelloWorld!上,不过还讨论命令行编程。

问专家

Q:Android和大多数的APIs一样有Label或者LabelView可以用吗?

A:没有。所有的文本显示通过TextView显示。你可以,和其他人一样,自定义一个和Label功能的View,并把它命名为LabelView,但是Android本身并没有LabelView这个包装。

Q:有任何的优点使用<应用程序>.java,而不是main.xml来创建Views吗?

A:没有文件说速度或者处理器方面的二者之间的差别,但是一个关键的优点是,使用main.xml,需要为你的Activity预先确定一组Views。然后,在编码中,你可以从一个View跳到另外一个View,而不需要手动创建它们。

第六章使用命令行工具和Android模拟器

使用命令行工具和Android模拟器-第六章(1)

关键技能和概念
●使用AndroidSDK命令行工具

●创建一个命令环境

●用一个壳导航到Android服务

●在Linux里使用AndroidSDK

到目前为止,本书包含了一些非常宽的科目关于学习如何运行Android平台。就这一点来说,对于使用Eclipse来创建并运行一个小的Adnroid应用程序,你应该非常的舒服。你创建一个新的项目,编辑main.xml文件和<activity>.java文件,然后编译R.java文件。这些是创建Android应用程序的一些基本技能。

在本章中,你会通过用命令行应用程序开发来扩展你的这些技能。Android开发没必要必须限定在EclipseIDE环境里进行。AndroidSDK提供了许多命令行工具,可以在没有图形化的IDE的帮助下,开发完整的应用程序。你会使用这些命令行工具首先在Windows,然后再Linux下来创建,编译和运行HelloWorld!应用程序。

利用WindowsCLI创建一个壳活动-第六章(2)

AndroidSDK有很多的工具来帮助你创建并编译Android应用程序。这些工具帮助那些不愿意使用,或者没有所支持GUIIDE系统的用户来工作的。总之,如果你一直在用Eclipse在编程,你仍然需要知道AndroidSDK命令行工具和它的功能。

当你运行Android相关的功能,如创建一个Android项目或者在Android模拟器内运行一个应用程序,你实际上是在呼叫到命令行工具的连接器。无论是从命令行接口或者GUIIDE运行,这些Android命令行工具是AndroidSDK的真正核心。

在下面的章节中,我演示Android工具的功能。ActivityCreator.bat是一个强有力的工具,被用来为你的程序建立一个活动壳(ActivtyShell)。

运行ActivityCreator.bat-第六章(3)

ActivityCreator.bat文件应当在AndroidSDK的…/tools/文件目录下。大多数“前向”命令行工具都放置在工具目录的根目录下。“前向”工具是依次呼叫在工具根目录下更深目录的工具。ActivityCreator.bat是工具根目录下一个示例的工具,它运行时会呼叫另外一个工具。使用vi,Notepad或者一个文本编辑器,打开ActivityCreator.bat。它应当包含下面的代码:

注意

ActivityCreator.bat是定义为MicrosoftWindows版本的AndroidSDK。在本章的后面部分,你将会学习ActivityVreator.py。这个是Linux版本的ActivityCreator。

@echooff
remCopyright(C)2007GoogleInc.
rem
remLicensedundertheApacheLicense,Version2.0(the"License");
remyoumaynotusethisfileexceptincompliancewiththeLicense.
remYoumayobtainacopyoftheLicenseat
rem
remhttp://www.apache.org/licenses/LICENSE-2.0
rem
remUnlessrequiredbyapplicablelaworagreedtoinwriting,software
remdistributedundertheLicenseisdistributedonan"ASIS"BASIS,
remWITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
remSeetheLicenseforthespecificlanguagegoverningpermissionsand
remlimitationsundertheLicense.
remdon'tmodifythecaller'senvironment
setlocal
"%~dp0\lib\activityCreator\activityCreator.exe"%*

浏览整个的rem声明(批处理文件注释声明),你会看到在文件的底部有一个实用的代码。ActivityCreator.bat被用来呼叫…/tools/lib/activitycreator/目录里的ActivityCreator.exe。这个ActivityCreator.bat是一个工具的示例,它只是放置在SDK其它工具的前端。

所以,ActivityCreator.bat(或者ActivityCreator.exe)做了什么?ActivityCreator被用来建立指向在哪里需要开始开发你的应用程序初始文件的开发环境。这个路径结构和在第五章第五章(1)程序:HelloWorld!讨论过的结构一致。ActivityCreator.bat创建R.java,AndroidManifest.xml,和所有你应用程序需要的支持文件。

让我们现在转到命令行环境并且浏览ActivityCreator。在开始菜单,点击运行,在运行的对话框内输入CMD或者COMMAND,然后点击确定。

执行这个命令会启动命令窗口。这个窗口和老版本的DOS操作系统环境相同。命令窗口出现后,在光标>后输入ActivityCreator

提示

Microsoft命令提示符接口没有大小写限制。在缺省情况下,如果你使用了大小写限制的不同的开发环境,本章中显示的屏幕截图会不同。

运行命令ActivityCreator,实际运行的是ActivityCreator.bat,产生下面的输出:
ActivityCreatorScript
Usage:
activityCreator[--outoutdir][--ideintellij]yourpackagename.ActivityName
CreatesthestructureofaminimalAndroidapplication.
Thefollowingwillbecreated:
-AndroidManifest.xml:Theapplicationmanifestfile.
-build.xml:AnAntscripttobuild/packagetheapplication.
-Res:Theresourcedirectory.
-Src:Thesourcedirectory.
-src/your/package/name/ActivityName.javatheActivityjavaclass.packageName
isafullyqualifiedjavaPackageintheformat<package1>.<package2>...(Withat
leasttwocomponents).
-Bin:Theoutputfolderforthebuildscript.
Options:
--out<folder>:specifieswheretocreatethefiles/folders.
--ideintellij:createsprojectfilesforIntelliJ

这个输出简单的指示了你需要提供更多的信息来运行ActivityCreator。更确切的是,你需要传递给命令一个你需要建造的壳应用程序的位置。

注意

从ActivityCreator输出的命令给了你很多不仅仅是你没有提供足够信息的信息。它给了你一个完整的使用工具创建的文件列表。这个文件列表和第五章第五章(1)程序:HelloWorld!看起来相似。虽然build.xml没有直接展示在Eclipse用户目前。

回到命令窗口并且使用下面的选项运行ActivityCreator(如果你使用Unix/Linux环境编程,ActivityCreator同样接受unix-风格路径参数):

--outc:\AndroidHelloWorld\android_programmers_guide.HelloWorldCommandLine

--out选项告诉ActivityCreator你想要它输出点东西。这个命令选项使用两个参数,<folder>和<package.activity>。第一行告诉ActivityCreator在一个不存在的文件夹里创建壳应用程序。c:\AndroidHelloWorld.

提示

如果你定义的文件夹或者路径不存在,AcitivityCreator将会在过程中自动创建一个。

--out的第二个参数是包装名称和活动名称。根据前面章节的习俗,这个实例在本项目中使用android_programmers_guide作为包装的名字并且HelloWorldCommandLine作为活动的名称。

注意

成功运行ActivityCreator并设置你的初始环境和运行新的AndroidProjectwizard是一致的。

NOTE
theparametersneededtosuccessfullyrunActivityCreatorandsetupyourinitial
environmentarethesameasthoserequiredbytheNewAndroidProjectwizard.

在新命令行选项和参数下运行ActivityCreator。你应当从工具的输出看到下面的内容:(略)。下面的章节涵盖了由ActivityCreator创建的文件,因为和由Eclipse创建的文件有一些不同。

项目结构-第六章(4

ActivityCreator为你的开发创建了一组文件目录和文件。浏览c:\AndroidHelloWorld\来看一下它的结构。ActivityCreator创建的结构如下图所示(略)

因为在Eclipse环境之外工作,你有一个不同的环境。当你在例如Eclipse的IDE内工作时,一些特定的功能在场景之后为你工作。假如你工作时没有任何IDE的帮助,ActivityCreator创建了一个文件来概述编译器如何的工作来创建你的项目。当手动运行ActivityCreator,为你创建build.xml文件。当你使用Eclipse来开始一个Android项目时,这个文件并没有被创建。

它包含了一个指令组,解释了如果转变你的.java文件到一个功能性的Android项目中。这个build.xml文件告诉编译器它需要怎么做来创建你的应用程序。在这个例子中的编译器是ApacheANT,一个Java基础的工具,被用来使用构造脚本文件到编译的项目。从http://ant.apache.org/bindownload.cgi.下载ANT。

一旦你下载并安装了ANT,你必须把它增加的PATH声明中。在Windows环境中,右击“我的电脑”,并且选择“属性”来改变PATH声明。build.xml文件被特地为ANT创建用于编译你的Android应用程序。它应该在你项目的根目录,如上图所示。用文本编辑器打开build.xml并看一下它里面有些什么。

第一个部分的build.xml包含了可以被用户编辑的代码。这部分是其它部分的开始,因为剩下的部分不应当被修改。
<?xmlversion="1.0"?>
<projectname="HelloWorldCommandLine"default="package">
<propertyname="sdk-folder"value="c:\Android\android-sdk_m5
rc14_windows\android-sdk_m5-rc14_windows"/>
<propertyname="android-tools"value="c:\Android\android-sdk_m5
rc14_windows\android-sdk_m5-rc14_windows\tools"/>
<propertyname="android-framework"value="${android-tools}/lib/framework.aidl"
/>
<!--Theintermediatesdirectory-->
<!--Eclipseuses"bin"foritsownoutput,sowedothesame.-->
<propertyname="outdir"value="bin"/>

第一部分的build.xml包含下面属性的值:
●Projectname项目名称
●AndroidSDKlocationAndroidSDK位置
●AndroidtoolslocationAndroid工具位置
●AndroidframeworklocationAndroid框架位置
●Outputlocation输出位置

如果你需要为项目改变任何的这些参数,你可以在这个文件做。但是在build.xml接下来的参数,你会立即看到通知你的警告,告诉你不应当去编辑剩下部分的值:
<!--Nouserservicablepartsbelow.-->
Followingthiswarninginbuild.xmlisalistofparametersandvaluesthatarecriticaltothepropercreationofyourproject.Thislistincludescompileroptions,inputdirectories,andtoollocations.Takealookatthefollowingoutputofthecoreprocessinginformationofbuild.xml:

注意

当Android建议反对改变下面这些参数时,如果你非常熟悉ANT是如何工作的,你可以修改这些选项来符合你特定的需求。

<!--Inputdirectories-->
<propertyname="resource-dir"value="res"/>
<propertyname="asset-dir"value="assets"/>
<propertyname="srcdir"value="src"/>
<!--Outputdirectories-->
<propertyname="outdir-classes"value="${outdir}/classes"/>
<!--CreateR.javainthesourcedirectory-->
<propertyname="outdir-r"value="src"/>
<!--Intermediatefiles-->
<propertyname="dex-file"value="classes.dex"/>
<propertyname="intermediate-dex"value="${outdir}/${dex-file}"/>
<!--Thefinalpackagefiletogenerate-->
<propertyname="out-package"value="${outdir}/${ant.project.name}.apk"/>
<!--Tools-->
<propertyname="aapt"value="${android-tools}/aapt"/>
<propertyname="aidl"value="${android-tools}/aidl"/>
<propertyname="dx"value="${android-tools}/dx"/>
<propertyname="adb"value="${android-tools}/adb"/>
<propertyname="android-jar"value="${sdk-folder}/android.jar"/>
92Android:AProgrammer’sGuide
<propertyname="zip"value="zip"/>
<!--Rules-->
<!--Createtheoutputdirectoriesiftheydon'texistyet.-->
<targetname="dirs">
<mkdirdir="${outdir}"/>
<mkdirdir="${outdir-classes}"/>
</target>
<!--GeneratetheR.javafileforthisproject'sresources.-->
<targetname="resource-src"depends="dirs">
<echo>GeneratingR.java...</echo>
<execexecutable="${aapt}"failonerror="true">
<argvalue="compile"/>
<argvalue="-m"/>
<argvalue="-J"/>
<argvalue="${outdir-r}"/>
<argvalue="-M"/>
<argvalue="AndroidManifest.xml"/>
<argvalue="-S"/>
<argvalue="${resource-dir}"/>
<argvalue="-I"/>
<argvalue="${android-jar}"/>
</exec>
</target>
<!--Generatejavaclassesfrom.aidlfiles.-->
<targetname="aidl"depends="dirs">
<applyexecutable="${aidl}"failonerror="true">
<argvalue="-p${android-framework}"/>
<argvalue="-I${srcdir}"/>
<filesetdir="${srcdir}">
<includename="**/*.aidl"/>
</fileset>
</apply>
</target>
<!--Compilethisproject's.javafilesinto.classfiles.-->
<targetname="compile"depends="dirs,resource-src,aidl">
<javacencoding="ascii"target="1.5"debug="true"extdirs=""
srcdir="."
destdir="${outdir-classes}"
bootclasspath="${android-jar}"/>
</target>
<!--Convertthisproject's.classfilesinto.dexfiles.-->
Chapter6:UsingtheCommand-LineToolsandtheAndroidEmulator93
<targetname="dex"depends="compile">
<execexecutable="${dx}"failonerror="true">
<argvalue="-JXmx384M"/>
<argvalue="--dex"/>
<argvalue="--output=${basedir}/${intermediate-dex}"/>
<argvalue="--locals=full"/>
<argvalue="--positions=lines"/>
<argpath="${basedir}/${outdir-classes}"/>
</exec>
</target>
<!--Puttheproject'sresourcesintotheoutputpackagefile.-->
<targetname="package-res-and-assets">
<echo>Packagingresourcesandassets...</echo>
<execexecutable="${aapt}"failonerror="true">
<argvalue="package"/>
<argvalue="-f"/>
<argvalue="-c"/>
<argvalue="-M"/>
<argvalue="AndroidManifest.xml"/>
<argvalue="-S"/>
<argvalue="${resource-dir}"/>
<argvalue="-A"/>
<argvalue="${asset-dir}"/>
<argvalue="-I"/>
<argvalue="${android-jar}"/>
<argvalue="${out-package}"/>
</exec>
</target>
<!--Sameaspackage-res-and-assets,butwithout"-A${asset-dir}"-->
<targetname="package-res-no-assets">
<echo>Packagingresources...</echo>
<execexecutable="${aapt}"failonerror="true">
<argvalue="package"/>
<argvalue="-f"/>
<argvalue="-c"/>
<argvalue="-M"/>
<argvalue="AndroidManifest.xml"/>
<argvalue="-S"/>
<argvalue="${resource-dir}"/>
<!--Noassetsdirectory-->
<argvalue="-I"/>
<argvalue="${android-jar}"/>
<argvalue="${out-package}"/>
</exec>
</target>
<!--Invokethepropertargetdependingonwhetherornot
anassetsdirectoryispresent.-->
<!--TODO:findanicerwaytoincludethe"-A${asset-dir}"argument
onlywhentheassetsdirexists.-->
<targetname="package-res">
<availablefile="${asset-dir}"type="dir"
property="res-target"value="and-assets"/>
<propertyname="res-target"value="no-assets"/>
<antcalltarget="package-res-${res-target}"/>
</target>
<!--Puttheproject's.classfilesintotheoutputpackagefile.-->
<targetname="package-java"depends="compile,package-res">
<echo>Packagingjava...</echo>
<jardestfile="${out-package}"
basedir="${outdir-classes}"
update="true"/>
</target>
<!--Puttheproject's.dexfilesintotheoutputpackagefile.
Usethezipcommand,availableonmostunix/Linux/MacOSsystems,
tocreatethenewpackage(Ant1.7hasaninternalzipcommand,
howeverAnt1.6.5lacksitandisstillwidelyinstalled.)
-->
<targetname="package-dex"depends="dex,package-res">
<echo>Packagingdex...</echo>
<execexecutable="${zip}"failonerror="true">
<argvalue="-qj"/>
<argvalue="${out-package}"/>
<argvalue="${intermediate-dex}"/>
</exec>
</target>
<!--Createthepackagefileforthisprojectfromthesources.-->
<targetname="package"depends="package-dex"/>
<!--Createthepackageandinstallpackageonthedefaultemulator-->
<targetname="install"depends="package">
<echo>Sendingpackagetodefaultemulator...</echo>
<execexecutable="${adb}"failonerror="true">
<argvalue="install"/>
<argvalue="${out-package}"/>
</exec>
</target>
</project>

现在你对于build.xml在人工下,命令行创建的Android项目是如何使用应该有了好的理解,你可以开始来编辑你的项目文件并且创建一个Android活动。第一个你需要看的文件是main.xml。使用Windows资源管理器,在AndroidHelloWorld\res\layout目录下找到main.xml。

在WindowsCLI下创建HelloWorld!活动-第六章(5

在本部分中,你会使用Windows命令行接口来编辑项目文件。在上一章中,项目文件是由ActivityCreator.bat创建的。你将不使用Eclipse来编辑这些文件并增加一些代码。

编辑项目文件

在一个XML编译器或者(如果你没有一个XML编辑器)记事本打开main.xml文件。这样你就可以编辑文件并且删除里面的<TextView/>定义。保存过后的main.xml文件是一个空壳。这样你就得到一个编辑<activity>.java文件的平台了。<activity>.java文件在更深层次的文件夹里,AndroidHelloWorld\src\android\programmers\guide。来创建你的HelloWorld!应用程序,增加下面的代码行来创建,设置并使用一个TextView:
/**HelloWorldJFD*/
/**BEGIN*/
/**CreateTextView*/
TextViewHelloWorldTextView=newTextView(this);
/**SettexttoHelloWorld*/
HelloWorldTextView.setText("HelloWorld!");
/**SetContentViewtoTextView*/
setContentView(HelloWorldTextView);
/**END*/

别忘了增加TextView包装到文件的开始部分:

importandroid.widget.TextView;
完成后的HelloWorldCommandLine.java文件应当看上去和下面一样(略)。你的项目文件现在应当被设置了。你现在可以在Android模拟器内编译并运行你的应用程序了

增加JAVA_HOME第六章(6

在编译你的项目之前,必须增加另外一个环境变量到你的PC-JAVA_HOME,可以指向你的JDK。即使它只是个PATH声明,你也必须创建一个新的名为JAVA_HOME的变量。

注意
JAVA_HOME变量是必须的仅仅是因为你使用命令行环境。如果你只使用Eclipse,你不用增加它。

1、右击“我的电脑”,并选择“属性”。
2、在系统属性下,选择高级选项并点击环境变量按钮。然后会打开一个环境变量窗口。
3、点击新建按钮来增加一个变量名为_HOME,它的值应该是你JavaSDK的完整路径。见下图(略)

编译并安装应用程序第六章(7)

是时候来做一个真正的测试了。你现在可以编辑你的命令行项目了。要编译项目,使用ANT。一旦项目编译完成,你需要在模拟器中安装它。

用ANT编译项目

如果运行ANT时出错该怎么办?第六章(8)

在你设置好JAVA_HOME环境变量并且ANT在你的PATH声明之后,你应当可以导航到含有build.xml文件的文件夹,并且只要简单的运行ant命令。在你的项目路径下运行ant。如下:(略)。

运行ant的结果就是一个.apk文件会直接安装到手机(模拟器)中,总之,Eclipse在模拟器中直接为你安装。而这里你需要使用AndoridDebugBridge(adb)工具来安装应用程序,下一节叙述

如果运行ANT时出错该怎么办?

当你运行ANT时出错该怎么办呢?不用害怕。因为在写本书时,Android还只是一个刚发布的阶段,有些项目可能需要被纠正,当你使用一项新技术时,总会有一些小的更改会发生。当我第一次试着运行ant并且编译我的项目时,我收到了一个错误,如下图(略)。

一些问题的研究在谷歌的Android开发者论坛上,一个重写的build.xml纠正了一些提供到ANT的命令。在修改过的主要部分已经被加粗。和原来的那个文件比较一下你会注意到明显的不同。

<?Xmlversion="1.0”?>
<projectname="HelloWorldCommandLine"default="package"basedir=".">
<propertyname="sdk-folder"value="c:\Android\android-sdk_m5
rc14_windows\android-sdk_m5-rc14_windows"/>
<propertyname="android-tools"value="c:\Android\android-sdk_m5
rc14_windows\android-sdk_m5-rc14_windows\tools"/>
<propertyname="android-framework"value="${android-tools}/lib/framework.aidl"
/>
<!--Theintermediatesdirectory-->
<!--Eclipseuses"bin"foritsownoutput,sowedothesame.-->
<!--Usefullpathforoutputdir-FIX-BLOCKSTART-->
<propertyname="outdir"value="${basedir}/bin"/>
<!--Usefullpathforoutputdir-FIX-BLOCKEND-->
<!--Nouserservicablepartsbelow.-->
<!--Inputdirectories-->
<propertyname="resource-dir"value="res"/>
<propertyname="asset-dir"value="assets"/>
<propertyname="srcdir"value="src"/>
<!--Outputdirectories-->
<propertyname="outdir-classes"value="${outdir}/classes"/>
<!--CreateR.javainthesourcedirectory-->
<propertyname="outdir-r"value="src"/>
<!--Intermediatefiles-->
<propertyname="dex-file"value="classes.dex"/>
<propertyname="intermediate-dex"value="${outdir}/${dex-file}"/>
<!--Thefinalpackagefiletogenerate-->
<propertyname="out-package"value="${outdir}/${ant.project.name}.apk"/>
<!--Tools-->
<propertyname="aapt"value="${android-tools}/aapt"/>
<propertyname="aidl"value="${android-tools}/aidl"/>
<conditionproperty="dx"value="${android-tools}/dx.bat"else="${android
tools}/dx">
<osfamily="windows"/>
</condition>
<propertyname="dx"value="${android-tools}/dx"/>
<propertyname="zip"value="zip"/>
<propertyname="android-jar"value="${sdk-folder}/android.jar"/>
<!--Rules-->
<!--Createtheoutputdirectoriesiftheydon'texistyet.-->
<targetname="dirs">
<mkdirdir="${outdir}"/>
<mkdirdir="${outdir-classes}"/>
</target>
<!--GeneratetheR.javafileforthisproject'sresources.-->
<targetname="resource-src"depends="dirs">
<echo>GeneratingR.java...</echo>
<execexecutable="${aapt}"failonerror="true">
<argvalue="compile"/>
<argvalue="-m"/>
<argvalue="-J"/>
<argvalue="${outdir-r}"/>
<argvalue="-M"/>
<argvalue="AndroidManifest.xml"/>
<argvalue="-S"/>
<argvalue="${resource-dir}"/>
<argvalue="-I"/>
<argvalue="${android-jar}"/>
</exec>
</target>
<!--Generatejavaclassesfrom.aidlfiles.-->
<targetname="aidl"depends="dirs">
<applyexecutable="${aidl}"failonerror="true">
<filesetdir="${srcdir}">
<includename="**/*.aidl"/>
</fileset>
</apply>
</target>
<!--Compilethisproject's.javafilesinto.classfiles.-->
<targetname="compile"depends="dirs,resource-src,aidl">
<javacencoding="ascii"target="1.5"debug="true"extdirs=""
srcdir="."
destdir="${outdir-classes}"
bootclasspath="${android-jar}"/>
</target>
<!--Convertthisproject's.classfilesinto.dexfiles.-->
<targetname="package-dex"depends="dex,package-res">
<echo>Packagingdex...</echo>
<execexecutable="${zip}"failonerror="true">
<!--<argvalue="-Xmx384M"/>-->
<!--MoveXmxparametertodx.bat-FIX-BLOCKEND-->
<argvalue="--dex"/>
<argvalue="--output=${intermediate-dex}"/>
<argvalue="--locals=full"/>
<argvalue="--positions=lines"/>
<argpath="${outdir-classes}"/>
</exec>
</target>
<!--Puttheproject'sresourcesintotheoutputpackagefile.-->
<targetname="package-res-and-assets">
<echo>Packagingresourcesandassets...</echo>
<execexecutable="${aapt}"failonerror="true">
<argvalue="package"/>
<argvalue="-f"/>
<argvalue="-c"/>
<argvalue="-M"/>
<argvalue="AndroidManifest.xml"/>
<argvalue="-S"/>
<argvalue="${resource-dir}"/>
<argvalue="-A"/>
<argvalue="${asset-dir}"/>
<argvalue="-I"/>
<argvalue="${android-jar}"/>
<argvalue="${out-package}"/>
</exec>
</target>
<!--Sameaspackage-res-and-assets,butwithout"-A${asset-dir}"-->
<targetname="package-res-no-assets">
<echo>Packagingresources...</echo>
<execexecutable="${aapt}"failonerror="true">
<argvalue="package"/>
<argvalue="-f"/>
<argvalue="-c"/>
<argvalue="-M"/>
<argvalue="AndroidManifest.xml"/>
<argvalue="-S"/>
<argvalue="${resource-dir}"/>
<!--Noassetsdirectory-->
<argvalue="-I"/>
<argvalue="${android-jar}"/>
<argvalue="${out-package}"/>
</exec>
</target>
<!--Invokethepropertargetdependingonwhetherornot
anassetsdirectoryispresent.-->
<!--TODO:findanicerwaytoincludethe"-A${asset-dir}"argument
onlywhentheassetsdirexists.-->
<targetname="package-res">
<availablefile="${asset-dir}"type="dir"
property="res-target"value="and-assets"/>
<propertyname="res-target"value="no-assets"/>
<antcalltarget="package-res-${res-target}"/>
</target>
<!--Puttheproject's.classfilesintotheoutputpackagefile.-->
<targetname="package-java"depends="compile,package-res">
<echo>Packagingjava...</echo>
<jardestfile="${out-package}"
basedir="${outdir-classes}"
update="true"/>
</target>
<!--Puttheproject's.dexfilesintotheoutputpackagefile.-->
<targetname="package-dex"depends="dex,package-res">
<echo>Packagingdex...</echo>
<execexecutable="${zip}"failonerror="true">
<argvalue="-qj"/>
<argvalue="${out-package}"/>
<argvalue="${intermediate-dex}"/>
</exec>
</target>
<!--Createthepackagefileforthisprojectfromthesources.-->
<targetname="package"depends="package-dex"/>
</project>
在修改过build.xml之后,你可以重新运行

用adb安装你的应用程序第六章(9)

第一步是启动你的模拟器。在Android/tools文件夹找到emulator.exe文件并且执行它。这样就会启动Android服务器。那就是启动了模拟器同时在你的电脑上启动了一个虚拟的手机。如下图(略)。然后你就可以使用不同的工具来和服务器交互了,和安装应用程序和呼叫一个壳环境一样。要在Android服务器安装你的命令行应用程序,你需要使用adb。adb是你到服务器的连接,同模拟器一同开启。

adb包含了很多有用的功能允许你和Android服务器交互;其中一个功能可以让你安装应用程序。

表格6-1列出了adb接受的命令描述。

要复制你的应用程序到服务器,打开一个Windows命令提示符窗口并且导航到build.xml文件所在的路径。对于adb,syntax命令如下:
adbinstall<apkpath>

如果应用程序正确的安装到手机,你会得到一个命令行关于包装大小的反馈。如下。(略)。

命令

描述

install<path>

安装应用程序到服务器

pull<remotefile><localfile>

将远程文件拉出服务器

push<localfile><remotefile>

将本地文件推进服务器

shell

在服务器上打开一个壳环境

forward<localport><remoteport>

从一个端口转递流量到另外一个端口(到或者从服务器上)

start-server

启动服务器

kill-server

停止服务器

ppp<tty><params>

通过USB使用一个ppp连接

devices

列出可用的模拟器

help

列出adb的命令

version

显示adb的版本

表6-1adb命令

转到运行的模拟器,你将会看到应用程序安装到手机上。

运行应用程序产生了一个错误怎么办-第六章(10)

我第一次在使用新的build.xml文件后,运行这个应用程序时,我在Android模拟器上接受到了一个错误。如下图(略)。错误指出一个丢失的类。

注意

你可能会或者不会遇到同样的错误。关键看本书发行时,哪个版本的AndroidSDK是可用的,你应当跟从这里的问题解决步骤,因为在后续的项目中会对你有所帮助。

这个错误似乎指出了一个事实,那就是在HelloWorldCommandLine.apk文件中丢失了一个类。我可以简单的自己去纠正这个错误而不用任何的AndroidSDK命令行工具。

根据结果,.apk文件就是一个.zip文件。就是说你可以用.zip解压缩文件打开它。下面的插图就是用winrar打开HelloWorldCommandLine.apk文件后的样子。(略)。

丢失的是classes.dex。这个是我的类的编译过的Dalvik可执行性文件。导航到Android项目下bin文件夹,我可以看到ANT成功的编译并且创建了classes.dex文件。这个文件只是被留在了HelloWorldCommandLine.apk文件之外了。在Winrar打开的HelloWorldCommandLine.apk状态下,我可以把classes.dex文件拖进HelloWorldCommandLine.apk。在classes.dex文件被加入HelloWorldCommandLine.apk后可以保存并关闭文件了。

卸载一个较早的活动-第六章(11)

在你增加文件到运行的服务器之前,你将要卸载前一个版本的HelloWorldCommandLine。在安装另外一个程序之前,卸载前一个版本的程序不是必须要做的事。但是,为了更好的查看如何的与服务器交互,在开始前,还是卸载前一个版本的程序吧。

保持Android模拟器在开启状态,返回到命令行提示符环境并且允许adb壳命令,它会打开Android服务器的壳环境。如果你成功了,你的命令提示符会从>变成#。现在你在Android服务器中有一个打开的壳。有很多的功能现在可以用,但是现在只关注一个:移除旧的HelloWorldCommandLine.apk文件。

提示

记住,Android是一个操作环境。你在壳中可以使用的是标准的POSIX命令。

在Android服务器中,用户安装的程序被保留在/data/app路径下。使用cd,导航到app路径,如下图(略)。运行ls命令来列出这个路径下所有的文件。你将看到一个HelloWorldCommandLine.apk文件。这个文件展示了你活动的安装。

现在你已经在服务器上定位了应用程序,你可以移除它了。使用命令语法rmHelloWorldCommandLine.apk来移除应用程序。下图就是rm命令(略)。如果成功,不会返回任何的信息。随后使用ls命令表明,文件已被移除。

警告

因为技术上你通过壳登入了一个Linux服务器,所有在壳内运行的命令是区分大小写的。

应用程序移除后,输入exit来退出壳并返回到你的命令提示符。

重新安装并启动应用程序-第六章(12

你现在可以使用adb重新安装应用程序了:

AdbinstallHelloWorldCommandLine.apk

一旦应用程序被安装回服务器,转到模拟器。从模拟器中启动应用程序。它应当能正常工作,如下图(略)。

现在我们已经谈论过如何在Windows内创建和编辑文件的过程,让我们看看在Linux上会怎么样。即使你是一个顽固的Windows用户,你可能需要注意下面的章节。我发现了对编程绝对有利的开源工具。

Linux上的HelloWorld!第六章(13

很多的程序员,特别是对开放源代码软件有兴趣的程序员喜欢使用Linux作为平台。谷歌和开放手机联盟已经为这些程序员准备了AndroidSDK。这个SDK实际上是同样的SDK(因为java是移动性的),但是被创建的工具特定的运行在Linux上。当我开始写这本书的时候,我在使用一个老版本的红帽Linux作为我的Linux平台。我下载并安装了Eclipse和AndroidSDK。然而,它很快成为可以安全运行Android的Linux的一些限制。因为最低要求,你必须有一个支持libstdc++.so.6的Linux。Android文档列出了UbuntuDapperDrake作为一个Linux的测试版本。

如果你还没有决定使用哪一个版本,你可以放心的使用。不幸的是,当我试图安装最新版本的Ubuntu的时候,我电脑的硬件有个问题。于是我决定移除推荐的并且试着用一些新的东西。

当我决定放弃红帽,我决定使用Fedora8。本书的下面部分所使用的Linux版本的例子都是从Fedora8而来。不过,它们应当在你选择的软件版本上工作的没有问题。

注意

如果你选择Fedora8,会有一个叫做FedoraEclipse的定制包装。如果你试图为FedoraLinux安装AndroidPlugin(使用本书早些时候的概述),它会提示一个错误要求pluginorg.eclipse.wst.sse.u。你可以要下面两种方式解决:

下载最新Linux版的Eclipse,或者使用Fedora的自动升级程序,这个可以使Linux版的FedoraEclipse成为最新。然后可以在这个Eclipse里使用AndroidSDK了。
配置PATH声明

第一步就是配置PATH声明。路径就是一个路径清单,当一个命令被执行时,操作系统会在这个路径下寻找该命令。要查看你当前配置的路径,从一个terminal里运行下面的内容:

echo$PATH

你会得到像下图(略)一样的一些路径声明。使用输出命令来增加Android到PATH声明中:

exportPATH=$PATH:<androidpath>

在Linux中编辑PATH声明会只是在当前的terminal部分改变PATH声明。要永久的改变你的PATH声明。你必须编辑.bash_profile.使用vi来编辑.bash_profile,如下图所示(略)。

如你所见,PATH声明清晰可见。使用命令:i来使vi成为插入模式,然后增加Android到PATH中。然后按下ESC按钮,使用命令:w来写文件,然后使用:q来退出。

Linux版本的AndroidSDK与一个Python脚本一起提供,activityCreator.py,这个被用来创建你的初始项目。不管怎么说,我喜欢手动创建路径来确保它在我需要它在的地方。使用mkdir来为你的项目创建一个目录。

创建好项目目录后,你可以运行activityCreator.pyPython脚本。这个脚本的语法非常接近于Windows.bat文件:

activityCreator.py--out<outputdirectory>package.activityName

使用activityCreator.py脚本来设置你的项目。看看下图activityCreator.py输出的脚本(略)。

提示

activityCreator.py命令是由sudo前缀。sudo命令被用来模拟其他用户的许可(本例为根目录),如果你没有足够的许可来运行要求的命令。在我安装的Fedora,我的用户帐号没有权利与根目录交互。

项目建好后,编辑HelloWorldLinux.java文件并增加TextView。你可以用很多中方法在Linux中编辑.java文件。可以再次使用vi,或者你可以使用一个如下图的标准文本编辑器(略)。

最后,从main.xml中移除定义的TextView。你现在编译你的Linux版本的HelloWorld!应用程序有两个小的改变。要编译应用程序,使用ANT(这个在Windows环境一样)。伺服ANT应当被预先安装在你的Linux下,特别是当你使用Fedora8.如果你没有使用Fedora8,你需要为伺服ANT下载,安装并设置路径。

当你运行ant,你应当看到一个如下图的输出(略)。

最后,你需要启动Android模拟器来安装你的应用程序。保持模拟器开启的状态下,执行下面的命令:

adbinstallHelloWorldLinux.apk

这个将安装应用程序到LinuxAndroid服务器。如果命令运行成功,你应当可以在模拟器运行活动了。下一章,研究如何使用AndroidSDK来对图片事件作出反应。

在CLI中创建一个图片基础的HelloWorld!第六章(14

在本章中使用命令行工具来从第五章中重新创建图片为基础的HelloWorld!当你创建这个项目时,记住下面的事宜:

●在res文件夹中放置图片。

●检查创建R.java所需要的带有指向图片的任何工具。

●使用ANT编译项目。

●使用adb命令来安装并推动应用程序到你的模拟器中。

问专家:

Q:当为Android编程时,有一种操作系统比其他的更好吗?

A:在使用过一些操作系统之后,我还没有注意到哪一种操作系统带有明显的优势。真的只是个人喜好。但是,经常发生的情况是,你可能会看到更多非官方的工具为了Linux平台而发布。因为Linux和Android是开放源码,更多的开放源码开发者倾向于为另一个开放源码平台创建工具。这个最终会给Android带来比Linux更多的好处。

Q:还有其它的命令可以从adb壳环境运行吗?

A:是的。例如,一个有趣的命令就是服务命令,可以被用来检查一个过程的状态,如:servicecheckphone

假定手机正在运行,你应当能得到反馈:

Servicephone:found

另外使用服务命令的方法是打一个电话。模拟器开启的情况下,输入命令并检查模拟器界面:
servicecallphone2s16"15555551212"

题外话:终于完成第六章的翻译工作了。

第七章使用Intents和电话拨号盘

使用Intents和电话拨号盘第七章(1)

关键技能&概念
●使用Intents

●创建和电话硬件交互的代码

●学习拨号和呼叫的差异

本书到目前为止已经介绍了Android编程的基础知识。你已经仔细检查了Android应用程序的概要并且安装了你的第一个应用程序到Android服务器中。你已经学习了如何使用Views和SetContentView(),同时知道如何在一个XML中创建UI。这些技能已经帮助你创建一个静态的应用程序。你还没有做的就是使用应用程序接口来和这个平台的硬件——手机来产生交互。

你不应该忘记一个事实,那就是Android创建的平台仍然是一个手机。这个Android会运行的设备潜在的硬件,是设计为个人与个人通信目的的。如果你揭开AndroidSDK外在的浮华之物,它最基本的能力必须要能接或者打电话。

基于这个原因,本章重点放在与手机硬件交互的代码上,你应当有一些与手机基本功能交互的技能。你将能使用拨号盘来接受和打电话。这些工具和技能将会是你在这个灵活平台创建应用程序的关键所在。

你在读者本书是因为你想要设计运行在一个手机上的应用程序,所以,显而易见你应当学习Android如何允许和手机硬件交互——特别是,打出电话和接收电话的过程。

当我们想到手机,一些基本的功能会出现在我们的脑海里。首先,绝大多数情况,是能打出并接收电话。这是一个不容争辩的手机核心功能。还有一些非核心的特点使得手机易于使用,比如有能力保留并管理联系人,有能力储存没有接到的电话。通过阅读本章的内容,你会进入并熟练操作这些功能的代码。

本章中,你看到的一个手机功能就是打出一个电话。你会创建一个应用程序,使用一个Intent,它将控制电话拨号盘并促使它呼叫一个号码。作为文章的进展,你将扩展这个应用程序并增加一些装饰到程序中。

注意

在Android平台,拨号和呼叫是不一样的。当你拨一个号码,你输入数字到键盘(或者通过程序)。但是没有呼叫实际发生。这就是,拨号没有包括呼叫按钮。但你呼叫一个号码,你从手机上发送一个信号。那就是在输入号码到拨号盘以后,你按下呼叫按钮——物理上或者程序上。你需要知道两个动作的不同来理解你会在本章中创建程序的应用范围。

Intents是什么?

Intents是什么?第七章(2)

在你开始与拨号盘交互之前,需要你理解你要使用的代码类型。Andriod使用Intent在应用程序中定义工作。一旦你掌握了Intents的使用,一个全新的应用程序开发世界将会向你敞开。本节定义了Intent是什么和如何使用它。

一个Intent是Android从一个Activity(活动)传递信息到另外一个活动的方法。你可以认为一个Intent是一个活动间交换的信息。例如,假定你有一个活动需要来打开一个网页浏览器并且在Android设备上显示一个页面。你的活动应当发送一个“在网页浏览器中打开某页的Intent(意图)”,就像一个WEB_SEARCH_ACTION的Intent,一个AndroidIntent解答器。Intent解答器从语法上分析一个活动的列表并且选择最匹配你的Intent的一个。那就是,网页浏览器的活动。Intent解答器然后传递你的网页到浏览器中并且启动网页浏览器活动。

Intents被分成两个主要目录

●ActivityActionIntents(活动动作意图)Intents用来呼叫应用程序以外的活动。只有一个活动可以处理Intent。例如,对于网页浏览器,你需要打开网页浏览器活动来显示一个页面。

●BroadcastIntents(广播意图)Intents被送出到多个活动来处理。一个被Android发出的广播意图的例子就是,当前电池的电量。任何活动处理这个意图并适时的反应。——例如,如果电池电量低到一定程度,取消一个活动。

表格7-1列出并且描述了通用的,可以使用活动动作意图。正如你注意到的一样,大多数情况下,从Intent名字可以看出这个Intent是做什么的。

ActivityActionIntent

Message

ADD_SHORTCUT_ACTION

增加一个功能快捷菜单到Android的主屏

ALL_APPS_ACTION

列出设备上可用的所有应用程序

ANSWER_ACTION

接电话

BUG_REPORT_ACTION

打开调试报告活动

CALL_ACTION

呼叫一个提供的位置

DELETE_ACTION

删除定义的数据

DIAL_ACTION

打开拨号活动并且拨打一个定义好的号码

EDIT_ACTION

对有权使用的数据提供编辑

EMERGENCY_DIAL_ACTION

拨打一个紧急号码

FACTORY_TEST_ACTION

回复工厂测试设定

GET_CONTENT_ACTION

选择并返回定义的数据

INSERT_ACTION

插入一个空的条目

MAIN_ACTION

建立一个活动开始点

PICK_ACTION

挑选一个条目并且返回一个选择

PICK_ACTIVITY_ACTION

挑选一个特定的活动(返回一个类)

RUN_ACTION

执行特定的数据

SEARCH_ACTION

在系统上启动搜索

SEND_ACTION

发送数据给没有定义的接收者

SENDTO_ACTION

发送数据到指定的接收者

SETTINGS_ACTION

启动系统设定

SYNC_ACTION

和外部的源同步手机

VIEW_ACTION(DEFAULT_ACTION)

打开一个视图

WALLPAPER_SETTINGS_ACTION

显示修改Android墙纸的设定

WEB_SEARCH_ACTION

打开谷歌搜索,或者其它定义过的网页

注意

本章中的应用程序会用到列在表7-1中的Intents:
CALL_ACTION和DIAL_ACTION。这些Intents使你有进入手机拨号和呼叫的能力。

表格7-2列出并描述了通用的广播意图。当你需要为一个定义的Intent建立一个接受器时,请参考这个表。

BroadcastIntent

信息

CALL_FORWARDING_STATE_CHANGED_ACTION

电话呼叫转接状态已经改变

CAMERA_BUTTON_ACTION

照相机的按钮被按下

CONFIGURATION_CHANGED_ACTION

设备配置发生改变

DATA_ACTIVITY_STATE_CHANGED_ACTION

设备的数据活动状态改变

DATA_CONNECTION_STATE_CHANGED_ACTION

数据连接状态改变

DATE_CHANGED_ACTION

手机系统数据改变

FOTA_CANCEL_ACTION

取消未决的系统更新下载

FOTA_INSTALL_ACTION

升级已经下载必须立即安装(由系统发送)

FOTA_READY_ACTION

升级已经下载可以延迟安装(由系统发送)

FOTA_RESTART_ACTION

重启一个系统升级下载

FOTA_UPDATE_ACTION

开始系统升级下载

GTALK_SERVICES_CONNECTED_ACTION

发送当GTALK已经成功建立

GTALK_SERVICES_DISCONNECTED_ACTION

发送当GTALK已经断开

MEDIA_BAD_REMOVAL_ACTION

发送当一个SD储存卡移开但是从系统中未成功移除

MEDIA_BUTTON_ACTION

发送当媒体按钮按下

MEDIA_EJECT_ACTION

发送当弹出动作为一个SD储存卡被初始化

MEDIA_MOUNTED_ACTION

发送当一个SD储存卡在系统中成功安装

MEDIA_REMOVED_ACTION

发送当检测到储存卡移出

MEDIA_SCANNER_FINISHED_ACTION

发送当扫描器完成

MEDIA_SHARED_STARTED_ACTION

发送当扫描器开始

MEDIA_UNMOUNTED_ACTION

发送当SD卡被检测到但是没有被安装

MESSAGE_WAITING_STATE_CHANGED

手机“信息等待”状态发生变化

NETWORK_TICKLE_RECEIVED_ACTION

一个新网络设备通知被接受

PACKAGE_ADDED_ACTION

当一个新的包装被安装在设备上发送

PACKAGE_CHANGE_ACTION

发送当现存的包装发生改变

PACKAGE_INSTALL_ACTION

一个包装可以被下载和安装

PACKAGE_REMOVED_ACTION

一个包装已经被移除

PHONE_INTERFACE_ADDED_ACTION

设备的手机界面已经被建立

PHONE_STATE_CHANGED_ACTION

设备的手机状态已经改变

PROVIDER_CHANGED_ACTION

设备从一个接收者处接收到通知

PROVISIONING_CHECK_ACTION

从供给服务中检测最新的设定

SCREEN_OFF_ACTION

屏幕被关闭(设备发送)

SCREEN_ON_ACTION

屏幕被打开(设备发送)

SERVICE_STATE_CHANGED_ACTION

服务状态被改变

SIGNAL_STRENGTH_CHANGED_ACTION

信号强度改变

注意

一些广播意图经常被发送,如TIME_TICK_ACTION
和SIGNAL_STRENGTH_CHANGED_ACTION。使用时请谨慎处理。你不应当试着去同时接受这样的广播。Intent只是大约三分之一。其实Intent只是做了某些事情,而且它不能自己来做任何事。你需要Intent过滤器和Intent接受器来听,翻译Intents.一个Intent接收器就像一个Activity的邮箱。Intent接收器被用来允许一个活动来接受定义的Intent。使用前一个网页浏览器的例子,网页浏览器活动被设定来接受网页浏览器Intent。一个像这样的系统允许不相关的活动来忽略不能处理的Intent。它同时允许需要其它活动辅助的活动利用这个活动,而不需要知道如何呼叫它。

有了Intents和Intents接收器,一个活动可以发送一个Intent并且另外一个可以接受。不过,需要一些东西来管理两个活动之间的信息类型。这就是为什么要用Intent过滤器了。

Intent过滤器被活动用来描述要接受的Intent类型。更重要的是,它们在Intent的内部概括了传递的数据类型。因此,在我们例子的方案中,我们要网页浏览器来打开网页。Intent过滤器将会陈述数据使用WEB_SEARCH_ACTIONIntent应当是URL格式的。

在下一节中,你将开始使用Intent来打开和利用电话的拨号盘。

使用拨号盘第七章(3)

现在你知道Intent是什么了,是时候来看它如何运转的了。本节向你展示如何使用DIAL_ACTION这个Intent来打开电话的拨号盘。你将用你的Intent来传递一个电话号码。如果应用程序工作正常,你将会看到由Intent传递,而显示在拨号盘内的号码。

第一步是为这个活动创建一个项目(具体操作见第五章:Android程序员向导目录)。把项目命名为AndroidPhoneDialer。下面的插图就是这个项目的新Android项目向导(略)。

在Eclipse内打开的新的应用程序,第一个要做的就是从main.xml中移除包含HelloWorld声明的TextView。在删除了TextView后,main.xml文件应当看起来如下:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</LinearLayout>

你需要增加两个新的包装到你的项目中来使用DIAL_ACTIONIntent,如下,第一个包装允许你设置Intents并且第二个允许你来分析URIs。

importandroid.content.Intent;
importandroid.net.Uri;

注意

对于DIAL_ACTION这个Intent有一些不同的Intent过滤器可以使用。你正在使用的是允许你把号码作为了一个URI来传递的过滤器。

下一步就是来创建你的Intent。创建一个Intent的语法如下:

Intent<intent_name>=newIntent(<Android_Intent>,<data>)

对于你的应用程序,把第一个参数<intent_name>用DialIntent替换掉。要获得第二个参数的数值,请参考ActivityAction中的列表。(列表在文章中:什么是Intent)。要呼叫拨号盘,你需要使用DIAL_ACTIONIntent。要正确的呼叫Intent,使用Intent.DIAL_ACTION这个格式。最后的参数<data>,就是电话号码。DIAL_ACTIONintent把号码作为一个URI。因此,你需要使用Uri.parse来分析出电话号码。使用Uri.parse将确保DIAL_ACTIONintent能够理解你试图拨打的号码。你传递了一个Uri.parse的字符串来展示你要拨打的号码,在本例中是"tel:5551212"。

为你项目创建的最后一个呼叫应该像这样:

IntentDialIntent=new
Intent(Intent.DIAL_ACTION,Uri.parse("tel:5551212"));

提示

你使用记号tel:<phone_number>来呼叫一个指定的电话号码。你还可以使用voicemail来替代tel:呼出一个电话voicemail的快捷方式。

Intent创建后,你现在必须告诉Android你想要拨号盘在新的活动中被启动。要这样做,你使用setLaunchFlags()的Intent方法。你必须为启动来传递setLaunchFlags()合适的参数。下面是可以设置接受启动旗帜的一组列表:

注意

在其它情况下,可能会有超过一个的旗帜被设置来完成希望的结果。
●NO_HISTORY_LAUNCH启动活动,不记录在系统启动历史中
●SINGLE_TOP_LAUNCH告诉系统不要启动活动,如果该活动已经在运行
●NEW_TASK_LAUNCH启动活动
●MULTIPLE_TASK_LAUNCH启动活动,即使它已经在运行了
●FORWARD_RESULT_LAUNCH允许新的活动来接受结果,这个结果通常被转递给现存的活动。本例中,你要使用intent.NEW_TASK_LAUNCH,这样可以简单的让你打开一个新的拨号盘活动示例:

DialIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);

创建拨号盘的最后一步是启动活动。(更精确的说,你告诉Android你有一个作为新任务来启动的拨号盘。最终由Android来启动拨号盘活动)。要告诉Android你要启动拨号盘,你需要使用startActivity():

startActivity(DialIntent);

请注意到你把intent传递到startActivity()。这个Intent然后传递到Andriod,然后活动被执行。完整的AndroidPhoneDialer.java文件代码应当如下:

packageandroid_programmers_guide.AndroidPhoneDialer;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.net.Uri;
publicclassAndroidPhoneDialerextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
/**CreateourIntenttocalltheDialer*/
/**PasstheDialerthenumber5551212*/
IntentDialIntent=new
Intent(Intent.DIAL_ACTION,Uri.parse("tel:5551212"));
/**UseNEW_TASK_LAUNCHtolaunchtheDialerActivity*/
DialIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
/**FinallystarttheActivity*/
startActivity(DialIntent);
}
}

你现在应当来编译AndroidPhoneDialer并且在模拟器中运行它。处理编译和运行应用程序的过程在前面的章节中描述过了。你应当已经熟悉这些过程了。一旦你运行应用程序,模拟器启动。在漫长的启动过程后,你的活动被启动。

提示

保持模拟器运行是一个好主意,即使你完成了你的活动并且以及返回到代码窗口。大多数人的本能习惯是在他们完成了测试活动后关闭模拟器。但是,我发现使模拟器一直开启会帮助两个主要的问题。第一个就是启动模拟器要花费大量的时间。保持模拟器开启会避开漫长的开启时间。第二,我已经注意到有好几次当我做一些小的修改到一个活动,而且它们没有被复制到模拟器。保持模拟器开启似乎可以缓解这个问题。如果你在模拟器中有问题,在你的电脑中移除userdata-qemu.img文件。这个会让模拟器从一个干净的镜像启动。

如果你正确的跟从本例中的代码,你应当能看到下面的结果(略):

如你所见,你已经打开了电话的拨号盘。这个拨号盘显示了你传递的号码,5551212。使用模拟器,点击呼叫按钮。现在电话应当虚拟的呼叫555-1212。显示拨号盘是有用的,加入你创建了一个应用程序运行用户来在呼叫前可以编辑号码,或者确认他们真的想要呼叫这个号码。那么你应当怎么做来让应用程序为你打电话呢?答案就在下一节中。

从你的活动中打出电话第七章(4)

在本节中你将会学到呼叫拨号盘时增加什么样的Intent。你还会学到在活动代码中的哪一个地方增加选择的Intent。另外,你将学习如何分析一个作为URI的电话号码。从拨号盘活动代码变成呼叫活动你需要更改一些代码。在本节中,你回去编辑AndroidPhoneDialer活动,在打开拨号盘后,来打一个电话。

在活动中增加一个Intent,你还是需要Intent和Uri包装,所以,在AndroidPhoneDialer.java的文件头部保留这一部分。

importandroid.content.Intent;
importandroid.net.Uri;

这些包装将确保你不仅需要intent而且同样会传递需要的电话号码数据到Intent中(用Uri包装)。

提示

如果你不按照顺序匆匆看完这个章节,而且没有运作前一节实际的项目,那么就简单的创建一个新的项目,命名为AndroidPhoneDialer,然后增加前面提到的两个包装进去。这样会赶上进度。

现在看看在本章早些时候表格7-1中可以使用的ActivityActionIntents。你真正需要的是CALL_ACTION。很多的时候DIAL_ACTION打开Andriod拨号盘,CALL_ACTION将会启动电话的呼叫过程并且开始呼叫提供的号码。

要创建Intent,使用和创建拨号盘同样的程序,只是这次使用CALL_ACTION:

IntentCallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:5551212"));

请注意你使用Uri.parse来传递一个正确的电话号码到活动中。下一步是告诉Android你要把这个活动设为启动,并且启动它。使用下面的两行代码来实现:

CallIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
startActivity(CallIntent);

在第一行,你发送启动旗帜到NEW_TASK_LAUNCH。这个会启动一个呼叫的新示例。最后,你告诉Android使用你的Intent启动活动。当结束时,你的AndroidPhoneDialer.java文件应当如下:

packageandroid_programmers_guide.AndroidPhoneDialer;
importandroid.app.Activity;
Chapter7:UsingIntentsandthePhoneDialer129
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.net.Uri;
publicclassAndroidPhoneDialerextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
/**CreateourIntenttocallthedevice'sCallActivity*/
/**PasstheCallthenumber5551212*/
IntentCallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:5551212"));
/**UseNEW_TASK_LAUNCHtolaunchtheCallActivity*/
CallIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
/**FinallystarttheActivity*/
startActivity(CallIntent);
}
}

编译这个应用程序并且观察结果,你应当看到如下类似的错误信息。我实际上有意的要你看看这个错误,因为它展示了我们还没有发现的Android的另一面,错误的文本如下:

Application_Error:

Java.lang.SecurityException:
PermissionDenial:startingIntent


Android通过要求许可被执行来准许恰当的行动,在下一节叙述。

编辑活动许可

编辑活动许可第七章(5)

大多数的ActivityActionIntents是在需要许可在Android允许它行动之前的目录内的。和大多数的系统一样,Android只是需要确保有资格的活动来执行在它们之外的活动。这儿是许可可以使用的活动:

●ACCESS_ASSISTED_GPS

●INTERNAL_SYSTEM_WINDOW

●ACCESS_CELL_ID

●RAISED_THREAD_PRIORITY

●ACCESS_GPS

●READ_CONTACTS

●ACCESS_LOCATION

●READ_FRAME_BUFFER

●ACCESS_SURFACE_FLINGER

●RECEIVE_BOOT_COMPLETED

●ADD_SYSTEM_SERVICE

●RECEIVE_SMS

●BROADCAST_PACKAGE_REMOVED

●RECEIVE_WAP_PUSH

●BROADCAST_STICKY

●RUN_INSTRUMENTATION

●CALL_PHONE

●SET_ACTIVITY_WATCHER

●CHANGE_COMPONENT_ENABLED_
STATE

●SET_PREFERRED_
APPLICATIONS

●DELETE_PACKAGES

●SIGNAL_PERSISTENT_
PROCESSES

●DUMP

●SYSTEM_ALERT_WINDOW

●FOTA_UPDATE

●WRITE_CONTACTS

●GET_TASKS

●WRITE_SETTINGS

●INSTALL_PACKAGES

把这个许可列表和表格7-1做比较你应当发现大多数的Intent可以匹配。CALL_ACTION也不例外。你需要赋值CALL_PHONE活动许可来执行Intent。

要赋值相关的许可到活动,第一,你需要知道需要赋值哪一种许可。当前的例子是使用拨号盘活动。进入拨号盘活动是由CALL_PHONE许可管理的。通过赋值这个许可到你的活动,Android将允许你的Intent启动拨号盘活动。

怎么增加许可到活动中呢?你需要编辑活动的Manifest。如果你使用Eclipse,双击AndroidManifest.xml文件,打开AndroidManifest窗口,如下图(略)。

要编辑活动的许可,点击Permission链接。会把你带到ManifestPermissions窗口,如下图(略)。这个窗口列出了当前赋值到你活动的许可。假定你在一个新的项目中,还没有任何的赋值。因此,点击增加按钮来开始进程。在对话框中,选择使用许可并且点击OK。

回到AndroidManifestPermission窗口,在名称的下拉框中,选择android.permission.CALL_PHONE,如下所示(略)。这样就会增加CALL_PHONE许可到你的活动中。现在,你已经增加了CALL_PHONE许可,看看AndroidManifest.xml文件。它应当和下面相类似:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidPhoneDialer">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidPhoneDialer"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<uses-permissionandroid:name="android.permission.CALL_PHONE">
</uses-permission></manifest>

最有意思的一行实在文件的最后:

<uses-permissionandroid:name="android.permission.CALL_PHONE">
</uses-permission>

这行代码是由AndrodpluginforEclipse增加的。如果你需要,你可以直接编辑AndroidManifefst.xml文件来赋值。但是,如果有多次情况当你不确定需要增加哪一种许可,或者什么语法来增加,你可以使用Manifest的向导。

现在许可已经到位了,重新编译并且允许你的活动。你的模拟器应当可以呼叫电话号码了,如下图(略)。

你创建的活动已经使用了一个Intent来启动设备的呼叫活动并且呼叫号码555-1212。这个演示了使用Intent的好处。总而言之,这个应用程序实际的为你做了一些事情。那就是说,启动一个带有电话号码代码的活动,只是打一个电话?在下一节中,你会通过增加一个按钮来启动Call_Action的Intent,增加一个文本框来运行用户输入他们选择的电话号码来更多的制作应用程序。

修改AndroidPhoneDialer

修改AndroidPhoneDialer第七章(6)

本节展示如何通过修改AndroidPhoneDialer来增加一些特性,使得它更加的具有实际价值。到本节结束时,你不仅仅对使用intent的得心应手,而且还会使用EditTexts和Buttons。

警告

如果你没有跟从上一节的项目,回去并创建那个活动。本节的教程假定你已经完成了从上个项目的可以自行支配的编码工作。

增加一个按钮

本节向你展示如何修改你的项目来包含一个按钮。当活动被启动,替代启动Intent的将会是一个按钮。除了文本,按钮是应用程序里最常用的对象。按钮组织用户和程序之间的交互作用。在Android里学会如何创建并利用按钮是必要的,如果要创建一个友好的活动。

你将要在main.xml里创建按钮了。回想一下第五章,你为HelloWorld!活动创建了TextView。TextView有一个清晰的结构,就像这样:

注意

记住,当你在main.xml里创建一个View时,你只是告诉Android,你想让这个View看上去是什么样的。你仍旧需要在AndroidPhoneDialer.java内把功能赋值给它。

<Viewandroid:id=<id>
android:layout_width=<width>
android:layout_height=<height>
>

这个格式对于所有的views都有用,并且Button也不例外。你需要为你的Button设定的XML属性是android:id,android:layout_width,
android:layout_height,和android:text。这4个XML属性充分的描述了按钮,那样你就可以在活动中使用它了。

1、赋值你的按钮的ID到callButton:

android:id="@+id/callButton"

2、独自设置layout_width和layout_height到fill_parent和wrap_content。

android:layout_width="fill_parent"
android:layout_height="wrap_content"

3、设置按钮的文本为“ShowDialer”,这个是一个清晰的描述来告知按钮的作用:

android:text="ShowDialer"
ThefullXMLfortheButton,withattributes,lookslikethis:
<Buttonandroid:id="@+id/callButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ShowDialer"/>


现在看看完成后的main.xml文件。按钮在上下文中显示并且等待着你的编码。

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Buttonandroid:id="@+id/callButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ShowDialer"/>
</LinearLayout>

要开始为按钮增加功能,你需要增加其它的包装到AndroidPhoneDialer.java这个文件。包含按钮View的是android.widget.Button;

现在你已经在项目里输入按钮窗口小部件了(Buttonwidget)。这些给了你必要的信息来开始你项目的编码工作。你项目的最终编码结果应当是有一个按钮在活动中,当点击它,启动呼叫活动。呼叫活动应当和数据“tel:5551212”一起启动。屏幕显示结果应该和初始的AndroidPhoneDialer相匹配。描述的功能围绕一些不同的概念。首先,你必须编程一个按钮,按钮的属性在main.xml文件内建立。下一步,你必须创建一个功能来启动前一个编码项目的CALL_ACTION这个Intent。最后,你的按钮应该能执行这个功能并且启动Intent。

创建按钮的语法是

finalButton<button_name>=<button>


等号左边的部分在代码中创建按钮。右边是从main.xml文件中调用按钮的属性。要调用它的属性,你使用findViewByID()把结果投递为一个按钮。这个听起来比它实际情况复杂。

记住,当你增加按钮的属性到main.xml文件中,你给了这个按钮一个定义好的android:id,callButton,而这个通过AndroidpluginforEclipse在id文件中以R.id.callbutton注册。使用findViewById()来从main.xml文件中通过传递idcallButton来找回:

findViewById(R.id.callButton)

别忘了投递为按钮

(Button)findViewById(R.id.callButton)

这个声明构成了等号右边的内容。创建完整的按钮应该是这样的:

finalButtoncallButton=(Button)findViewById(R.id.callButton);

现在你有一个可以工作的按钮了,但是你需要它来做点什么。按钮本身在没有代码的情况下无法完成太多的工作。根据本例的目的,你需要让它来执行CALL_ACTION这个Intent。因此,你需要在你现有的Intent呼叫中创建一个小的功能。这样,当按钮被按下时,你可以呼叫了。

如果你熟悉Java编程,这里就没什么好奇怪的了。你将要设置onClick()方法来从前一个部分呼叫Intent代码。onClick()方法拿去一个View作为一个变量;但是,本例中,并没有在onClick()方法本身内呼叫任何的View:

publicvoidonClick(Viewv){
IntentcallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:5551212"));
callIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
startActivity(callIntent);
}

代码左边是程序中仅有的绑定按钮到onClick的接受器。接收器对于Java编程者是非常熟悉的。对于不熟悉Java或者接收器的人来说。接收器是Java对象可以从其它对象“接受”的方法。同样的概念也适用于Android。你可以在Android中建立接收器,来允许Android的Views从其它的输入中处理呼叫。对于本项目,你需要为你的按钮创建一个接收器来接受活动中的按钮的onclick事件。当用户按下按钮,接收器,接收器会呼叫在onClick()方式中的代码。要建立接收器,你需要使用按钮的setOnClickListener()方法。

如果你熟悉Java开发,这个结构不应当看起来陌生。这个是Java中典型的onClickListener接口执行。你在这里将会看到的是使用一个Java匿名的类来为按钮执行onClickListener。还有,作为一个匿名类,你可以用作本地变量来使用-本例中是按钮,如果变量被定义为最终的。

setOnClickListener()方法抓取一对变量。第一个是onClickListener()的实例。第二个是早前建立的onClick。你的setOnClickListener()应当看上去想这样:

callButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
IntentcallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:5551212"));
callIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
startActivity(callIntent);
}
});

这部分代码陈述了当callButton被按下,onClickListerner将执行onClick中的代码。onClick中的代码将执行CALL_ACTIONIntent并且呼叫电话号码555-1212。

你完成的AndroidPhoneDialer.java看起来像这样:

packageandroid_programmers_guide.AndroidPhoneDialer;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.view.View;
importandroid.content.Intent;
importandroid.net.Uri;
publicclassAndroidPhoneDialerextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
/**CreatetheButton*/
finalButtoncallButton=(Button)findViewById(R.id.callButton);
/**SettheonClickListenertocalltheonClick*/
callButton.setOnClickListener(newButton.OnClickListener(){
/**UsetheonClicktocalltheexistingIntentcode*/
publicvoidonClick(Viewv){
IntentcallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:5551212"));
callIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
startActivity(callIntent);
}
});
}
}

在模拟器中编译并运行这个活动。主要的活动将显示标签为ShowDialer的按钮。点击这个按钮,它应当打开Call活动并拨号555-1212。主要活动应该如下图(略)。

如你所见,Android是一个非常健全并灵活的平台。有几行相关的代码,比一页少,你已经创建了一个利用设备电话硬件的活动并且由按钮启动。同样,就这一点来说,你应当对Android处理活动,Intent和Views的方式刚到舒适。

你的AndroidPhoneDialer活动仍然不是很健全。你需要增加一个更多的条目。本章的最后一节展示如何使用EditTextView来运行用户输入一个电话号码。这个号码然后会被传递到CALL_ACTIONIntent(而不是代码写好的数值:tel:5551212)。

执行一个EditTextView第七章(7)

你需要增加一个View到活动中来使得用户输入一些文本。然后你会分析那个文本并把它发送到前一节的Intent呼叫中。因为所有的视图是从基本的视图中派生出来的,它们在结构和使用方面非常的相似。你会发现执行一个EditText是一个非常简单的操作。

首先,在main.xml文件中放置Views。实际上这里要放两个View:一个TextView来实现作为一个标签并且给出一些指示给用户,另外一个就是EditView来接收用户的输入。这个Views一起将增加深度和实用性到你的活动中。

因为你组成活动的外观,记住.xml是在视觉上构成的。这个就意味着加入你要TextView在最后的活动中显示在EditText的上面,你应当在main.xml文件中把它放在EditText之前。

因为你已经用过好几次TextViews了,所以这里不会讲的太多。简单的看一些你设置的TextView的属性:

<TextViewandroid:id="@+id/textLabel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="EnterNumbertoDial:"
/>

没什么特别的地方。这只是个简单的TextView用文本输入号码来拨号:。这个TextView将会用做EditView的显示标签。这里是你为EditView设置的属性:

<EditTextandroid:id="@+id/phoneNumber"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>

注意

你没有必要一定去设置androd:text属性,因为你不需要任何缺省的文本。

这个id被用来设为phoneNumber,这是个名字,你将要用来在代码中参阅到EditText。再说一次,当设置main.xml时,没什么特别之处。最后的文件应当看起来如下:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
142Android:AProgrammer’sGuide
Chapter7:UsingIntentsandthePhoneDialer143
android:layout_height="fill_parent"
>
<TextViewandroid:id="@+id/textLabel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="EnterNumbertoDial:"
/>
<EditTextandroid:id="@+id/phoneNumber"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Buttonandroid:id="@+id/callButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="ShowDialer"/>
</LinearLayout>

main.xml现在完成了。你可以转移到AndroidPhoneDialer.java来继续工作。假如你不在使用一个现存的AndroidPhoneDialer.java文件——本章中的前一个项目——你可能需要参阅前一部分去看看增加到.java文件中是什么样的代码。这样会确保你从代码中的正确部分开始。

在.java文件中你第一个增加的条目是包装定义。你不仅需要增加包装到Uri,按钮和Intent,同时还要到EditText:

importandroid.widget.Button;
importandroid.content.Intent;
importandroid.net.Uri;
importandroid.widget.EditText;

设置EditTextView的语法和设置按钮的语法一致:

finalEditText<edittext_name>=<edittext>


再说一次,呼叫你的EditTextphoneNumber。创建EditText的代码如下:

finalEditTextphoneNumber=(EditText)findViewById(R.id.phoneNumber);

一旦你的phoneNumber这个EditTexT创建好了,你可以使用它来参阅在设备上输入的文本。现在你要做的就是呼叫phoneNumber.getText()来找回用户的输入。在下面的行里替换代码数值“tel:5551212”

Intent(Intent.CALL_ACTION,Uri.parse("tel:5551212"));
withthevalueofgetText():
Intent(Intent.CALL_ACTION,Uri.parse("tel:"+phoneNumber.getText()));

这就是本项目所有你需要更新的新代码。有了这两个新的附加内容,你可以给用户一个可以输入电话号码的对象,并且把号码发送到电话的呼叫活动中。完整的.java文件中的代码如下:

packageandroid_programmers_guide.AndroidPhoneDialer;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.view.View;
importandroid.content.Intent;
importandroid.net.Uri;
importandroid.widget.EditText;
publicclassAndroidPhoneDialerextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
finalEditTextphoneNumber=(EditText)findViewById(R.id.phoneNumber
);
finalButtoncallButton=(Button)findViewById(R.id.callButton);
callButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
IntentCallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:"+phoneNumber.getText()));
CallIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
startActivity(CallIntent);
}
});
}
}


当你在模拟器中运行应用程序,你应当看到一个类似于下面插图的屏幕(略)。

试试这个:修改AndoridPhoneDialer项目

试试这个:修改AndoridPhoneDialer项目第七章(8)

如果你一直在搞最后版本的AndroidPhoneDialer,你或许发现有些东西错过了。不幸的是,项目当前的写作方式总是允许你输入任何类型的数值到EditTextView中,并且发送到Call活动中。这个真不是最佳的应用程序开发。研究一下并且增加验证到EditText中。使用下面的参数来修改项目:

●使用常规的表达式来验证一个号码被输入到EditText中(packagejava.regex)。
●使用showAlert()语法来显示一条信息告诉用户他们输入的内容和你的常规表达式不匹配。当你觉得已经找到解决方案,和下面的代码做个比较:

main.xml

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextViewandroid:id="@+id/textLabel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="EnterNumbertoDial:"
/>
<EditTextandroid:id="@+id/phoneNumber"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Buttonandroid:id="@+id/callButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="ShowDialer"/>
</LinearLayout>
AndroidPhoneDialer.java
packageandroid_programmers_guide.AndroidPhoneDialer;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.view.View;
importandroid.content.Intent;
importandroid.net.Uri;
importandroid.widget.EditText;
importjava.util.regex.*;
publicclassAndroidPhoneDialerextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
finalEditTextphoneNumber=(EditText)
findViewById(R.id.phoneNumber);
finalButtoncallButton=(Button)findViewById(R.id.callButton);
callButton.setOnClickListener(newButton.OnClickListener(){
Chapter7:UsingIntentsandthePhoneDialer147
publicvoidonClick(Viewv){
if(validatePhoneNumber(phoneNumber.getText().toString())){
IntentCallIntent=new
Intent(Intent.CALL_ACTION,Uri.parse("tel:"+phoneNumber.getText()));
CallIntent.setLaunchFlags(Intent.NEW_TASK_LAUNCH);
startActivity(CallIntent);
}
else{
showAlert("PleaseenteraphonenumberintheX-XXX-XXX-XXXX
format.",0,"FormatError","Re-enterNumber",false);
}
}
});
}
publicbooleanvalidatePhoneNumber(Stringnumber){
PatternphoneNumber=Pattern.compile("(\\d-)?(\\d{3}-)?\\d{3}
\\d{4}");
Matchermatcher=phoneNumber.matcher(number);
returnmatcher.matches();
}
}

当你运行本项目,它应当产生一个信息和下图类似(略)。

在下一章中,你将学习更多的Views。你将创建创建一个多活动应用程序来允许你浏览并创建本书中还没有谈论过的Views。你也会创建并利用一个菜单系统来启动你的活动。

问专家

Q:有办法来建立一个呼叫或者从模拟器中来确保这些活动正在工作吗?

A:当本书写的时候,还没有办法。但是,谷歌内部的讨论是在将来发布的SDK,开发者将有可能打开两个模拟器并且在两个模拟器之间呼叫。

Q:还有其它类型的按钮可用于活动吗?看上去或者感觉不一样的。

A:是的。你可以使用风格属性来创建小按钮,或者上下左右指示的小按钮。

题外话:到现在为止,第七章的翻译工作完成,下面是第八章。时间已过去一个半月。

第八章列表,菜单和其它Views

列表,菜单和其它Views第八章(1)

关键技能&概念
●建造活动
●使用Android菜单
●使用AutoCompleteTextView

本章会讨论更深层次的Views和Intents,正如被证明这些是作为一个Android新手最好要掌握的特性。这个两个实体将组成早期活动的主体。几乎每一个你创建的活动需要至少一个View,并且其中的大多数将需要呼叫一个到两个Intent。学习这些东西的最好办法就是动手实践。阅读这些话题并且回顾属性清单是另外一个需要做的事情,但是自己执行代码却是另外一件事。那就是如你在上一章所作,你将建造一个使用Views和Intents的活动。通过构造这个应用程序,你将获得对Views和Intents最好的体验。

前两章通过创建非常简单的,开发Views和Intents的少量基本功能的活动来大致介绍了Views和Intents。在本章中,你会去建造一个稍微复杂一点的使用Intent来呼叫一个新活动的活动,而这个新活动你也要创建。这个新活动会展示在本版本SDK中可用的Views。

本章解释这些View的功能性,如AutoComplete清单和图表种类,并介绍每个View属性的不同。作为开始,创建一个新的Eclipse项目并命名为AdnrodiViews。创建有下列插图(略)参数的项目:包装的名称为android_
programmers_guide.AndroidViews,活动的名称为:AndroidViews,并且应用程序的名称为AndroidViews。

项目创建好以后打开main.xml文件,从里面把HelloWorld!代码移除。有了一个创建的项目和干净的main.xml文件,你可以开始来增加你的代码了。

建造活动
到目前为止,你仅仅创建过单活动应用程序。那就是说,你创建的非常简单的应用程序,只包含了一个屏幕的数据。稍等片刻,想一下你使用的最后几个应用程序。偶尔它们使用了超过一个“窗口”。大多数应用程序使用多重窗口来采集,显示并保存数据。你的Android应用程序应当是一样的。

虽然你还没有学习如何创建在Android上运行多重活动的应用程序,但是在最后几章中你得到了如何改变多重活动的提示。你使用了一个新的概念——Intents来呼叫并且运行一个核心的Android活动。这个概念在本章中仍旧适用,但是,当你需要来呼叫创建的活动,与呼叫核心的Android活动相反,它执行起来是不同的。

你一件你要做的事情是构造活动。然后你可以创建呼叫它们的Intents。构造活动时,需要按照下列步骤进行。
●为.xml文件准备Intent代码
●为.java文件准备Intent代码
●使用一个Intent呼叫活动

一旦你创建你的第一个附加活动,其它的应当非常的简单。

注意

这些步骤并不是必须的。你可以已任何的次序来执行它们。
NOTE
为.xml文件准备Intent代码

记住Android活动包含三个主要部分:包含代码的.java文件,控制输出的.xml文件和包装的Manifest。到本书的这里,你只用到main.xml来控制单活动的输出。但是,要更好的使用多活动,你必须有multiple.xml输出文件。

要创建一个新的.xml文件,打开你的Eclipse项目并且找到包装资源管理器。打开res目录,右击layout文件夹,并且选择新建文件。在新建文件对话框,如下所示(略),命名为test.xml。

警告

确保以小写字母输入test.xml。新的.xml文件名必须是小写字母。

输出文件创建后。要使活动正常的工作,增加下列代码到test.xml文件中。这个代码会为你的输出提供一个基础。如果你需要,你可以简单的复制在main.xml文件现有的代码。

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</LinearLayout>

为.java文件准备Intent代码

再次使用包装资源浏览器,找到src目录,打开它,并在android_programmers_guide.AndroidViews这个包装上右击,如下所示(略)。

再一次,你将在这文件夹中增加新文件。在你右击AndroidViews包装后,从上下文菜单中选择新建文件。这个文件会在本项目中为第二个活动控制所有的代码。把文件命名为test.java。你现在有了一个非常好的,新的(但是是空的).java文件,只需要在其中增加代码来使它工作了:

packagetestPackage.test;
importandroid.app.Activity;
importandroid.os.Bundle;
publicclasstestextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.test);
/**ThisisourTestActivity
Allcodegoesbelow*/
}
}



注意,使用上下文R.layout.test,你以setContentView方法呼叫test.xml,。这行告诉新的活动为本“页”使用你创建的.xml文件作为输出文件。

修改AndroidManifest.xml文件

修改AndroidManifest.xml文件第八章(2)

在Eclipse内打开你的Androidmanifest.xml文件。在本书中还没有大量的讨论这个Androidmanifest.xml文件呢。Androidmanifest.xml文件包含项目的全局设置。更重要的是,Androidmanifest.xml还为项目包含了Intent过滤器。

第七章讨论了Android如何使用过滤器来排列哪种Intent可以被哪种活动所接受。使这个过程方便的信息就保留在Androidmanifest.xml中了。

注意

每个项目只能有一个Androidmanifest.xml文件。

如果你的Androidmanifest.xml文件是打开的,它应当如下显示:

<activityandroid:name=".AndroidViews"android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

你在这里要看的是AndroidView活动——项目创建的主要活动的Intent过滤器。对于这个文件,你可以增加任何其它的Intent过滤器来交给项目处理。本例中,你要增加处理你创建的Test活动的过滤器。下面是你需要为Intent过滤器增加的代码到Androidmanifest.xml文件中。

<activityandroid:name=".Test"android:label="TestActivity">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>


增加代码到AndroidManifest.xml文件中确保Android为Test活动传递Intent到正确的地方。完整的AndroidManifest.xml文件应当如下:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android

package="android_programmers_guide.AndroidViews">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
</application>
</manifest>

现在你的活动可以为Test活动处理Intent呼叫了。要让你的Intent呼叫Test活动,你将要使用和在第七章呼叫电话拨号盘非常类似的结构。下面的代码会设置你的Intent:

注意

当你启动应用程序,将要打开的活动是你创建项目的AndroidViews活动。因此,放置下面的代码在AndroidViews.java中来启动Test活动。

IntenttestActivity=newIntent(this,test.class);

这一行创建一个叫做testActivity的Intent。参数test.class告诉呼叫,你要testActivity这个Intent来展示创建的和本活动相关联的Test活动。

警告

当你使用Intents时,不要忘记输入android.content.intent包装。

最后,使用startActivity()方法来精确启动Test活动:

startActivity(autocomplete);
YourcompletedAndroidViews.javafileshouldlooklikethis:
packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.content.Intent;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated./
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
/**SetupourIntent/

在模拟器中运行这个应用程序。Android应当启动AndroidViews活动,紧跟着Test活动。

在下一节中,你将使用这些技巧来创建一个启动多重活动的应用程序。每个活动将在一个View里,这样你可以应用不同的选项。这个将会给你大量的练习显示并熟练掌握Views和使用活动。

注意

要使用本章剩下的例子,移除本节创建的Test活动。你要继续做没有Test活动的AndroidViews项目的作品。

使用菜单

使用菜单第八章(3)

在本节中,你将建造一个应用程序来允许用户从一些不同的Views中进行选择。当用户选择一个View,一个新的活动将被启动。你将要使用给用户选择的工具就是Android菜单。看一下这个插图(略)。当用户激活菜单按钮,菜单就会显示。

如你所见,从Android主屏幕选择菜单按钮产生一个墙纸的设定选项。你将为你的主活动创建一个类似的菜单,该菜单为View保留所有用户可以从中选择的选项。现在,AndroidViews.java文件的编码应当如下:

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
}

你增加任何东西到活动中,你需要输入包装一个包装来创建你的菜单。输入android.view.Menu到AndroidViews活动中:

Importandroid.view.Menu;

要创建菜单,你需要优先活动的onCreateOptionMenu()方法。onCreateOptionMenu()方法是一个以布尔方式被呼叫的当用户第一次选择菜单按钮。你将使用这个方式来建造菜单并增加可选择的条目到其中。增加下面的代码到AndroidViews.java:

@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
}

你将增加代码在onCreaterOptionMenu()内增加菜单。需要增加的条目是你将要在本项目中创建的Views。下面是将要增加到菜单的清单:

●AutoComplete自动完成
●Button按钮
●CheckBox选择框
●EditText
●RadioGroup按钮组
●Spinner
在前面创建的代码优先onCreateOptionsMenu()方法,你在菜单中传递一个菜单变量呼叫菜单。这个变量代表实际的在Android界面中创建的菜单条目。要增加这些条目到菜单中,你要使用menu.add()方法。这个呼叫的语句是:

menu.add(<group>,<id>,<title>)

参数组被用来与菜单条目相关联。在本例中,你将不使用组。但是,这个值是非常重要的。参数id被用来检测哪一个菜单条目被选择。最后,参数标题是显示在菜单上的文本。

增加下列代码到onCreateOptionsMenu()方法中:

menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");

你完整的AndroidViews.java文件应当看上去像这样:

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
}

如果你执行如上面代码所写,你应当看见如下图所示的菜单(略)。

这个就是你想要达成的。试着去点击菜单中的任意一个选项。当用户选择一个菜单项目时,你在处理世间的活动中还什么都没有呢。

你增加的方法优先处理到菜单的呼叫是onOptionsItemSelected()。再一次,像onCreateOptionsMenu(),当菜单项被选择,onOptionsItemSelected()是你需要优先一个拥有代码,被执行的布尔方法。优先代码应当如下:

@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
}

这个代码有个问题:当任何菜单项被选择,onOptionsItemSelected()是个常规被呼叫的方法。你需要给定onOptionsItemSelected()一条路来识别不同菜单项之间的差别并执行相应的代码。因此,使用一个switch/case声明来帮助方法从不同的项目中选择。当你创建了菜单项,你定义了一系列的从0到5的数字作为菜单项的值。你可以在case声明中使用一个呼叫来getI()来检测哪一个菜单项被选择:

switch(item.getId()){
case0:
returntrue;
case1:
returntrue;
case2:
returntrue;
case3:
returntrue;
case4:
returntrue;
case5:
returntrue;
}
returntrue;

在这个case声明中,对于每一个id当前设定的动作是返回true.这个不会做任何的事情但是会保留一个开放的可以增加代码的区域。你的AndroidViews.java文件现在可以被用来创建被新菜单系统启动的活动了。完整的AndroidViews.java文件代码应当看上去如下:

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated./
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
/**AddonemenuitemforeachViewinourproject*/
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
/**OverrideonOptionsItemSelectedtoexecutecodeforeach
menuitem*/
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
}
/**Selectstatementtohandlecalls
tospecificmenuitems*/
switch(item.getId()){
case0:
returntrue;
case1:
returntrue;
case2:
returntrue;
case3:
returntrue;
case4:
returntrue;
case5:
returntrue;
}
returntrue;
}
}

完成了AndroidViews.java,你可以重点去创建其它的活动了,在下一节中,你将在项目中为每一个View创建一个活动并增加代码来启动case声明中view的活动。

为AutoComplete创建一个活动

为AutoComplete创建一个活动第八章(4)

在本节中,你将创建一个突出AutoCompleteTextView的活动。AutoCompleteTextView对你的有应用程序来说可以成为一个非常有力的工具。特别是对于Android主屏幕有限的空间来说。

AutoCompleteTextView,正如这个名字所说,是修改后的TextView,而它可以参考到可用的单词或者短语并自动完成输入。这样的Views是在移动应用程序里是非常有用的当你不想花费大量的空间到一个ListView,或者你想要加速你输入文本的过程。

要开始为AutoCompleteTextView创建活动,你需要为布局增加一个新的.xml文件,为代码增加一个.java文件,并且一个Intent过滤器来处理呼叫。

提示

创建这些条目的过程出现在本章“构造活动”一节中。创建下面项目的部分时可以参考那个部分。

创建一个autocomplete.xml文件
在你的AndroidViews项目中创建一个新的.xml文件,并命名为autocomplete.xml。记住文件名必须用小写。这个文件应当出现在layout文件夹。双击这个文件来编辑它。这个文件会控制AutoCompleteTextView活动的布局,所以你需要在布局中有一个AutoCompleteTextView。增加过AutoCompleteTextView的XML文件应当如下:

<AutoCompleteTextViewandroid:id="@+id/testAutoComplete"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>

你已经在.xml文件中创建了几个Views了,所以你应当熟悉这个格式。对于AutoCompleteTextView,没什么特别之处。你设定id到testAutoComplete,还有相应的宽度和高度到fill_parent和wrap_content,还应该为两个按钮增加布局。这些按钮将被用于你将改变的属性控制。命名按钮为autoCompleteButton和textColorButton,如下:

<Buttonandroid:id="@+id/autoCompleteButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeLayout"/>
<Buttonandroid:id="@+id/textColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeTextColor"/>


有了新增的三个View布局,你完成的autocomplete.xml文件看上去应该像这样:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<AutoCompleteTextViewandroid:id="@+id/testAutoComplete"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/autoCompleteButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeLayout"/>
<Buttonandroid:id="@+id/textColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeTextColor"/>
</LinearLayout>

创建一个autocomplete.java文件
跟从本章“创建一个新的java文件”的介绍。第一件要做的事就是为你的Views输入包装。在这个活动中,使用了两个Views,AutoCompleteTextView和按钮。你还需要设置颜色和一个ArrayAdapter。因此,输入下面包装到活动中:

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.ArrayAdapter;
importandroid.widget.AutoCompleteTextView;
importandroid.widget.Button;
importandroid.graphics.Color;

注意

现在你可能不知道它们的用途,先加入它们吧,我会解释的。

为AutoCOmplete类增加初始的结构到autocomplete.java文件中:

publicclassAutoCompleteextendsActivity{
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
}
}

这个类给了你建造活动其它部分的基础。这个活动的所有功能将会被围绕这个类建造。第一件要做的事就是从autocomplete.xml中装载布局:

setContentView(R.layout.autocomplete);

对于本例,将创建AutoCompleteTextView,所以它包含一年中的月份。当一个用户在框中输入,它会猜测那个月份用户试图去输入。假定AutoCompleteTextView将包含月份的清单,你需要来创建一个可以被赋值到AutoCompleteTextView的清单。

创建字符串数组并赋值月份数值到其中:

staticfinalString[]Months=newString[]{
"January","February","March","April","May","June","July","August",
"September","October","November","December"
};

下一个任务是复制这个字符串到AutoCompleteTextView。到目前为止,你已经创建了一些Views了。所以,创建AutoCompleteTextView的代码看上去应该很熟悉。你之前没看到过的就是把字符串赋值给View:

ArrayAdapter<String>monthArray=newArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,Months);
finalAutoCompleteTextViewtextView=(AutoCompleteTextView)
findViewById(R.id.testAutoComplete);
textView.setAdapter(monthArray);

在第一行,拿去创建的字符串数组并且复制到一个名为monthArray的ArrayAdapter。下一步,你通过在.xml文件中定位来例示AutoCompleteTextView。最后,使用setAdapter()方法来赋值monthArrayArrayAdapter到AutoCompleteTextView中。

下一个零星的代码例示那两个按钮。与上一章的代码相同。唯一和你所写代码不同的是你正在呼叫两个函数,changeOption和changeOption2,而这些,你还没有创建呢。

注意

你在传递AutoCompleteTextView到函数呼叫。当你创建函数还需要创建参数。

finalButtonchangeButton=(Button)findViewById(R.id.autoCompleteButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(textView);
}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.textColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(textView);
}
});

被这些按钮呼叫的函数将被用于在AutoCompleteTextView改变布局属性。这两个我选择来修改的属性(通过两个按钮)是布局的高度和文本的颜色。你将设置一个按钮来改变AutoCompleteTextView的布局高度,从30到100并且返回。另一个按钮将改变View内文本的为红色。

函数changeOption()会改变AutoCompleteTextView的布局高度。代码非常的简单:

publicvoidchangeOption(AutoCompleteTextViewtext){
if(text.getHeight()==100){
text.setHeight(30);
}
else{
text.setHeight(100);
}
}

在这个函数中你要做的就是检查当前AutoCompleteTextView的高度。如果高度是100,把它设为30,否则设为100。

changeOption2()函数也简单:

publicvoidchangeOption2(AutoCompleteTextViewtext){
text.setTextColor(Color.RED);
}
}

这个函数简单的把AutoCompleteTextView的文本颜色设为Color.RED。

Color.RED的数值从android.graphics.Color包装中导入。你可以浏览这个包装并且改变这个颜色到任何的数值。我选择红色是因为它比较突出。

完整的autocomplete.java文件应当看起来像这样:

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.ArrayAdapter;
importandroid.widget.AutoCompleteTextView;
importandroid.widget.Button;
importandroid.graphics.Color;
publicclassAutoCompleteextendsActivity{
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.autocomplete);
ArrayAdapter<String>monthArray=newArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,Months);
finalAutoCompleteTextViewtextView=(AutoCompleteTextView)
findViewById(R.id.testAutoComplete);
textView.setAdapter(monthArray);
finalButtonchangeButton=(Button)
findViewById(R.id.autoCompleteButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(textView);
}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.textColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(textView);
}
});
}
staticfinalString[]Months=newString[]{
"January","February","March","April","May","June","July","August",
"September","October","November","December"
};
publicvoidchangeOption(AutoCompleteTextViewtext){
if(text.getHeight()==100){
text.setHeight(30);
}
else{
text.setHeight(100);
}
}
publicvoidchangeOption2(AutoCompleteTextViewtext){
text.setTextColor(Color.RED);
}
}

增加一个Intent过滤器
在运行这个应用程序之前最后一件事就是在AndroidManifest.xml文件中设置intent过滤器。然后你能从菜单中呼叫intent了。Intent过滤器的代码如下:

<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

这儿是本项目完成的AndroidManifest.xml文件:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidViews">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
</application>
</manifest>
HandlingtheIntentCall
WithAndroidManifest.xmlcomplete,addthefollowingfunctiontoAndroidViews.java:
publicvoidshowAutoComplete(){
Intentautocomplete=newIntent(this,AutoComplete.class);
startActivity(autocomplete);
}

当从select/case声明中呼叫,这个函数将打开autocomplete活动,编辑select声明的case0来让它呼叫新的函数:

case0:
showAutoComplete();
returntrue;

在Android模拟器中运行这个应用程序。当主活动启动后,点击菜单按钮的AutoComplete菜单项。点击后应当把你带到autocomplete活动。

要测试AutoCompleteTextView,开始输入单词January。在你输入几个字母后,你将会看到January出现在文本框中。下一步,点击ChangeLayoutButton按钮,结果会是一个扩展的文本输入框。现在点击changTextColor按钮并且输入一些文本。

下一节会给你项目中剩下5个Views的代码支持。

按钮

按钮第八章(5)

看看下面的代码。这段代码代表了四个文件,AndroidManifest.xml,
Button.xml,testButton.java,和AndroidViews.java。增加代码到现存的AndroidViews活动中。

警告

如果你没有一开始就跟从本章,你执行代码时可能会遇到麻烦。要确保得到完整的项目,请从本章的开始开始阅读。

AndroidManifest.xml

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidViews"
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testButton"android:label="TestButton">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>


Button.xml

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
Chapter8:Lists,Menus,andOtherViews173
android:layout_height="fill_parent">
<Buttonandroid:id="@+id/testButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ThisisthetestButton"/>
<Buttonandroid:id="@+id/layoutButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeLayout"/>
<Buttonandroid:id="@+id/textColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeTextColor"/>
</LinearLayout>


testButton.java

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.Button;
importandroid.graphics.Color;
publicclasstestButtonextendsActivity{
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.Button);
finalButtonButton=(Button)findViewById(R.id.testButton);
finalButtonchangeButton=(Button)findViewById(R.id.layoutButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(Button);}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.textColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(Button);
}
});
}
publicvoidchangeOption(ButtonButton){
if(Button.getHeight()==100){
Button.setHeight(30);
}
174Android:AProgrammer’sGuide
Chapter8:Lists,Menus,andOtherViews175
else{
Button.setHeight(100);
}
}
publicvoidchangeOption2(ButtonButton){
Button.setTextColor(Color.RED);
}
}


AndroidViews.java

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.content.Intent;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
case0:
showAutoComplete();
returntrue;
case1:
showButton();
returntrue;
case2:
returntrue;
case3:
returntrue;
case4:
returntrue;
case5:
returntrue;
}
returntrue;
}
publicvoidshowButton(){
IntentshowButton=newIntent(this,testButton.class);
startActivity(showButton);
}
publicvoidshowAutoComplete(){
Intentautocomplete=newIntent(this,AutoComplete.class);
startActivity(autocomplete);
}
}


启动你的应用程序并且选择从菜单上选择按钮选项。试着点击ChangeLayout按钮。再一次,对文本来说,结果是一个较宽的显示区域,点击改变TextColor按钮并且文本变成红色。

CheckBox

CheckBox第八章(6)

在本节中,将为CheckBoxView创建一个活动。创建活动的步骤和前面的章节一致。因此,将会为你提供三个主要活动文件——AndroidManifest.xml,
checkbox.xml,和testCheckBox.java。这些文件在下面的部分提供。

AndroidManifest.xml

这个部分包含当前AndroidView的完整代码。如果你使用Eclipse,修改你活动的AndroidManifest.xml文件,来使得它和下面一样:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidViews">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testButton"android:label="TestButton">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testCheckBox"android:label="TestCheckBox">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

checkbox.xml

这个部分显示了完整的checkbox.xml代码。使用在本章早些时候提到的方法,在项目中创建一个新的XML文件,把它命名为checkbox.xml。使用下面的代码修改你的文件。

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<CheckBoxandroid:id="@+id/testCheckBox"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ThisisthetestCheckBox"/>
<Buttonandroid:id="@+id/layoutButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeLayout"/>
<Buttonandroid:id="@+id/textColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeTextColor"/>
</LinearLayout>

testCheckBox.java
本部分涵盖了执行Checkbox活动所需的最后新文件。在项目中创建一个新的.java文件,并把它命名为testCheckBox.java。这个是活动的主文件并且包含可执行代码。在testCheckBox.java文件中使用下列代码:

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.CheckBox;
importandroid.widget.Button;
importandroid.graphics.Color;
publicclasstestCheckBoxextendsActivity{
180Android:AProgrammer’sGuide
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.checkbox);
finalCheckBoxcheckbox=(CheckBox)findViewById(R.id.testCheckBox);
finalButtonchangeButton=(Button)findViewById(R.id.layoutButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(checkbox);}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.textColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(checkbox);
}
});
}
publicvoidchangeOption(CheckBoxcheckbox){
if(checkbox.getHeight()==100){
checkbox.setHeight(30);
}
else{
checkbox.setHeight(100);
}
}
publicvoidchangeOption2(CheckBoxcheckbox){
checkbox.setTextColor(Color.RED);
}
}

AndroidViews.java
创建这个活动的最后一步就是编辑AndroidViews.java文件。如果你要从AndroidViews主活动中呼叫testCheckBox活动,你必须增加代码到AndroidViews.java中。用下面的代码和你现存在AndroidViews.java中的代码作个比较。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.content.Intent;
Chapter8:Lists,Menus,andOtherViews181
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
case0:
showAutoComplete();
returntrue;
case1:
showButton();
returntrue;
case2:
showCheckBox()
returntrue;
case3:
returntrue;
case4:
returntrue;
case5:
returntrue;
}
returntrue;
}
publicvoidshowButton(){
IntentshowButton=newIntent(this,testButton.class);
startActivity(showButton);
}
publicvoidshowAutoComplete(){
Intentautocomplete=newIntent(this,AutoComplete.class);
startActivity(autocomplete);
}
publicvoidshowCheckBox(){
Intentcheckbox=newIntent(this,testCheckBox.class);
startActivity(checkbox);
}
}

启动应用程序并从菜单中选择CheckBox选项。点击ChangeLayout和ChangeTextColor按钮。

EditText

EditText第八章(7)

在本节中,和上一节很类似,你为EditTextView创建一个活动。创建活动的步骤和前几节是一样的。因此,你将被提供三个主要活动文件的代码。—AndroidManifest.xml,edittext.xml,和testEditText.java。这些在下面提供给你。

AndroidManifest.xml
本部分包含当前AndroidView的AndroidManifest.xml完整代码。如果你使用Eclipse,修改你的活动的AndroidManifest.xml文件,使它和下面类似:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidViews">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testButton"android:label="TestButton">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testCheckBox"android:label="TestCheckBox">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testEditText"android:label="TestEditText">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>


edittext.xml
这个部分展示了edittext.xml文件的完整代码。使用本章前面的指示,在项目中创建一个名为edittext.xml的文件。使用下面的代码来修改你的文件。

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditTextandroid:id="@+id/testEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Buttonandroid:id="@+id/layoutButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeLayout"/>
<Buttonandroid:id="@+id/textColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeTextColor"/>
</LinearLayout>

testEditText.java
本节包含了执行EditText活动需要的最后一个文件。在项目中创建一个名为testEditText.java的文件。这是个活动的主要文件并且包含了可执行代码。在testEditText.java文件中使用下面的代码来完成这个活动。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.Button;
importandroid.graphics.Color;
publicclasstestEditTextextendsActivity{
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.edittext);
finalEditTextedittext=(EditText)findViewById(R.id.testEditText);
finalButtonchangeButton=(Button)findViewById(R.id.layoutButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(edittext);}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.textColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(edittext);
}
});
}
publicvoidchangeOption(EditTextedittext){
if(edittext.getHeight()==100){
edittext.setHeight(30);
}
else{
edittext.setHeight(100);
}
}
publicvoidchangeOption2(EditTextedittext){
edittext.setTextColor(Color.RED);
}
}


AndroidViews.java
创建这个活动的最后一步是编辑AndroidView.java。如果你要从主要AndroidView活动中呼叫testEditText活动,你必须增加代码到AndroidView.java中。与你当前AndroidViews.java文件中的代码与下面进行比较。增加需求的代码来完成文件。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.content.Intent;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
case0:
showAutoComplete();
returntrue;
case1:
showButton();
returntrue;
case2:
showCheckBox();
returntrue;
case3:
showEditText();
returntrue;
case4:
showRadioGroup();
returntrue;
case5:
showSpinner();
returntrue;
}
returntrue;
}
publicvoidshowButton(){
IntentshowButton=newIntent(this,testButton.class);
startActivity(showButton);
}
publicvoidshowAutoComplete(){
Intentautocomplete=newIntent(this,AutoComplete.class);
Chapter8:Lists,Menus,andOtherViews187
startActivity(autocomplete);
}
publicvoidshowCheckBox(){
Intentcheckbox=newIntent(this,testCheckBox.class);
startActivity(checkbox);
}
publicvoidshowEditText(){
Intentedittext=newIntent(this,testEditText.class);
startActivity(edittext);
}
}

启动应用程序并从菜单中选择EditText选项。点击ChangaeLayout和ChangeTestColor按钮。

RadioGroup

RadioGroup第八章(8)

在本章中将为RadioGroupView创建一个活动。创建活动的步骤和前节一致。因此会为你提供三个主要文件—AndroidManifest.xml,radiogroup.xml,和testRadioGroup.java。这些文件将在下面提供给你。

AndroidManifest.xml
这个部分包含当前AndroidViewAndroidManifest.xml的完整代码。如果你使用Eclipse,修改活动的AndnroidManifest.xml文件如下:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidViews">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testButton"android:label="TestButton">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testCheckBox"android:label="TestCheckBox">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testEditText"android:label="TestEditText">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testRadioGroup"android:label="Test
RadioGroup">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

radiogroup.xml
这个部分展示了完整的radiogroup.xml文件的完整代码。使用本章前节描述的方法,在项目中创建一个名为radiogroup.xml的文件。

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
Chapter8:Lists,Menus,andOtherViews191
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RadioGroupandroid:id="@+id/testRadioGroup"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RadioButton
android:text="Radio1"
android:id="@+id/radio1"
/>
<RadioButton
android:text="Radio2"
android:id="@+id/radio2"/>
</RadioGroup>
<Buttonandroid:id="@+id/enableButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="SetisEnabled"/>
<Buttonandroid:id="@+id/backgroundColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeBackgroundColor"/>
</LinearLayout>

testRadioGroup.java
本部分包含执行RadioGroup活动最后所需的文件。在项目中创建一个名为testRadioGroup.java的文件。这是个活动的主要文件并且包含可执行的代码。在testRadioGroup.java文件中使用下面的代码来完成文件。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.RadioGroup;
importandroid.widget.Button;
importandroid.graphics.Color;
publicclasstestRadioGroupextendsActivity{
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.radiogroup);
finalRadioGroupradiogroup=(RadioGroup)
findViewById(R.id.testRadioGroup);
finalButtonchangeButton=(Button)findViewById(R.id.enableButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(radiogroup);}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.backgroundColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(radiogroup);
}
});
}
publicvoidchangeOption(RadioGroupradiogroup){
if(radiogroup.isEnabled()){
radiogroup.setEnabled(false);
}
else{
radiogroup.setEnabled(true);
}}
publicvoidchangeOption2(RadioGroupradiogroup){
radiogroup.setBackgroundColor(Color.RED);
}
}


AndroidViews.java
最后创建活动的部分是编辑AndroidViews.java。如果你要从主要活动中呼叫testRadioGroup活动,你必须在AndroidViews.java文件中增加代码。与你当前AndroidViews.java文件中的代码相比较,增加所需的代码来完成文件。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.content.Intent;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
case0:
showAutoComplete();
returntrue;
case1:
showButton();
returntrue;
case2:
showCheckBox();
returntrue;
case3:
showEditText();
returntrue;
case4:
showRadioGroup();
returntrue;
case5:
showSpinner();
returntrue;
}
returntrue;
}
publicvoidshowButton(){
IntentshowButton=newIntent(this,testButton.class);
startActivity(showButton);
Chapter8:Lists,Menus,andOtherViews193
194Android:AProgrammer’sGuide
}
publicvoidshowAutoComplete(){
Intentautocomplete=newIntent(this,AutoComplete.class);
startActivity(autocomplete);
}
publicvoidshowCheckBox(){
Intentcheckbox=newIntent(this,testCheckBox.class);
startActivity(checkbox);
}
publicvoidshowEditText(){
Intentedittext=newIntent(this,testEditText.class);
startActivity(edittext);
}
publicvoidshowRadioGroup(){
Intentradiogroup=newIntent(this,testRadioGroup.class);
startActivity(radiogroup);
}
publicvoidshowSpinner(){
}
}

启动应用程序并从菜单中选择RadioGroup选项。

试着点击SetisEnabled和ChangeBackGroudColor按钮。注意SetisEnabled按钮把RadioGroup设为不可用,而ChangeBackgroudColor按钮改变组的背景色。

Spinner

Spinner第八章(9)

在本节中将为SpinnerView创建一个活动。SpinnerView和其它编程语言里的ComboBox相类似。创建这个活动的步骤和前面部分的一样。因此,还是会提供给你三个主要活动的代码文件—AndroidManifest.xml,spinner.xml,
和testSpinner.java。下面就是这些提供的文件。

AndroidManifest.xml

本节包含当前AndroidViews的AndroidManifest.xml文件的完整代码。如果你使用Eclipse,修改活动的AndroidManifest.xml文件使它和下面一样:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidViews">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidViews"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".AutoComplete"android:label="AutoComplete">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testButton"android:label="TestButton">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testCheckBox"android:label="TestCheckBox">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testEditText"android:label="TestEditText">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testRadioGroup"android:label="Test
RadioGroup">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activityandroid:name=".testSpinner"android:label="TestSpinner">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

spinner.xml

本节展示了spinner.xml文件的完整代码。在项目中创建一个名为spinner.xml的文件。使用下面的代码修改你的文件。

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Spinnerandroid:id="@+id/testSpinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Buttonandroid:id="@+id/enableButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="SetisEnabled"/>
<Buttonandroid:id="@+id/backgroundColorButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ChangeBackgroundColor"/>
</LinearLayout>


testSpinner.java

本节包含了执行Spinner活动所需要的最后一个文件。在项目中创建一个名为testSpinner.java的新文件。这是个活动的主要文件并且包含可执行代码。在testSpinner.java文件中使用下面的代码来完成这个活动。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.ArrayAdapter;
importandroid.widget.Spinner;
importandroid.widget.Button;
importandroid.graphics.Color;
198Android:AProgrammer’sGuide
publicclasstestSpinnerextendsActivity{
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.spinner);
finalSpinnerspinner=(Spinner)findViewById(R.id.testSpinner);
ArrayAdapter<String>adapter=newArrayAdapter<String>(this,
android.R.layout.simple_spinner_item,Months);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
finalButtonchangeButton=(Button)findViewById(R.id.enableButton);
changeButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption(spinner);}
});
finalButtonchangeButton2=(Button)
findViewById(R.id.backgroundColorButton);
changeButton2.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
changeOption2(spinner);
}
});
}
staticfinalString[]Months=newString[]{
"January","February","March","April","May","June","July","August",
"September","October","November","December"
};
publicvoidchangeOption(Spinnerspinner){
if(spinner.isEnabled()){
spinner.setEnabled(false);
}
else{
spinner.setEnabled(true);
}
}
publicvoidchangeOption2(Spinnerspinner){
spinner.setBackgroundColor(Color.RED);
}
}

AndroidViews.java

创建活动的最后一个步骤就是编辑AndroidViews.java。如果你要从主活动AndroidViews中呼叫testSpinner活动,你必须增加代码到AndroidViews.java中。用当前的AndroidViews.java和下面的代码作个比较。增加代码来完成文件。

packageandroid_programmers_guide.AndroidViews;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.content.Intent;
publicclassAndroidViewsextendsActivity{
/**CalledwhentheActivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,0,"AutoComplete");
menu.add(0,1,"Button");
menu.add(0,2,"CheckBox");
menu.add(0,3,"EditText");
menu.add(0,4,"RadioGroup");
menu.add(0,5,"Spinner");
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
case0:
showAutoComplete();
returntrue;
case1:
showButton();
returntrue;
case2:
showCheckBox();
returntrue;
case3:
showEditText();
returntrue;
case4:
showRadioGroup();
returntrue;
case5:
showSpinner();
returntrue;
}
returntrue;
}
publicvoidshowButton(){
IntentshowButton=newIntent(this,testButton.class);
startActivity(showButton);
}
publicvoidshowAutoComplete(){
Intentautocomplete=newIntent(this,AutoComplete.class);
startActivity(autocomplete);
}
publicvoidshowCheckBox(){
Intentcheckbox=newIntent(this,testCheckBox.class);
startActivity(checkbox);
}
publicvoidshowEditText(){
Intentedittext=newIntent(this,testEditText.class);
startActivity(edittext);
}
publicvoidshowRadioGroup(){
Intentradiogroup=newIntent(this,testRadioGroup.class);
startActivity(radiogroup);
}
publicvoidshowSpinner(){
Intentspinner=newIntent(this,testSpinner.class);
startActivity(spinner);
}
}

启动应用程序并从菜单中选择Spinner选项。试着点击SetisEnabled和ChangeBackgroudColor按钮。

试试这个:修改更多的View属性

试试这个:修改更多的View属性第八章(10)

为活动修改按钮动作在每个View中来改变不同的属性:

●使用Eclipse的特性列表来查看每个View有哪些属性。
●在任一个给定的活动中编辑两个按钮的功能来更改按钮和View的交互活动。

在下一章中,将用到谷歌的API(GoogleAPI)。将创建一个应用程序和GTalk交互。这将会给你更多的知识关于如何构造独特的应用程序。

问专家

Q:如果我在应用程序中使用多个Views,我可以使用android.widget.*输入整个widget包装吗?

A:是的。但是,我会保守的使用呼叫。当你输入整个包装,你增加了包装的所有代码到活动中。这个管理的不好会让活动速度变慢。我会只输入我需要的包装,试图减少活动中的代码。

第九章使用手机的GPS功能

使用手机的GPS功能第九章(1)

关键技能&概念

●使用Android的定位服务APIs

●从GPS硬件获得坐标数据

●改变活动的外观并且和RelativeLayout接触

●使用一个MapView来绘制你的当前位置

●使用谷歌地图来找到你的当前位置

在本章中,你将学习关于Android定位的API。本章的作用是非常重要的,如果你想要让Android和GPS硬件一起工作的话。你将使用位置基础的API来收集你当前的位置并把它在屏幕上显示出来。到本章的结尾,你将在手机上使用谷歌地图来显示你的当前位置。

你还会学习到一些关于活动的更深,创新的技巧。资源,如RelativeLayouts和小按钮将允许你创建更友好,可视的活动。第一节,你将学习使用设备的GPS硬件来获得当前的位置。但是,在跳到那个部分之前,你需要先创建一个项目。在Eclipse中创建一个项目并命名为AndroidLBS。

使用Android位置基础API

AndroidSDK包含了一个API,它被定制为帮助接口活动与设备上任何的GPS硬件。这章假定你的设备包含GPS硬件。

警告

Android平台的手机不要求包含一个照相机,也不要求包含GPS硬件,虽然很多的型号可能包含照相机和GPS硬件。Android包含了Android位置基础API预期GPS硬件会被包含在很多手机上。

因为你工作在一个软件模拟器中,并且不是一个真的设备,GPS硬件没有被模拟。在本例中,Android在adb服务器中提供了一个文件模拟GPS硬件。这个文件放置在data/misc/location/<provider>,<provider>代表位置信息提供者。Android提供的<provider>是data/misc/location/gps

提示

你可以有多重的提供者来模拟不同的方案。因此,你可以创建一个提供者为test或者gps1;无论你愿意用哪个。在具体的provider的文件夹内可以用任何数量的文件保留你想要Android使用的例子。当你使用Android模拟器,你可以使用下面类型的文件来储存/找回GPS文体的坐标。每一个文件类型有个不同的格式来提供信息给Android位置基础API
●kml
●nmea
●track

我们来看看每一个文件都做些什么并且互相之间有什么不同。

创建一个kml文件

一个.kml文件KeyholeMarkup语言文件。这些文件通常被开发用于并且可以被GoogleEarth(一款Google软件)。Adnroid位置基础API可以分析一个.kml文件来模拟一个GPS。

注意

假如你没有GoogleEarth。可以从Google免费下载。如果你想要开发更多的Android位置基础API活动,安装这个软件是值得的。

要从GoogleEarth创建一个.kml文件,打开GoogleEarth并且导航到一个位置。选择文件|另存为并选择KML。在本例中,它为你所导航到的地点产生一个.kml文件。下面的.kml代码就是来自这个文件。仔细看看<coordinates>标签,那就是Android位置基础API会读取的。

<?xmlversion="1.0"encoding="UTF-8"?>
<kmlxmlns="http://earth.google.com/kml/2.2">
<Document>
<name>Tampa,FL.kml</name>
<Styleid="default+icon=http://maps.google.com/mapfiles/kml/pal3/icon52.png">
<IconStyle>
<scale>1.1</scale>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pal3/icon52.png</href>
</Icon>
</IconStyle>
<LabelStyle>
<scale>1.1</scale>
</LabelStyle>
</Style>
<Styleid="default+icon=http://maps.google.com/mapfiles/kml/pal3/icon60.png">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/pal3/icon60.png</href>
</Icon>
</IconStyle>
</Style>
<StyleMapid="default+nicon=http://maps.google.com/mapfiles/kml/pal3/
icon60.png+hicon=http://maps.google.com/mapfiles/kml/pal3/icon52.png">
<Pair>
<key>normal</key>
<styleUrl>#default+icon=http://maps.google.com/mapfiles/kml/pal3/
icon60.png</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#default+icon=http://maps.google.com/mapfiles/kml/pal3/
icon52.png</styleUrl>
</Pair>
</StyleMap>
<Placemark>
<name>Tampa,FL</name>
<open>1</open>
<address>Tampa,FL</address>
<LookAt>
<longitude>-82.451142</longitude>
<latitude>27.98146</latitude>
<altitude>0</altitude>
<range>38427.828125</range>
<tilt>0</tilt>
<heading>0</heading>
</LookAt>
<styleUrl>#default+nicon=http://maps.google.com/mapfiles/kml/pal3/
icon60.png+hicon=http://maps.google.com/mapfiles/kml/pal3/icon52.png</styleUrl>
<Point>
<coordinates>-82.451142,27.98146,0</coordinates>
</Point>
</Placemark>
</Document>
</kml>


你可以用GoogleEarth来创建自己的.kml文件来模拟不同的位置。当你想要制作一个相应用户不同位置的活动时,这个非常有用。创建.kml文件如此简单使得模拟GPS硬件非常的灵活。

什么是轨迹文件

什么是轨迹文件第九章(2)

Android提供的在gps文件夹里的文件是一个.nmea文件(国家海事电子协会文件)。一个.nmea文件可以从任何通用的GPS产品中输出。这些文件是常用格式并且可以包含多重坐标和海拔,来表现行程和轨迹。下面的部分讨论并且在Windows和Linux下各自打开这个文件。

在Windows中得到nmea文件

Android提供的nmea文件展示了一个贯穿旧金山的短的线路。让我们看看nmea文件的内部。使用adb工具把文件从服务器中pull到你的桌面:

adbpull<远程文件><本地文件>

下面的插图描述使用adb工具pull命令来检索文件(略)。如果命令执行成功,你应当看到一条消息指示文件下载的大小。导航到C:\Android文件夹,你可以看到adbpull工具放在这里。

现在nmea文件在桌面上,把它与Notepad关联。最后打开它来看看它的内容。你会看到很多的坐标数据。

在Linux中得到nmea文件

如果你在使用Linux开发Android,启动一个终端部分来进入adb服务器。让我们来看看如何在Linux中检索并且编辑nmea文件。

注意(和插图有关,略)

第一步是打开一个新的终端部分(Applications|SystemTools|Terminal)。

下一步,使用adbpull命令来pullnmea文件到Android文件夹:

adbpulldata/misc/location/gps/nmeaAndroid/

如果你读了关于Windows如何得到nmea文件的说明,你会发现语法上的不同。C:\是没有必要的因为路径结构的不同。

从终端中执行了命令后,结果应当如下所示:

使用Is命令来在Android文件夹中列出文件。如果命令执行正确,nmea文件应当出现。我使用FedoraGUI来导航并且使用系统的TextEditor打开它。

提示

你也可以使用vi编辑器从命令行来打开,读取并且编辑nmea文件。

现在你已经查看了nmea文件并且知道模拟一个GPS设备的不同方式,你可以开始来使用Android位置基础API来创建一个完整特性的活动了。

使用Android位置基础API读取GPS

使用Android位置基础API读取GPS第九章(3)

本章剩下的部分是致力于建造一个活动,AndroidLBS,它会从服务器中nmea文件中识别用户的位置。本活动的第一个过程非常的简单。

你会创建一个简单的过程,该过程会得到用户当前的GPS位置。然后你可以在屏幕上显示这个位置的坐标。在做这个的时候,你会了解到一个对Android位置基础API比较到位的介绍和它的功用。

创建AndroidLBS活动

下面是创建这个简单活动的步骤:

1.调整许可的权限

2.创建活动的布局

3.书写代码来允许活动。

4.运行活动。

调整许可的权限

使用Android位置基础API是调整认可的权限。使用Android位置基础本身不要求任何特别的许可。但是在GPS使用Android位置基础来存取位置信息需要。

从Eclipse中有两种方式可以设置许可。第一个是通过AndroidManifest许可向导,这个你在第七章用过。在Eclipse中,双击AndroidManifest.xml来打开AndroidManifest综览窗口。点击许可链接并使用第七章的方法增加ACCESS_GPS和ACCESS_LOCATION使用许可。

第二种方法是,你可以手动编辑AndroidManifest.xml文件增加许可值到活动中。你会需要下面的代码行到AndroidManifest.xml中:

<uses-permissionandroid:name="android.permission.ACCESS_GPS">
</uses-permission>
<uses-permissionandroid:name="android.permission.ACCESS_LOCATION">
</uses-permission>


这里的语句是用来在<uses-permission>标签中增加许可名称。

当你结束了增加许可,你的AndroidManifest.xml文件应当像下面的代码片段。这样的代码应当非常的熟悉了。你在Intent过滤器内使用了一个活动和一对许可。

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android=http://schemas.android.com/apk/res/android
package="android_programmers_guide.AndroidLBS">
<applicationandroid:icon="@drawable/icon">
<activityandroid:name=".AndroidLBS"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<uses-permissionandroid:name="android.permission.ACCESS_GPS">
</uses-permission><uses-permission
android:name="android.permission.ACCESS_LOCATION">
</uses-permission></manifest>

创建你的布局
要开始创建布局,在Eclipse中打开main.xml,你会一共增加一个按钮和4个TextViews到布局中。按钮可以从GPS呼叫信息并显示到TextViews中。

按照下面设置按钮,也就是在屏幕的顶部并使用“WhereamI”作为显示文本。

<Button
android:id="@+id/gpsButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="WhereAmI"
/>

下一步,设置4个Texviews,你应当在布局中安排它们,2个TextViews会显示在另外2个TextViews之上。这样可以把其中的2个作为另外2个的标签来使用。要完成这个工作,你需要多两个LinearLayouts.

请注意在main.xml文件中的所有元素是包含在一个LinearLayout标签中。这个标签用某些规则来绑定内含的元素。对于LinearLayouts,元素被以依次水平或者垂直的方乡堆栈。

LinerLayout的方向由android:orientation属性管理。假如属性没有被赋值,初始的设定是水平方式。

请注意,有一些槽(或者架子)是垂直放置的。你可以在屏幕上的这些槽上放置元素。总之,如果你要在立式LinearLayout布局的同一个架子上放置少量的条目,你需要先在架子上放置一个水平的LinearLayout。

现在你可以上下左右的码放这些元素了。这是个在本活动中需要利用的概念。因此,在按钮的下面,增加一个水平的LinearLayout来放置2个TextViews。

<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/latLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Latitude:"
/>
<TextView
android:id="@+id/latText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Figure9-2VerticalLinearLayoutwithembeddedhorizontalLinearLayout
HorizontalLinearLayout
Androidscreen
/>
</LinearLayout>

这两个TextViews保留标签和你将要从GPS中采集的纬度数据。下一个,增加另一个水平LinearLayout来保留剩下的TextViews:

<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lngLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Longitude:"
/>
<TextView
android:id="@+id/lngText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>

这个为特定的活动提供了比较好的布局。你完成的main.xml文件应当如下:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/gpsButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="WhereAmI"
/>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/latLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Latitude:"
/>
<TextView
android:id="@+id/latText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lngLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Longitude:"
/>
<TextView
android:id="@+id/lngText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</LinearLayout>

书写代码来允许活动

书写代码来允许活动第九章(4)

现在已经创建了布局,你可以开始写代码来允许活动了。你的按钮需要从GPS中来呼叫用户当前的位置。一旦你有了这些信息,你可以发送纬度和经度坐标到TextViews中了。

首先,你需要增加输入声明。你需要输入4个包装:

importandroid.view.View;
importandroid.widget.TextView;
importandroid.content.Context;
importandroid.widget.Button;

和一个Android位置基础API:

importandroid.location.LocationManager;

下一步,为按钮创建代码。目标是从GPS中检索当前坐标信息。你已经在本书中创建了一些按钮了,而且这个的格式没有不同。你需要设置按钮并且从main.xml中装载布局。然后你可以设置onClick事件来呼叫一个函数,LoadCoords()。

finalButtongpsButton=(Button)findViewById(R.id.gpsButton);
gpsButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
LoadCoords();
}});

创建活动的最后步骤是填充代码到LoadCoords()函数中。创建TextViews需要接受坐标数据:

TextViewlatText=(TextView)findViewById(R.id.latText);
TextViewlngText=(TextView)findViewById(R.id.lngText);

注意

你没必要必须创建两个TextViews来作为标签,因为你不会发送任何东西到它们。

现在,创建一个可以pull坐标数据的LocationManager。这个示例的重要部分是你必须要传递给LocationManager一个上下文;使用LOCATION_SERVICE:

LocationManagermyManager=
(LocationManager)getSystemService(Context.LOCATION_SERVICE);

要从myManager中pull坐标,使用getCurrentLocation()方法。这个方法需要一个参数,一个提供者,它们会展示API将要从中pull的坐标。在这种情况下,Android提供了一个本章早些时候讨论过的包含nmea文件的模拟位置GPS:

DoublelatPoint=myManager.getCurrentLocation("gps").getLatitude();
DoublelngPoint=myManager.getCurrentLocation("gps").getLongitude();

最后,拿去双击数据并且把它们传递到TextViews:

latText.setText(latPoint.toString());
lngText.setText(lngPoint.toString());
Yourfinishedcodeshouldlooklikethis:
packageandroid_programmers_guide.AndroidLBS;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.location.LocationManager;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.content.Context;
importandroid.widget.Button;
publicclassAndroidLBSextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
finalButtongpsButton=(Button)findViewById(R.id.gpsButton);
gpsButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
LoadCoords();
}});
}
publicvoidLoadCoords(){
TextViewlatText=(TextView)findViewById(R.id.latText);
TextViewlngText=(TextView)findViewById(R.id.lngText);
LocationManagermyManager=(LocationManager)
getSystemService(Context.LOCATION_SERVICE);
DoublelatPoint=myManager.getCurrentLocation("gps").getLatitude();
DoublelngPoint=myManager.getCurrentLocation("gps").getLongitude();
latText.setText(latPoint.toString());
lngText.setText(lngPoint.toString());
}
}

运行活动

在Android模拟器中运行你的活动。该活动将会打开如下的屏幕(略)。点击“WhereAmI”按钮。你将会看到图片中显示的坐标。

传递坐标到Google地图

传递坐标到Google地图第九章(5)

在本节中,你将继续在前一节的基础上构造。对AndroidLBS活动的主要修改就是传递坐标到Google地图中。你将使用Google地图来显示用户的当前位置。在main.xml文件中的唯一修改指出就是为MpaView增加一个布局。在目前版本的AndroidSDK中,MapView被建立为一个类View。可能在将来的版本中MapView会相当于这个布局。

<viewclass="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

完成后的main.xml文件应当像这样:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/gpsButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="WhereAmI"
/>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/latLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Latitude:"
/>
<TextView
android:id="@+id/latText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lngLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Longitude:"
/>
<TextView
android:id="@+id/lngText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<viewclass="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

因为在这个活动中嵌入MapView,你需要改变类的定义。现在,主要类扩展了活动。但是要正确的使用GoogleMapView,你必须扩展MapActivity。因此,你需要输入MapActivity包装并且替换在头部的Activity包装。

输入下列包装:

importcom.google.android.maps.MapActivity;
importcom.google.android.maps.MapView;
importcom.google.android.maps.Point;
importcom.google.android.maps.MapController

Point包装将被用于保留point的值,它就是展示地图坐标的,而MapController将你的point置于地图中央。这两个包装在使用MapView时非常的关键。

现在准备增加建立地图并传递坐标的代码。首先,设置一个一个MapView,并且从main.xml文件中把它赋值到布局:

MapViewmyMap=(MapView)findViewById(R.id.myMap);

下一步,设置一个Point并且把从GPS检索的数值赋值给latPoint和IngPoint:

PointmyLocation=newPoint(latPoint.intValue(),lngPoint.intValue());

现在,可以创建MapController了,它将被用于移动Google地图来定位你定义的Point。从MapView使用getController()方法在定制的地图中建立一个控制器:

MapControllermyMapController=myMap.getController();

唯一剩下的工作就是使用控制器来移动地图到你的位置(要让地图更容易辨认,把zoom设定为9):

myMapController.centerMapTo(myLocation,false);
myMapController.zoomTo(9);

你刚才所写的所有代码就是从活动中利用Google地图。完整的类应当像这样:

packageandroid_programmers_guide.AndroidLBS;
importandroid.os.Bundle;
importandroid.location.LocationManager;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.content.Context;
importandroid.widget.Button;
importcom.google.android.maps.MapActivity;
importcom.google.android.maps.MapView;
importcom.google.android.maps.Point;
importcom.google.android.maps.MapController;
publicclassAndroidLBSextendsMapActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
finalButtongpsButton=(Button)findViewById(R.id.gpsButton);
gpsButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
LoadProviders();
}});
}
publicvoidLoadProviders(){
TextViewlatText=(TextView)findViewById(R.id.latText);
TextViewlngText=(TextView)findViewById(R.id.lngText);
LocationManagermyManager=(LocationManager)
getSystemService(Context.LOCATION_SERVICE);
DoublelatPoint=
myManager.getCurrentLocation("gps").getLatitude()*1E6;
DoublelngPoint=
myManager.getCurrentLocation("gps").getLongitude()*1E6;
latText.setText(latPoint.toString());
lngText.setText(lngPoint.toString());
MapViewmyMap=(MapView)findViewById(R.id.myMap);
PointmyLocation=newPoint(latPoint.intValue(),lngPoint.intValue());
MapControllermyMapController=myMap.getController();
myMapController.centerMapTo(myLocation,false);
myMapController.zoomTo(9);
}
}

在模拟器中运行活动。活动应当打开一个空白的地图。点击“WhereAmI”按钮,应当会看到地图聚焦并且放大到旧金山。看看下图就会知道地图会如何出现(略)。

增加缩放控制

增加缩放控制第九章(6)

本章的最后一个练习是再增加两个按钮到AndroidLBS活动中。这些按钮将控制GoogleMapView放大和缩小的方法。让这个修改有一点不同的是我将为main.xml文件介绍一个布局的新类型:RelativeLayout。LinearLayouts允许你直接的一个接一个的放置Views,RelativeLayouts允许你在每一个View上放置。

对于这个活动,将会放置两个按钮到GoogleMap上,要实现这个效果,你可以增加在地图上的按钮。

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<viewclass="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

现在可以增加另外的两个按钮了。它们会出现在MapView的左上方和左下方。你需要对Button布局做一个修改。按照默认的方式,RelativeLayout增加Button来和锚视图顶部的边缘排列,本例中,就是这个MapView。因此,在这个布局,使用android:layout_alignBottom属性并赋值MapView的id。这样就排列按钮到地图的底部了。

<Buttonandroid:id="@+id/buttonZoomIn"
style="?android:attr/buttonStyleSmall"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomOut"
style="?android:attr/buttonStyleSmall"
android:text="-"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

提示

仔细看一下按钮的布局属性。我使用一个新的属性,style,来把这个按钮改小。完整的main.xml应当看上去像这样:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/gpsButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="WhereAmI"
/>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/latLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Latitude:"
/>
<TextView
android:id="@+id/latText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lngLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Longitude:"
/>
<TextView
android:id="@+id/lngText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<viewclass="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomIn"
style="?android:attr/buttonStyleSmall"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomOut"
style="?android:attr/buttonStyleSmall"
android:text="-"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</LinearLayout>

你会去适当的修改这个代码。除了为新的views增加代码之外,你需要移除现存的一些代码。为了让活动更灵活,你需要移除MapView的示例和类主要部分的MapController。这样将会允许你传递这些项目到其它所需的函数(比如将要创建的放大和缩小特性)。

finalMapViewmyMap=(MapView)findViewById(R.id.myMap);
finalMapControllermyMapController=myMap.getController();

现在可以创建两个新按钮的代码了。和你之前创建的按钮一样,增加呼叫到下一步将要构建的函数:

finalButtonzoomIn=(Button)findViewById(R.id.buttonZoomIn);
zoomIn.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomIn(myMap,myMapController);
}});
finalButtonzoomOut=(Button)findViewById(R.id.buttonZoomOut);
zoomOut.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomOut(myMap,myMapController);
}});

最后,创建控制放大缩小特性的函数。最大放大位置是21并且最小是1.因此,在函数中,在调整前测试当前的位置。这样将确保不会出现任何的运行问题。

publicvoidZoomIn(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=21){
mc.zoomTo(mv.getZoomLevel()+1);
}
}
publicvoidZoomOut(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=1){
mc.zoomTo(mv.getZoomLevel()-1);
}
}

注意你传递MapView和MapController到函数中,从那里,对缩放位置进行整数处理非常简单。唯一的关键是这个函数是MapController本身移动MapView到希望的缩放位置,而MapView本身保留缩放位置。

提示

想想这个关系和一个遥控器和电视机相类似。遥控器切换电视到第5频道,但是频道本身是储存在电视上的。

你完成的AndroidLBS.java文件应当看上去像这样:

packageandroid_programmers_guide.AndroidLBS;
importandroid.os.Bundle;
importandroid.location.LocationManager;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.content.Context;
importandroid.widget.Button;
importcom.google.android.maps.MapActivity;
importcom.google.android.maps.MapView;
importcom.google.android.maps.Point;
importcom.google.android.maps.MapController;
publicclassAndroidLBSextendsMapActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
finalMapViewmyMap=(MapView)findViewById(R.id.myMap);
finalMapControllermyMapController=myMap.getController();
finalButtonzoomIn=(Button)findViewById(R.id.buttonZoomIn);
zoomIn.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomIn(myMap,myMapController);
}});
finalButtonzoomOut=(Button)findViewById(R.id.buttonZoomOut);
zoomOut.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomOut(myMap,myMapController);
}});
finalButtongpsButton=(Button)findViewById(R.id.gpsButton);
gpsButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
LoadProviders(myMap,myMapController);
}});
}
publicvoidLoadProviders(MapViewmv,MapControllermc){
TextViewlatText=(TextView)findViewById(R.id.latText);
TextViewlngText=(TextView)findViewById(R.id.lngText);
LocationManagermyManager=(LocationManager)
getSystemService(Context.LOCATION_SERVICE);
DoublelatPoint=myManager.getCurrentLocation("gps").getLatitude()*1E6;
DoublelngPoint=
myManager.getCurrentLocation("gps").getLongitude()*1E6;
latText.setText(latPoint.toString());
lngText.setText(lngPoint.toString());
PointmyLocation=newPoint(latPoint.intValue(),lngPoint.intValue());
mc.centerMapTo(myLocation,false);
mc.zoomTo(9);
}
publicvoidZoomIn(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=21){
mc.zoomTo(mv.getZoomLevel()+1);
}
}
publicvoidZoomOut(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=1){
mc.zoomTo(mv.getZoomLevel()-1);
}
}
}

在Android模拟器中运行这个活动。活动会打开一个新的MapView,如图所示放了按钮(略)。

试一下放大和缩小按钮。当你放大,你应当看到和下面类似的图形(略)。

试试这个:在MapView之间转换

试试这个:在MapView之间转换第九章(7)

标准视图和卫星视图再编辑AndroidLBS活动一次。再增加两个按钮到RelativeLayout。这些按钮可以转换标准视图和卫星视图。下面是需要考虑的地方:

●使用排列布局属性使这转换按钮在MapView的另外一面。

●研究MapView来找到转换的方式。

●创建一个可以传递并转换MapView的函数。

完成的main.xml和AndroidLBS.java文件应当如下:

main.xml

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/gpsButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="WhereAmI"
/>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/latLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Latitude:"
/>
<TextView
android:id="@+id/latText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/lngLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Longitude:"
/>
<TextView
android:id="@+id/lngText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<viewclass="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomIn"
style="?android:attr/buttonStyleSmall"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonMapView"
style="?android:attr/buttonStyleSmall"
android:text="Map"
android:layout_alignRight="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonSatView"
style="?android:attr/buttonStyleSmall"
android:text="Sat"
android:layout_alignRight="@+id/myMap"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomOut"
style="?android:attr/buttonStyleSmall"
android:text="-"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</LinearLayout>

AndroidLBS.java

packageandroid_programmers_guide.AndroidLBS;
importandroid.os.Bundle;
importandroid.location.LocationManager;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.content.Context;
importandroid.widget.Button;
importcom.google.android.maps.MapActivity;
importcom.google.android.maps.MapView;
importcom.google.android.maps.Point;
importcom.google.android.maps.MapController;
publicclassAndroidLBSextendsMapActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
finalMapViewmyMap=(MapView)findViewById(R.id.myMap);
finalMapControllermyMapController=myMap.getController();
finalButtonzoomIn=(Button)findViewById(R.id.buttonZoomIn);
zoomIn.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomIn(myMap,myMapController);
}});
finalButtonzoomOut=(Button)findViewById(R.id.buttonZoomOut);
zoomOut.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomOut(myMap,myMapController);
}});
finalButtongpsButton=(Button)findViewById(R.id.gpsButton);
gpsButton.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
LoadProviders(myMap,myMapController);
}});
finalButtonviewMap=(Button)findViewById(R.id.buttonMapView);
viewMap.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ShowMap(myMap);
}});
finalButtonviewSat=(Button)findViewById(R.id.buttonSatView);
viewSat.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ShowSat(myMap);
}});
}
publicvoidLoadProviders(MapViewmv,MapControllermc){
TextViewlatText=(TextView)findViewById(R.id.latText);
TextViewlngText=(TextView)findViewById(R.id.lngText);
LocationManagermyManager=(LocationManager)
getSystemService(Context.LOCATION_SERVICE);
DoublelatPoint=
myManager.getCurrentLocation("gps").getLatitude()*1E6;
DoublelngPoint=
myManager.getCurrentLocation("gps").getLongitude()*1E6;
latText.setText(latPoint.toString());
lngText.setText(lngPoint.toString());
PointmyLocation=newPoint(latPoint.intValue(),lngPoint.intValue());
mc.centerMapTo(myLocation,false);
mc.zoomTo(9);
}
publicvoidZoomIn(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=21){
mc.zoomTo(mv.getZoomLevel()+1);
}
}
publicvoidZoomOut(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=1){
mc.zoomTo(mv.getZoomLevel()-1);
}
}
publicvoidShowMap(MapViewmv){
if(mv.isSatellite()){
mv.toggleSatellite();
}
}
publicvoidShowSat(MapViewmv){
if(!mv.isSatellite()){
mv.toggleSatellite();
}
}
}

当你运行活动时,应当可以启动和关闭卫星视图,如下图(略)。

在下一章,你将进入更深层次的GoogleAPI。第十章将一步一步学习使用GoogleAPI从Android手机发送信息到GTalk。

问专家

Q:最终版本的Android还会继续使用.kml或者.nmea文件吗?

A:本书写的时候,最终的Android还没有发布,可以假定的是,是的,最后版本的Android还会利用.kml和/或者.nmea文件。这将允许应用程序开发者在应用程序内使用包括静态坐标文件。

Q:有没有可能来创建有标记的GoogleMap?

A:是的,在第十一章,你将学习如何熟练操作Google地图Overlays。这些视图允许在Google地图的上面你绘制文本,标记和其它形状。

第十章使用GoogleAPI的Gtalk

使用GoogleAPIGTalk第十章(1)

关键技能&概念

●执行一个GoogleAPI包装

●为Google存取配置XMPP开发环境设置

●执行View.OnClickListener()方法

第九章为你介绍了GoogleAPI。你创建了一个影响GoogleAPI和Google地图的活动。因为API的易用和灵活性,可以快速的在Google地图上显示用户的当前位置。同样,你还学习到了如何使用很少量的相关代码来熟练操控地图。

GoogleAPI包括了不仅是进入Google地图的功能。在上一章中,你使用了很大的API中很小的一个部分。对于GoogleAPI的基本包装是com.google。从这个基础中,GoogleAPI包含了允许你创建操控GTalk(Google的聊天服务),Google日历,Google文档,Google电子表格和Google服务等等的活动的权力。

当我看是写这本书时,这个版本的AndroidSDK是m3-rc22。写完书时,Google不提倡这些包装中的一些,但是还是留在SDK中。有显示Google日历,Google电子表格和Google服务仍旧需要升级,遗憾的是,在m5-rc15版本的SDK中还处于未完成的状态。为了避免混乱,Google还移除了任何与这些包装相关联的帮助文件。因此,本章的重点是在最新发布的AndroidSDK中工作很好的GTalk。

在本章中,将会构造一个小的,利用AndroidSDK的GTalk包装的活动。当活动完成,你将可以利用手机发送并接受信息到/从另外一个GTalk用户。

注意

在第一个GoogleAPI的反复中,处理GTalk的包装是一个非常广泛的XMPP包装。(XMPP是很多聊天平台的基础协议,包括GTalk和Jabber)。使用最新版的SDK,初始的XMPP包装为反映GTalk的特性而加强并重命名。要开始,用Eclipse创建一个新项目,并且命名GoogleAPI。

为GTalk配置Android模拟器

在开始为本项目写代码之前,你需要在Android模拟器中调整开发环境设定,XMPP设定。

在项目打开的状态下,需要离开常规的程序一会儿。如果你熟悉GTalk,你知道只有登录Google帐户以后才可以使用这个产品。因此,你必须要采取特别的步骤来确保你的设备(本例是Android模拟器)可以登录你的Google帐户,这样,可以确保发送和接受信息。

导航到AndroidSDK/tools文件夹并且启动模拟器。你可以从Eclipse开发环境中启动它,但是那样会需要同时启动还没有写代码的活动。为了节约时间,手动启动模拟器。

模拟器启动后,点击所有的快捷方式(Allshortcut)。找到DevTools条目并且启动它。你将会看到和下面类似的图(略)。

滚动DevTools菜单直到看见XMPP设置。选择XMPP设定。

注意

当你打开XMPP设定,活动的名称是GTalk设定。这个可能是个表象说明Google将在GoogleAPI包装中留下这个包装。命名显而易见的断开可能是从不同SDK版本之间改变而剩下的。

活动应当读取账户:<None>,如图所示(略)。这表明了设备中没有储存登录信息。你需要为Google帐户增加登录信息来允许活动来有权使用Google的服务器。

点击增加帐户来显示一个屏幕。输入用户名和密码之后,点击登录。Android模拟器应当试着去验证你的信息。当模拟器尝试验证信息时,它显示“Authenticating”信息。

警告

取决于你的连接和你是否有一个调试器和模拟器相连接。你可能会看到“Authenticating”信息一会儿。如果你的帐户几分钟后无法被验证,重新启动模拟器再试试。

一旦你的信息被验证,你应当看到下图(略)。注意,这里没有返回按钮,点击模拟器中的Home键返回到主屏幕。

现在模拟器配置好了,并且项目也被设置好,可以开始为活动写代码了。

在Android中执行GTalk

在Android中执行GTalk第十章(2)

在本节中将使用GoogleAPI来创建一个使用GTalk的活动。这个活动会从GTalk网络发送并接收信息,在屏幕上保存它们,并在通知条中显示。活动可以和其它GTalk用户通信,而不管他们是在使用Android手机或者电脑上的GTalk。

下一部分将从如何创建应用程序的布局开始。第一步为活动的编码工作就是增加布局到GoogleAPI.xml中。

在GoogleAPI.xml中创建活动的布局

本活动由一些Views组成。需要一个ListView来显示需要发送的文本信息。同样需要两个EditText,用于接收者的地址和信息,还有一个发送的Button。

首先设置一个id是messageList的ListView,如下所示。在本布局中,将使用一个新的属性,android:scrollbars。设置这个属性为立式会让你可以在信息列表中滚动。

<ListView
android:id="@+id/messageList"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:scrollbars="vertical"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>

把ListView放到主要布局标签中。在ListView布局的下方,放置一个EditText的布局,如下。这个EditText保留你会发信息的收信人地址。

<EditText
android:id="@+id/messageTo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:minWidth="250dp"
android:scrollHorizontally="true"/>

这个EditText视图应该没有什么特殊之处。

最后,再创建一个水平的布局来保留信息内容——EditText和发送按钮:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:minWidth="250dp"
android:scrollHorizontally="true"/>
<Button
android:id="@+id/btnSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SendMsg">
</Button>
</LinearLayout>

这个布局会以线性的方式放置视图,所以它们相互排成一排。把这个LinearLayout放置进主要的LinearLayout。最后,GoogleAPI.xml文件将会如下:

<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/messageList"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:scrollbars="vertical"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>
<EditText
android:id="@+id/messageTo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:minWidth="250dp"
android:scrollHorizontally="true"/>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
246Android:AProgrammer’sGuide
android:layout_height="wrap_content">
<EditText
android:id="@+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:minWidth="250dp"
android:scrollHorizontally="true"/>
<Button
android:id="@+id/btnSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SendMsg">
</Button>
</LinearLayout>
</LinearLayout>

为GoogleAPI.java增加包装
布局文件完成后,有一些新的包装需要增加到GoogleAPI.java文件中。第一个必须导入的包装应该和增加到布局中的Views相对应。因此,必须为EditText,ListView,ListAdapter,和按钮导入包装:

importandroid.widget.EditText;
importandroid.widget.ListView;
importandroid.widget.ListAdapter;
importandroid.widget.Button;

同样需要导入的是GoogleAPI和GTalk打交道的包装:

importcom.google.android.gtalkservice.IGTalkSession;
importcom.google.android.gtalkservice.IGTalkService;
importcom.google.android.gtalkservice.GTalkServiceConstants;
importcom.google.android.gtalkservice.IChatSession;

其它需要的包装有intent,ServiceConnection,Color,和Im.完整的列表如下:

importandroid.content.ComponentName;
importandroid.content.Intent;
importandroid.content.ServiceConnection;
importandroid.database.Cursor;
Chapter10:UsingtheGoogleAPIwithGTalk247
248Android:AProgrammer’sGuide
importandroid.os.Bundle;
importandroid.os.DeadObjectException;
importandroid.os.IBinder;
importandroid.provider.Im;
importandroid.graphics.Color;
importandroid.view.View;
importandroid.widget.SimpleCursorAdapter;

如你所见,本活动所需的包装不多。而且,你会发现,发送和接受信息所需要的代码非常的少。现在,需要执行一个onClickListnerner来允许代码。

执行View.OnClickListener
需要修改GoogleAPI的类来执行View.OnClickListener。当任何按钮被点击,这将允许你从活动的主类来呼叫onClick()方法。通常,这种执行onClick()方法是有效的:只有当你在一个活动中有一组按钮,并且以一种方式来出处理所有的onClick呼叫。但是,我感觉你仍旧需要看看这个方式是如何工作的,那样你就可以在将来用于自己的代码中。记住,展示这个方式是因为在很多情况下,它可以是非常有用的工具

publicclassGoogleAPIextendsActivityimplementsView.OnClickListener{
}

在活动中执行常规变量是本书中另外一个未曾谈到的内容。你需要在活动中建立一些常规变量,这样就可以以很多方法来使用:

EditTextmessageText;
ListViewmessageList;
IGTalkSessionmyIGTalkSession;
EditTextmessageTo;
ButtonsendButton;

在onCreate()方法中,你将执行正常的初始化。应当赋值布局到活动并且设置IGTalkSession为null。同样,在活动中增加点小乐趣,改变ListView的背景色为灰色。

myIGTalkSession=null;
messageText=(EditText)findViewById(R.id.messageText);
messageList=(ListView)findViewById(R.id.messageList);
messageTo=(EditText)findViewById(R.id.messageTo);
sendButton=(Button)findViewById(R.id.btnSend);
sendButton.setOnClickListener(this);
messageList.setBackgroundColor(Color.GRAY);

提示

因为你是从类中执行View.OnClickListener的,可以设置SendButton的OnClickListener()方式到其中。最后在onCreate()方法内执行是绑定你的服务。这个过程创建创建需要使用的连接,由Google帐户使用,传递GTalk信息:

this.bindService(new
Intent().setComponent(GTalkServiceConstants.GTALK_SERVICE_COMPONENT),
connection,0);

在上面的bindService声明中,传递到setComponent()方法的一个参数就是连接。这个变量表示一个ServiceConnection执行onServiceConnected()和onServiceDisconnected()方法。下面的代码构造约束前面bindService声明的连接:

privateServiceConnectionconnection=newServiceConnection(){
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
try{
myIGTalkSession=
IGTalkService.Stub.asInterface(service).getDefaultSession();
}catch(DeadObjectExceptione){
myIGTalkSession=null;
}
}
publicvoidonServiceDisconnected(ComponentNamename){
myIGTalkSession=null;
}
};

在onServiceConnected()方法中,你建立了一个使用IGTalkService.Stub的片段。如果这个过程失败,你需要再次把这个片段设为null。现在,可以为类的onClick事件创建代码。在每一个onClick事件中你应当执行一些行动:


1.为任何的信息检查数据库。

2.从查询的结果中创建一个ListAdapter并显示到ListView中。

3.创建一个ChatSession到EditView中的地址并发送你的信息文本。

注意

Android服务器包括SQLite数据库,你可以使用来保留任何你认为需要放入的活动相关条目和任何的定制数据。这个数据库在第十一章做深入的介绍。

下面的代码为你和接受者之间发生的信息查询数据库:

Cursorcursor=managedQuery(Im.Messages.CONTENT_URI,null,
"contact=\'"+messageTo.getText().toString()+"\'",null,null);

使用下面的代码来从查询结果创建一个ListAdapter并且赋值接收器到ListView。在前一个活动,你已经使用了一个类似的过程,所以,对你应该不陌生。

ListAdapteradapter=newSimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,cursor,
newString[]{Im.MessagesColumns.BODY},
newint[]{android.R.id.text1});
this.messageList.setAdapter(adapter);

信息可以显示了,最后的步骤是发送你的信息。下面的代码用定义的messageTOaddress创建一个IchatSession。这个信息文本然后被从这里传递到接受者。

try{
IChatSessionchatSession;
chatSession=
myIGTalkSession.createChatSession(messageTo.getText().toString(););
chatSession.sendTextMessage(messageText.getText().toString());
}catch(DeadObjectExceptionex){
myIGTalkSession=null;
}

所有的东西在一起,完成后的GoogleAPI.java文件如下:

packageandroid_programmers_guide.GoogleAPI;
importandroid.app.Activity;
importandroid.content.ComponentName;
importandroid.content.Intent;
importandroid.content.ServiceConnection;
importandroid.database.Cursor;
importandroid.os.Bundle;
importandroid.os.DeadObjectException;
importandroid.os.IBinder;
importandroid.provider.Im;
importandroid.graphics.Color;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.ListView;
importandroid.widget.ListAdapter;
importandroid.widget.Button;
importandroid.widget.SimpleCursorAdapter;
importcom.google.android.gtalkservice.IGTalkSession;
importcom.google.android.gtalkservice.IGTalkService;
importcom.google.android.gtalkservice.GTalkServiceConstants;
importcom.google.android.gtalkservice.IChatSession;
publicclassGoogleAPIextendsActivityimplementsView.OnClickListener{
EditTextmessageText;
ListViewmessageList;
IGTalkSessionmyIGTalkSession;
EditTextmessageTo;
ButtonmSend;
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.main);
myIGTalkSession=null;
messageText=(EditText)findViewById(R.id.messageText);
messageList=(ListView)findViewById(R.id.messageList);
messageTo=(EditText)findViewById(R.id.messageTo);
mSend=(Button)findViewById(R.id.btnSend);
mSend.setOnClickListener(this);
messageList.setBackgroundColor(Color.GRAY);
Chapter10:UsingtheGoogleAPIwithGTalk251
this.bindService(new
Intent().setComponent(GTalkServiceConstants.GTALK_SERVICE_COMPONENT),
connection,0);
}
privateServiceConnectionconnection=newServiceConnection(){
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
try{
myIGTalkSession=
IGTalkService.Stub.asInterface(service).getDefaultSession();
}catch(DeadObjectExceptione){
myIGTalkSession=null;
}
}
publicvoidonServiceDisconnected(ComponentNamename){
myIGTalkSession=null;
}
};
publicvoidonClick(Viewview){
Cursorcursor=managedQuery(Im.Messages.CONTENT_URI,null,
"contact=\'"+messageTo.getText().toString()+"\'",
null,null);
ListAdapteradapter=newSimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,cursor,
newString[]{Im.MessagesColumns.BODY},
newint[]{android.R.id.text1});
this.messageList.setAdapter(adapter);
try{
IChatSessionchatSession;
chatSession=
myIGTalkSession.createChatSession(messageTo.getText().toString());
chatSession.sendTextMessage(messageText.getText().toString());
}catch(DeadObjectExceptionex){
myIGTalkSession=null;
}
}
}

编译并运行GoogleAPI

编译并运行GoogleAPI第十章(3)

现在,在模拟器中运行GoogleAPI。如何你的连接成功,你应当能看见下面的屏幕。

要测试活动,我发送“hello”信息给androidprogrammersguide@gmail.com,显示如下:

下一个插图,你将看到点击发送按钮后,移动我发送的信息到ListView。

当我作为androidprogrammersguide登录,我发现信息确实到了预想的收件人,如下:

我回复“Greetings!”,要查看这个,看下面的两个图。注意活动屏幕顶部的信息条。紧接的图,你可以看到发送者的信息被显示。

下一章,将创建最后一个应用程序,你会在Google地图上使用SQLite数据库和Google地图Overlays来绘制数据记录。这些是非常有力的技术来提升Android超越其它的移动操作系统。

试试这个:为GoogleAPI活动增加设置特性

试试这个:为GoogleAPI活动增加设置特性第十章(4)

编辑GoogleAPI活动来包括一个设置特性。使用第八章的AndroidViews活动作为向导,增加一个可以改变应用程序布局属性按钮到GoogleAPI活动。这里是一些提示关于如何设置按钮:

●改变信息列表的字体

●改变信息列表的字体颜色,使得发送和接受相反。

●改变信息列表的背景色。

问专家

Q:GTalkAPI可用于其它基于XMPP的聊天客户端吗?

A:对于这个的回答还不清晰。m3-rc22版本的SDK包括一个XMPPAPI而M5-15SDK只含了GTalkAPI。有可能这两者在将来发布的AndroidSDK绑在一起。那样,GTalkAPI可以被用来与其它基于XMPP聊天客户端的程序通信。

第十一章应用程序:找一个朋友

应用程序:找一个朋友第十一章(1)

关键技能&概念
●创建一个SQLite数据库

●创建一个定制内容提供者

●从数据库检索条目并且传递到一个GoogleMapsOverlay

这是你将创建应用程序的最后一章,但是会是本书介绍的最大的一个应用程序。我将介绍一些到目前为止没有遇到过的话题,而且你会用到这些话题所谈到的技能创建一个非常健全的应用程序。

在本章中,会学习如何在Android模拟器中创建SQLite数据库。我会向你展示如何在定制的数据库中读取,写入并且输出数据。这个过程包括创建并使用你自己的ContentProvider来和数据库一同工作。然后,你拿取存储在数据库中的数据并写入到GoogleMapsOverlay中。当你在前一章用Google地图时,还没有使用Overlay。使用GoogleMapsOverlays在地图上绘制形状并且写文本,得到一个有信息量的地图。在这个项目中,将创建一个两部分应用程序。应用程序的第一部分将允许用户输入“friends”到移动数据库中。(一个friend由姓名和地理坐标位置组成)。用户将能增加,修改并且删除friends.

第二个部分将包括一个菜单项目。当用户选择这个菜单项目,应用程序将显示一个Google地图。这个Google地图和第九章创建的Google地图不同之处就是,这个地图会包含一个GoogleMapsOverlay,它会允许你在Google地图的标题处写入姓名,给定信息并且绘制项目。

要开始,在Eclipse内创建一个新的Android项目,命名为FindFriend,使用下图的设定(略)。

现在,你应该对创建一个Android应用程序非常的熟悉了,创建本项目会需要一点小小的帮助。Google在AndroidSDK里有一个应用程序叫NotePad,非常简单但是允许你储存,修改并且删除数据库里的“notes”。你会去修改这个例子的一些代码来创建Friends数据库。

如果你想要知道GoogleNotePad如何工作,在继续之前,在Eclipse中装载项目并且在模拟器中运行它。不久将会开始修改这个代码,但是首先,在下一节里,将创建你的第一个SQLite数据库。

创建一个SQLite数据库

创建一个SQLite数据库第十一章(2)

Android设备将发布时会有一个内部的SQLite数据库。这个数据库的目的是允许用户和开发者一个可以在活动中储存信息的地方。

如果你用过MicrosoftSQL服务器或者SQLite,使用Andorid的SQLite数据库的结构和过程对你将不会陌生。不管你有多少的经验,这个部分将涵盖所有需要创建和使用全功能SQlite数据库的技能。你将要在Android模拟器上创建一个数据库。要实现这个,你需要进入AndroidSDK命令行编辑器工具并使用shell命令来进入Android服务器。

提示

参考第三章来重拾你的记忆关于路径声明和使用命令行工具。

一旦你进入服务器,你需要导航到数据库的位置。所有的AndroidSQLite数据库的位置是在data/data/<package>/
databases目录。使用cd命令来从当前的目录改变到data目录,并且再到<package>目录。如果你不确定<package>目录的名称,使用ls来列出文件和当前目录。改变目录至<package>android_programmers_
guide.FindAFriend,如下所示(略)

警告

如果你没有android_programmers_guide.FindAFriend目录,按照前一部分描述的方式创建你的应用程序并且运行“HelloWorld!”默认的由项目创建的应用程序,那样会确保你有个正确的目录。

找到android_programmers_guide.FindAFriend目录后,运行Is命令。这个命令列出特定文件夹内所有的文件和目录。改命令应当返回空的内容。因为,此时在该目录内没有文件和文件夹。

假定SQLite数据库必须在本目录下的一个数据库目录内,是时候来创建一个了。mkdir工具为你创建目录。因此,运行mkdirdatabases命令。它将创建保留数据库的目录。

警告

现在,你几乎是在服务器的根目录上。因此你刚刚创建的目录将被作为根目录进入。当你运行活动时,可能会出问题,因为每一个活动有一个不同的用户。出于开发的目的,要解决这个问题,运行chmod777databases来准许每个人都能进入到数据库目录。将来,你必须对给予每个人的权力到一些敏感的Android条目非常谨慎才行。只给予特定的用户需要使用特定条目的权力。

已经创建了数据库目录了,可以创建数据库了。使用cd命令导航到数据库目录。在数据库目录后,使用sqlite3工具来创建数据库并命名它为friends.db,如下:

#sqlitefriends.db

如果执行命令成功,你应当能看到一个SQLite3版本信息,本例是3.5.0,和一个SQLite3prompt—sqlite>。这说明数据库已被建立但是是空的。数据库没有包含表格和数据。记住,下一步是为活动数据创建一个表格。

你需要创建一个名为friends的表格。这个表将保留id,name,location,created,和modified字段。这些字段将为你的项目提供足够的信息。

提示

如果你对SQLite不熟悉,一个SQLite命令必须以分号结束。如果你想要跨越一个命令这个会有帮助。没有终止SQLite命令的情况下,按下ENTER键会继续给你一个提示符,…>。你不能在提示符继续输入命令,除非你使用分号。一旦分号被使用,SQLite将把连续的命令作为一个完整的命令。

要在数据库内创建friends表格,在sqlite>提示符输入下列命令:

CREATETABLEfriends(_idINTEGERPRIMARYKEY,nameTEXT,locationTEXT,
createdINTEGER,modifiedINTEGER);

如果命令执行成功,将返回到sqlite>提示符,如下图所示(略)。

数据库现在可以被使用了,你可以退出SQLite了。使用.exit来退出。然后可以退出shell部分返回到Eclipse.

创建数据库是创建应用程序的第一步。现在数据库和相应的表格已经被创建,你需要一个方法来存储数据。受雇Android数据的存储方式是一个ContentProvider。下面的部分带你走进如何为新数据库创建一个定制ContentProvider并存储数据。

创建一个定制的ContentProvider

创建一个定制的ContentProvider第十一章(3)

Android使用ContentProvider来存储数据。你在第九章使用过ContentProvider存储并从一个GPS中读取坐标信息。同样的过程应用于数据库。有这样一些ContentProviderContactLists,IMs,和Recent
Calls。总之,没有为你数据库准备的ContentProvider。Android是非常之灵活并且允许你为定制的数据创建定制的ContentProvider。在本节,将创建一个和Friends数据工作的ContentProvider。这是存取friend数据和最终在屏幕上显示它们的关键所在。

下一节,让我们来编辑string.xml文件。这个文件保留全局遍及活动的字符串内容。

编辑string.xml文件
首先,为项目编辑string.xml文件。string.xml文件由每个项目创建但是你还没有使用过它。这个文件保留可以在活动中使用的静态字符串。

通常,你不大可能在写这些字符串之前仔细查看所有的部分。那就是,当你构造活动时,你通常增加到string.xml的入口。因为那样会打断本书的流程,所以我预先给你所有string.xml文件的所有内容。编辑string.xml文件使它看上去像这样:

<?xmlversion="1.0"encoding="utf-8"?>
<resources>
<stringname="app_name">FindAFriend</string>
<stringname="menu_delete">Delete</string>
<stringname="menu_insert">AddFriend</string>
<stringname="find_friends">FindFriends</string>
<stringname="menu_revert">Revert</string>
<stringname="menu_discard">Discard</string>
<stringname="resolve_edit">Editlocation</string>
<stringname="resolve_title">Editname</string>
<stringname="title_create">CreateFriend</string>
<stringname="title_edit">EditFriend</string>
<stringname="title_notes_list">Friends</string>
<stringname="title_note">Location</string>
<stringname="title_edit_title">FriendName:</string>
<stringname="button_ok">OK</string>
<stringname="error_title">Error</string>
<stringname="error_message">Errorloadingnote</string>
</resources>

完成string.xml文件后,需要创建一个.java文件来保留你的代码。应该命名这个文件为FriendsProvider.java。还要创建另外一个.java文件来保留数据定义。命名这个文件为Friends.java,因为它会定义一个Friends数据结构像什么样子并且允许你的ContentProvider正常进入。(因为provider将会是项目中的一个类,没有必要来构建一个相应的.xml布局文件)。

提示

技术上,对于应用程序,你定制的ContentProvider不需要定居在同一项目或包装内作为代码的剩余部分。为了简单明了,我在FindAFriend项目里做了一个类。假如你计划创建一个可能用户多重项目的ContentProvider,在单独的包装中创建它吧。这样,当你只想要使用ContentProvider时,会允许你呼叫一个包装。

让我们开始Friends.java文件。你只要为相关的类需要输入两个包装:

importandroid.net.Uri;
importandroid.provider.BaseColumns;

BaseColumns将被一个从主Friends类中的subclass执行。命名这个subclass为Friend,因为它代表Friends数据集中的一个friend。下面的代码显示如何设置类的概要:

publicfinalclassFriends{
publicstaticfinalclassFriendimplementsBaseColumns{
}
}

这个类将保留一下静态变量,它们定义Friends数据库中的每一列,ContentURI,和记录的默认排序。

提示

ContentURI被用于识别将要处理的内容。这个数值必须唯一。

需要定义的字符串如下:

publicstaticfinalUriCONTENT_URI
=
Uri.parse("content://android_programmers_guide.FindAFriend.Friends/friend");
publicstaticfinalStringDEFAULT_SORT_ORDER="modifiedDESC";
publicstaticfinalStringNAME="name";
publicstaticfinalStringLOCATION="location";
publicstaticfinalStringCREATED_DATE="created";
publicstaticfinalStringMODIFIED_DATE="modified";

有了变量的设定以后,Friends类放在一起应该是这样的:

packageandroid_programmers_guide.FindAFriend;
importandroid.net.Uri;
importandroid.provider.BaseColumns;
publicfinalclassFriends{
publicstaticfinalclassFriendimplementsBaseColumns{
publicstaticfinalUriCONTENT_URI
=
Uri.parse("content://android_programmers_guide.FindAFriend.Friends/friend");
publicstaticfinalStringDEFAULT_SORT_ORDER="modifiedDESC";
publicstaticfinalStringNAME="name";
publicstaticfinalStringLOCATION="location";
publicstaticfinalStringCREATED_DATE="created";
publicstaticfinalStringMODIFIED_DATE="modified";
}
}

创建ContentProvider

创建ContentProvider第十一章(4)

使用Eclipse打开将会成为项目ContentProvider的FriendsProvider.java文件。你将要在活动中使用这个定制的ContentProvider来从Friends数据库中检索数据。

和往常一样。让我们从导入文件开始。你需要输入Friends类和一些其它的类:

importandroid_programmers_guide.FindAFriend.Friends;
importandroid.content.*;
importandroid.database.Cursor;
importandroid.database.SQLException;
importandroid.database.sqlite.SQLiteOpenHelper;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteQueryBuilder;
importandroid.net.Uri;
importandroid.text.TextUtils;
importandroid.util.Log;
importjava.util.HashMap;

如你所见,你输入类的大多数和SQL打交道。当你用这些包装的时候,我再向你解释。

第一个将要使用的包装是android.content。要成为一个ContentProvider,需要利用并优先要求的方法,你的FriendsProvider类需要扩展ContentProvider。看一下下面类的概要,它们包括一些将要使用在Provider的变量定义:

publicclassFriendsProviderextendsContentProvider{
privateSQLiteDatabasemDB;
privatestaticfinalStringTAG="FriendsProvider";
privatestaticfinalStringDATABASE_NAME="friends";
privatestaticfinalintDATABASE_VERSION=2;
privatestaticHashMap<String,String>FRIENDS_PROJECTION_MAP;
privatestaticfinalintFRIENDS=1;
privatestaticfinalintFRIENDS_ID=2;
privatestaticfinalUriMatcherURL_MATCHER;}


ContentProvider包含一些需要优先的方法,包括onCreate(),query(),insert(),delete(),和update()。因为这些方法将被活动使用ContentProvider呼叫,你必须优先使用它们来进入Friends数据库。

你将优先onCreate()方法呼叫一个SQLiteOpenHelper。因此,在能优先ContentProvider的onCreate()之前,你不得不创建一个类扩展SQLiteIpenHelper。

代码块跟从的是一个ContentProvider的子类。它扩展SQLiteOpenHelper:

privatestaticclassDatabaseHelperextendsSQLiteOpenHelper{
@Override
publicvoidonCreate(SQLiteDatabasedb){
db.execSQL("CREATETABLEfriends(_idINTEGERPRIMARYKEY,"
+"nameTEXT,"+"locationTEXT,"+"createdINTEGER,"
+"modifiedINTEGER"+");");
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,int
newVersion){
Log.w(TAG,"Upgradingdatabasefromversion"+oldVersion+"to"
+newVersion+",whichwilldestroyallolddata");
db.execSQL("DROPTABLEIFEXISTSfriends");
onCreate(db);
}
}

刚创建的DatabaseHelper类包含两个优先方法:onCreater()和onUpgrade()。onCreate()方法被用于当从代码中创建数据库,或者表格定义不存在的示例中。

注意

假定你从adb壳中创建数据库结构,你不会依赖于DatabaseHelper的onCreate()方法来建立你的数据库。

DatabaseHelper类创建后,你现在可以为ContentProvider优先onCreate()方法了:

@Override
publicbooleanonCreate(){
DatabaseHelperdbHelper=newDatabaseHelper();
mDB=dbHelper.openDatabase(getContext(),DATABASE_NAME,null,
DATABASE_VERSION);
return(mDB==null)?false:true;
}


这是个非常之简单的方法,结果就是返回一个布尔值代表你的数据库是否可以被打开。你使用在兄弟类中创建的SQLiteOpenHelper来打开Friends数据库。注意你传递数据库名称到DatabaseHelper类。如果数据库对象——mDB返回的不是null,然后数据库就成功的被打开并可以查询了。

下一步,优先ContentProvider类的query()方法。这将是ContentProvider的主要部分。query()方法是从活动通过ContentProvider被呼叫来从数据库中获得记录。看下优先版本的query()方法代码:

@Override
publicCursorquery(Uriurl,String[]projection,Stringselection,
String[]selectionArgs,Stringsort){
SQLiteQueryBuilderqb=newSQLiteQueryBuilder();
switch(URL_MATCHER.match(url)){
caseFRIENDS:
qb.setTables("friends");
qb.setProjectionMap(FRIENDS_PROJECTION_MAP);
break;
caseFRIENDS_ID:
qb.setTables("friends");
qb.appendWhere("_id="+url.getPathSegments().get(1));
break;
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
StringorderBy;
if(TextUtils.isEmpty(sort)){
orderBy=Friends.Friend.DEFAULT_SORT_ORDER;
}else{
orderBy=sort;
}
Cursorc=qb.query(mDB,projection,selection,selectionArgs,null,
null,orderBy);
c.setNotificationUri(getContext().getContentResolver(),url);
returnc;
}


query()方法做了一点家务管理之类的事宜,通过检查传递到其中的数据库URL的有效性和定义一个查询分类序列达到的。URL检查是为了确保你只是要进入Friends数据库。如果你试图从其它活动进入数据库,或者从其它的ContentProvider,query()方法投递一个例外。

到方法的结尾,你使用SQLiteQueryBuilder来执行一个查询。通过下面的代码,导致的数据集被赋值到一个光标:

Cursorc=qb.query(mDB,projection,selection,selectionArgs,null,
null,orderBy);

注意

光标是一个设备允许你移动记录并从数据列中返回信息。

update(),delete(),和insert()方法同样的简单。看一下这三个方法,应当优先它们:

@Override
publicUriinsert(Uriurl,ContentValuesinitialValues){
longrowID;
ContentValuesvalues;
if(initialValues!=null){
values=newContentValues(initialValues);
}else{
values=newContentValues();
}
if(URL_MATCHER.match(url)!=FRIENDS){
thrownewIllegalArgumentException("UnknownURL"+url);
}
Longnow=Long.valueOf(System.currentTimeMillis());
Resourcesr=Resources.getSystem();
if(values.containsKey(Friends.Friend.CREATED_DATE)==false){
values.put(Friends.Friend.CREATED_DATE,now);
}
if(values.containsKey(Friends.Friend.MODIFIED_DATE)==false){
values.put(Friends.Friend.MODIFIED_DATE,now);
}
if(values.containsKey(Friends.Friend.NAME)==false){
values.put(Friends.Friend.NAME,
r.getString(android.R.string.untitled));
}
if(values.containsKey(Friends.Friend.LOCATION)==false){
values.put(Friends.Friend.LOCATION,"");
}
270Android:AProgrammer’sGuide
rowID=mDB.insert("friends","friend",values);
if(rowID>0){
Uriuri=ContentUris.withAppendedId(Friends.Friend.CONTENT_URI
,rowID);
getContext().getContentResolver().notifyChange(uri,null);
returnuri;
}
thrownewSQLException("Failedtoinsertrowinto"+url);
}
@Override
publicintdelete(Uriurl,Stringwhere,String[]whereArgs){
intcount;
longrowId=0;
switch(URL_MATCHER.match(url)){
caseFRIENDS:
count=mDB.delete("friends",where,whereArgs);
break;
caseFRIENDS_ID:
Stringsegment=url.getPathSegments().get(1);
rowId=Long.parseLong(segment);
count=mDB
.delete("friends","_id="
+segment
+(!TextUtils.isEmpty(where)?"AND("+where
+')':""),whereArgs);
break;
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
getContext().getContentResolver().notifyChange(url,null);
returncount;
}
@Override
publicintupdate(Uriurl,ContentValuesvalues,Stringwhere,String[]
whereArgs){
intcount;
switch(URL_MATCHER.match(url)){
caseFRIENDS:
count=mDB.update("friends",values,where,whereArgs);
break;
caseFRIENDS_ID:
Stringsegment=url.getPathSegments().get(1);
count=mDB
.update("friends",values,"_id="+segment
Chapter11:Application:FindaFriend271
+(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);
break;
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
getContext().getContentResolver().notifyChange(url,null);
returncount;
}

这些方法的代码应当不需要再加以说明了。如果看过在每个方法的处理,代码的核心就是发出一个数据库声明来执行要求的动作——更新,删除,或者插入。

最后的ContentProvider的部分就是一个getType()方法,它返回Friends数据类型。当创建自己的类型,应当跟从一下协议:

vnd.android.cursor.dir/vnd.<package>
TakealookatthegetType()method:
@Override
publicStringgetType(Uriurl){
switch(URL_MATCHER.match(url)){
caseFRIENDS:
return
"vnd.android.cursor.dir/vnd.android_programmers_guide.friend";
caseFRIENDS_ID:
return
"vnd.android.cursor.item/vnd.android_programmers_guide.friend";
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
}

这样就完成了新的定制的ContentProvider。看一下完成的FriendsProvider代码:

packageandroid_programmers_guide.FindAFriend;
importandroid_programmers_guide.FindAFriend.Friends;
importandroid.content.*;
importandroid.database.Cursor;
importandroid.database.SQLException;
importandroid.database.sqlite.SQLiteOpenHelper;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteQueryBuilder;
importandroid.net.Uri;
importandroid.text.TextUtils;
importandroid.util.Log;
importjava.util.HashMap;
publicclassFriendsProviderextendsContentProvider{
privateSQLiteDatabasemDB;
privatestaticfinalStringTAG="FriendsProvider";
privatestaticfinalStringDATABASE_NAME="friends";
privatestaticfinalintDATABASE_VERSION=2;
privatestaticHashMap<String,String>FRIENDS_PROJECTION_MAP;
privatestaticfinalintFRIENDS=1;
privatestaticfinalintFRIENDS_ID=2;
privatestaticfinalUriMatcherURL_MATCHER;
privatestaticclassDatabaseHelperextendsSQLiteOpenHelper{
@Override
publicvoidonCreate(SQLiteDatabasedb){
db.execSQL("CREATETABLEfriends(_idINTEGERPRIMARYKEY,"
+"nameTEXT,"+"locationTEXT,"+"createdINTEGER,"
+"modifiedINTEGER"+");");
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,int
newVersion){
Log.w(TAG,"Upgradingdatabasefromversion"+oldVersion+"to"
+newVersion+",whichwilldestroyallolddata");
db.execSQL("DROPTABLEIFEXISTSfriends");
onCreate(db);
}
}
@Override
publicbooleanonCreate(){
DatabaseHelperdbHelper=newDatabaseHelper();
mDB=dbHelper.openDatabase(getContext(),DATABASE_NAME,null,
DATABASE_VERSION);
return(mDB==null)?false:true;
}
@Override
publicCursorquery(Uriurl,String[]projection,Stringselection,
String[]selectionArgs,Stringsort){
SQLiteQueryBuilderqb=newSQLiteQueryBuilder();
switch(URL_MATCHER.match(url)){
caseFRIENDS:
qb.setTables("friends");
qb.setProjectionMap(FRIENDS_PROJECTION_MAP);
break;
caseFRIENDS_ID:
qb.setTables("friends");
qb.appendWhere("_id="+url.getPathSegments().get(1));
break;
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
StringorderBy;
if(TextUtils.isEmpty(sort)){
orderBy=Friends.Friend.DEFAULT_SORT_ORDER;
}else{
orderBy=sort;
}
Cursorc=qb.query(mDB,projection,selection,selectionArgs,null,
null,orderBy);
c.setNotificationUri(getContext().getContentResolver(),url);
returnc;
}
@Override
publicStringgetType(Uriurl){
switch(URL_MATCHER.match(url)){
caseFRIENDS:
return
"vnd.android.cursor.dir/vnd.android_programmers_guide.friend";
caseFRIENDS_ID:
return
"vnd.android.cursor.item/vnd.android_programmers_guide.friend";
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
}
@Override
publicUriinsert(Uriurl,ContentValuesinitialValues){
longrowID;
ContentValuesvalues;
if(initialValues!=null){
values=newContentValues(initialValues);
}else{
values=newContentValues();
}
if(URL_MATCHER.match(url)!=FRIENDS){
thrownewIllegalArgumentException("UnknownURL"+url);
}
Longnow=Long.valueOf(System.currentTimeMillis());
Resourcesr=Resources.getSystem();
if(values.containsKey(Friends.Friend.CREATED_DATE)==false){
values.put(Friends.Friend.CREATED_DATE,now);
}
if(values.containsKey(Friends.Friend.MODIFIED_DATE)==false){
values.put(Friends.Friend.MODIFIED_DATE,now);
}
if(values.containsKey(Friends.Friend.NAME)==false){
values.put(Friends.Friend.NAME,
r.getString(android.R.string.untitled));
}
if(values.containsKey(Friends.Friend.LOCATION)==false){
values.put(Friends.Friend.LOCATION,"");
}
rowID=mDB.insert("friends","friend",values);
if(rowID>0){
Uriuri=ContentUris.withAppendedId(Friends.Friend.CONTENT_URI
,rowID);
getContext().getContentResolver().notifyChange(uri,null);
returnuri;
}
thrownewSQLException("Failedtoinsertrowinto"+url);
}
@Override
publicintdelete(Uriurl,Stringwhere,String[]whereArgs){
intcount;
longrowId=0;
switch(URL_MATCHER.match(url)){
caseFRIENDS:
Chapter11:Application:FindaFriend275
count=mDB.delete("friends",where,whereArgs);
break;
caseFRIENDS_ID:
Stringsegment=url.getPathSegments().get(1);
rowId=Long.parseLong(segment);
count=mDB
.delete("friends","_id="
+segment
+(!TextUtils.isEmpty(where)?"AND("+where
+')':""),whereArgs);
break;
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
getContext().getContentResolver().notifyChange(url,null);
returncount;
}
@Override
publicintupdate(Uriurl,ContentValuesvalues,Stringwhere,String[]
whereArgs){
intcount;
switch(URL_MATCHER.match(url)){
caseFRIENDS:
count=mDB.update("friends",values,where,whereArgs);
break;
caseFRIENDS_ID:
Stringsegment=url.getPathSegments().get(1);
count=mDB
.update("friends",values,"_id="+segment
+(!TextUtils.isEmpty(where)?"AND("+where+')':""),whereArgs);
break;
default:
thrownewIllegalArgumentException("UnknownURL"+url);
}
getContext().getContentResolver().notifyChange(url,null);
returncount;
}
static{
URL_MATCHER=newUriMatcher(UriMatcher.NO_MATCH);
URL_MATCHER.addURI("android_programmers_guide.FindAFriend.Friends",
"friend",FRIENDS);
URL_MATCHER.addURI("android_programmers_guide.FindAFriend.Friends",
"friend/#",FRIENDS_ID);
FRIENDS_PROJECTION_MAP=newHashMap<String,String>();
FRIENDS_PROJECTION_MAP.put(Friends.Friend._ID,"_id");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.NAME,"name");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.LOCATION,"location");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.CREATED_DATE,"created");
FRIENDS_PROJECTION_MAP.put(Friends.Friend.MODIFIED_DATE,
"modified");
}
}


有了最根本的数据素材(数据库,定义和ContentProvider),你可以开始来建造周围的活动。记住,活动将使用数据库内的数据,显示到列表,并允许用户启动另一个活动来放置数据库条目到一个GoogleMapsOverlay。在下一节,将构建活动并完成FindFriend应用程序。

创建FindAFriend活动

创建FindAFriend活动第十一章(5)

如果你花了些时间运行Google的NotePad示例,那么你就对活动的布局非常熟悉了。你将修改NotePad的接口来使用Friends数据库和Google地图。FindAFriend活动将和一些小的活动交互:NameEditor,LocationEditor,和FriendsMap。下面的章节中你将构建所有的这些活动。

注意

除了NotePad,Google提供了一些写的非常好的示例活动,展示了多编程状态的基本技术。

如你所做的过去的几个活动,从文件AndroidManifest.xml开始。要想熟悉复杂的应用程序,你需要多次更改AndroidManifest.xml文件。

编辑AndroidManifest.xml
看看下面为FindAFriend项目准备的AndroidManifest.xml文件。需要为新活动增加一些Intent过滤器,包括一个编辑friend的姓名,并且启动你的Google地图。

同样,需要仔细注意每个Intent过滤器的动作。这些动作会被传递到每个活动并处理Intent。最后,不要忘记增加Access_LocationandAccess_GPS许可,这样就可以增加你的当前闻之了。完整的AndroidManifest.xml文件应当像这样显示:

<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="android_programmers_guide.FindAFriend">
<applicationandroid:icon="@drawable/icon">
<providerandroid:name="FriendsProvider"
android:authorities="android_programmers_guide.FindAFriend.Friends"/>
<activityandroid:name=".FindAFriend"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<actionandroid:name="android.intent.action.VIEW"/>
<actionandroid:name="android.intent.action.EDIT"/>
<actionandroid:name="android.intent.action.PICK"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
<dataandroid:mimeType="vnd.android.cursor.dir/
vnd.android_programmers_guide.friend"/>
</intent-filter>
<intent-filter>
<actionandroid:name="android.intent.action.GET_CONTENT"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend"/>
</intent-filter>
</activity>
<activityandroid:name=".FriendsMap"android:label="FriendsMap">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activityandroid:name="LocationEditor"
android:label="@string/title_note">
<intent-filterandroid:label="@string/resolve_edit">
<actionandroid:name="android.intent.action.VIEW"/>
<actionandroid:name="android.intent.action.EDIT"/>
<action
android:name="com.google.android.notepad.action.EDIT_LOCATION"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend"/>
</intent-filter>
<intent-filter>
<actionandroid:name="android.intent.action.INSERT"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
<dataandroid:mimeType="vnd.android.cursor.dir/
vnd.android_programmers_guide.friend"/>
</intent-filter>
</activity>
<activityandroid:name="NameEditor"
android:label="@string/title_edit_title"
android:theme="@android:style/Theme.Dialog">
<intent-filterandroid:label="@string/resolve_title">
<actionandroid:name="com.google.android.notepad.action.EDIT_NAME"
/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
<categoryandroid:name="android.intent.category.ALTERNATIVE"/>
<categoryandroid:name="android.intent.category.SELECTED_ALTERNATIVE"/>
<dataandroid:mimeType="vnd.android.cursor.item/
vnd.android_programmers_guide.friend"/>
</intent-filter>
</activity>
</application>
<uses-permissionandroid:name="android.permission.ACCESS_GPS">
</uses-permission><uses-permission
android:name="android.permission.ACCESS_LOCATION">
</uses-permission></manifest>

在下一节中,将为NameEditor项目创建第一个活动。正如名称所示,当用户期望编辑一个friend的名称时,这个活动将被启动。

创建NameEditor活动

创建NameEditor活动第十一章(6)

在本节中,你将为FindAFriend项目创建NameEditor活动。这个活动将被从主活动FindAFriend菜单项目中启动(还没有创建)。NameEditor活动的目的是修改一个Friend记录的姓名字段。

增加一个name_editor.xml布局文件并且一个相应的NameEditor.java文件到应用程序中。你将编辑这些文件来创建你的活动。

首先,编辑name_editor.xml文件来为活动创建布局文件。活动将有一个EditText和一个Button。EditText将允许你修改名称字段,Button将写入结果并退出。如果你一开始就跟从本书的话,你已经增加了不少的View布局到XML文件中。因此,我可以免去细节,玩战的name_editor.xml文件应当如下所示:

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingBottom="3dip">
<EditTextandroid:id="@+id/name"
android:maxLines="1"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:ems="25"
android:layout_height="wrap_content"
android:autoText="true"
android:capitalize="sentences"
android:scrollHorizontally="true"/>
<Buttonandroid:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/button_ok"/>
</LinearLayout>

现在,编辑NameEditor.java并开始写代码。需要导入前一节的Friends类并且导入Cursor包装来帮助你使用数据库记录:

importandroid.app.Activity;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.EditText;

应当建立活动来执行View.OnClickListener()。这将允许你在活动中优先OnClickListener()方法。这部分代码显示NameEditor类的概要和一些你需要的变量定义:

publicclassNameEditorextendsActivityimplementsView.OnClickListener{
publicstaticfinalStringEDIT_NAME_ACTION=
"android_programmers_guide.FindAFriend.action.EDIT_NAME";
privatestaticfinalintNAME_INDEX=1;
privatestaticfinalString[]PROJECTION=newString[]{
Friends.Friend._ID,
Friends.Friend.NAME,
};
CursormCursor;
EditTextmText;
}

下一步,需要优先一些方法,从onCreate()开始。已经在其它章节里看过方法被优先。通常,当活动被创建,它保留所有被执行的代码:

publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.name_editor);
Uriuri=getIntent().getData();
mCursor=managedQuery(uri,PROJECTION,null,null);
mText=(EditText)this.findViewById(R.id.name);
mText.setOnClickListener(this);
Buttonb=(Button)findViewById(R.id.ok);
b.setOnClickListener(this);
}

注意,在前面的代码示例中,你赋值布局到各自的Views并且初始化你的变量。可是,你可能想知道为姓名字段准备的数据在哪里。那就是,你已经创建了一个光标,但是从中还没有检索任何东西。为此,你将使用onResume()方法。

下面两个优先的方法是,onResume()和onPause(),作用是独自的读取和写入数据库。在Android生命周期中,当活动被打开并且在焦点之上,onResume()被呼叫。onPause()被呼叫的情况是当活动被打开,但是在焦点被传递到另一个活动之前。

优先onResume()方法来读取数据库并检索姓名字段:

protectedvoidonResume(){
super.onResume();
if(mCursor!=null){
mCursor.first();
Stringtitle=mCursor.getString(NAME_INDEX);
mText.setText(title);
}
}

在这种方式下,你移动光标到第一个记录,使用前面赋值的索引从中读取姓名字段,然后设置EditText到姓名字段的内容。下一步,修改onPause()方法来写回EditText内容到数据库:

protectedvoidonPause(){
super.onPause();
if(mCursor!=null){
Stringtitle=mText.getText().toString();
mCursor.updateString(NAME_INDEX,title);
mCursor.commitUpdates();
}
}

最后,从onClick句柄呼叫活动方法finish()。它将清除并关闭活动。完成后的NameEditor.java文件应当如下:

packageandroid_programmers_guide.FindAFriend;
importandroid_programmers_guide.FindAFriend.Friends;
importandroid.app.Activity;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.EditText;
publicclassNameEditorextendsActivityimplementsView.OnClickListener{
publicstaticfinalStringEDIT_NAME_ACTION=
"android_programmers_guide.FindAFriend.action.EDIT_NAME";
privatestaticfinalintNAME_INDEX=1;
privatestaticfinalString[]PROJECTION=newString[]{
Friends.Friend._ID,
Chapter11:Application:FindaFriend281
Friends.Friend.NAME,
};
CursormCursor;
EditTextmText;
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.name_editor);
Uriuri=getIntent().getData();
mCursor=managedQuery(uri,PROJECTION,null,null);
mText=(EditText)this.findViewById(R.id.name);
mText.setOnClickListener(this);
Buttonb=(Button)findViewById(R.id.ok);
b.setOnClickListener(this);
}
@Override
protectedvoidonResume(){
super.onResume();
if(mCursor!=null){
mCursor.first();
Stringtitle=mCursor.getString(NAME_INDEX);
mText.setText(title);
}
}
@Override
protectedvoidonPause(){
super.onPause();
if(mCursor!=null){
Stringtitle=mText.getText().toString();
mCursor.updateString(NAME_INDEX,title);
mCursor.commitUpdates();
}
}
publicvoidonClick(Viewv){
finish();
}
}


这样,你可以在Friends数据库编辑姓名的值。总之,数据库中的两个字段重要,姓名和位置。下一节,将为位置字段创建编辑器。

创建LocationEditor活动

创建LocationEditor活动第十一章(7)

在本节中,你将为Friends数据库的“位置”字段创建一个编辑器。做这个活动,你将会从NameEditor活动做一点小的修改。因此,代码和过程不同。

如果你浏览了GoogleNotePad演示版,你应当注意到“notes”编辑器是一个白色屏幕,带有动态自身重复划线。这是个使用定制View执行的结果。你会用这个相同的View来制作LocationEditor。

location_editor.xml
第一步是独自创建location_editor.xml和LocationEditor.java文件的布局和代码。布局文件应当包含一个到定制View布局的呼叫。完整的布局文件如下:

<?xmlversion="1.0"encoding="utf-8"?>
<viewxmlns:android="http://schemas.android.com/apk/res/android"
class="android_programmers_guide.FindAFriend.LocationEditor$MyEditText"
android:id="@+id/location"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff"
android:padding="10dip"
android:scrollbars="vertical"
android:fadingEdge="vertical"/>

LocationEditor还会包含一个菜单系统,允许用户废弃,删除或者恢复他们做的任何改变。这会是一个非常复杂的活动。因此,最好从头开始,那就是LocationEditor.java文件的输入部分。

LocationEditor.java
看看这个活动的输入,很多是处理屏幕上View的图样:

importandroid.app.Activity;
importandroid.content.ComponentName;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.database.Cursor;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Rect;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.util.AttributeSet;
importandroid.view.Menu;
importandroid.widget.EditText;
importjava.util.Map;

下一步,设置活动的主类概要。使用LocationEditor期间,这里有不少的变量需要定义:

publicclassLocationEditorextendsActivity{
privatestaticfinalStringTAG="Friends";
privatestaticfinalintFRIEND_INDEX=1;
privatestaticfinalintNAME_INDEX=2;
privatestaticfinalintMODIFIED_INDEX=3;
privatestaticfinalString[]PROJECTION=newString[]{
Friends.Friend._ID,//0
Friends.Friend.LOCATION,//1
Friends.Friend.NAME,//2
Friends.Friend.MODIFIED_DATE//3
};
privatestaticfinalStringORIGINAL_CONTENT="origContent";
privatestaticfinalintREVERT_ID=Menu.FIRST;
privatestaticfinalintDISCARD_ID=Menu.FIRST+1;
privatestaticfinalintDELETE_ID=Menu.FIRST+2;
privatestaticfinalintSTATE_EDIT=0;
privatestaticfinalintSTATE_INSERT=1;
privateintmState;
privatebooleanmNoteOnly=false;
privateUrimURI;
privateCursormCursor;
Chapter11:Application:FindaFriend285
privateEditTextmText;
privateStringmOriginalContent;
}

所有本章上一节执行的任务,变量的定义是无需加以说明的。

下一小块的代码展示需要创建的子类。这个子类将把EditText画在屏幕上。你把它分开,这样可以从活动中按照需要呼叫它。记住,你会在屏幕上根据用户的需要动态的绘制一个新的EditText。特别注意需要优先的onDraw类:

publicstaticclassMyEditTextextendsEditText{
privateRectmRect;
privatePaintmPaint;
publicMyEditText(Contextcontext,AttributeSetattrs,Mapparams){
super(context,attrs,params);
mRect=newRect();
mPaint=newPaint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0000FF);
}
@Override
protectedvoidonDraw(Canvascanvas){
intcount=getLineCount();
Rectr=mRect;
Paintpaint=mPaint;
for(inti=0;i<count;i++){
intbaseline=getLineBounds(i,r);
canvas.drawLine(r.left,baseline+1,r.right,baseline+1,
paint);
}
super.onDraw(canvas);
}
}

还有,看上去好像很多的代码,但是都不陌生。这个子类只是绘制一个所需的新EditText到屏幕上。

和NameEditor一样,你会使用onResume()和onPause()方法来使用数据库。看看下面每个的代码:

protectedvoidonResume(){
super.onResume();
if(mCursor!=null){
mCursor.first();
if(mState==STATE_EDIT){
setTitle(getText(R.string.title_edit));
}elseif(mState==STATE_INSERT){
setTitle(getText(R.string.title_create));
}
Stringnote=mCursor.getString(FRIEND_INDEX);
mText.setTextKeepState(note);
if(mOriginalContent==null){
mOriginalContent=note;
}
}else{
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
protectedvoidonPause(){
super.onPause();
if(mCursor!=null){
Stringtext=mText.getText().toString();
intlength=text.length();
if(isFinishing()&&(length==0)&&!mNoteOnly){
setResult(RESULT_CANCELED);
deleteFriend();
}else{
if(!mNoteOnly){
mCursor.updateLong(MODIFIED_INDEX,
System.currentTimeMillis());
if(mState==STATE_INSERT){
Stringtitle=text.substring(0,Math.min(30,
length));
Chapter11:Application:FindaFriend287
if(length>30){
intlastSpace=title.lastIndexOf('');
if(lastSpace>0){
title=title.substring(0,lastSpace);
}
}
mCursor.updateString(NAME_INDEX,title);
}
}
mCursor.updateString(FRIEND_INDEX,text);
managedCommitUpdates(mCursor);
}
}
}

和NameEditor很像,在onResume()期间从数据库读取,在onPause()期间写回到数据库。在LocationEditor中增加的一个和NameEditor相反的特性是当你修改时,还写完修改的数据。

最后,需要两个方法来取消和删除friends。这些方法将被从菜单系统中呼叫:

privatefinalvoidcancelFriend(){
if(mCursor!=null){
if(mState==STATE_EDIT){
mCursor.updateString(FRIEND_INDEX,mOriginalContent);
mCursor.commitUpdates();
mCursor.deactivate();
mCursor=null;
}elseif(mState==STATE_INSERT){
deleteFriend();
}
}
setResult(RESULT_CANCELED);
finish();
}
privatefinalvoiddeleteFriend(){
if(mCursor!=null){
mText.setText("");
mCursor.deleteRow();
mCursor.deactivate();
mCursor=null;
}
}

假定你已经学会了第八章中的创建菜单系统,简单的检查一下完整的LocationEditor.java文件来所有的这些方法和子类如何一起工作:

packageandroid_programmers_guide.FindAFriend;
importandroid.app.Activity;
importandroid.content.ComponentName;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.database.Cursor;
importandroid.graphics.Canvas;
importandroid.graphics.Paint;
importandroid.graphics.Rect;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.util.AttributeSet;
importandroid.view.Menu;
importandroid.widget.EditText;
importjava.util.Map;
publicclassLocationEditorextendsActivity{
privatestaticfinalStringTAG="Friends";
privatestaticfinalintFRIEND_INDEX=1;
privatestaticfinalintNAME_INDEX=2;
privatestaticfinalintMODIFIED_INDEX=3;
privatestaticfinalString[]PROJECTION=newString[]{
Friends.Friend._ID,//0
Friends.Friend.LOCATION,//1
Friends.Friend.NAME,//2
Friends.Friend.MODIFIED_DATE//3
};
privatestaticfinalStringORIGINAL_CONTENT="origContent";
privatestaticfinalintREVERT_ID=Menu.FIRST;
privatestaticfinalintDISCARD_ID=Menu.FIRST+1;
privatestaticfinalintDELETE_ID=Menu.FIRST+2;
privatestaticfinalintSTATE_EDIT=0;
privatestaticfinalintSTATE_INSERT=1;
privateintmState;
288Android:AProgrammer’sGuide
Chapter11:Application:FindaFriend289
privatebooleanmNoteOnly=false;
privateUrimURI;
privateCursormCursor;
privateEditTextmText;
privateStringmOriginalContent;
publicstaticclassMyEditTextextendsEditText{
privateRectmRect;
privatePaintmPaint;
publicMyEditText(Contextcontext,AttributeSetattrs,Mapparams){
super(context,attrs,params);
mRect=newRect();
mPaint=newPaint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(0xFF0000FF);
}
@Override
protectedvoidonDraw(Canvascanvas){
intcount=getLineCount();
Rectr=mRect;
Paintpaint=mPaint;
for(inti=0;i<count;i++){
intbaseline=getLineBounds(i,r);
canvas.drawLine(r.left,baseline+1,r.right,baseline+1,
paint);
}
super.onDraw(canvas);
}
}
@Override
protectedvoidonCreate(Bundleicicle){
super.onCreate(icicle);
finalIntentintent=getIntent();
finalStringtype=intent.resolveType(this);
finalStringaction=intent.getAction();
if(action.equals(Intent.EDIT_ACTION)){
mState=STATE_EDIT;
mURI=intent.getData();
}elseif(action.equals(Intent.INSERT_ACTION)){
mState=STATE_INSERT;
290Android:AProgrammer’sGuide
mURI=getContentResolver().insert(intent.getData(),null);
if(mURI==null){
finish();
return;
}
setResult(RESULT_OK,mURI.toString());
}else{
finish();
return;
}
setContentView(R.layout.location_editor);
mText=(EditText)findViewById(R.id.location);
mCursor=managedQuery(mURI,PROJECTION,null,null);
if(icicle!=null){
mOriginalContent=icicle.getString(ORIGINAL_CONTENT);
}
}
@Override
protectedvoidonResume(){
super.onResume();
if(mCursor!=null){
mCursor.first();
if(mState==STATE_EDIT){
setTitle(getText(R.string.title_edit));
}elseif(mState==STATE_INSERT){
setTitle(getText(R.string.title_create));
}
Stringnote=mCursor.getString(FRIEND_INDEX);
mText.setTextKeepState(note);
if(mOriginalContent==null){
mOriginalContent=note;
}
}else{
setTitle(getText(R.string.error_title));
mText.setText(getText(R.string.error_message));
}
}
Chapter11:Application:FindaFriend291
@Override
protectedvoidonFreeze(BundleoutState){
outState.putString(ORIGINAL_CONTENT,mOriginalContent);
}
@Override
protectedvoidonPause(){
super.onPause();
if(mCursor!=null){
Stringtext=mText.getText().toString();
intlength=text.length();
if(isFinishing()&&(length==0)&&!mNoteOnly){
setResult(RESULT_CANCELED);
deleteFriend();
}else{
if(!mNoteOnly){
mCursor.updateLong(MODIFIED_INDEX,
System.currentTimeMillis());
if(mState==STATE_INSERT){
Stringtitle=text.substring(0,Math.min(30,
length));
if(length>30){
intlastSpace=title.lastIndexOf('');
if(lastSpace>0){
title=title.substring(0,lastSpace);
}
}
mCursor.updateString(NAME_INDEX,title);
}
}
mCursor.updateString(FRIEND_INDEX,text);
managedCommitUpdates(mCursor);
}
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
if(mState==STATE_EDIT){
menu.add(0,REVERT_ID,R.string.menu_revert).setShortcut('0',
'r');
if(!mNoteOnly){
menu.add(0,DELETE_ID,
R.string.menu_delete).setShortcut('1','d');
}
}else{
menu.add(0,DISCARD_ID,R.string.menu_discard).setShortcut('0',
'd');
}
if(!mNoteOnly){
Intentintent=newIntent(null,getIntent().getData());
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(
Menu.ALTERNATIVE,0,
newComponentName(this,LocationEditor.class),null,
intent,0,null);
}
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
caseDELETE_ID:
deleteFriend();
finish();
break;
caseDISCARD_ID:
cancelFriend();
break;
caseREVERT_ID:
cancelFriend();
break;
}
returnsuper.onOptionsItemSelected(item);
}
privatefinalvoidcancelFriend(){
if(mCursor!=null){
if(mState==STATE_EDIT){
mCursor.updateString(FRIEND_INDEX,mOriginalContent);
mCursor.commitUpdates();
mCursor.deactivate();
mCursor=null;
}elseif(mState==STATE_INSERT){
deleteFriend();
}
}
setResult(RESULT_CANCELED);
finish();
}
privatefinalvoiddeleteFriend(){
if(mCursor!=null){
mText.setText("");
292Android:AProgrammer’sGuide
mCursor.deleteRow();
mCursor.deactivate();
mCursor=null;
}
}
}

在下一节,会创建绘制GoogleMapsOverlay的活动,FriendsMap活动将从Friends数据库中读取完整的friends记录集并把每一个写入Overlay。

创建FriendsMap活动第十一章(8)

FriendsMap是最后一个可以从主程序中呼叫的活动。本活动将从Friends数据库中呼叫数据集并且在GoogleMap上为每一个friend画一个圆圈。该活动还将为你的当前位置画一个圆圈。

从增加两个新文件来开始本项目,friendsmap.xmlFriendsMap.java文件。因为你已经在第九章内看过friendsmap.xml文件了,所以没有必要再解释一遍。使用的是一个RelativeLayout来把4个按钮放在一个GoogleMap上的。完整的friendsmap.xml文件应当看上去如下:

<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<viewclass="com.google.android.maps.MapView"
android:id="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomIn"
style="?android:attr/buttonStyleSmall"
android:text="+"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonMapView"
style="?android:attr/buttonStyleSmall"
android:text="Map"
android:layout_alignRight="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonSatView"
style="?android:attr/buttonStyleSmall"
android:text="Sat"
android:layout_alignRight="@+id/myMap"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Buttonandroid:id="@+id/buttonZoomOut"
style="?android:attr/buttonStyleSmall"
android:text="-"
android:layout_alignBottom="@+id/myMap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>

因为在第九章已经看过绝大多数的FriendsMap.java文件了,我不会再阐述每一个细节。但是,有一个方法需要解释。

你会创建一个叫做LoadFriends()的方法来存取数据库,读取记录,并且绘制Overlay。看一下LoadFriends()的代码。注意,你打开数据库,匹配并分析位置字段,从位置字段的纬度和经度创建点,并且绘制点到Overlay中。这个方法最后所做的事情就是从GPS上抓取坐标并且在Overlay上绘制出来,用标签“ME”表示。

publicvoidLoadFriends(MapViewmv,MapControllermc,Cursorc){
PointmyLocation=null;
DoublelatPoint=null;
DoublelngPoint=null;
c.first();
do{
if(c.getString(c.getColumnIndex("location"))!=null){
finalStringgeoPattern="(geo:[\\-]?[0-9]{1,3}\\.[0
9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";
Patternpattern=Pattern.compile(geoPattern);
CharSequenceinputStr=
c.getString(c.getColumnIndex("location"));
Matchermatcher=Pattern.matcher(inputStr);
booleanmatchFound=matcher.find();
if(matchFound){
StringgroupStr=matcher.group(0);
latPoint=
Double.valueOf(groupStr.substring(groupStr.indexOf(":")+1,
groupStr.indexOf(",")));
lngPoint=
Double.valueOf(groupStr.substring(groupStr.indexOf(",")+1,
groupStr.indexOf("#")));
PointfriendLocation=new
Point(latPoint.intValue(),lngPoint.intValue());
294Android:AProgrammer’sGuide
drawFriendsOverlay.addNewFriend(c.getString(c.getColumnIndex("name")),
friendLocation);
}
}
}while(c.next());
LocationManagermyManager=(LocationManager)
getSystemService(Context.LOCATION_SERVICE);
DoublemyLatPoint=
myManager.getCurrentLocation("gps").getLatitude()*1E6;
DoublemyLngPoint=
myManager.getCurrentLocation("gps").getLongitude()*1E6;
myLocation=newPoint(myLatPoint.intValue(),myLngPoint.intValue());
drawFriendsOverlay.addNewFriend("Me",myLocation);
mc.centerMapTo(myLocation,false);
mc.zoomTo(9);
mv=null;
}

剩余的FriendsMap.java文件操控第十章介绍的缩放和棒性按钮:

packageandroid_programmers_guide.FindAFriend;
importandroid.os.Bundle;
importandroid.location.LocationManager;
importandroid.view.View;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.database.Cursor;
importandroid.widget.Button;
importjava.util.regex.Pattern;
importjava.util.regex.Matcher;
importandroid.graphics.Canvas;
importandroid.graphics.RectF;
importandroid.graphics.Paint;
importcom.google.android.maps.MapActivity;
importcom.google.android.maps.MapView;
importcom.google.android.maps.Point;
importcom.google.android.maps.MapController;
importcom.google.android.maps.Overlay;
importcom.google.android.maps.OverlayController;
publicclassFriendsMapextendsMapActivity{
privatestaticfinalString[]PROJECTION=newString[]{
Friends.Friend.NAME,Friends.Friend.LOCATION};
publicCursormCursor;
DrawFriendsOverlaydrawFriendsOverlay=newDrawFriendsOverlay();
@Override
publicvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setContentView(R.layout.friendsmap);
Intentintent=getIntent();
if(intent.getData()==null){
intent.setData(Friends.Friend.CONTENT_URI);
}
mCursor=managedQuery(getIntent().getData(),PROJECTION,null,null);
finalMapViewmyMap=(MapView)findViewById(R.id.myMap);
finalMapControllermyMapController=myMap.getController();
LoadFriends(myMap,myMapController,mCursor);
OverlayControllermyOverlayController=
myMap.createOverlayController();
myOverlayController.add(drawFriendsOverlay,true);
finalButtonzoomIn=(Button)findViewById(R.id.buttonZoomIn);
zoomIn.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomIn(myMap,myMapController);
}});
finalButtonzoomOut=(Button)findViewById(R.id.buttonZoomOut);
zoomOut.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ZoomOut(myMap,myMapController);
}});
finalButtonviewMap=(Button)findViewById(R.id.buttonMapView);
viewMap.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ShowMap(myMap,myMapController);
}});
finalButtonviewSat=(Button)findViewById(R.id.buttonSatView);
viewSat.setOnClickListener(newButton.OnClickListener(){
publicvoidonClick(Viewv){
ShowSat(myMap,myMapController);
}});
}
publicvoidLoadFriends(MapViewmv,MapControllermc,Cursorc){
PointmyLocation=null;
DoublelatPoint=null;
DoublelngPoint=null;
c.first();
do{
if(c.getString(c.getColumnIndex("location"))!=null){
finalStringgeoPattern="(geo:[\\-]?[0-9]{1,3}\\.[0
9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";
Patternpattern=Pattern.compile(geoPattern);
CharSequenceinputStr=
c.getString(c.getColumnIndex("location"));
Matchermatcher=pattern.matcher(inputStr);
booleanmatchFound=matcher.find();
if(matchFound){
StringgroupStr=matcher.group(0);
latPoint=
Double.valueOf(groupStr.substring(groupStr.indexOf(":")+1,
groupStr.indexOf(",")));
lngPoint=
Double.valueOf(groupStr.substring(groupStr.indexOf(",")+1,
groupStr.indexOf("#")));
PointfriendLocation=new
Point(latPoint.intValue(),lngPoint.intValue());
drawFriendsOverlay.addNewFriend(c.getString(c.getColumnIndex("name")),
friendLocation);
}
}
}while(c.next());
LocationManagermyManager=(LocationManager)
getSystemService(Context.LOCATION_SERVICE);
DoublemyLatPoint=
myManager.getCurrentLocation("gps").getLatitude()*1E6;
DoublemyLngPoint=
myManager.getCurrentLocation("gps").getLongitude()*1E6;
myLocation=newPoint(myLatPoint.intValue(),myLngPoint.intValue());
drawFriendsOverlay.addNewFriend("Me",myLocation);
mc.centerMapTo(myLocation,false);
mc.zoomTo(9);
mv=null;
}
publicvoidZoomIn(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=21){
mc.zoomTo(mv.getZoomLevel()+1);
}
}
publicvoidZoomOut(MapViewmv,MapControllermc){
if(mv.getZoomLevel()!=1){
mc.zoomTo(mv.getZoomLevel()-1);
}
}
publicvoidShowMap(MapViewmv,MapControllermc){
if(mv.isSatellite()){
mv.toggleSatellite();
}
}
publicvoidShowSat(MapViewmv,MapControllermc){
if(!mv.isSatellite()){
mv.toggleSatellite();
}
}
protectedclassDrawFriendsOverlayextendsOverlay{
publicString[]friendName=newString[0];
publicPoint[]friendPoint=newPoint[0];
finalPaintpaint=newPaint();
@Override
publicvoiddraw(Canvascanvas,PixelCalculatorcalculator,Boolean
shadow){
for(intx=0;x<friendPoint.length;x++){
int[]coords=newint[2];
calculator.getPointXY(friendPoint[x],coords);
RectFoval=newRectF(coords[0]-7,coords[1]+7,
coords[0]+7,coords[1]-7);
paint.setTextSize(14);
canvas.drawText(friendName[x],
coords[0]+9,coords[1],paint);
canvas.drawOval(oval,paint);
}
}
publicvoidaddNewFriend(Stringname,Pointpoint){
intx=friendPoint.length;
String[]friendNameB=newString[x+1];
Point[]friendPointB=newPoint[x+1];
System.arraycopy(friendName,0,friendNameB,0,x);
System.arraycopy(friendPoint,0,friendPointB,0,x);
friendNameB[x]=name;
friendPointB[x]=point;
friendName=newString[x+1];
friendPoint=newPoint[x+1];
System.arraycopy(friendNameB,0,friendName,0,x+1);
System.arraycopy(friendPointB,0,friendPoint,0,x+1);
}
}
}

完成本项目的最后一个任务是创建主活动,FindAFriend。该活动被放置在一个壳内来呼叫本章创建的活动。

创建FindAFriend活动第十一章(9)

要开始本节,创建两个文件,findafriend.xmlFindAFriend.java。再说一次,这些文件将为当前部分独自保留你的布局和代码。布局文件非常的基本并且只有一个TextView。这个TextView将被用来写入到friends的列表中。完整的findafriend.xml文件应当显示如下:

<?xmlversion="1.0"encoding="utf-8"?>
<TextViewxmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:gravity="center_vertical"
android:paddingLeft="27dip"
/>

完整的FindAFriend.java文件如下。文件中的所有代码在本章中已经讨论过。首先,读取数据库并且写入结果到一个ListView。给予用户一个菜单项目来编辑或者删除条目,或者启动FriendsMap活动。很简单,对不对?

packageandroid_programmers_guide.FindAFriend;
importandroid_programmers_guide.FindAFriend.Friends;
importandroid.app.ListActivity;
importandroid.content.ComponentName;
importandroid.content.Intent;
importandroid.content.ContentUris;
importandroid.database.Cursor;
importandroid.graphics.Color;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.view.View.MeasureSpec;
importandroid.widget.ListAdapter;
importandroid.widget.ListView;
importandroid.widget.SimpleCursorAdapter;
importandroid.widget.TextView;
publicclassFindAFriendextendsListActivity{
300Android:AProgrammer’sGuide
publicstaticfinalintDELETE_ID=Menu.FIRST;
publicstaticfinalintINSERT_ID=Menu.FIRST+1;
publicstaticfinalintFIND_FRIENDS=Menu.FIRST+2;
privatestaticfinalString[]PROJECTION=newString[]{
Friends.Friend._ID,Friends.Friend.NAME};
privateCursormCursor;
@Override
protectedvoidonCreate(Bundleicicle){
super.onCreate(icicle);
setDefaultKeyMode(SHORTCUT_DEFAULT_KEYS);
Intentintent=getIntent();
if(intent.getData()==null){
intent.setData(Friends.Friend.CONTENT_URI);
}
setupList();
mCursor=managedQuery(getIntent().getData(),PROJECTION,null,
null);
ListAdapteradapter=newSimpleCursorAdapter(this,
R.layout.findafriend_item,mCursor,
newString[]{Friends.Friend.NAME},newint[]
{android.R.id.text1});
setListAdapter(adapter);
}
privatevoidsetupList(){
Viewview=getViewInflate().inflate(
android.R.layout.simple_list_item_1,null,null);
TextViewv=(TextView)view.findViewById(android.R.id.text1);
v.setText("X");
getListView().setBackgroundColor(Color.GRAY);
v.measure(MeasureSpec.makeMeasureSpec(View.MeasureSpec.EXACTLY,
100),
MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED,
0));
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
super.onCreateOptionsMenu(menu);
menu.add(0,INSERT_ID,R.string.menu_insert).setShortcut('3','a');
Intentintent=newIntent(null,getIntent().getData());
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(
Menu.ALTERNATIVE,0,newComponentName(this,FindAFriend.class),
null,intent,0,null);
returntrue;
}
@Override
publicbooleanonPrepareOptionsMenu(Menumenu){
super.onPrepareOptionsMenu(menu);
finalbooleanhaveItems=mCursor.count()>0;
if(haveItems){
Uriuri=ContentUris.withAppendedId(getIntent().getData(),
getSelectedItemId());
Intent[]specifics=newIntent[1];
specifics[0]=newIntent(Intent.EDIT_ACTION,uri);
Menu.Item[]items=newMenu.Item[1];
Intentintent=newIntent(null,uri);
intent.addCategory(Intent.SELECTED_ALTERNATIVE_CATEGORY);
menu.addIntentOptions(Menu.SELECTED_ALTERNATIVE,0,null,
specifics,intent,0,items);
menu.add(Menu.SELECTED_ALTERNATIVE,DELETE_ID,
R.string.menu_delete)
.setShortcut('2','d');
menu.add(Menu.SELECTED_ALTERNATIVE,FIND_FRIENDS,
R.string.find_friends).setShortcut('4','f');
if(items[0]!=null){
items[0].setShortcut('1','e');
}
}else{
menu.removeGroup(Menu.SELECTED_ALTERNATIVE);
}
menu.setItemShown(DELETE_ID,haveItems);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(Menu.Itemitem){
switch(item.getId()){
caseDELETE_ID:
deleteItem();
returntrue;
caseINSERT_ID:
insertItem();
returntrue;
caseFIND_FRIENDS:
Intentfindfriends=newIntent(this,FriendsMap.class);
startActivity(findfriends);
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
@Override
protectedvoidonListItemClick(ListViewl,Viewv,intposition,long
id){
Uriurl=ContentUris.withAppendedId(getIntent().getData(),id);
Stringaction=getIntent().getAction();
if(Intent.PICK_ACTION.equals(action)
||Intent.GET_CONTENT_ACTION.equals(action)){
setResult(RESULT_OK,url.toString());
}else{
startActivity(newIntent(Intent.EDIT_ACTION,url));
}
}
privatefinalvoiddeleteItem(){
mCursor.moveTo(getSelectedItemPosition());
mCursor.deleteRow();
}
privatefinalvoidinsertItem(){
startActivity(newIntent(Intent.INSERT_ACTION,
getIntent().getData()));
}
}

这个是本书中最长的一个活动,需要注意的是你所做的相关所需的编程工作还是非常的少。下一步,运行这个活动并且查看所有工作的成果。

运行FindAFriend活动第十一章(10)

在Android模拟器中运行FindAFriend活动。应当从一个空的列表开始,如下图(略)。要增加第一个朋友,点击菜单按钮并选择AddFriend选项。

本选项启动你创建的定制View。在提供的行处输入一个朋友的名称,点击返回按钮返回到主活动。

现在在ListView中应当有一个朋友的名称。再次点击菜单按钮,显然你拥有更多的选项了。如下图(略)。选择EditLocation选项。会再次来到定制控制。输入坐标信息。

最后,返回到主活动并选择FindFriend选项。这样分别会清除在旧金山的位置和你朋友在非洲海岸的位置。

试试这个:实时位置更新
试着修改FindAFriend应用程序,当你移动时,来更新“ME”标记。这个应该很容易通过使用update()方法实现。

问专家

Q:SQLite数据库可以通过代码的方式创建吗?

A:是的。但是,为了更好的了解Android的目的,我选择了手工创建数据库例子。可以在FriendsProviderContentProvider通过代码创建数据库的creation方法自行修改本项目。

Q:需要一个分开的类来执行BaseColumns?

A:不。可以直接从Friends类中定义条目。如果你创建的一个ContentProvider被其它开发者使用,但是他们不知道数据库的底层结构,那么你就需要提供一个定义的类。

AndroidSDK工具参考第十二章(完)

AndroidSDK工具参考第十二章(完)

本章提供了一些有价值的AndroidSDK工具参考项目,这些工具你已经在本书的课程中使用过了。它们给了你一些命令行选项,可以在Android模拟器和Android调试桥中使用。

Android模拟器命令

下表包含了大多数常规的Android模拟器命令。这些命令在2008年3月份发布的SDK版本中可用。每个命令提供了简短的描述。

EmulatorCommand模拟器命令

功能

emulator-console

Enablestheconsoleshellonthecurrentterminal
在当前终端上激活控制台外壳


emulator-data<filename>

Usesadifferentfileastheworkinguser-datadiskimage
使用一个不同的文件作为工作用户数据磁盘镜像

emulator-debug-kernel

Sendskerneloutputtotheconsole
发送核心输出到控制台

emulator-flash-keys

Flasheskeypressesonthedeviceskin
在设备皮肤上闪烁keypress

emulator-help

PrintsalistofallEmulatorcommands
列出模拟器列表

emulator-http-proxy<proxy>

MakesallTCPconnectionsthroughaspecifiedHTTP/HTTPSproxy通过一个定义的HTTP/HTTPS代理制作所有的TCP连接。

emulator-image<file>Uses<file>

asthesystemimage作为系统镜像

emulator-kernel<file>Uses<file>

astheemulatedkernel作为模拟的核心

emulator-logcat<logtags>

Enableslogcatoutputwithgiventags
用指定的标签激活logcat输出

emulator-mic<deviceorfile>

UsesdeviceorWAVfileforaudioinput
使用设备或者WAV文件作为音频输入

emulator-netdelay<delay>

设置网络反应时间模拟到<delay>Setsnetworklatencyemulationto<delay>.
(The<delay>parametersimulatesthedelayexperiencedonspecifictypesofnetworks.<delay>参数模拟在定义类型的网络)
The<delay>syoucanuseareasfollows
可以使用的<delay>如下:
●Gprs
●Edge
●Umts
●None
●<num>
●<min>:<max>

emulator-netfastShortcutfor-netspeedfull-netdelay

none

emulator-netspeed<speed>

设置网络速度模拟到<speed>。Setsnetworkspeedemulationto<speed>.(The
<speed>parametersimulatesthedataspeed
experiencedonspecifictypesofnetworks.)The
<speed>syoucanuseareasfollows:
●Gsm
●Hscsd
●Gprs
●Edge
●Umts
●Hsdpa
●Full
●<num>
●<up>:<down>

emulator-noaudio

DisablesAndroidaudiosupport废除Android音频支持

emulator-nojni

DisablesJNIchecksintheDalvikvirtualmachine在Dalvikvirtualmachine中废除JNI检查

emulator-noskin

SpecifiesnottouseanyEmulatorskin
定义为不使用任何模拟器皮肤

emulator-onion<image>

Usesoverlayimageoverscreen
在屏幕上使用覆盖图像

emulator-onion-alpha<percent>

Specifiesonionskintranslucencyvalue(aspercent)定义半透明皮肤值(百分比)

emulator-qemu

PassesargumentstoQEMU
传递参数到QEMU

emulator-qemu-h

DisplaysQEMUhelp
显示QEMU帮助

emulator-radio<device>

Redirectstheradiomodeminterfacetoahostcharacterdevice重定向无线调制解调器接口到一个主字符设备

emulator-ramdisk<file>Uses<file>

astheramdiskimage作为ramdisk镜像

emulator-raw-keys

DisablesUnicodekeyboardreversemapping禁用Unicode键盘转换映射

emulator-sdcard<file>Uses<file>

astheSDMemoryCardimage作为SD内存卡镜像

emulator-skin<skinID>

使用定义的皮肤启动模拟器StartstheEmulatorwiththespecifiedskin:
●HVGA-L480x320,landscape
●HVGA-P320x480,portrait(default)
●QVGA-L320x240,landscape
●QVGA-P240x320,portrait

emulator-skindir<dir>

SearchesforEmulatorskinsin<dir>
在<dir>内查找模拟器皮肤


emulator-system<dir>

Searchessystem,ramdisk,anduser-datadiskimagesin<dir>
在<dir>内查找系统,ramdisk和用户数据磁盘镜像

emulator-trace<name>

Enablescodeprofiling(pressF9tostart),writtentoaspecifiedfile
启动代码分析(按F9启动),写入一个定义的文件

emulator-useaudio

EnablesAndroidaudiosupport启动Android音频支持

emulator-verbose

Enablesverboseoutput激活冗长的输出

emulator-verbose-keys

Enablesverbosekeypressmessages激活冗长的的按键信息

emulator-verbose-proxy

Enablesverboseproxydebugmessages激活冗长的的代理调试信息

emulator-wipe-data

Deletesalldataontheuser-datadiskimage在用户数据磁盘镜像中删除所有数据(开始前参见emulator-data<filename>)
(seeemulator–data<filename>)before
starting

Android调试桥命令

下面的是gsm命令。通过连接到Android模拟器的终端控制台使用。如果你不了解端口终端控制台,它是一个比调试端口少的端口。执行adb来获得活动的设备列表和相关的端口号。


adbBugreport

Printsdumpsys,dumpstate,andlogcatdatatothescreen,forthepurposesofbugreporting
为故障报告在屏幕上打印dumpsys,dumpstate和logcat数据

adbcall<phonenumber>

Simulatesaninboundphonecallfrom
<phonenumber>模拟一个呼入的电话

adbcancel<phonenumber>

Cancelsaninboundphonecallfrom
<phonenumber>取消一个呼入的电话

adb-d{<ID>|<serialNumber>}

LetsyoudirectanadbcommandtoaspecificEmulator/deviceinstance,referredtobyitsadb-assignedIDorserialnumber
允许你指引一个adb命令到一个定义的模拟器/设备示例中,通过它的赋值adbID或者序列号来引用

adbdata<state>

ChangesthestateoftheGPRSdata
connectionto<state>改变GPRS数据连接状态到<state>

adbDevices

Printsalistofallattachedmulator/device
instances
打印所附模拟器列表/设备示例

adbforward<local><remote>

ForwardssocketconnectionsfromaspecifiedlocalporttoaspecifiedremoteportontheEmulator/deviceinstance

在模拟器/设备示例中从一个定义的本来端口转递socket连接到一个定义的远程端口

adbget-serialno

Printstheadbinstanceidentifierstring
打印adb示例标识符字符串

adbget-state

Printstheadbstateofanemulator/deviceinstance
打印一个模拟器/设备示例的adb状态

adbhelp

Printsalistofsupportedadbcommands
打印支持的adb命令列表

adbinstall<path-to-apk>

PushesanAndroidapplication(specifiedasafullpathtoan.apkfile)tothedatafileofanEmulator/device
推入Android应用程序(作为一个完整路径一个.apk文件)到模拟器/设备示例

adbjdwp

PrintsalistofavailableJDWPprocessesonagivendevice
在指定的设备上列出可用的JDWP进程

adbkill-server

Terminatestheadbserverprocess
终止adb服务器进程

adblogcat[<option>][<filter-specs>]

Printslogdatatothescreen在屏幕上打印log数据

adbppp<tty>[parm]...

RunsPPPoverUSB通过USB运行PPP:
●<tty>ThettyforPPPstream;for
example,dev:/dev/omap_csmi_ttyl
<tty>ppp流;例如,dev:/dev/omap_csmi_ttyl
●[parm]...ZeroormorePPP/PPPD
options,suchasdefaultroute,local,
notty,etc.
0或者更多的PPP/PPPD选项,如defaultroute,local,notty等等
Notethatyoushouldnotautomaticallystart
aPDPconnection.
注意,你不应当自动启动一个PDP连接

adbpull<remote><local>

Copiesaspecifiedfilefroman
Emulator/deviceinstancetoyour
developmentcomputer
从模拟器/设备复制定义的文件到你的电脑

adbpush<local><remote>

Copiesaspecifiedfilefromyourdevelopment
computertoanEmulator/deviceinstance
从电脑中复制文件到模拟器/设备

adbShell

Startsaremoteshellinthetarget
Emulator/deviceinstance
在目标模拟器/设备上启动远程外壳

adbstart-server

Checkswhethertheadbserverprocessis
runningand,ifnot,startsit
检测adb服务器进程是否启动,如果否,启动它

adbStatus

ReportsthecurrentGSMvoice/datastate
报告当前GSM声音/数据状态

adbunregistered

Indicatesnonetworkisavailable
指示无网络可用

adbVersion

Printstheadbversionnumber
打印adb版本号

adbvoice<state>

ChangesthestateoftheGPRSvoice
connectionto<state>改变GPRS声音状态连接到<state>

adbwait-for-bootloader

Blocksexecutionuntilthebootloaderis
online—thatis,untiltheinstancestateis
bootloader
阻止执行直到引导装入完成。也就是除非设备完成引导

adbwait-for-device

Blocksexecutionuntilthedeviceis
online—thatis,untiltheinstancestateis
device
阻止执行直到设备在线。也就是示例状态是设备

注意:为不产生歧义,本表格的解释部分仍保留英文。

至此,历经两个多月的时间,本书的翻译工作全部完成,在英文版的书中还有索引部分,这里就没有必要翻译了。


更多相关文章

  1. Android中Timer使用示例
  2. Android(安卓)Studio打包设置分支
  3. android 日期时间选择器
  4. 【翻译】(25)ANDROID ATOMICS OPERATIONS
  5. Android(安卓)SDK Manager无法更新解决方法
  6. Android应用程序中的多个Activity的显示创建和调用
  7. 【Android】Android(安卓)Studio中gradle scripts的配置,如何运行
  8. android .apk 反编译
  9. android获取手机号码以及imsi信息

随机推荐

  1. 第四课 box-sizing、伪类、媒体查询移动
  2. PHP基础
  3. iOS tableView右侧索引视图状态获取的方
  4. 浅谈IOS屏幕刷新ADisplayLink
  5. 详解IOS WebRTC的实现原理
  6. 详解如何使用ReactiveObjC
  7. 1.HTML上手练习 2. 预习css知识
  8. 仿作京东网页
  9. JavaScript 事件
  10. PHP基础语法demo练习