首页资源分类嵌入式系统安卓 > 《Android应用程序开发与典型案例》完整版

《Android应用程序开发与典型案例》完整版

已有 445176个资源

下载专区

文档信息举报收藏

标    签:androidandroid开发安卓

分    享:

文档简介

《Android应用程序开发与典型案例》完整版

作者:华清远见

简介:本书共有23个章节。第一部分主要对Android的概况进行了介绍,包括各种基本信息的介绍和基本代码和插件的介绍。第二部分介绍了android开发代码和各种应用插件,第三部分通过实例结合扫了如何进行android应用程序开发。

文档预览

《Android 应用程序开发与典型案例》 作者:华清远见 第 1 章 Android 基本概念 本章目标 本章介绍 Android 基本概念方面的内容,包括 Android 平台特性、Android 系统 架构、Android 开发框架以及 OMS 介绍。 1.1 Android 简介 专业始于专注 卓识源于远见 Android 一词的本义指“机器人”,它是由 Google 公司于 2007 年 11 月推出的基于 Linux 平台的开源手 机操作系统,依靠 Google 的强大开发和媒体资源,Android 成为众多手机厂商竞相追逐的对象,逐渐发展 成为目前最流行的手机开发平台。图 1-1 所示为 Android 系统的 Logo。 图 1-1 Android 系统的 Logo Android 是一个包括操作系统、中间件、用户界面和关键应用软件的移动设备软件堆。换句话说,Android 是基于 Java 并运行在 Linux 内核上的轻量级操作系统,功能全面,包括一系列 Google 公司在其内置的应 用软件,如电话、短信等基本应用功能。图 1-2 所示为 Android 的模拟器,从中可以大概了解 Android 的 运行界面。 图 1-2 Android 模拟器 问:模拟器和真机有什么区别呢? 答:  模拟器不支持呼叫和接听实际来电,但可以通过控制台模拟电话呼叫(呼入和呼出)。  模拟器不支持USB连接。  模拟器不支持相机/视频捕捉。  模拟器不支持音频输入(捕捉),但支持输出(重放)。  模拟器不支持扩展耳机。  模拟器不能确定连接状态。  模拟器不能确定电池电量水平和充电状态。  模拟器不能确定SD卡的插入/弹出。  模拟器不支持蓝牙。 1.2 Android 平台特性 ‐2‐ 专业始于专注 卓识源于远见 随着科技的发展,移动电话(Mobile Phone)正向着智能化的方向迈步,并逐步成为多种工具的功能 载体,而 Android 就是这样一个智能手机的平台,一个多种工具的功能载体。 1.通信工具 移动电话的最基本功能即为通信,因此,使用运营商提供的通信网络进行语音通话也是 Android 平台 的最基本功能。除了传统的语音通话功能外,Android 平台还具有短消息功能,以及通常移动电话都具有 的个人信息系统管理方面的功能(如电话本等)。 2.网络工具 随着数字业务使用的普遍化,移动电话通常作为网络工具使用,这样移动电话可以完成电脑的部分功 能。由此,Android 平台在网络方面的功能主要包括浏览器、IM(即时信息)、邮件等,基本包含了网络方 面的大部分功能。 3.媒体播放器 随着多媒体技术的应用,在移动电话上进行音频和视频播放已经成为经常使用的功能。由此,Android 平台具有支持更多的音频/视频格式,支持更高分辨率的视频、更流畅地播放,以及和网络结合的流媒体方 面等功能。 4.媒体获取设备 随着移动电话与媒体获取设备的集成日益增强,Android 平台提供了照相机、录音机、摄像机等功能。 5.多类型的连接设备 Android 平台提供了多种连接方式,如 USB、GPS、红外、蓝牙、无线局域网等。 6.具有友好和绚丽的用户界面 Android 平台具有友好的用户界面,使用户容易学习和操作,同时具有绚丽的用户界面,具有良好的 视觉效果。 7.可以个性化定制的平台 Android 平台对于用户的个性化需求,提供了全面自定义手机的功能。 除了以上介绍 Android 平台的功能以外,其在技术上还具有以下几个方面的特性。  全开放智能移动电话平台。  支持多硬件平台。  使用众多的标准化技术。  核心技术完整、统一。  完善的SDK和文档。  完善的辅助开发工具。 1.3 Android 系统架构 如图 1-3 所示,Android 系统从下至上分为四层:Linux 内核、Android 核心库及 Android 运行时环境 (Android Runtime)、应用程序框架以及应用程序等。 ‐3‐ 专业始于专注 卓识源于远见 图 1-3 Android 系统框架 1.3.1 Linux 内核(Linux Kernel) Android 的核心系统服务依赖于 Linux 2.6,例如,安全、内存管理、进程管理、网络堆栈、驱动模型。 Linux Kernel 也作为硬件和软件之间的抽象层,它隐藏具体硬件细节而为上层提供统一的服务。 除了标准的 Linux 内核以外,Android 系统还增加了内核的驱动程序,例如,显示驱动、蓝牙驱动、 相机驱动、闪存卡驱动、Binder IPC 驱动、输入设备驱动、USB 驱动、WiFi 驱动、音频系统驱动、电源管 理等,为 Android 系统的运行提供基础性支持。 这样分层的好处就是使用下层提供的服务而为上层提供统一的服务,屏蔽本层及以下层的差异,当本 层及以下层发生了变化时,不会影响到上层。也就是说,各层各尽其职,各层提供固定的 SAP(Service Access Point),即高内聚、低耦合。 1.3.2 Android 核心库(Libraries) Android 包含一个 C/C++库的集合,以供 Android 系统的各个组件使用。这些功能通过 Android 的应用 程序框架(Application Framework)展现给开发者。下面列出一些核心库:  系统 C 库——由 BSD 继承衍生的标准 C 系统函数库(libc),调整为基于嵌入式 Linux 设备的库。  媒体库——基于 PacketVideo 的 OpenCORE。这些库支持播放和录制多种流行的音频和视频格式, 以及多种媒体格式的编码/解码格式,包括 MPEG4、 H.264、 MP3、AAC、AMR、JPG、PNG。  界面管理——显示子系统的管理器,管理访问显示子系统和无缝组合多个应用程序的二维和三维 图形层。  LibWebCore——新式的 Web 浏览器引擎,驱动 Android 浏览器和可嵌入的 Web 视图。  SGL——Skia 图形库,基本的 2D 图形引擎。  3D libraries——基于 OpenGL ES APIs 的实现。该库使用硬件 3D 加速或使用高度优化的 3D 软加 速。  FreeType ——位图(Bitmap)和矢量(Vector)字体渲染。  SQLite ——所有应用程序都可以使用的强大而轻量级的关系数据库引擎。 1.3.3 Android 运行时环境(Android Runtime) 在 Linux 内核层上还有一个 Android 运行时层,该层包括 Dalvik 虚拟机及 Java 核心库,提供了 Java 编程语言核心库的大多数功能。 ‐4‐ 专业始于专注 卓识源于远见 Dalvik 虚拟机是 Android 使用的 Java 虚拟机。每一个 Android 应用程序是 Dalvik 虚拟机中的实例,运 行在它们自己的进程中。Dalvik 虚拟机设计成在一个设备上可以高效地运行多个虚拟机。Dalvik 虚拟机可 执行的文件格式是.dex,.dex 格式是专为 Dalvik 设计的一种压缩格式,适合内存和处理器速度有限的系统。 大多数虚拟机(包括 JVM)都是基于栈的,而 Dalvik 虚拟机则是基于寄存器的。两种架构各有优劣, 一般而言,基于栈的机器需要更多的指令,而基于寄存器的机器指令更大。DX 是一套工具,可以将 Java .class 转换成 .dex 格式。一个.dex 文件通常会有多个.class。由于.dex 有时必须进行最佳化,会使文 件大小增加 1~4 倍,以 ODEX 结尾。 Dalvik 虚拟机依赖于 Linux 内核提供基本功能,如线程和底层内存管理。 1.3.4 Android 应用程序框架(Application Framework) 位于 Android 程序库和运行时上面的是应用程序框架层。通过提供开放的开发平台,Android 使开发 者能够访问核心应用程序所使用的 API 框架,这样使得组件的重用得以简化,任何应用程序都能发布它的 功能且任何其他应用程序可以使用这些功能(需要服从框架执行的安全限制)。从而使开发者可以编制极 其丰富和新颖的应用程序,自由地利用设备硬件优势、访问位置信息、运行后台服务、设置闹钟、向状态 栏添加通知等。 每个应用程序其实是一组服务和系统,包括:  视图(View)——丰富的、可扩展的视图集合,用来构建应用程序。包括列表(ListView)、网格 (Grid)、文本框(EditText/TextView)、按钮(Button)等,甚至是可嵌入的网页浏览器(WebView)。  内容提供器(Content Providers)——使应用程序可以访问其他应用程序(如通讯录)的数据,或 共享自己的数据。  资源管理器(Resource Manager)——提供对于非代码资源的访问,如本地化字符串、图形和布局 文件。  通知管理器(Notification Manager)——使应用程序能够在状态栏显示自定义的提示信息。  活动管理器(Activity Manager)——管理应用程序生命周期,并提供常用的导航回退功能。 1.3.5 Android 应用程序 Android 装配一个核心应用程序集合,连同系统一起发布,这些应用程序包括电子邮件客户端、SMS 程序、日历、地图、浏览器、联系人和其他设置等。而所有应用程序都是用 Java 语言编写的,由用户开发 的 Android 应用程序和 Android 核心应用程序是同一层次的。 1.4 Android 开发框架 Android 系统作为一个开放的系统,它体积庞大,对于不同的开发者来说,在开发过程中并不需要掌 握整个 Android 系统,只需要进行其中某一部分的开发。由此,从功能上将 Android 开发分为移植开发移 动电话系统、Android 应用程序开发,以及 Android 系统开发三种。 从商业模式的角度来讲,移植开发移动电话系统和 Android 应用程序开发是 Android 开发的主流。移 植开发移动电话系统主要是由移动电话的制造者来进行开发,其产品主要是 Android 手机;而公司、个人 和团体一般进行 Android 应用程序的开发,产生各种各样的 Android 应用程序。 对于 Android 移植开发,其主要工作集中于 Linux 内核中的相关设备驱动程序及 Android 本地框架中 的硬件抽象层接口的开发;对于 Android 应用程序开发,其开发的应用程序与 Android 系统的第四个层次 的应用程序是一个层次的内容;对于 Android 系统的开发,涉及 Android 系统的各个层次,一般情况下是 从底层到上层的整体开发。 Android 开发框架包括基本的应用功能开发、数据存储、网络访问三大块。 ‐5‐ 1.4.1 应用方面 专业始于专注 卓识源于远见 一般而言,一个标准的 Android 程序包括 Activity、Broadcast Intent Receiver、Service、Content Provider 四部分。 1.Activity Activity 是在 Android 应用开发中最频繁、最基本的模块。在 Android 中,Activity 类主要与界面资源 文件相关联(res/layout 目录下的 xml 资源,也可以不含任何界面资源),包含控件的显示设计、界面交互 设计、事件的响应设计以及数据处理设计、导航设计等 Application 设计的方方面面。 因此,对于一个 Activity 来说,它就是手机上的一个界面,相当于一个网页,所不同的是,每个 Activity 运行结束时都返回一个返回值,类似一个函数。Android 系统会自动记录从首页到其他页面的所有跳转记 录并且自动将以前的 Activity 压入系统堆栈,用户可以通过编程的方式删除历史堆栈中的 Activity Instance。 2.Broadcast Intent Receiver Intent 为不同的 Activity 进行跳转提供了机制,譬如从 A Activity 跳转到 B Activity,使用 Intent 来实现, 语句如下。 Intent in = new Intent(A.this, B.class); startActivity(in); Broadcast Intent Receiver 为各种不同的 Android 应用程序间进行进程间的通信提供了可能。如当电话 呼叫来临时,可以通过 Broadcast Intent Receiver 发布广播消息。对用户而言,用户是无法看到 Broadcast Intent Receiver 事件的,它对用户是不透明的,Broadcast Intent Receiver 通过 Notification Manager 来通知用户这 些事件发生了,它既可以在资源 AndroidManifest.xml 中注册,也可以在代码中通过 Context.registerReceiver() 进行注册在 AndroidManifest.xml 中注册以后,当事件来临时,即使程序没有启动,系统也会自动启动此应 用程序。另外,各应用程序可以很方便地通过 Context.sendBroadcast()将自己的事件广播给其他应用程 序。 3.Service Android 中的 Service 和 Windows 中的 Service 完全是一个概念,用户可以通过 StartService(Intent service) 启动一个 Service,也可通过 Context.bindService 来绑定一个 Service。 4.Content Provider Content Provider 提供了应用程序之间数据交换的机制,一个应用程序通过实现一个 Content Provider 的抽象接口将自己的数据暴露出去,并且隐蔽了具体的数据存储实现,这样实现了 Android 应用程序内部 数据的保密性。标准的 Content Provider 提供了基本的 CRUD(Create、Read、Update、Delete)接口,并 且实现了权限机制,保护了数据交互的安全性。 一个标准的 Android 应用程序的工程文件包含以下几大部分。  Java源代码部分(包含Activity)——放置在src目录中。  R.java文件——由Eclipse自动生成与维护,开发者不需要修改,提供了对Android资源的全局索引。  Android Library——应用程序运行的Android库。  assets目录——主要用于放置多媒体等文件。  res目录——放置的是资源文件: drawable包含的是图片文件,layout里面包含的是布局文件,values 里面主要包含的是字符串(strings.xml)、颜色(colors.xml)以及数组(arrays.xml)资源。  AndroidManifest.xml——应用的配置文件,在这个文件中,需要声明所有用到的Activity、Service、 Receiver等。 1.4.2 数据存储 Android 中提供的存储方式包括:SharedPreferences、文件存储、SQLite 数据库存储方式、内容提供器 方式 Content Provider 以及网络方式 5 种,具体介绍如下。 ‐6‐ 专业始于专注 卓识源于远见 1.SharedPreferences 作为 Android 提供的一种配置文件读/写方式,默认保存在应用的目录 data//shared_prefs 下,通过方法 getSharedPreferences(xx, 0);来获取 SharedPreferences 对象进行读/写操作。 2.文件存储 Android 系统提供了进行数据读/写访问的 API,例如,openFileInput、openFileOutput 等,需要特别注 意的是,Android 中应用程序的数据是私有的,也就是说,其他应用程序无法访问当前应用程序产生的文 件。 3.SQLite 数据库存储方式 通过继承 SQLiteOpenHelper 类提供的 CRUD 接口来进行数据库操作,方便了应用程序的数据存储操 作。 4.内容提供器方式(Content Provider) 通过调用其他应用程序的数据接口来实现数据的读/写访问。 5.网络方式 主要通过下面要提到的网络访问该网络提供的网络服务接口,实现数据的读/写服务(如 WebService 数据访问接口)。 1.4.3 网络访问方面 Android 主要通过 java.net.*及 Android.net.*来进行 HTTP 访问技术的封装;利用其提供的 HttpPost、 DefaultHttpClient、HttpResponse 等类提供的访问接口来实现具体的 Web 服务访问。 1.4.4 开发流程 软件开发流程(Software Development Process)即软件设计思路和方法的一般过程,包括设计软件的 功能及实现的算法和方法、软件的总体结构设计和模块设计、编程和调试、程序联调和测试以及编写、提 交程序。软件开发的生命周期如图 1-4 所示。 图 1-4 软件开发的生命周期 需求分析:根据客户的要求,清楚了解客户需求中的产品功能、特性、性能、界面和具体规格等,然 后进行分析,确定软件产品所能达到的目标。 设计:根据需求分析的结果,考虑如何在逻辑、程序上去实现所定义的产品功能、特性等,可以分为 概要设计和详细设计,也可以分为数据结构设计、软件体系结构设计、应用接口设计、模块设计、界面设 计等。 编程(实现):开始具体的编写程序工作,分别实现各模块的功能,从而实现对目标系统的功能、性 能、接口、界面等方面的要求,将设计转换成计算机可读的形式。 ‐7‐ 专业始于专注 卓识源于远见 测试:对设计、编程进行验证,对用户需求进行确认。 维护:维持软件运行、修改软件缺陷、增强已有功能、增加新功能、升级等。 对一个软件产品或者一项软件工程来说,参与角色通常包括以下几种。  高级经理:参与项目过程中各个关键环节的活动,关注产品开发的进度,对风险控制、资源提供 做出决策。  产品经理或项目经理:客户方和公司内部交流的纽带,主要对项目过程进行监控,对项目的进度、 质量负责,制定计划、协调资源、关注和控制计划进度、控制客户期望值等。  开发经理:负责界定需求,确定适当的技术构架和体系,保证软件产品按照设计的标准开发。  设计师:通常设计师可以分需求分析师、构架设计师、业务设计师 3 种,能够准确把握客户需求 并提供可行的实现思路,进行需求分析、进行构架设计和功能设计,按照规范编写相应的文档, 将设计思路传播给开发人员、测试人员。  测试经理及测试人员:测试经理主要负责计划和组织测试人员对目标产品进行测试,发现bug、跟 踪bug,直到解决bug;测试人员则根据测试经理的计划和测试总体方案对目标产品进行测试,编 写测试用例和测试代码,发现和跟踪bug;编写用户手册;进行用户培训和教育。  开发人员:根据设计师的设计成果进行具体编码工作,对自己的代码进行基本的单元测试。  项目实施人员:负责软件系统安装配置、系统割接、运行期间的维护工作。 1.5 OMS 简介 OMS 是 Open Mobile System 的简称,是中国移动和 Google 联合开发的一款开放式手机操作系统。该 系统基于 Linux 内核,为 Android 平台而构建,并在原有的 Android 平台基础上针对中国市场进行特殊优 化,内置了中国移动的服务菜单、音乐随身听、手机导航、号簿管家、139 邮箱、飞信、快讯和移动梦网 等特色业务,以满足中国市场的需求。 1.5.1 OPhone 介绍 联想的 OPhone 手机是首款使用 OMS 系统的手机。OPhone 是基于 Linux 的面向移动互联网的终端基 础软件及系统解决方案。为了突破 TD 终端“瓶颈”,以及促进手机终端与中国移动的网络及应用服务进行 无缝对接,中国移动在 Google Android 操作系统的基础上,基于 Linux 内核,推出了 “深度定制”的移动 操作系统 OPhone,其在业务层和用户体验层与此前的谷歌手机不完全一样。目前来讲,OPhone 与 Android 是兼容的。 下面介绍 OPhone 和 Android 的不同。  移动业务层面:在终端手机上完整深度定制了多种中国移动数据业务,例如,飞信、快讯、无线 音乐随身听、139 邮箱、移动梦网、号簿管家、百宝箱等。使中国移动的数据业务第一次和手机 的自身用户体验达到深度结合,例如,电话本中可以探测出好友飞信的在线状态,音乐播放器本 地和网络的用户体验完全一致,短信中如果收到邮件地址可以用 139 邮箱直接回复,移动梦网的 浏览器和普通网页的浏览器完全相同等。  手机基本功能:在手机基本通信功能上也结合了中国人的使用习惯并集成了很多品牌优秀的地方。 例如,手写输入和拼音T9 键盘的集成,拼音和手写的切换,拨号键盘可以用拼音直接调出联系人, 对话模式和文件夹模式可以随意选择短信息用户界面,彩信和短信结合的信息操作逻辑,还有其 可以随意定制的主屏幕,绚丽的动画及奇妙的解锁方式等都体现了OMS 对手机基本功能的重视。  用户体验层面:结合中国人自己的用户行为和喜好方式,并吸取了iPhone、Android、Windows Mobile、 Nokia、Black Berry 等多种移动终端的优势,设计出了完全区别于Android 的用户界面。其主要特 点是大屏幕全触摸的操作风格,面向移动互联网应用的设计理念。  OMS 的开放:除了易用、美观的界面,API的开发和兼容是OMS 另一大核心竞争力。开发者可 以在OMS 上开发多种平台API 的小工具,例如,OMS 可以兼容iPhone、Android、S60、Windows ‐8‐ 专业始于专注 卓识源于远见 Mobile 等小工具的使用,OMS 是一个百家争鸣,百花齐放的平台,为移动互联网的发展推波助 澜。 1.5.2 Widget 介绍 Widget 是指小工具,比如,Windows Vista 系统界面中的侧边栏、博客网页中的侧边栏等。而手机平 台也有对应的 Widget,它是安装在手机上的小容量客户端应用程序。例如,桌面时钟、实时天气等。以下 主要介绍手机平台上的 Widget。 Widget 不仅兼容多种网页技术(XHTML、CSS、JavaScript、AJAX),而且还可以通过三星 Widget API 获取各种手机功能。通常 Widget 主要用于应付简单任务,如提供网站搜寻界面。执行简单计算、显示新 闻标题、天气预报、提供股票行情等。此外,Widget 还可以充当提供多重功能的复合应用程序,Widget 的开发和手机安装是十分快捷便利的。 Widget 在 Widget 系统托盘条中都有自己的托盘图标,都可以通过 Widget 包格式来实现分发,都拥有 XHTML 文件、CSS 样式表、JavaScript 代码及图像文件。Widget 可以分为在线 Widget 与离线 Widget。在 线 Widget 通过手机数据连接(WiFi/3G/EDGE/GPRS)来获取内容/数据,而离线 Widget 只能使用用户生成 的数据或存储在手机中或 Widget 中固定编程的数据。 1.6 本章小结 本章从介绍 Android 基本概念开始,让读者了解了 Android 平台特性、Android 系统架构、Android 开 发框架,以及 OMS 等知识,使读者可以对 Android 的应用前景有一个很好的认识。 关键知识点测评 1.以下有关 Android 平台的说法,不正确的一个是( )。 A.Android 平台具有传统的语音通话功能 B.Android 具有短消息功能,以及通常移动电话都具有的个人信息系统管理方面的功能 C.Android 平台提供了 USB、GPS、红外、蓝牙、无线局域网等多种连接方式 D.Android 平台不能自定义手机的功能 2.以下有关 Android 的叙述中,正确的一个是( )。 A.Android 系统自上而下分为三层 B.Android 系统在核心库层增加了内核的驱动程序 C.Android 包含一个 C/C++库的集合,以供 Android 系统的各个组件使用。这些功能通过 Android 的应用程序框架(Application Framework)展现给开发者 D.Android 的应用程序框架包括 Dalvik 虚拟机及 Java 核心库,提供了 Java 编程语言核心库的大多 数功能 3.以下有关 Android 程序库层的叙述,不正确的一个是( )。 A.系统 C 库是专门为基于嵌入式 Linux 的设备定制的库 B.媒体库支持播放和录制多种流行的音频和视频格式以及多种媒体格式的编码/解码格式 C.SGL 是 Skia 图形库,基本的 3D 图形引擎 D.FreeType 包含位图(Bitmap)和矢量(Vector)字体渲染 4.以下有关 Android 开发框架的描述,正确的是( )。 A.一般而言,一个标准的 Android 程序包括 Activity、Broadcast Intent Receiver、Service、Content Provider 四部分 B.Android 中的 Service 跟 Windows 当中的 Service 不同 C.Broadcast Intent Receiver 提供了应用程序之间数据交换的机制 D.Content Provider 为不同的 Activity 进行跳转提供了机制 ‐9‐ 专业始于专注 卓识源于远见 5.下列有关 OMS 的描述,正确的一个是( )。 A.OPhone 与 Android 的用户界面相同 B.OMS 是在原有的 Android 平台基础上针对中国市场进行特殊优化,以满足中国市场的需求 C.OMS 不能兼容 IPhone、Android、S60、Windows Mobile 等小工具的使用 D.OPhone 在业务层和用户体验层与此前的谷歌手机相同 学习小助手  马上扫描下方华清远见官方微信二维码(公众号:farsight2013),即可免费获取价值 388 元的免费图书大礼包! 包含 7 本嵌入式、android.、Linux、ARM、FPGA、DSP 相关图书 PDF 完整版,您还可以通过华清远见微信第 一时间了解华清远见更多线上线下免费技术活动,获取更多学习上的帮助! ‐ 10 ‐ 《Android 应用程序开发与典型案例》 作者:华清远见 第 2 章 Android 开发环境搭建 本章简介 本章主要介绍在 Windows 环境下,Android 开发环境的搭建步骤及注意事项, 包括 JDK 和 Java 开发环境的安装和配置、Eclipse 的安装、Android SDK 和 ADT 的安 装和配置等;同时介绍了 Android 开发的基本步骤。 2.1 Android 开发环境的安装与配置 Android 应用软件开发需要的开发环境如表 2-1 所示。 专业始于专注 卓识源于远见 表 2-1 所需项 版本需求 说明 备注 操作系统 Windows XP/Vista/7 Mac OS X10.4.8+ Linux Ubuntu Drapper 选择自己最熟悉的操作 系统 软件开发 包 Android SDK 选择最新版本的 SDK 截止到目前,其最新版本 为 2.3 Eclipse 3.3(Europa),3.4(Ganymede) IDE Eclipse IDE+ADT ADT(Android Development Tools)开发 选择“for Java Developer” 插件 其他 JDK Apache Ant Java SE Development Kit 5 或 6 单独的 JRE 可不易,必须 Linux 和 Mac 上使用 Apache Ant 1.6.5+, 要有 JDK;不兼容 Gnu Windows 上使用 1.7+版本 Java 编译器 以上所提到的软件开发包的下载地址如下:  JDK1.6,http://www.oracle.com/technetwork/java/javase/downloads/index.html。  Eclipse 3.4(Eurpa),http://www.eclipse.org/downloads/下载 Eclipse IDE for Java Developers。  Android SDK2.2,http://developer.android.com。 以下主要介绍一下在 Windows 环境下搭建 Android 开发环境的步骤和注意事项。 2.1.1 安装 JDK 和配置 Java 开发环境 首 先 下 载 JDK 安 装 包 , 并 进 行 安 装 。 例 如 , 得 到 JDK1.6 版 本 的 安 装 文 件 jdk-6u10-rc2-bin-b32-windows-i586-p-12_sep_2008.exe,双击进行安装。接受许可证,选择需要安装的组件 和安装路径后,单击“下一步”按钮,完成安装过程。 安装完成后,利用以下步骤检查安装是否成功:打开 CMD 窗口,在 CMD 窗口中输入 java –version 命令,如果屏幕出现如图 2-1 所示的代码信息,说明 JDK 安装成功。 2.1.2 Eclipse 的安装 图 2-1 JDK 安装检查 JDK 安 装 成 功 后 , 可 以 直 接 安 装 Eclipse , 例 如 , 使 用 Eclipse 3.6 , 得 到 其 压 缩 包 eclipse-SDK-3.6.1-win32.zip,该包不需要安装,直接解压即可执行其中的 eclipse.exe 文件进行安装,Eclipse 可以自动找到用户前期安装的 JDK 路径。 2.1.3 SDK 和 ADT 的安装和配置 JDK 和 Eclipse 安装成功后,下载安装 Android 的 SDK,得到 android-sdk-windows.zip,解压后运行 SDK Manager.exe,选择需要的 API 版本进行安装,如图 2-2 所示。 ‐2‐ 专业始于专注 卓识源于远见 图 2-2 SDK Manager 完成以上步骤后,在 Eclipse 中安装配置 ADT。 ADT(Android Development Tools)是 Android 为 Eclipse 定制的一个插件,为用户提供了一个强大的 用户开发 Android 应用程序的综合环境。ADT 扩展了 Eclipse 的功能,可以让用户快速地建立 Android 项 目,创建应用程序界面,在基于 Android 框架 API 的基础上添加组件,以及用 SDK 工具集调试应用程序, 甚至导出签名(或未签名)的 APKs 以便发行应用程序。 在 Eclipse 中安装 ADT,首先在启动 Eclipse,选择“Help”→“Software Updates”命令,准备安装插 件。在弹出的对话框中选择“Available Software”,得到如图 2-3 所示界面。 图 2-3 “Available Software”界面 单击“Add”按钮,弹出如图 2-4 所示设置界面。 图 2-4 “Add Site”对话框 ‐3‐ 专业始于专注 卓识源于远见 在 Location 文本框中输入 Android 插件的路径:https://dll-ssl.google.com/android/eclipse/,单击“OK”按钮, 返回至图 2-3 所示界面,可以看到刚刚添加的站点已添加至搜索列表,选择“Developer Tools”,然后单击 “Install”按钮。在 Install 界面中,选择“Android DDMS”和“Android Development Tools”,单击“Next” 按钮,阅读并接受许可协议,单击“Finish”按钮完成安装。安装完成后重启 Eclipse 即可。 在 Eclipse 中增加 SDK 的路径:“Window”→“Preference”,在左侧的列表中选择 Android 项,设置 SDK Location 为 SDK 的目录,如图 2-5 所示。 图 2-5 选择 Android SDK 路径 问:安装 ADT 过程中没有网络怎么办? 答:在“Add Site”对话框中单击“Archive”按钮,直接指定磁盘中的 ADT 包(例如:ADT-10.0.0.zip 等)。 2.2 创建第一个 Android 应用 Android 的 SDK 环境安装完成后,就可以在 SDK 中建立工程并进行调试了。 建立 Android 工程的步骤如下: (1)选择“File”→“New”→“Project”命令。 (2)选择“Android”→“Android Project”命令,单击“Next”按钮,如图 2-6 所示。 图 2-6 使用 Eclipse 建立 Android 工程 (3)在“New Android Project”对话框中,输入项目名称(AndroidTest)、Package name、Activity name, 以及 Application name,最后单击“Finish”按钮,即可完成新建项目成功。 ‐4‐ 专业始于专注 卓识源于远见 图 2-7 “New Android Project”对话框 (4)工程建立后,可以通过 Eclipse 环境查看 Android 应用程序中各个文件,例如 AndroidManifest.xml 文件、布局文件、代码等。如图 2-8 所示为布局文件的编辑界面,可以直观地查看程序的 UI 布局。 图 2-8 Android 的 UI 布局文件的编辑界面 2.3 在模拟器上运行程序 在运行 Android 应用程序之前,需要建立 Android 虚拟设备(Android Virtual Device,AVD),即通常 所说的手机模拟器。在 Eclipse 环境中,选择“Window”→“Android SDK and AVD Manager”命令,出现 “Android SDK and AVD Manager”对话框,如图 2-9 所示。 ‐5‐ 专业始于专注 卓识源于远见 图 2-9 “Android SDK and AVD Manager”对话框 单击“New”按钮,新建 Android 虚拟设备,输入 Android 虚拟设备的名称(Name)、目标的 Target (SDK)、SD Card 的路径,以及虚拟设备的 Skin(窗口的尺寸,默认情况下为 WVGA800)。单击“Create AVD”按钮来创建虚拟设备,如图 2-10 所示。 图 2-10 在 Eclipse 中新建 Android 虚拟设备 创建虚拟设备完成后,在建立好的项目上单击鼠标右键,在弹出的快捷菜单中选择“Run As” → “Android Application”命令,如图 2-11 所示。 图 2-11 选择“Run As” →“Android Application”命令 Eclipse 将打开刚才建立的默认的 Android 模拟器,运行画面如同真的手机开机一般,开机后,随即打 开运行的程序,运行画面如图 2-12 所示。 ‐6‐ 专业始于专注 卓识源于远见 图 2-12 没写一行程序的 Hello World 2.4 在手机上运行程序 开发期间,在实际的设备上运行 Android 程序与在模拟器上运行该程序的效果几乎相同,需要做的就 是用 USB 电缆连接手机与计算机,并安装一个对应手机的设备驱动程序。如果模拟器窗口已打开,请将 其关闭。只要将手机与计算机相连,应用程序就会在手机上加载并运行。 在 Eclipse 中选择“Run” →“Run”(或 Debug)命令,这时会弹出一个窗口(见图 2-13),让你选择 用模拟器还是手机来显示,如果选择手机,即可在手机上运行该程序。 图 2-13 选择在手机还是模拟器上运行 2.5 本章小结 本章首先介绍了 Android 开发环境的搭建,让读者了解到 Android 开发环境搭建的步骤及注意事项, 同时介绍了 Android 应用程序开发的过程和步骤,使读者可以对 Android 的应用程序开发有一个很好的认 识。 关键知识点测评 1.以下有关 Android 开发环境所需条件的说法,不正确的一个是( )。 A.可在 Windows/Linux 操作系统上进行开发 B.使用 Eclipse IDE 进行开发 C.需在 Eclipse IDE 中安装配置 ADT D.可以仅仅安装 JRE ‐7‐ 专业始于专注 卓识源于远见 2.以下有关 ADT 的叙述,不正确的一个是( )。 A.ADT 是 Android Development Tools 的缩写 B.ADT 是 Android 为 Eclipse 定制的一个插件,为用户提供了一个强大的用户开发 Android 应用程 序的综合环境 C.ADT 安装过程中必须通过网络下载 Android 插件 D.ADT 扩展了 Eclipse 的功能,可以让用户快速地建立 Android 项目,创建应用程序界面,在基于 Android 框架 API 的基础上添加组件,以及用 SDK 工具集调试应用程序,甚至导出签名(或未 签名)的 APKs 以便发行应用程序 3.以下有关 Android 应用的描述,正确的是( )。 A.开发期间,在实际的设备上运行 Android 程序与在模拟器上运行该程序的效果几乎相同 B.可以直接用 USB 电缆连接手机与计算机,在手机上加载应用程序 C.应用程序可以利用模拟器进行视频捕捉 D.创建 Android 应用时可以不填写 Package name 学习小助手  马上扫描下方华清远见官方微信二维码(公众号:farsight2013),即可免费获取价值 388 元的免费图书大礼包! 包含 7 本嵌入式、android.、Linux、ARM、FPGA、DSP 相关图书 PDF 完整版,您还可以通过华清远见微信第 一时间了解华清远见更多线上线下免费技术活动,获取更多学习上的帮助! ‐8‐ 《Android 应用程序开发与典型案例》 作者:华清远见 第 3 章 程序设计基础 本章简介 在上一章的学习中,主要了解了 Eclipse+ADT 的开发流程,对其有了初步的认 识和了解。对初学者来说,这一章的内容比较烦琐,但是又必须掌握,这也是进行 Android 开发必须经过的第一步,有了这个基础,我们将进行 Android 应用程序设 计。 3.1 Android 程序框架 上一章我们建立了 AndroidTest 项目,在项目中,所有的代码是由 ADT 插件自动生成的,我们并没有 对其进行编码,所以没有对其框架进行分析。其实每一个平台都有自己的结构框架,比如,在最初学习 Java 专业始于专注 卓识源于远见 或者 C/C++时,第一个程序总是 main 方法,以及文件类型和存储方式等。本节将对 Android 平台的目录结 构、文件类型及其负责的功能和 Android 平台的 main 方法进行剖析。 3.1.1 Android 项目目录结构 有了前两章的基础,再来打开上一章建立的 AndroidTest 项目,分析其项目目录结构,对 Android 项目 进一步深入了解。首先启动 Eclipse,展开“Package Explorer”导航器中的“AndroidTest”项目,如图 3-1 所示。 图 3-1 JDK 安装检查 Android 项目结构 与一般的 Java 项目一样,src 文件夹是项目的所有包及源文件(.java),res 文件夹中则包含了项目中 的所有资源,比如,程序图标(drawable)、布局文件(layout)、常量(values)等。下面来介绍其他 Java 项目中没有的 gen 文件夹中的 R.java 文件和每个 Android 项目都必须有的 AndroidManfest.xml 文件。 R.java 是在建立项目时自动生成的,这个文件是只读模式,不能更改,R.java 文件是定义该项目所有 资源的索引文件。先来看看 AndroidTest 项目的 R.java 文件,如代码清单 3-1 所示。 代码清单 3-1 R.java package com.examples.android.helloactivity; public final class R{ public static final class attr{ } public static final class drawable{ public static final int icon=0x7f020000; } public static final class layout{ public static final int main=0x7f030000; } public static final class string{ public static final int app_name=0x7f040001; public static final int hello=0x7f040000; } } 可以看到这里定义了很多常量,仔细一看就发现这些常量的名字都与 res 文件夹中的文件名相同,再 次证明 R.java 文件中所存储的是该项目所有资源的索引。有了这个文件,在程序中使用资源将变得更加方 便,可以很快地找到要使用的资源,由于这个文件不能被手动编辑,所以当在项目中加入了新的资源时, 只需要刷新一下该项目,R.java 文件便自动生成了所有资源的索引。 AndroidManfest.xml 文件则包含了该项目中所使用的 Activity、Service、Receiver,先来打开 AndroidTest 项目中的 AndroidManfest.xml 文件,如代码清单 3-2 所示。 ‐2‐ 专业始于专注 卓识源于远见 代码清单 3-2 AndroidManfest.xml 代码清单 3-2 中,intent-filters 描述了 Activity 启动的位置和时间。每当一个 Activity(或操作系统)要 执行一个操作时,它将创建出一个 Intent 的对象,这个 Intent 对象能承载的信息可描述你想做什么,你想 处理什么数据,数据的类型,以及一些其他信息。而 Android 则会和每个 Application 所暴露的 intent-filter 的数据进行比较,找到最合适 Activity 来处理调用者所指定的数据和操作。下面我们来仔细分 析 AndroidManfest.xml 文件,如表 3-1 所示。 表 3-1 AndroidManfest.xml 分析 manifest 根节点,描述了 package 中所有的内容 xmlns:android 包含命名空间的声明。xmlns:android=http://schemas.android.com/apk/res/android, 使得 Android 中各种标准属性能在文件中使用,提供了大部分元素中的数据 package 声明应用程序包 application 包含 Package 中 Application 级别组件声明的根节点。此元素也可包含 Application 的一些全局和默认的属性,如标签、icon、主题、必要的权限,等等。一个 manifest 能包含零个或一个此元素(不能大于一个) android:icon 应用程序图标 android:label 应用程序名字 activity 用来与用户交互的主要工具。Activity 是用户打开一个应用程序的初始页面, 大部分被使用到的其他页面也由不同的 Activity 所实现,并声明在另外的 Activity 标记中。注意,每一个 Activity 必须有一个标记对应,无论 它给外部使用或是只用于自己的 Package 中。如果一个 Activity 没有对应的标 记,你将不能运行它。另外,为了支持运行时查找 Activity,可包含一个或多 个元素来描述 Activity 所支持的操作 android:name 应用程序默认启动的 Activity intent-filter 声明了指定的一组组件支持的 Intent 值,从而形成了 IntentFilter。除了能在此 元素下指定不同类型的值,属性也能放在这里来描述一个操作所需的唯一的标 签、icon 和其他信息 action 组件支持的 Intent action category 组件支持的 Intent Category。这里指定了应用程序默认启动的 Activity uses-sdk 该应用程序所使用的 SDK 版本相关 下面我们看看资源文件中一些常量的定义,如 String.xml,如代码清单 3-3 所示。 代码清单 3-3 String.xml Hello World, HelloActivity! ‐3‐ 专业始于专注 卓识源于远见 HelloWorld 这个文件很简单,就定义了两个字符串资源,与 R.java 中对应的索引如代码清单 3-4 所示。 代码清单 3-4 R.java 中的 String 类 public static final class string{ public static final int app_name=0x7f040001; public static final int hello=0x7f040000; } 在程序中装载并使用这个字符串资源如代码清单 3-5 所示。 代码清单 3-5 String 资源的使用 Resources r = this.getContext().getResources(); String appname = ((String) r.getString(R.string.appname)); String hello = ((String) r.getString(R.string.hello)); 基本上可以定义出项目中所有使用的常量,例如颜色。所以,可根据需要对资源常量进行定义。下面 是定义了颜色的常量 colors.xml,如代码清单 3-6 所示。 代码清单 3-6 colors.xml #cccccc #637a47 #cc9900 #ac4444 现在我们来分析 AndroidTest 项目的布局文件(layout),首先打开 res→layout→main.xml 文件,如代 码清单 3-7 所示。 代码清单 3-7 main.xml 在代码清单 3-7 中,有以下几个布局和参数。  < LinearLayout>:线性版面配置,在这个标签中,所有元件都是由上到下排成的。  android:orientation:表示这个介质的版面配置方式是从上到下垂直地排列其内部的视图。这里 “vertical”表示是水平排列。  android:layout_width:定义当前视图在屏幕上所占的宽度,fill_parent/ match_parent 即填充整个屏 幕。  android:layout_height:定义当前视图在屏幕上所占的高度,fill_parent/ match_parent 即填充整个屏 幕。  wrap_content:随着文字大小的不同而改变这个视图的宽度或高度。  layout_weight :用于给一个线性布局中的多个视图的重要度赋值。所有视图都有 layout_weight 值, 默认为零,即需要显示多大的视图就占据多大的屏幕空间。如果值大于零,则将父视图中的可用 ‐4‐ 专业始于专注 卓识源于远见 空间分割,分割大小具体取决于每一个视图的 layout_weight 值和该值在当前屏幕布局的整体 layout_weight 值,以及在其他视图屏幕布局的 layout_weight 值中所占的比例。 在这里,布局中设置了一个 TextView,用来配置文本标签 Widget,其中设置的属性 android:layout_width 为整个屏幕的宽度,android:layout_height 可以根据文字来改变高度,而 android:text 则设置了这个 TextView 要显示的文字内容,这里引用了@string 中的 hello 字符串,即 String.xml 文件中的 hello 所代表的字符串资 源。hello 字符串的内容"Hello World, HelloActivity!"这就是在 AndroidTest 项目运行时看到的字符串。 最后,分析 AndroidTest 项目的主程序文件 HelloActivity.java,如代码清单 3-8 所示。 代码清单 3-8 HelloActivity.java package com.examples.android.helloactivity; import android.app.Activity; import android.os.Bundle; public class HelloActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 主程序 HelloActivity 类继承自 Activity 类,重写了 void onCreate(Bundle savedInstance State)方法。在 onCreate 方法中通过 setContentView(R.layout.main)设置了 Activity 要显示的布局文件 (/layout/main.xml)。 到这里,是不是明白了为什么在创建项目时没有进行编码就可以直接运行程序呢?当然,这也是 Android 开发的特点,这样可以很轻松地将代码和 UI 分开,在国际化和程序维护方面有着巨大的作用。如 果 Android 程序需要自适应国际化,比如说多国语言等问题,那么就可以定义不同语言的 UI 布局,在程序 装载时调用不同的布局。而且,如果需要修改 UI 的一些问题,就不必查看代码了,直接更改这些布局文 件即可,是不是很方便?当然,这需要开发者在开发时使用这种 MVC 框架,尽量减少使用“硬编码”。 3.1.2 Android 应用解析 上面了解了 Android 应用程序的目录结构和其中每个文件的功能,要对其进行应用开发,还需要对 Android 应用构造进行深入的分析。下面介绍一下 Android 应用开发中所应用到的重要模块。  Activity  Intent  Content Provider  Service 当然,并非所有的 Android 应用程序都必须由这 4 部分组成,它可以根据开发者需求来进行组合,比 如,上面建立的 HelloActivity 项目就只使用了 Activity 这一个模块。但是,对于任何一个应用程序来说, 都必须在 AndroidManfest.xml 文件中声明使用到的模块。 1.Activity Android 中最基本的模块就是 Activity,在之前的 HelloActivity 项目中已经使用过。我们称之为“活动”, 在应用程序中,一个活动(Activity)通常就是一个单独的屏幕。每一个活动都被实现为一个独立的类,并 且继承于从活动基类,活动类将会显示由视图控件组成的用户接口,并对事件作出响应。例如,HelloActivity 项目中的 HelloActivity.java 即继承了活动(Activity)类。大多数的应用都是由多个 Activity 显示组成,例 如,对一个文本信息应用而言,第一个屏幕用来显示发送消息的联系人列表,第二个屏幕用来写文本消息 和选择收件人,第三个屏幕查看消息历史或者消息设置操作等。 ‐5‐ 专业始于专注 卓识源于远见 这里的每一个屏幕就是一个活动,很容易实现从一个屏幕到一个新的屏幕,并且完成新的活动。当一 个新的屏幕打开后,前一个屏幕将会暂停,并保存在历史栈中。用户可以返回到历史栈中的前一个屏幕, 当屏幕不再使用时,还可以从历史栈中删除。 简单理解,Activity 就代表着用户所能看到的屏幕,它主要处理了应用程序的整体性工作,例如,为 用户显示指定的 View,启动其他 Activity,监听系统事件(按键事件、触摸屏事件等)等。 所有应用的 Activity 都继承于 Android 提供的基类 android.app.Activity 类,其他的 Activity 继承该父类 后,通过父类的方法来实现各种功能,这种设计在其他领域也较为常见。 2.Intent 在 Android 中,利用 Intent 类实现了在 Activity1 与 Activity2 之间的切换(以及启动 Service 等)。Intent 类主要用于描述应用的功能。在 Intent 的描述结构中,有动作(Action)和动作对应的数据(data)两个最 重要的部分。其中,典型的动作类型有:MAIN、VIEW、PICK、EDIT 等,而动作对应的数据以 URI 的形 式表示。例如,如果要查看一个人的联系方式,需要先创建一个动作类型为 VIEW 的 Intent,以及一个表 示这个动作对应的数据(这里就是这个人)的 URI。 从一个屏幕导航到另一个屏幕,我们需要解析各种 Intent。为了实现向前导航,首先,我们调用 Activity 的 startActivity(Intent myIntent)方法。此时,系统为找到最匹配 myIntent 的 Intent 对应的 Activity,就将 在所有已安装的应用程序中定义的 IntentFilter 中查找。而匹配的对应的新的 Activity 在接收到 myIntent 的 通知后,开始运行。当 startActivity 方法被调用时,将触发解析 myIntent 的动作,该机制提供了两个关键 好处。 (1)Activities 能够重复利用从其他组件中以 Intent 的形式产生的请求。 (2)Activities 可以在任何时候被具有相同 Action 的新的 Activity 取代。 下面举例说明两个 Activity 之间的切换。运行效果:当应用程序启动时,显示布局 main.xml,如图 3-2 所示,当我们单击“切换”按钮时,屏幕显示布局 main2.xml,如图 3-3 所示,再单击“切换”按钮,又回 到如图 3-2 所示的状态。就这样通过 Intent 完成了两个 Activity 之间的切换。 图 3-2 Activity1 图 3-3 Activity2 以下是两个 Activity 的代码。 代码清单 3-9 Activity1.java package com.example.android.Examples_03_01; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; / ** * 在 Examples_02_01 项目中一共使用了两个 Activity, * 每使用一个 Activity,都必须在 AndroidManifest.xml 中 * 进行声明 */ public class Activity1 extends Activity { public void onCreate(Bundle savedInstanceState) { ‐6‐ 专业始于专注 卓识源于远见 super.onCreate(savedInstanceState); /* 设置显示 main.xml 布局 */ setContentView(R.layout.main); /* findViewById(R.id.button1)取得布局 main.xml 中的 button1 */ Button button = (Button) findViewById(R.id.button1); /* 监听 button 的事件信息 */ button.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* 新建一个 Intent 对象 */ Intent intent = new Intent(); /* 指定 intent 要启动的类 */ intent.setClass(Activity1.this, Activity2.class); /* 启动一个新的 Activity */ startActivity(intent); /* 关闭当前的 Activity */ Activity1.this.finish(); } }); } } 代码清单 3-10 Activity02.java package com.example.android.Examples_03_01; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class Activity2 extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 设置显示 main2.xml 布局 */ setContentView(R.layout.main2); /* findViewById(R.id.button2)取得布局 main.xml 中的 button2 */ Button button = (Button) findViewById(R.id.button2); /* 监听 button 的事件信息 */ button.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* 新建一个 Intent 对象 */ Intent intent = new Intent(); /* 指定 intent 要启动的类 */ intent.setClass(Activity2.this, Activity1.class); /* 启动一个新的 Activity */ startActivity(intent); /* 关闭当前的 Activity */ Activity2.this.finish(); } }); } } 代码清单 3-11 main.xml ‐7‐ 专业始于专注 卓识源于远见 代码清单 3-12 Main2.xml 如代码清单 3-9 所示,需要在 AndroidManifest.xml 中声明使用的 Activity2,如代码清单 3-13 所示。 代码清单 3-13 AndroidManifest.xml 问:Android 应用如何对外部事件(如当电话呼入时,或者数据网络可用时,或者到了晚上时)做出响应? 答:使用 IntentReceiver。  在相应的事件发生时,IntentReceiver 使用 NotificationManager 通知用户,但它并不能生成 UI。  它在 AndroidManifest.xml 中注册,也可以在代码中使用 Context.registerReceiver()进行注册。  当 IntentReceiver 被触发时,应用不必对请求调用 IntentReceiver,系统会在需要时启动应用。  各种应用还可以通过使用 Context.broadcastIntent()将它们自己的 intentreceiver 广播给其他应用。 ‐8‐ 专业始于专注 卓识源于远见 3.Content Provider Android 应用能够将它们的数据保存到文件和 SQLite 数据库中,甚至是任何有效的设备中。利用 Content Provider 实现应用数据与其他的应用共享。因为 Content Provider 类实现了一组标准的方法,能够 让其他的应用保存或读取此内容提供器处理的各种数据类型。 Content Provider 可以理解为在不同的应用包之间共享数据的工具。在 Android 中,默认的系统数据库 为 SQLite。但是在 Android 中,使用方法略有区别。在 Android 中,每一个应用都单独运行在各自的进程 中,当一个应用需要访问其他应用的数据时,这里就需要在不同的虚拟机之间传递数据,这样的情况操作 起来可能有些困难(正常情况下,你不能读取其他应用的 db 文件)。Content Provider 就解决了上述困难。 在 Android 中,Content Povider 是一个特殊的存储数据的类型,它提供了一套标准的接口用来获取和 操作数据。并且,Android 自身也提供了现成的 Content Provider:Contacts、Browser、CallLog、Settings、 MediaStore。当通过 Content Resolver 提供的方法来使用 Content Provider 时,应用可以通过唯一的 Content Resolver interface 来使用具体的某个 Content Provider。其中,Content Resolver 提供的方法包括 query()、 insert()、update()等。要使用这些方法,还会涉及 URI。可以将它理解成 string 形式的 Content Provider 的完 全路径。 4.Service Service 即“服务”的意思,既然是服务,那么 Service 将是一个生命周期长而且没有用户界面的程序。 比如,一个正在从播放列表中播放歌曲的媒体播放器,在这个媒体播放器应用中,应该会有多个 Activity, 让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的 Activity,因为使用者会认为 在 导 航 到 其 他 屏 幕 时 音 乐 应 该 还 在 播 放 。 在 这 个 例 子 中 , 媒 体 播 放 器 这 个 Activity 会 使 用 Context.Startservice()来启动一个 Service,从而可以在后台保持音乐的播放。同时,系统也将保持这个 Service 一直执行,直到这个 Service 运行结束。另外,还可以通过使用 Context.Bindservice()方法连接到一个 Service 上(如果这个 Service 当前还没有处于启动状态,则将启动它)。当连接到一个 Service 之后,还可用 Service 提供的接口与它进行通信。以媒体播放器为例,还可以执行暂停、重播等操作。 3.2 Android 程序 UI 设计 在前面章节的例子中,我们已经接触了 TextView、Button 等 UI 控件,其实这里所说的 UI 就是布局文 件(layout),UI 是一个应用程序展现给用户的界面,一个应用程序要想受用户喜爱,那么 UI 可不能差。 自从 Android SDK 1.0_r2 版本开始,ADT 提供了 UI 预览的功能。现在只需要打开一个 Android 项目的 “/res/layout/main.xml”并右键单击鼠标右键,在弹出的快捷菜单中选择“Open With”→ “Android Layout Editor”命令,或者直接双击 main.xml 文件,即可以切换到 UI 设计界面,如图 3-4 所示。 图 3-4 Android Layout Editor 命令 左边的 layouts 标签的内容则是一些线性布局,可以使用它轻松地完成对布局的排版,比如,横向或者 纵向布局。Views 标签则是一些 UI 控件,可以将这些控件直接拖动到右边的窗口进行编辑,这些 UI 控件 的类型如图 3-5 所示。 ‐9‐ 专业始于专注 卓识源于远见 图 3-5 Android layout Editor 当然,还可以单击右下角的 main.xml 标签来切换到 XML 编辑器,对代码进行编排,如图 3-6 所示。 将这些功能配合起来使用,基本可以满足开发者需求。 图 3-6 XML 编辑器 3.3 Java 语言在 Android 程序中的使用 Android 应用程序采用 Java 语言编写,Java 语法和 C/C++有很大的相似性,但也有一些特别之处。 3.3.1 Interface 的使用 从名字上看,Interface 即为接口的意思,多用于实现回调(Call Back)方法。 在 Interface 的定义中,一般的代码架构如代码清单 3-14 所示。 代码清单 3-14 InterfaceServer.java public class InterfaceServer { public interface OnClickListener{ public void onClick(); } private OnClickListener mOnClickListener=null; public void onClick(){ if(mOnClickListener!=null) mOnClickListener.onClick(); } public void setOnClickListener(OnClickListener l){ mOnClickListener = l; } ‐ 10 ‐ 专业始于专注 卓识源于远见 对于 Interface 内部的方法而言,只需要声明,而不需要具体实现。从编译器的角度来看,Interface 会 被认为是一个指向方法的指针。 使用 InterfaceServer 的代码一般如代码清单 3-15 所示。 代码清单 3-15 使用 InterfaceServer public void addToButton { Button b = (Button)findViewById(R.id.button); onClickListener l = new OnClickListener(){ public void onClick(View v){ TextView tv1 = (TextView) findViewById(R.id.tv1); tv1.setText("The Button has been clicked"); } }; b.setOnClickListener(l); } 3.3.2 abstract class 的使用 abstract 是一个修饰符,其类似于 Static 这样的关键字。Android 程序中常用 abstract 修饰一个类,如 abstract class,当然能它也可以修饰一些变量或是方法。 抽象类所包含的方法可以只是定义,也可以是已实现的。对于没有实现的方法,基于该方法的子类必 须实现;而对于已经实现的方法,子类可以重写该方法,若没有重写,则使用父类的方法。 在一定程度上,abstract class 可以代替 Interface,例如,3.3.1 节中 Interface 的例子做如下的 abstract class 替换,其效果是等价的。 代码清单 3-16 InterfaceServer.java public class InterfaceServer { abstract class OnClickListener2{ public void onClick2(); } private OnClickListener2 mOnClickListener2=null; public void onClick2(){ if(mOnClickListener2!=null) mOnClickListener2.onClick2(); } public void setOnClickListener2(OnClickListener2 l){ mOnClickListener2 = l; } 3.3.3 Interface 与 abstract class 的区别 从语法角度讲,接口和抽象类有以下区别。  Java 语法规定,一个子类只能有一个父类,但可以实现多个接口。  abstract class 可以代替 Interface。  定义 Interface 时,只需要列出所包含方法的定义而不必实现。而定义 Abstract 类时,方法必须有实 现部分,这就是所谓的默认实现,除非该方法也是 Abstract 类型。  接口的子类必须实现接口所定义的全部方法,而抽象类的子类不必实现抽象类所定义的任何方法, 除非该方法是 Abstract 或者子类想重写某个方法。  接口中的成员变量必须是 Static Final 类型(实际应用中则很少包含变量,因为接口多用于引用), 而 abstract class 内部可以包含任意变量。 从应用的角度来讲,Interface 和 abstract class 的区别在于:Interface 提供了一个方法集合的接口,该 接口用于客户端和服务端的方法调用,如图 3-7 所示。 ‐ 11 ‐ 专业始于专注 卓识源于远见 … … 图 3-7 Interface 的使用机制 接口一般是由服务端定义,比如操作系统,客户端根据自己的需求对接口做不同的实现;而 abstract class 则仅提供了一个基类,该基类没有任何服务端或者客户端的概念,它的作用就是为了继承并重写,如 图 3-8 所示。 3.3.4 for 循环的使用 图 3-8 abstract class 的使用机制 除了传统的 for 循环(语法是 for(inti=0;i mMap= new HashMap()。 <>里面的数据类型用于指定 Map 集合中“键值对”的类型。 给 Map 集合添加和删除键值对的方法如表 3-2 所示。 表 3-2 Map 集合添加和删除键值对的方法 方法 描述 clear() 删除该 Map 集合中的全部元素 remove(Objectkey) 删除键名为 key 所对应的键值对 put(Objectkey,Objectvalue) 添加一个新的键值对 putAll(Mapmap) 将该 Map 集合的元素全部复制到新的 Map 中 Map 类没有提供直接遍历键值对的方法,要遍历所有键值对需要一个中间过程。Map 提供了 3 个方法 用于间接遍历键值对,如下:  entrySet() 返回所有键值对类型为 Set 对象。  keySet() 返回所有键值对类型为 Set 对象。  valueSet() 返回所有键值对类型为 Collection 对象。 要得到具体的键值对,需要再解析 Set 和 Collection 对象,但仅有这两个对象还不能获得键值对,还 需要借助于 Iterator 类。到这里,可能觉得有些复杂,别着急,结果马上就要出来了。 Set、Collection、Iterator 实际上是 Map 内部进行操作的 3 个辅助类,要得到具体 Map 键值对,如代码 清单 3-19 所示。 代码清单 3-19 得到具体的 Map 键值对 Map mMap = new HashMap(); Iterator kv = mMap.entrySet().iterator(); Iterator k = mMap.keySet().iterator(); Iterator v = mMap.values().iterator(); Int size = mMap.size(); for(int i = 0;i中的 launchMode 属性进行 设置,如代码清单 4-2 所示。 代码清单 4-2 AndroidManifest.xml 也可以在 Eclipse ADT 图形界面中编辑,如图 4-5 所示。 专业始于专注 卓识源于远见 图 4-5 设置 Activity 启动模式 下面通过一个简单的例子——LaunchMode_Test 来对四种启动模式进行简要分析。在该例中涉及 Fx_Main、Activity2 及 Activity3 三个 Activity。 下面介绍一下例子中涉及的三个 Activity 及其界面。 首先是 Fx_Main,其界面如图 4-6 所示。 图 4-6 Fx_Main 的界面 在图 4-6 所示的界面中,单击“跳转到 AC2”按钮之后,跳转至 Activity2,具体代码如代码清单 4-3 所示。 代码清单 4-3 Fx_Main.Activity import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Fx_Main extends Activity { /** Called when the activity is first created. */ private Button b; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView tv=(TextView)findViewById(R.id.TextView01); tv.setText("Main---->"+getTaskId()); Log.i("System.out", "Main---->"+this.toString()+"Task ID---->"+getTaskId()); b=(Button)findViewById(R.id.Button01); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Fx_Main.this,Activity2.class); startActivity(i); ‐8‐ }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Fx_Main--->Destory"); } } 其次是 Activity2,其界面如图 4-7 所示。 专业始于专注 卓识源于远见 图 4-7 Activity2 的界面 在该界面中,单击“跳回到 Main”按钮,则跳转至 Fx_Main,而单击“跳到本页面”则仍显示 Activity2 的界面,单击“跳到 AC3”则跳转到 Activity3 的界面,具体代码如代码清单 4-4 所示。 代码清单 4-4 Activity2.Activity import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Activity2 extends Activity { private Button b; private Button b2; private Button b3; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity2); b=(Button)findViewById(R.id.Button02); b2=(Button)findViewById(R.id.Button03); b3=(Button)findViewById(R.id.Button04); TextView tv=(TextView)findViewById(R.id.TextView02); tv.setText("Ac2---->"+getTaskId()); Log.i("System.out", "Ac2---->"+this.toString()+"Task ID---->"+getTaskId()); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Fx_Main.class); ‐9‐ startActivity(i); }}); b2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Activity2.class); startActivity(i); }}); b3.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity2.this,Activity3.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Ac2--->destory"); } } 最后是 Activity3,其界面如图 4-8 所示。 专业始于专注 卓识源于远见 图 4-8 Activity3 的界面 如图 4-8 所示,单击“返回 Main”则跳转至 Fx_Main,单击“返回 AC2”,则跳转到 Activity2。具体 代码如代码清单 4-5 所示。 代码清单 4-5 Activity3.Activity import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Activity3 extends Activity { private Button b; private Button b2; ‐ 10 ‐ 专业始于专注 卓识源于远见 @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity3); b=(Button)findViewById(R.id.Button03); b2=(Button)findViewById(R.id.Button04); TextView tv=(TextView)findViewById(R.id.TextView03); tv.setText("Ac3---->"+getTaskId()); Log.i("System.out", "Ac3---->"+this.toString()+"Task ID---->"+getTaskId()); b.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity3.this,Fx_Main.class); startActivity(i); }}); b2.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub Intent i=new Intent(Activity3.this,Activity2.class); startActivity(i); }}); } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("System.out", "Ac3--->Destory"); } } 4.4.1 standard 标准模式 在 standard 模式也就是默认模式下,不需要配置 launchMode。此时的 AndroidManifest.xml 如代码清单 4-6 所示。 代码清单 4-6 AndroidManifest.xml 运行例子,从 Fx_Main 开始,一直点回到 Activity2 按钮时,Log 信息如图 4-9 所示。 ‐ 11 ‐ 专业始于专注 卓识源于远见 图 4-9 Standard 启动模式下 Log 信息 发现每次都创建了 Activity2 的新实例。standard 的加载模式就是这样的,Intent 将发送给它新的 Activity 实例。 现在点击 Android 设备的回退键,可以看到 Log 信息按照刚才创建 Activity 实例的倒序依次出现,类 似退栈的操作,而刚才操作跳转按钮的过程是压栈的操作。 4.4.2 singleTop singleTop 和 standard 模式,都会将 Intent 发送到新的实例(如果已经有了,singleTask 模式和 singleInstance 模式不发送到新的实例)。不过,singleTop 要求如果创建 intent 时栈顶已经有要创建 Activity 的实例,则将 Intent 发送给该实例,而不发送给新的实例。 还是用刚才的示例,只需将 Activity2 的 launchMode 改为 singleTop,就能看到区别。修改后 AndroidManifest.xml 中代码如代码清单 4-7 所示。 代码清单 4-7 AndroidManifest.xml 运行 Fx_Main,跳转到 Activity2---->Actvity2 时会发现,单击多少遍按钮,都是相同的 Activity2 实例, 因为该实例在栈顶,所以不会创建新的实例。如果回退,回到 Fx_Main,将退出应用,如图 4-10 所示。 图 4-10 singleTop 模式下“跳转到 AC2”的 Log 信息 singleTop 模式,可用来解决栈顶多个重复相同的 Activity 的问题。 如果是 Fx_Main 跳转到 Activity2,再跳转到 Fx_Main,行为就和 standard 一样了,会在 Activity2 跳转 到 Fx_Main 时创建 Fx_Main 的新实例,因为当时的栈顶不是 Activity2 实例,如图 4-11 所示。 图 4-11 singleTop 模式下“跳转到 AC2”后“跳回到 Main”的 Log 信息 4.4.3 singleTask ‐ 12 ‐ 专业始于专注 卓识源于远见 singleTask 模式和后面的 singleInstance 模式都是只创建一个实例的。 当 Intent 到来,需要创建 singleTask 模式 Activity 时,系统会检查栈里面是否已经有该 Activity 的实例。 如果有直接将 Intent 发送给它(注意此时原在此 Activity 栈中上面的 Activity 将会被关闭)。 把 Activity2 的启动模式改成 singleTask,修改后 AndroidManifest.xml 中代码如代码清单 4-8 所示。 代码清单 4-8 AndroidManifest.xml 启动 Fx_Main,跳转到 Activity2---->Activity3---->Actvity2,此时看 Log 信息,如图 4-12 所示。 图 4-12 singleTask 启动模式下 Log 信息 可见从 AC3 再跳转到 AC2 时,因为 AC2 之前在栈中是存在的所以不生成新的 AC2 实例,而是在栈 中找到此 AC2,并将在 AC2 上面的 AC3 关闭,所以此时栈中只有 Fx_Main 和 AC2,在 AC2 点返回会直 接退到 Fx_Main 然后退出。 4.4.4 singleInstance 在 singleInstance 模式下,加载该 Activity 时如果没有实例化,它会在创建新的 Task 后,实例化入栈, 如果已经存在,则直接调用 onNewIntent,该 Activity 的 Task 中不允许启动其他的 Activity,任何从该 Activity 启动的其他 Activity 都将被放到其他 Task 中,先检查是否有在应用的 Task,没有的话就创建。 在这里介绍一下 Task(任务)的概念。按照字面意思,任务就是自己要实现的一个目的,而在 Android 中的 Task 的定义是一系列 Activity 的集合,即要达到自己最终要到的 Actvity,之前所有经历过的 Actvity 的集合。它可以是同一个应用内部的,也可以是两个不同应用的。Task 可以认为是一个栈,可放入多个 Activity。比如,启动一个应用,那么 Android 就创建了一个 Task,然后启动这个应用的入口 Activity,就 是 intent-filter 中配置为 main 和 launch 的那个。这个 Activity 是根(Root)Activity,可能会在它的界面调 用其他 Activity,这些 Activity 如果按照上面那 3 个模式,也会在这个栈(Task)中,只是实例化的策略不 同而已。 把 Activity2 的启动模式改成 singleInstance,修改后 AndroidManifest.xml 中代码如代码清单 4-9 所示。 代码清单 4-9 AndroidManifest.xml ‐ 13 ‐ 专业始于专注 卓识源于远见 然后进行测试,启动 Fx_Main---->Actvity2---->Actvity3 然后看一下 Log 信息,如图 4-13 所示。 图 4-13 singleInstance 启动模式下 Log 信息 可以看到 Fx_Main 以及 Activity3 的 Task ID 为 9,而 Actvity2 的 Task ID 为 10,此时在 Actvity3 单击 “返回”按钮会发现先退到 Fx_Main,继续返回会回到 Actvity2 最后退出。从该过程可以看出:如果从其 他应用程序调用 singleInstance 模式的 Activity(Fx_Main),从该 Activity 开启其他 Activity(Activity2)时, 会创建一个新的 Task(Task ID 为 10 的那个),实际上,如果包含该 Activity(Activity2)的 Task 已经运行 的话,他会在该运行的 Task 中重新创建。 经过上述的介绍,用下面的表格来进行一个简单的总结,如表 4-2 所示。 表 4-2 Activity4 种启动模式对比 区别 standard singleTop singleTask singleInstance 是否允许多个 实例 如何决定所属 Task 是否每次都生成新 是否允许其他 Activity 存 实例 在于本 Task 内 可 被 多 次 实 例 存放于 Start 是 化,同一个 Task Activity() 的 的 不 同 的 实 例 Task。除非设置 可 位 于 不 同 的 FLAG_ Task 中 , 每 个 ACTIVITY_NE Task 也 可 包 含 W_TASK 标记 多个实例 允许 同 standard 同 standard 如果寄存 Activity 的 栈顶为该 Activity, 则直接用该 Activity 处理;否则,创建新 实例 允许 不能有多个实 例。由于该模式 下 Activity 总是 位于栈顶,所以 Actvity 在 同 一 个设备里最多 只有一个实例 放 入 新 的 Task 内,并且位于该 Task 的根 只有在第一次才创 建新的实例,其他情 况复用该 Activity 允许。如果存放 singleTask 的 栈 寄 存 在 Task 内,响应一个 Intent 时,如果 singleTask 位于 栈顶,则处理 Intent,否 则会丢失 Intent,但该 Task 会处于前台 同 singleTask 同 singleTask 同 singleTask 不允许 4.5 程序调试 ‐ 14 ‐ 专业始于专注 卓识源于远见 Android 系统提供了两种调试工具 LogCat 和 DevTools,用于定位、分析及修复程序中出现的错误。 4.5.1 LogCat 命令行工具 LogCat 是可以显示在 Eclipse 集成开发环境中的用来获取系统日志信息的工具。它的主要功能就是能 够捕获包括 Dalvik 虚拟机产生的信息、进程信息、ActivityManager 信息、PackagerManager 信息、Homeloader 信息、WindowsManager 信息、Android 运行时信息和应用程序信息等可被捕获的信息。 1.LogCat 的使用方法 打开方式:选择“Window”→“Show View ”→“Other”命令,打开 Show View 的选择菜单,然后 在 Andoird → LogCat 中选择 LogCat。打开 LogCat 后,它便显示在 Eclipse 的下方区域,其界面如图 4-14 所示。 图 4-14 LogCat 界面 从图中我们可以看到 LogCat 的右上方有 5 个不同的字母,这 5 个字母分别表示 5 种不同类型的日志 信息,它们的级别依次增高,表示含义如下。  V:详细(Verbose)信息。  D:调试(Debug)信息。  I:通告(Info)信息。  W:警告(Warn)信息。  E:错误(Error)信息。 在 LogCat 中,用户可以通过 5 个字母图标选择显示的信息类型,级别高于所选类型的信息也会在 LogCat 中显示,但级别低于所选类型的信息则不会被显示。 同时,LogCat 提供了“过滤”功能,在右上角的“+”号和“-”号,分别是添加和删除过滤器。用户 可以根据日志信息的标签(Tag)、产生日志的进程编号(Pid)或信息等级(Level),对显示的日志内容进 行过滤。 2.程序调试原理  引入 android.util.Log 包。  使用 Log.v()、 Log.d()、 Log.i() 、Log.w() 和 Log.e() 5 个方法在程序中设置“日志点”。  Log.v()用来记录详细信息。  Log.d()用来记录调试信息。  Log.i()用来记录通告信息。  Log.w()用来记录警告信息。  Log.e()用来记录错误信息。  当程序运行到“日志点”时,应用程序的日志信息便被发送到 LogCat 中。  判断“日志点”信息与预期的内容是否一致。  进而判断程序是否存在错误。 下面的例子演示了 Log 类的具体使用方法。 代码清单 4-10 LogCat.java package com.example.LogCat; import android.app.Activity; import android.os.Bundle; import android.util.Log; ‐ 15 ‐ 专业始于专注 卓识源于远见 public class LogCat extends Activity { final static String TAG = "LOGCAT"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.v(TAG,"Verbose"); Log.d(TAG,"Debug"); Log.i(TAG,"Info"); Log.w(TAG,"Warn"); Log.e(TAG,"Error"); } } 在本段代码中,程序第 5 行“import android.util.Log;”引入 android.util.Log 包;第 8 行定义标签,标 签帮助用户在 LogCat 中找到目标程序生成的日志信息,同时也能够利用标签对日志进行过滤;第 14 行记 录一个详细信息,Log.v()方法的第一个参数是日志的标签,第二个参数是实际的信息内容;第 15~18 行分 别产生了调试信息、通告信息、警告信息和错误信息。 最终运行结果如图 4-15 所示,从图中还可以看出 LogCat 对不同类型的信息使用了不同的颜色加以区 别。 图 4-15 LogCat 工程的运行结果 3.添加过滤器 上文中提到 LogCat 提供了“过滤”功能,下面就来介绍一下 LogCat 是如何添加过滤器的。 首先,单击右上角的“+”,在弹出的对话框中填入过滤器的名称:LogcatFilter,设置过滤条件为“标 签=LOGCAT”即可,操作方法如图 4-16 所示。 图 4-16 添加过滤器 经过上述过滤器过滤后,无论什么类型的日志信息,属于哪一个进程,只要标签为 LogCat,都将显示 在 LogcatFilter 区域内。LogCat 过滤后的输入结果如图 4-17 所示。 图 4-17 LogCat 过滤后的输入结果 4.5.2 DevTools 开发调试工具 ‐ 16 ‐ 专业始于专注 卓识源于远见 DevTools 是用于调试和测试的工具,它包括了如下所示一系列各种用途的用户小工具:Development Settings、Exception Browser、Google Login Service、Instrumentation、Media Scanner、Package Browser、Pointer Location、Raw Image Viewer、Running processes 和 Terminal Emulator。 如图 4-18 所示,为 DevTools 使用时的界面。由使用时的界面也可以看出其中的各个小工具。 图 4-18 DevTools 的使用界面 以下着重讲解 Dev Tools 的一些小工具。 1.Development Settings Development Settings 中包含了程序调试的相关选项,单击功能前面的选择框,出现绿色的“对号”表 示功能启用,模拟器会自动保存设置。 图 4-19 显示了 Development Settings 的运行界面。 图 4-19 Development Settings 运行界面 下面就详细介绍 Development Settings 中各个选项的含义,如表 4-3 所示。 表 4-3 Development Settings 中各选项的含义 选项 说明 Debug App 为 Wait for debugger 选项指定应用程序,如果不指定(选择 none), Wait for debugger 选项将适用于所有应用程序。Debug App 可以有效地 防止 Android 程序长时间停留在断点而产生异常 Wait for debugger 阻塞加载应用程序,直到关联到调试器(Debugger)。用于在 Activity 的 onCreate()方法的进行断点调试 Show running processes 在屏幕右上角显示运行中的进程 ‐ 17 ‐ 专业始于专注 卓识源于远见 Show screen updates 选中该选项时,界面上任何被重绘的矩形区域会闪现粉红色,有利于发 现界面中不必要的重绘区域 No App Process limit 允许同时运行进程的数量上限 Immediately destroy activities Activity 进 入 停 止 状 态 后 立 即 销 毁 , 用 于 测 试 在 方 法 onSaveInstanceState()、onRestoreInstanceState()和 onCreate()中的代码 Show CPU usage 在屏幕顶端显示 CPU 使用率,上层红线显示总的 CPU 使用率,下层绿 线显示当前进程的 CPU 使用率 Show background 应用程序没有 Activity 显示时,直接显示背景面板,一般这种情况仅在 调试时出现 Show Sleep state on LED 在休眠状态下开启 LED Windows Animation Scale 窗口动画规模 Transition Animation 转换动画 Light Hinting 轻显示 Show GTalk connection status service 显示 GTalk 服务连接状态 2.Pointer Location Pointer Location 是屏幕点位置查看工具,能够显示触摸点的 X 轴坐标和 Y 轴坐标,如图 4-20 所示。 图 4-20 Pointer Location 的使用画面 3.Running processes Running processes 能够查看在 Android 系统中正在运行的进程,并能查看进程的详细信息,包括进程 名称和进程所调用的程序包。 图 4-21 Andoird 模拟器默认情况下运行的进程和 com.android.phone 进程的详细信息 4.Terminal Emulator ‐ 18 ‐ 专业始于专注 卓识源于远见 Terminal Emulator 可以打开一个连接底层 Linux 系统的虚拟终端,但具有的权限较低,且不支持提升 权限的 su 命令。如果需要使用 root 权限的命令,可以使用 ADB 工具。 图 4-22 是 Terminal Emulator 运行时的画面,输入 ls 命令,显示出根目录下的所有文件夹。 图 4-22 Terminal Emulator 运行时的画面 4.6 本章小结 本章主要介绍了 Android 系统的进程优先级排序、不同优先级进程之间的变化方式,Android 系统的 4 大基本组件及其用途,Activity 的生命周期中各个状态及状态间的变化关系、Android 应用程序的调试方法 和工具。 关键知识点测评 1.以下有关 Android 系统进程优先级的说法,不正确的一个是( )。 A.前台进程是 Android 系统中最重要的进程 B.空进程在系统资源紧张时会被首先清除 C.服务进程没有用户界面并且在后台长期运行 D.Android 系统中一般存在数量较多的可见进程 2.以下有关 Android 组件的叙述,正确的一个是( )。 A.Service 是 Android 程序的呈现层 B.BroadcaseReceiver 本身包含界面,用于通知用户接收到重要信息 C.应用程序可以通过 ContentProvider 访问其他应用程序的私有数据 D.不是所有的 Android 组件都具有自己的生命周期 3.以下有关 Activity 生命周期的描述,不正确的是( )。 A.Activity 的状态之间是可以相互转换的 B.Activity 的全生命周期是从 Activity 建立到销毁的全部过程,始于 onCreate(),结束于 onDestroy() C.活动生命周期是 Activity 在屏幕的最上层,并能够与用户交互的阶段 D.onPause()函数在 Android 系统因资源不足终止 Activity 前调用 学习小助手 ‐ 19 ‐ 专业始于专注 卓识源于远见  马上扫描下方华清远见官方微信二维码(公众号:farsight2013),即可免费获取价值 388 元的免费图书大礼包! 包含 7 本嵌入式、android.、Linux、ARM、FPGA、DSP 相关图书 PDF 完整版,您还可以通过华清远见微信第 一时间了解华清远见更多线上线下免费技术活动,获取更多学习上的帮助! ‐ 20 ‐ 《Android 应用程序开发与典型案例》 作者:华清远见 第 5 章 用户界面开发 本章简介 在上一章的学习中,主要了解了 Android 系统的进程优先级排序、不同优先级 进程之间的变化方式,Android 系统的 4 大基本组件及其用途,Activity 的生命周 期中各个状态及状态间的变化关系、Android 应用程序的调试方法和工具。在此基 础上,本章将对 Android 程序界面开发的学习,包括用户界面基础、用户界面的控 件的使用、界面布局的特点及使用方法、菜单的使用方法、界面事件的处理方法等。 5.1 用户界面基础 用户界面(User Interface,UI)是系统和用户之间进行信息交换的媒介,实现信息的内部形式与用户 可以接收形式之间的转换。 专业始于专注 卓识源于远见 在 Android 系统中,Android 自带有许多要求,预示着其用户界面的复杂性:它是一个支持多个并发 应用程序的多处理系统,接受多种形式的输入,有着高交互性,必须具有足够的灵活性,以支持现在和未 来广泛的设备。令人印象深刻的是丰富的用户界面及其易用性,实现了所有给定的功能。但为了使用应用 程序在不同的设备上正常的显示以及运行,避免对系统性能造成过大的负担,应该明白其工作原理。 Android 使用 XML 文件描述用户界面;资源文件独立保存在资源文件夹中;对用户界面描述非常灵活, 允许不明确定义界面元素的位置和尺寸,仅声明界面元素的相对位置和粗略尺寸。以下就来介绍一下 Android 的用户界面框架。 Android 是在 Java 环境中增加了一个图形用户界面(GUI)工具包,联合了 AWT,Swing,SWT,和 J2ME(撇开 Web UI 的工具包)。Android 框架和他们一样,它是单线程的,事件驱动的,并建立一个嵌套 的组件库。 Android 用 户 界 面 框 架 ( Android UI Framework ), 像 其 他 的 UI 框 架 一 样 , 采 用 了 MVC (Model-View-Controller)模型,提供了处理用户输入的控制器(Controller),显示用户界面和图像的视图 (View),以及保存数据和代码的模型(Model)。 图 5-1 Android 用户界面框架 MVC 模型 其中 Model 是应用程序的核心。虽然特定应用程序的视图(View)和控制器(Controller)必然反映他 们操纵的 Model,但一个 Model 可能是由几个不同的应用使用。想想看,例如,一个 MP3 播放器的应用 程序以及一个将 MP3 文件转换成 WAV MP3 文件的程序,对于这两个应用程序,Model 包括它的 MP3 文 件格式和编解码器。然而,前者的应用程序,有熟悉的停止,启动和暂停控制等操作。后者可能不会产生 任何声音;相反,它会设置比特率的控制等。此时,他们的 Model 都是对所有的文件数据。 其中的控制器(Controller)能够接收并响应程序的外部动作,如按键动作或触摸屏动作等。控制器使 用队列处理外部动作,每个外部动作作为一个对应的事件被加入队列中,然后 Android 用户界面框架按照 “先进先出”的规则从队列中获取事件,并将这个事件分配给所对应的事件处理方法。例如,当用户按下 他的手机上的键,Android 系统生成的 KeyEvent,并将其添加到事件队列中。最后,在之前已排队的事件 被处理后,KeyEvent 是从队列中删除的,并作为当前选择 View 的 dispatchKeyEvent 方法的调用参数传递。 一旦事件被分派到的焦点组件,该组件可能会采取适当的行动来改变程序的内部状态。例如,在 MP3 播 放器应用程序中,当用户点击屏幕上的播放/暂停按钮时,触发该按钮的事件,处理方法可能更新 Model, 恢复播放一些先前所选乐曲。 视图(View)是应用程序给用户的反馈。它负责应用程序的部分渲染显示,发送音频扬声器,产生触 觉反馈等。视图部分应用视图树(View Tree)模型。视图树是由 Android 用户界面框架中的界面元素以一 种树形结构组织在一起的,Android 系统会依据视图树的结构从上至下绘制每一个界面元素。每个元素负 责对自身的绘制,如果元素包含子元素,该元素会通知其下所有子元素进行绘制。 下面就来详细介绍一下视图树。Android 当中的可视化界面单元,可分为“容器”与“非容器”两类, 容器类继承 ViewGroup,非容器类则从 View 衍生出来,如图 5-2 所示。 ‐2‐ 专业始于专注 卓识源于远见 图 5-2 Android 视图树(View Tree) 视图树由 View 和 ViewGroup 构成。其中,View 是界面的最基本的可视单元,存储了屏幕上特定矩形 区域内所显示内容的数据结构,并能够实现所占据区域的界面绘制、焦点变化、用户输入和界面事件处理 等功能。同时 View 也是一个重要的基类,所有在界面上的可见元素都是 View 的子类。ViewGroup 是一种 能够承载含多个 View 的显示单元,它承载了界面布局,同时还承载了具有原子特性的重构模块。 如图 5-3 所示,这些 Layout 可以套叠式地组成一棵视图树。其中,父节点的 Layout 与子节点的 LayoutParams 之 间 有 控 制 关 系 , 例 如 , 若 父 节 点 是 RelativeLayout , 则 子 节 点 的 单 元 中 可 以 指 定 RelativeLayout.LayoutParams 中的属性,以控制子节点在父节点中的排列状况。 图 5-3 ViewGroup 树形层次结构 在单线程用户界面中,控制器从队列中获取事件和视图在屏幕上绘制用户界面,使用的都是同一个线 程。这样的单线程用户界面使得处理方法具有顺序性,能够降低应用程序的复杂程度,同时也能降低开发 的难度。 问:单线程用户界面有什么缺点呢? 答:如果事件处理方法过于复杂,可能会导致用户界面失去响应。 5.2 界面布局 界面布局(Layout)是用户界面结构的描述,定义了界面中所有的元素、结构和相互关系。 界面布局(Layout)是为了适应多种 Android 设备上的屏幕而设计的解决方案:它们可以有不同的像 素密度、尺寸和不同的纵横比。典型的 Android 设备,如 HTC G1 手机,甚至允许应用程序运行时改变屏 幕的方向(纵向或横向),因此布局的基础设施需要能够应对这种情况。布局的目的是为开发人员提供一 种方式来表示 View 之间的物理关系,因为它们是在屏幕上绘制。作为 Android 的界面布局,它使用开发 需求来满足与开发要求最接近的屏幕布局。 ‐3‐ 专业始于专注 卓识源于远见 Android 开发者使用术语“布局”,指的是两种含意中的一种。布局的两种定义如下。  一种资源,它定义了在屏幕上画什么。布局资源存储在应用程序的/res/layout 资源目录下的 XML 文件中。布局资源简单地说就是一个用于用户界面屏幕,或屏幕的一部分,以及内容的模板。  一种视图类,它主要是组织其他控件。这些布局类(LinearLayout,RelativeLayout,TableLayout 等)用于在屏幕上显示子控件,如文本控件、按钮或图片。 Eclipse 的 Android 开发插件包含了一个很方便的用于设计和预览布局资源的布局资源设计器。这个工 具包括两个标签视图:布局视图允许你预览在不同的屏幕下及对于每一个方向控件会如何展现;XML 视 图告诉你资源的 XML 定义。 这里有一些关于在 Eclipse 中使用布局资源编辑器的技巧。  使用概要(Outline)窗格在你的布局资源中添加和删除控件。  选择特定的控件(在预览或概要窗口)并使用属性窗格来调整特定控件的属性。  使用 XML 标签来直接编辑 XML 定义。 很重要的是要记住一点,Eclipse 布局资源编辑器不能完全精确的模拟出布局在最终用户设备上的呈现 形式。对此,必须在适当配置的模拟器中测试,更重要的是在目标设备上测试。而且一些“复杂”控件, 包括标签或视频查看器,也不能在 Eclipse 中预览。 声明 Android 程序的界面布局有两种方法。  使用 XML 文件描述界面布局。  在程序运行时动态添加或修改界面布局。 用户既可以独立使用任何一种声明界面布局的方式,也可以同时使用两种方式。 使用 XML 文件声明界面布局有以下 3 个特点:将程序的表现层和控制层分离;在后期修改用户界面 时,无须更改程序的源代码;用户还能够通过可视化工具直接看到所设计的用户界面,有利于加快界面设 计的过程,并且为界面设计与开发带来极大的便利性。 设计程序用户界面最方便且可维护的方式是创建 XML 布局资源。这个方法极大地简化了 UI 设计过程, 将许多用户界面控件的布局,以及控件属性定义移到 XML 中,代替了写代码。它适应了 UI 设计师(更关 心布局)和开发者(了解 Java 和实现应用程序功能)潜在的区别。开发者依然可以在必要时动态地改变屏 幕内容。复杂控件,像 ListView 或 GridView,通常用程序动态地处理数据。 XML 布局资源必须存放在项目目录的/res/layout 下。对于每一屏(与某个活动紧密关联)都创建一个 XML 布局资源是一个通用的做法,但这并不是必需的。理论上来说,可以创建一个 XML 布局资源并在不 同的活动中使用它,为屏幕提供不同的数据。如果需要的话,也可以分散布局资源并用另外一个文件包含 它们。 现在把注意力转向对组织其他控件很有用的布局控件。Android 中 Layout 的列表,如表 5-1 所示。 表 5-1 Layout 分类表 Layout 类别 说明 线性布局 LinearLayout 线性(水平或垂直)排版的容器 框架布局 FrameLayout 单一界面的容器 表格布局 TableLayout 以表格方式排版的容器 相对布局 RelativeLayout 以相对坐标排版的容器 绝对布局 AbsoluteLayout 以绝对坐标排版的容器,不推荐使用 5.2.1 线性布局(LinearLayout) 线性布局是最简单的布局之一,它提供了控件水平或者垂直排列的模型。如图 5-4 所示,线性布局中, 所有的子元素如果垂直排列,则每行仅包含一个界面元素;如果水平排列,则每列仅包含一个界面元素。 ‐4‐ 专业始于专注 卓识源于远见 图 5-4 线性布局(LinearLayout)效果图 同 时 , 使 用 此 布 局 时 可 以 通 过 设 置 控 件 的 Weight 参 数 控 制 各 个 控 件 在 容 器 中 的 相 对 大 小 。 LinearLayout 布局的属性既可以在布局文件(XML)中设置,也可以通过成员方法进行设置。表 5-2 给出 了 LinearLayout 常用的属性及这些属性的对应设置方法。 表 5-2 LinearLayout 常用属性及对应方法 属性名称 android:orientation android:gravity 对应方法 setOrientation(int) setGravity(int) 描述 设置线性布局的朝向,可取 horizontal 设置线性布局的内部元素的布局方式 在线性布局中可使用 gravity 属性来设置控件的对齐方式,gravity 可取的值及说明如表 5-3 所示。 提示:当需要为 gravity 设置多个值时,用“|”分隔即可。 表 5-3 gravity 可取的属性及说明 属性 top bottom left right center_vertical center-horizontal center fill_vertical fill_horizontal fill 说明 不改变控件大小,对齐到容器顶部 不改变控件大小,对齐到容器底部 不改变控件大小,对齐到容器左侧 不改变控件大小,对齐到容器右侧 不改变控件大小,对齐到容器纵向中央位置 不改变控件大小,对齐到容器横向中央位置 不改变控件大小,对齐到容器中央位置 若有可能,纵向拉伸以填满容器 若有可能,横向拉伸以填满容器 若有可能,纵向横向同时拉伸以填满容器 以下用一个线性布局的例子来加深对线性布局的理解。 1.创建一个名为 LinearLayout 的 Android 工程 包名称是 edu.hrbeu.LinearLayout,Activity 名称为 LinearLayout。为了能够完整体验创建线性布局的过 程,我们需要删除 Eclipse 自动建立的/res/layout/main.xml 文件,之后我们将手动创建一个 XML 布局文件。 2.建立 XML 线性布局文件 首先,删除 Eclipse 自动建立的/res/layout/main.xml 文件;其次,建立用于显示垂直排列线性布局的 XML 文件:右击/res/layout 文件夹,选择“New”→“File”命令打开新文件建立向导,文件名为 main_vertical.xml,保存位置为 LinearLayout/res/layout,如图 5-5 所示。 ‐5‐ 专业始于专注 卓识源于远见 图 5-5 新建线性布局 XML 文件 3.编辑 XML 线性布局文件 打开 XML 文件编辑器,对 main_vertical.xml 文件的代码做如代码清单 5-1 所示的修改。 代码清单 5-1 main_vertical.xml 第 2 行代码是声明 XML 文件的根元素为线性布局;第 4、5、6 行代码是在属性编辑器中修改过的宽 度、高度和排列方式的属性。同样地,用户可以在可视化编辑器和属性编辑器中对页面布局进行修改,这 些修改会同步地反映在 XML 文件中。 4.添加控件 将四个界面控件 TextView、EditText、Button、Button 先后拖曳到可视化编辑器中,所有控件都自动获 取控件名称,并把该名称显示在控件上,如 TextView01、EditText01、Button01 和 Button02。 图 5-6 线性布局添加控件 修改界面控件的属性如表 5-4 所示。 表 5-4 线性布局控件属性 编号 类型 属性 id 1 TextView text id 2 EditText Layout_width text 3 Button id text 4 Button id 值 @+id/label 用户名: @+id/entry match_parent [null] @+id/ok 确认 @+id/cancel ‐6‐ 专业始于专注 卓识源于远见 打开 XML 文件编辑器查看 main_vertical.xml 文件代码,发现在属性编辑器内填入的文字已经正常写 入 XML 文件中,如代码清单 5-2 中第 11、20、25 行代码。 代码清单 5-2 main_vertical.xml 5.修改 LinearLayout.java 文件 将 LinearLayout.java 文件中的 setContentView(R.layout.main),更改为 setContentView (R.layout.main_vertical)。 同理,按照以上步骤,可以得到横向线性布局。  建立 main_ horizontal.xml 文件。  线性布局的 Orientation 属性的值设置为 horizontal。  将 EditText 的 Layout width 属性的值设置为 wrap_content。  将 LinearLayout.java 文 件 中 的 setContentView(R.layout.main_vertical) 修 改 为 setContentView(R.layout.main_ horizontal)。 5.2.2 框架布局(FrameLayout) 框架布局(FrameLayout)是最简单的界面布局,它在屏幕上开辟出了一块区域,在这块区域中可以添 加多个子控件,但是所有的子控件都被对齐到屏幕的左上角。框架布局的大小由子控件中尺寸最大的那个 子控件来决定。如果子控件一样大,同一时刻只能看到最上面的子控件。 FrameLayout 继承自 ViewGroup,除了继承自父类的属性和方法,FrameLayout 类中包含了自己特有 的属性和方法,如表 5-5 所示。 表 5-5 FrameLayout 常用属性及对应方法 属性名称 对应方法 描述 android:foreground setForeground(Drawable) 设置绘制在所有子控件之上的内容 android:foregroundGravity setForegroundGravity(int) 设置绘制在所有子控件之上内容的 gravity 提示: ‐7‐ 专业始于专注 卓识源于远见 在 FrameLayout 中,子控件是通过栈来绘制的,所以后添加的子控件会被绘制在上层。 以下用一个 FrameLayout 的例子来加深对 FrameLayout 的理解。 (1)在 Eclipse 中新建一个项目 FrameLayout。打开其 res/values 目录下的 strings.xml,在其中输入如 代码清单 5-3 所示代码。在该段代码中声明了应用程序总会用到的字符串资源。 代码清单 5-3 strings.xml FrameExample 大的 中的 小的 (2)在项目 rers/values 目录下新建一个 colors.xml,在其中输入如代码清单 5-4 所示代码。该段代码 声明了应用程序中将会用到的颜色资源。这样将所有颜色资源统一管理有助于提高程序的可读性及可维护 性。 代码清单 5-4 colors.xml #FF0000 #00FF00 #0000FF #FFFFFF (3)打开项目 res/layout 目录下的 main.xml 文件,将其中已有的代码替换为如代码清单 5-5 所示代码。 代码清单 5-5 main.xml < ‐8‐ 专业始于专注 卓识源于远见 代码第 2~7 行声明了一个框架布局,并设置其在父控件中的显示方式及自身的背景颜色;代码第 8~16 行声明了一个 TextView 控件,该控件 ID 为 TextView01,第 13 行定义了其显示内容的字号为 60px,第 14 行定义了所显示内容的字体颜色为绿色;代码第 17~25 行声明了一个 TextView 控件,该控件 ID 为 TextView02,第 22 行定义了其显示内容的字号为 40px,第 23 行定义了所显示内容的字体颜色为红色;代 码第 26~34 行声明了一个 TextView 控件,该控件 id 为 TextView03,第 22 行定义了其显示内容的字号为 20px,第 23 行定义了所显示内容的字体颜色为蓝色。 运行程序,在图 5-7 所示的运行效果图中可以看到,程序运行时所有的子控件都自动地对齐到容器的 左上角,由于子控件的 TextView 是按照字号从大到小排列的,所以字号小的在最上层。 图 5-7 框架布局运行效果图 5.2.3 表格布局(TableLayout) TableLayout 类以行和列的形式管理控件,每行为一个 TableRow 对象,也可以为一个 View 对象,当 为 View 对象时,该 View 对象将跨越该行的所有列。在 TableRow 中可以添加子控件,每添加一个子控 件为一列。 TableLayout 布局中并不会为每一行、每一列或每个单元格绘制边框,每一行可以有 0 或多个单元格, 每个单元格为一个 View 对象。TableLayout 中可以有空的单元格,单元格也可以像 HTML 中那样跨越多 个列。 图 5-8 是表格布局的示意图。 图 5-8 表格布局示意图 在表格布局中,一个列的宽度由该列中最宽的那个单元格指定,而表格的宽度是由父容器指定的。在 TableLayout 中,可以为列设置 3 种属性。  Shrinkable,如果一个列被标识为 shrinkable,则该列的宽度可以进行收缩,以使表格能够适应其 父容器的大小。  Stretchable,如果一个列被标识为 stretchable,则该列的宽度可以进行拉伸,以填满表格中空闲的 空间。  Collapsed,如果一个列被标识为 collapsed,则该列将会被隐藏。 注意: 一个列可以同时具有 Shrinkable 和 Stretchable 属性,在这种情况下,该列的宽度将任意拉伸或收缩以适应父容器。 TableLayout 继承自 LinearLayout 类,除了继承来自父类的属性和方法,TableLayout 类中还包含表格 布局所特有的属性和方法。这些属性和方法说明如表 5-6 所示。 表 5-6 TableLayout 类常用属性及对应方法说明 ‐9‐ 专业始于专注 卓识源于远见 属性名称 对应方法 描述 android:collapseColumns android:shrinkColumns android:stretchColumns setColumnCollapsed(int,boolean) setShrinkAllColumns(boolean) setStretchAllColumns(boolean) 设置指定列号的列为 Collapsed,列号从 0 设置指定列号的列为 Shrinkable,列号 从0 设置指定列号的列为 Stretchable,列号 从0 以下我们用一个表格布局的例子来加深对表格布局的理解。 首先,建立表格布局要注意以下几点。 (1)向界面中添加一个表格布局,无须修改布局的属性值。其中,ID 属性为 TableLayout01,Layout width 和 Layout height 属性都为 wrap_content。 (2)向 TableLayout01 中添加两个 TableRow。TableRow 代表一个单独的行,每行被划分为几个小的 单元,单元中可以添加一个界面控件。其中,ID 属性分别为 TableRow01 和 TableRow02,Layout width 和 Layout height 属性都为 wrap_content。 (3)通过 Outline,向 TableRow01 中添加 TextView 和 EditText;向 TableRow02 中添加两个 Button。 图 5-9 向 TableRow01 中添加 TextView 和 EditText 参考表 5-7 设置 TableRow 中 4 个界面控件的属性值。 表 5-7 表格布局控件属性 编号 类型 属性 id text 1 TextView gravity padding Layout_width id text 2 EditText padding Layout_width id 3 Button text padding id 4 Button text (4)main.xml 完整代码如代码清单 5-6 所示。 值 @+id/label 用户名: right 3dip 160dip @+id/entry [null] 3dip 160dip @+id/ok 确认 3dip @+id/cancel 取消 代码清单 5-6 main.xml ‐ 10 ‐ 专业始于专注 卓识源于远见 代码中,第 3 行代码使用了标签声明表格布局;第 7 行和第 23 行代码声明了两个 TableRow 元素;第 12 行设定宽度属性 android:layout_width:160dip;第 13 行设定属性 android:gravity,指 定文字为右对齐;第 15 行使用属性 android:padding,声明 TextView 元素与其他元素的间隔距离为 3dip。 (5)表格布局运行效果如图 5-10 所示。 图 5-10 表格布局运行图 5.2.4 相对布局(RelativeLayout) ‐ 11 ‐ 专业始于专注 卓识源于远见 相对布局(RelativeLayout)是一种非常灵活的布局方式,能够通过指定界面元素与其他元素的相对位 置关系,确定界面中所有元素的布局位置,能够最大限度保证在各种屏幕类型的手机上正确显示界面布局。 在相对布局中,子控件的位置是相对兄弟控件或父容器而决定的。出于性能考虑,在设计相对布局时 要按照控件之间的依赖关系排列,如 View A 的位置相对于 View B 来决定,则需要保证在布局文件中 View B 在 View A 的前面。 在进行相对布局时用到的属性很多,首先来看属性值只为 true 或 false 的属性,如表 5-8 所示。 表 5-8 相对布局中只取 true 或 false 的属性及说明 属性名称 属性说明 android:layout_centerHorizontal 当前控件位于父控件的横向中间位置 android:layout_centerVertical 当前控件位于父控件的纵向中间位置 android:layout_centerInParent 当前控件位于父控件的中央位置 android:layout_alignParentBottom 当前控件底端与父控件底端对齐 接下来看属性值为其他控件 ID 的属性,如表 5-9 所示。 表 5-9 相对布局中取值为其他控件 ID 的属性及说明 属性名称 属性说明 android:layout_toRightOf 使当前控件位于给出 ID android:layout_toLeftOf 使当前控件位于给出 ID android:layout_above 使当前控件位于给出 ID android:layout_below 使当前控件位于给出 ID android:layout_alignTop 使当前控件的上边界位于给出 ID android:layout_alignBottom 使当前控件的下边界位于给出 ID android:layout_alignLeft 使当前控件的左边界位于给出 ID android:layout_alignRight 使当前控件的右边界位于给出 ID 最后要介绍的是属性值以像素为单位的属性及说明,如表 5-10 所示。 表 5-10 相对布局中取值为像素的属性及说明 属性名称 属性说明 android:layout_marginLeft 当前控件左侧的留白 android:layout_marginRight 当前控件右侧的留白 android:layout_marginTop 当前控件上方的留白 android:layout_marginBottom 当前控件下方的留白 需要注意的是在进行相对布局时要避免出现循环依赖,例如,设置相对布局在父容器中的排列方式为 WRAP_CONTENT,就不能再将相对布局的子控件设置为 ALIGN_PARENT_BOTTOM。因为这样会造成 子控件和父控件相互依赖和参照的错误。 以下用一个相对布局的例子来加深对线性布局的理解。首先来看一下相对布局的效果图,如图 5-11 所示。 ‐ 12 ‐ 图 5-11 相对布局效果图 专业始于专注 卓识源于远见 为达到以上效果,按以下步骤进行操作。 (1)添加 TextView 控件(用户名),相对布局会将 TextView 控件放置在屏幕的最上方。 (2)添加 EditText 控件(输入框),并声明该控件的位置在 TextView 控件的下方,相对布局会根据 TextView 的位置确定 EditText 控件的位置。 (3)添加第一个 Button 控件(“取消”按钮),声明在 EditText 控件的下方,且在父控件的最右边。 (4)添加第二个 Button 控件(“确认”按钮),声明该控件在第一个 Button 控件的左方,且与第一个 Button 控件处于相同的水平位置。 相对布局在 main.xml 文件的完整代码如代码清单 5-7 所示。 代码清单 5-7 main.xml 在 代 码 中 , 第 3 行 使 用 了 标 签 声 明 一 个 相 对 布 局 ; 第 15 行 使 用 位 置 属 性 android:layout_below , 确 定 EditText 控 件 在 ID 为 label 的 元 素 下 方 ; 第 20 行 使 用 属 性 android:layout_alignParentRight , 声 明 该 元 素 在 其 父 元 素 的 右 边 边 界 对 齐 ; 第 21 行 设 定 属 性 android:layout_marginLeft,左移 10dip;第 22 行声明该元素在 ID 为 entry 的元素下方;第 28 行声明使用 属 性 android:layout_toLeftOf , 声 明 该 元 素 在 ID 为 cancel 元 素 的 左 边 ; 第 29 行 使 用 属 性 android:layout_alignTop,声明该元素与 ID 为 cancel 的元素在相同的水平位置。 5.2.5 绝对布局(AbsoluteLayout) 绝对布局(AbsoluteLayout)能通过指定界面元素的坐标位置,来确定用户界面的整体布局。所谓绝 对布局,是指屏幕中所有控件的摆放由开发人员通过设置控件的坐标来指定,控件容器不再负责管理其子 控件的位置。由于子控件的位置和布局都通过坐标来指定,因此 AbsoluteLayout 类中并没有开发特有的属 性和方法。 ‐ 13 ‐ 专业始于专注 卓识源于远见 绝对布局是一种不推荐使用的界面布局,因为通过 X 轴和 Y 轴确定界面元素位置后,Android 系统不 能够根据不同屏幕对界面元素的位置进行调整,降低了界面布局对不同类型和尺寸屏幕的适应能力。每一 个界面控件都必须指定坐标(X,Y),例如图 5-12 中,“确认”按钮的坐标是(40,120),“取消”按钮的 坐标是(120,120)。坐标原点(0,0)在屏幕的左上角。 图 5-12 绝对布局效果图 绝对布局示例在 main.xml 文件的完整代码如代码清单 5-8 所示。 代码清单 5-8 main.xml 上述涉及的界面布局(LinearLayout,TableLayout,RelativeLayout 等)像其他控件一样也是一个控件。 这意味着布局控件可以被嵌套。比如,为了组织屏幕上的控件你可以在一个 LinearLayout 中使用一个 RelativeLayout,反过来也行。但是需注意在界面设计过程中,尽量保证屏幕相对简单,复杂布局加载很慢 并且可能引起性能问题。 ‐ 14 ‐ 专业始于专注 卓识源于远见 同时,在设计程序布局资源时需要考虑设备的差异性。通常情况下是可能设计出在各种不同设备上看 着都不错的灵活布局的,不管是竖屏还是模屏模式。必要时可以引入可选布局资源来处理特殊情况。例如, 可以根据设备的方向或设备是不是有超大屏幕(如网络平板)来提供不同的布局供加载。 Android SDK 提供了几个可以帮助我们设计、调试和优化布局资源的工具。除了 Eclipse 的 Android 插 件中内置的布局资源设计器,还可以使用 Android SDK 提供的 Hierarchy Viewer(层次结构查看器)和 layoutopt。这些工具在 Android SDK 的/tools 目录下可以找到。可以使用 Hierarchy Viewer 来查看布局运行 时的详细情况;可以使用 layoutopt(布局优化)命令行工具来优化你的布局文件。优化布局非常重要,因 为复杂的布局文件加载很慢。layoutopt 工具简单地扫描 XML 布局文件并找出不必要的控件。在 Android 开发者网站的 layoutopt 部分查看更多信息。 5.3 界面控件 Android 系统的界面控件分为定制控件和系统控件。 定制控件是用户独立开发的控件,或通过继承并修改系统控件后所产生的新控件。能够为用户提供特 殊的功能或与众不同的显示需求方式;系统控件是 Android 系统提供给用户已经封装的界面控件,它提供 应用程序开发过程中常见功能控件。同时,系统控件更有利于帮助用户进行快速开发,能够使 Android 系 统中应用程序的界面保持一致性。 这里着重讲解一下系统控件的使用。 常见的系统控件包括 TextView、EditText、Button、ImageButton、Checkbox、RadioButton、Spinner、 ListView 和 TabHost。 5.3.1 TextView 和 EditText TextView 是一种用于显示字符串的控件;EditText 则是用来输入和编辑字符串的控件,它是一个具有编 辑功能的 TextView。 每个 TextView 期望的这样一个组件的属性:可以改变它的高度、宽度、字体、文字颜色、背景颜色等。 TextView 也有一些有用的独特属性,如表 5-11 所示。 表 5-11 TextView 也有一些有用的独特属性 属性名称 属性说明 autoLink 如果设置(TRUE),发现文本中所显示的 URL,并自动将它们转换为可点 击链接 autoText 如果设置(TRUE),发现并纠正在文本简单的拼写错误 editable 如果设置(TRUE),表示程序已定义的输入方法来接收输入文字(对 TextView 来说默认是 false,对 EditText 来说默认是 true) inputMethod 标识的输入法(EditText 上定义一个通用文本) 下面就通过一个例子来加深对这两个控件的理解。 图 5-13 TextView 与 EditView 效果图 ‐ 15 ‐ 专业始于专注 卓识源于远见 首先,建立一个“TextViewDemo”的程序,包含 TextView 和 EditText 两个控件,如图 5-13 所示。上 方“用户名”部分使用的是 TextView,下方的文字输入框使用的是 EditText。 TextViewDemo 在 XML 文件中的代码如代码清单 5-9 所示。 代码清单 5-9 main.xml 在上述代码中,第 1 行 android:id 属性声明了 TextView 的 ID,这个 ID 主要用于在代码中引用这个 TextView 对象;“@+id/TextView01”表示所设置的 ID 值;@表示后面的字符串是 ID 资源;加号(+)表 示需要建立新资源名称,并添加到 R.java 文件中;斜杠后面的字符串(TextView01)表示新资源的名称; 如果资源不是新添加的,或属于 Android 框架的 ID 资源,则不需要使用加号(+),对于 Android 框架中的 ID 资源,还必须添加 Android 包的命名空间,如 android:id="@android:id/empty"。 第 2 行的 android:layout_width 属性用来设置 TextView 的宽度,wrap_content 表示 TextView 的宽度只 要能够包含所显示的字符串即可。 第 3 行的 android:layout_height 属性用来设置 TextView 的高度。 第 4 行表示 TextView 所显示的字符串,在后面将通过代码更改 TextView 的显示内容。 第 7 行中“fill_content”表示 EditText 的宽度将等于父控件的宽度。 在上述步骤之后,修改 TextViewDemo.java 文件中代码为代码清单 5-10 所示的代码: 代码清单 5-10 TextViewDemo.java TextView textView = (TextView)findViewById(R.id.TextView01); EditText editText = (EditText)findViewById(R.id.EditText01); textView.setText("用户名:"); editText.setText(""); 第 1 行代码的 findViewById()方法能够通过 ID 引用界面上的任何控件,只要该控件在 XML 文件中定 义过 ID 即可。 第 3 行代码的 setText()方法用来设置 TextView 所显示的内容。 5.3.2 Button 和 ImageButton Button 是一种按钮控件,用户能够在该控件上点击,并后引发相应的事件处理方法;ImageButton 用 以实现能够显示图像功能的控件按钮。 下面通过一个例子来加深对这两个控件的理解。 1.建立一个“ButtonDemo”的程序 程序包含 Button 和 ImageButton 两个按钮,上方是“Button 按钮”,下方是一个 ImageButton 控件,如 图 5-14 所示。 ‐ 16 ‐ 专业始于专注 卓识源于远见 图 5-14 Button 与 ImageButton 效果图 ButtonDemo 在 XML 文件中的代码如代码清单 5-11 所示。 代码清单 5-11 main.xml 在上述代码中,定义 Button 控件的高度、宽度和内容及 ImageButton 控件的高度和宽度,但是没定义 显示的图像,在后面的代码中进行定义。 2.引入资源 将 download.png 文件复制到/res/drawable 文件夹下,在/res 目录上选择 Refresh,就可以看到新添加的 文件显示在/res/drawable 文件夹下,同时 R.java 文件内容也得到了更新,否则提示无法找到资源的错误。 3.更改 Button 和 ImageButton 内容 在 ButtonDemo.java 中引入 android.widget.Button 和 android.widget.ImageButton,并修改其代码如代码 清单 5-12 所示。 代码清单 5-12 ButtonDemo.java Button button = (Button)findViewById(R.id.Button01); ImageButton imageButton = (ImageButton)findViewById(R.id.ImageButton01); button.setText("Button 按钮"); imageButton.setImageResource(R.drawable.download); 上述代码中,第 1 行代码用于引用在 XML 文件中定义的 Button 控件。 第 2 行代码用于引用在 XML 文件中定义的 ImageButton 控件。 第 3 行代码将 Button 的显示内容更改为“Button 按钮”。 第 4 行代码利用 setImageResource()方法,将新加入的 png 文件 R.drawable.download 传递给 ImageButton。 4.按钮响应点击事件:添加点击事件的监听器 在 ButtonDemo.java 中添加代码清单 5-13 所示的代码。 代码清单 5-13 ButtonDemo.java final TextView textView = (TextView)findViewById(R.id.TextView01); button.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { textView.setText("Button 按钮"); } }); imageButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { textView.setText("ImageButton 按钮"); } }); 在本段代码中,第 2 行代码中 button 对象通过调用 setOnClickListener()方法,注册一个点击(Click) 事件的监听器 View.OnClickListener()。 第 3 行代码是点击事件的回调方法。 第 4 行代码将 TextView 的显示内容更改为“Button 按钮”。 这里我们来了解一下 View.OnClickListener()。 ‐ 17 ‐ 专业始于专注 卓识源于远见 View.OnClickListener()是 View 定义的点击事件的监听器接口,并在接口中仅定义了 onClick()方法。当 Button 从 Android 界面框架中接收到事件后,首先检查这个事件是否是点击事件,如果是点击事件,同时 Button 又注册了监听器,则会调用该监听器中的 onClick()方法。每个 View 仅可以注册一个点击事件的监 听器,如果使用 setOnClickListener()方法注册第二个点击事件的监听器,之前注册的监听器将被自动注销。 多个按钮注册到同一个点击事件的监听器上,代码如代码清单 5-14 所示。 代码清单 5-14 多个按钮注册到一个点击事件的监听器上 Button.OnClickListener buttonListener = new Button.OnClickListener(){ @Override public void onClick(View v) { switch(v.getId()){ case R.id.Button01: textView.setText("Button 按钮"); return; case R.id.ImageButton01: textView.setText("ImageButton 按钮"); return; } }}; Button.setOnClickListener(buttonListener); ImageButton.setOnClickListener(buttonListener); 该段代码中,第 1 行至第 12 行代码定义了一个名为 buttonListener 的点击事件监听器;第 13 行代码将 该监听器注册到 Button 上;第 14 行代码将该监听器注册到 ImageButton 上。 5.3.3 CheckBox 和 RadioButton CheckBox 是一个同时可以选择多个选项的控件;而 RadioButton 则是仅可以选择一个选项的控件; RadioGroup 是 RadioButton 的承载体,程序运行时不可见,应用程序中可能包含一个或多个 RadioGroup, 一个 RadioGroup 包含多个 RadioButton,在每个 RadioGroup 中,用户仅能够选择其中一个 RadioButton。 下面就通过一个例子来加深对这两个控件的理解,其效果如图 5-15 所示。 图 5-15 CheckBox 与 RadioButton 效果图 1.建立一个“CheckboxRadiobuttonDemo”程序 程序包含 5 个控件,从上至下分别是 TextView01、CheckBox01、 CheckBox02、RadioButton01、 RadioButton02,当选择 RadioButton01 时,RadioButton02 则无法选择。 CheckboxRadiobuttonDemo 在 XML 文件中的代码如代码清单 5-15 所示。 代码清单 5-15 main.xml 上述代码中,第 15 行标签声明了一个 RadioGroup;在第 18 行和第 23 行分别声明了两 个 RadioButton,这两个 RadioButton 是 RadioGroup 的子元素。 2.引用 CheckBox 和 RadioButton 引用 CheckBox 和 RadioButton 的方法参考代码清单 5-16 所示的代码。 代码清单 5-16 引用 CheckBox 和 RadioButton CheckBox checkBox1= (CheckBox)findViewById(R.id.CheckBox01); RadioButton radioButton1 =(RadioButton)findViewById(R.id.RadioButton01); 3.响应点击事件:添加点击事件的监听器 CheckBox 设置点击事件监听器的方法与 Button 设置点击事件监听器中介绍的方法相似,唯一不同在 于将 Button.OnClickListener 换成了 CheckBox.OnClickListener。 代码清单 5-17 设置 CheckBox 点击事件监听器 CheckBox.OnClickListener checkboxListener = new CheckBox.OnClickListener(){ @Override public void onClick(View v) { //过程代码 }}; checkBox1.setOnClickListener(checkboxListener); checkBox2.setOnClickListener(checkboxListener); RadioButton 设置点击事件监听器的方法如代码清单 5-18 所示。 代码清单 5-18 设置 RadioButton 点击事件监听器 RadioButton.OnClickListener radioButtonListener = new RadioButton.OnClickListener(){ @Override public void onClick(View v) { //过程代码 }}; radioButton1.setOnClickListener(radioButtonListener); radioButton2.setOnClickListener(radioButtonListener); 通过上述的讲解,可以得出这样的结论:CheckBox 是可以选择多个选项的复选框控件,当其中选项 被选中时,显示相应的 checkmark。这时,需要创建一个“OnClickListener”捕获点击事件,并可以添加所 需的功能代码。 ‐ 19 ‐ 专业始于专注 卓识源于远见 RadioGroup 是一个包含一些 RadioButton 的 ViewGroup。用户可选择一个按钮,通过对每一个 RadioButton 设置监听 OnClickListeners 来获取其选择。这里需注意,点击 RadioButton 并不触发 RadioGroup 的 Click 事件。 5.3.4 Spinner Spinner 是一种能够从多个选项中选择选项的控件,类似于桌面程序的组合框(ComboBox),但没有组合框 的下拉菜单,而是使用浮动菜单为用户提供选择,如图 5-16 所示。 下面就通过一个例子来加深对 Spinner 的理解。 图 5-16 Spinner 效果图 1.建立一个程序“SpinnerDemo” 程序包含 3 个子项,Spinner 控件在 XML 文件中的代码如代码清单 5-19 所示。 代码清单 5-19 main.xml 在上述代码中,第 5 行使用标签声明了一个 Spinner 控件;第 6 行代码中指定了该控件的宽 度为“300dip”。 2.修改 SpinnerDemo.java 文件 在 SpinnerDemo.java 文件中,定义一个 ArrayAdapter 适配器,在 ArrayAdapter 中添加 Spinner 的内容, 需要在代码中引入 android.widget.ArrayAdapter 和 android.widget.Spinner。 代码清单 5-20 SpinnerDemo.java Spinner spinner = (Spinner) findViewById(R.id.Spinner01); List list = new ArrayList(); list .add("Spinner 子项 1"); list .add("Spinner 子项 2"); list .add("Spinner 子项 3"); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, list ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); 本段代码中,第 2 行代码建立了一个字符串数组列表(ArrayList),这种数组列表可以根据需要进行增 减,表示数组列表中保存的是字符串类型的数据。 ‐ 20 ‐ 专业始于专注 卓识源于远见 在代码的第 3、4、5 行中,使用 add()方法分别向数组列表中添加 3 个字符串。 第 6 行代码建立了一个 ArrayAdapter 的数组适配器,数组适配器能够将界面控件和底层数据绑定在一 起。 第 7 行代码设定了 Spinner 的浮动菜单的显示方式,其中,android.R.layout.simple_ spinner_dropdown_item 是 Android 系统内置的一种浮动菜单。 第 8 行代码实现绑定过程,所有 ArrayList 中的数据,将显示在 Spinner 的浮动菜单中。 利用该段代码,适配器绑定界面控件和底层数据,如果底层数据更改了,用户界面也相应修改显示内 容,因此不需要应用程序再监视,从而极大地简化了代码的复杂性。 由上述例子可以得出结论:与上一小节中的 CheckBox 和 RadioButton 相比,Sipnner 需要的工作量最 大,但可以为用户提供相对来说较好的屏幕显示。如上所示,Spinner 显示当前选中的选项,当单击右侧的 下拉列表时,弹出一个可供选择的选项列表。为了实现该功能需满足以下条件。 (1)创建一个可供选择的选项列表(该列表可以是动态创建并被应用程序修改)。 (2)为 Spinner 的列表创建一个 ArrayAdapter 以实现其下拉列表的显示。这里需注意 ArrayAdapter 的格式(simple_spinner_item 和 simple_spinner_dropdown_item)是由 Android 系统定义的,它们不会出现 在资源 XML 文件中。 (3)创建 onItemSelectedListener 来捕捉 Spinner 的选择事件。监听 onItemSelected Listener 包含 onItemSelected()方法和 onNothingSelected()方法。 5.3.5 ListView ListView 是一种用于垂直显示的列表控件,如果显示内容过多,则会出现垂直滚动条。 ListView 能够通过适配器将数据和自身绑定,在有限的屏幕上提供大量内容供用户选择,所以是经常 使用的用户界面控件。同时,ListView 支持点击事件处理,用户可以用少量的代码实现复杂的选择功能。 例如,调用 setAdapter()提供的数据和 View 子项,并通过 setOnItemSelectedListener()方法监听 ListView 上 子项选择事件。 若 Activity 由一个单一的列表控制,则 Activity 需继承 ListActivity 类而不是之前介绍的常规的 Activity 类。如果主视图仅仅只是列表,甚至不需要建立一个 layout,ListActivity 会为用户构建一个全屏幕的列表。 如果想自定义布局,则需要确定 ListView 的 id 为@android:id/list,以便 ListActivity 知道其 Activity 的主要 清单。 下面就通过一个例子来加深对 ListView 的理解,如图 5-17 所示。 图 5-17 ListView 效果图 1.建立一个“ListViewDemo”程序 XML 文件中的代码如代码清单 5-21 所示。 ‐ 21 ‐ 代码清单 5-21 main.xml 专业始于专注 卓识源于远见 2.修改 ListViewDemo.java 文件 在 ListViewDemo.java 文件中,首先需要为 ListView 创建适配器,配置和连接列表,添加 ListView 中 所显示的内容。 代码清单 5-22 ListViewDemo.java public class ListViewDemo extends ListActivity { TextView selection; String[] items={"lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "morbi", "vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam", "vel", "erat", "placerat", "ante", "porttitor", "sodales", "pellentesque", "augue", "purus"}; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1,items)); selection=(TextView)findViewById(R.id.selection); } public void onListItemClick(ListView parent, View v, int position,long id) { selection.setText(items[position]); } } 继承 ListActivity 后,可以通过 setListAdapter()方法设置列表。这种情况下,提供了一个 ArrayAdapter 包装的字符串数组。其中 ArrayAdapter 的第二个参数 android.R.layout.simple_ list_item_1 控制了 ListView 中行的显示,上例中 android.R.layout.simple_list_item_1 该值提供了标准的 Android 清单行:大字体、很多的填充、文本和白色。重写 onListItemClick 方法以在列表上子项的选择发生变化时及时 更新其文本。 在默认情况下,ListView 只对列表子项的点击事件进行监听。但 ListView 也跟踪用户的选择,或多个 可能的选择列表,但它需要一些变化。  在 Java 代码中调用 ListView 的 setChoiceMode()方法来设置选择模式,可供选择的模式有: CHOICE_MODE_SINGLE 和 CHOICE_MODE_MULTIPLE 两种。可以通过 getListView()方法在 ListActivity 中获取 ListView。  在构造 ArrayAdapter 时,第二个参数选择使用以下两种参数可以使列表上子项单选或是复选: android.R.layout.simple_list_item_single_choice 和 android.R.layout. simple_list_item_multiple_choice,如图 5-18 所示。 ‐ 22 ‐ 专业始于专注 卓识源于远见 图 5-18 单选、复选模式  通过调用 getCheckedItemPositions()方法来判断用户选择的子项。 5.3.6 TabHost Tab 标签页是界面设计时经常使用的界面控件,可以实现多个分页之间的快速切换,每个分页可以显 示不同的内容。 对 Tab 标签页的使用,首先要设计所有的分页的界面布局,在分页设计完成后,使用代码建立 Tab 标 签页,并给每个分页添加标识和标题,最后确定每个分页所显示的界面布局。其中,每个分页建立一个 XML 文件,用以编辑和保存分页的界面布局,使用的方法与设计普通用户界面一样。 下面就通过一个例子来加深对 Tab 标签页的理解,如图 5-19 所示的效果图。 图 5-19 Tab 标签页效果图 1.建立一个“TabDemo”程序 程序包含两个 XML 文件,分别为 tab1.xml 和 tab2.xml,这两个文件分别使用线性布局、相对布局和 绝对布局示例中的 main.xml 的代码,并将布局的 ID 分别定义为 layout01 和 layout02。 其中,tab1.xml 文件代码如代码清单 5-23 所示。 代码清单 5-23 tab1.xml tab2.xml 文件代码如代码清单 5-24 所示。 代码清单 5-24 tab2.xml ‐ 23 ‐ 专业始于专注 卓识源于远见 2.修改 TabDemo.java 文件 在 TabDemo.java 文件中输入代码清单 5-25 所示的代码,创建 Tab 标签页,并建立子页与界面布局直 接的关联关系。 代码清单 5-25 TabDemo.java package com.example.TabDemo; import android.app.TabActivity; import android.os.Bundle; import android.widget.TabHost; import android.view.LayoutInflater; public class TabDemo extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabHost tabHost = getTabHost(); LayoutInflater.from(this).inflate(R.layout.tab1, tabHost.getTabContentView(),true); LayoutInflater.from(this).inflate(R.layout.tab2, tabHost.getTabContentView(),true); tabHost.addTab(tabHost.newTabSpec("TAB1") .setIndicator("线性布局").setContent(R.id.layout01)); tabHost.addTab(tabHost.newTabSpec("TAB2") .setIndicator("相对布局").setContent(R.id.layout02)); } 该段代码中,第 8 行代码“public class TabDemo extends TabActivity”的声明 TabDemo 类继承于 TabActivity,与以往继承 Activity 不同,TabActivity 支持内嵌多个 Activity 或 View。 第 12 行代码“TabHost tabHost = getTabHost();”通过 getTabHost()方法获得 Tab 标签页的容器,用以承 载可以点击的 Tab 标签和分页的界面布局。 第 13 行代码“LayoutInflater.from(this).inflate(R.layout.tab1,tabHost. getTabContent View(),true);”通过 LayoutInflater 将 tab1.xml 文件中的布局转换为 Tab 标签页可以使用的 View 对象。 第 14 行 代 码 “ tabHost.addTab(tabHost.newTabSpec("TAB1").setIndicator(" 线 性 布 局 ").setContent(R.id.layout01));”使用 addTab()方法添加了第 1 个分页,tabHost.newTabSpec ("TAB1")表明在第 12 行代码中建立的 tabHost 上,添加一个标识为 TAB1 的 Tab 分页,同时使用 setIndicator() 方法设定分页显示的标题,使用 setContent()方法设定分页所关联的界面布局。 问:在使用 Tab 标签页时,只能像上述例子中一样将不同分页的界面布局保存在不同的 XML 文件中吗? 答:除了像上述中将不同分页的界面布局保存在不同的 XML 文件中外,也可以将所有分页的布局保存在同一个 XML 文件中。两者有不同的利弊:  第一种方法有利于在 Eclipse 开发环境中进行可视化设计,并且不同分页的界面布局在不同的文件中更加易 于管理。  第二种方法则可以产生较少的 XML 文件,同时编码时的代码也会更加简洁。 5.4 菜单 菜单是应用程序中非常重要的组成部分,能够在不占用界面空间的前提下,为应用程序提供统一的功 能和设置界面,并为程序开发人员提供了易于使用的编程接口。Android 系统支持 3 种菜单:选项菜单 (Option Menu)、子菜单(Submenu)、快捷菜单(Context Menu)。 ‐ 24 ‐ 5.4.1 选项菜单 专业始于专注 卓识源于远见 选项菜单是一种经常被使用的 Android 系统菜单,可以分为图标菜单(Icon Menu)和扩展菜单 (Expanded Menu)两类,可通过“菜单键”(Menu key)打开。 图标菜单能够同时显示文字和图标,最多支持 6 个子项,但图标菜单不支持单选框和复选框。 扩展菜单在图标菜单子项多余 6 个时才出现,通过点击图标菜单最后的子项“More”才能打开。扩展 菜单是垂直的列表型菜单,不能够显示图标,但支持单选框和复选框。 图 5-20 图标菜单 图 5-21 扩展菜单 1.重写 onCreateOptionMenu()方法 在 Android 应用程序中使用选项菜单,需重载 Activity 的 onCreateOptionMenu()方法。初次使用选项菜 单时,会调用 onCreateOptionMenu()方法,用来初始化菜单子项的相关内容,因此这里需要设置菜单子项 自身的子项 ID 和组 ID、菜单子项显示的文字和图片等。代码如代码清单 5-26 所示。 代码清单 5-26 重载 onCreateOptionMenu()方法 final static int MENU_DOWNLOAD = Menu.FIRST; final static int MENU_UPLOAD = Menu.FIRST+1; @Override public boolean onCreateOptionsMenu(Menu menu){ menu.add(0,MENU_DOWNLOAD,0,"下载设置"); menu.add(0,MENU_UPLOAD,1,"上传设置"); return true; } 第 1 行和第 2 行代码将菜单子项 ID 定义成静态常量,并使用静态常量 Menu.FIRST(整数类型,值为 1)定义第一个菜单子项,以后的菜单子项仅需在 Menu.FIRST 增加相应的数值即可。 第 4 行代码 Menu 对象作为一个参数被传递到方法内部,因此在 onCreateOptionsMenu()方法中,用户 可以使用 Menu 对象的 add()方法添加菜单子项。其中 add()方法的语法如下。 MenuItem android.view.Menu.add(int groupId, int itemId, int order, CharSequence title) 第 1 个参数 groupId 是组 ID,用以批量的对菜单子项进行处理和排序;第 2 个参数 itemId 是子项 ID, 是每一个菜单子项的唯一标识,通过子项 ID 使应用程序能够定位到用户所选择的菜单子项;第 3 个参数 order 是定义菜单子项在选项菜单中的排列顺序;第 4 个参数 title 是菜单子项所显示的标题。 第 7 行代码是 onCreateOptionsMenu()方法返回值,方法的返回值类型为布尔型:返回 true 将显示方法 中设置的菜单,否则不能够显示菜单。 做完以上步骤后,使用 setIcon()方法和 setShortcut()方法,添加菜单子项的图标和快捷键,如代码 清单 5-27 所示。 代码清单 5-27 添加菜单子项的图标和快捷键 menu.add(0,MENU_DOWNLOAD,0,"下载设置") .setIcon(R.drawable.download); .setShortcut(','d'); 代码中,利用 MENU_DOWNLOAD 菜单设置图标和快捷键的代码;第 2 行代码中使用了新的图像资 源,用户将需要使用的图像文件复制到/res/drawable 目录下;setShortcut()方法第一个参数是为数字键盘设 定的快捷键,第二个参数是为全键盘设定的快捷键,且不区分字母的大小写。 ‐ 25 ‐ 2.重写 onPrepareOptionsMenu()方法 专业始于专注 卓识源于远见 重载 onPrepareOptionsMenu()方法,能够动态地添加、删除菜单子项,或修改菜单的标题、图标和可 见性等内容。onPrepareOptionsMenu()方法的返回值的含义与 onCreateOptions Menu()方法相同:返回 true 则显示菜单,返回 false 则不显示菜单。 代码清单 5-28 所示的代码是在用户每次打开选项菜单时,在菜单子项中显示用户打开该子项的次数。 代码清单 5-28 菜单子项中显示用户打开该子项的次数 static int MenuUploadCounter = 0; @Override public boolean onPrepareOptionsMenu(Menu menu){ MenuItem uploadItem = menu.findItem(MENU_UPLOAD); uploadItem.setTitle("上传设置:" +String.valueOf(MenuUploadCounter)); return true; } 第 1 行代码设置一个菜单子项的计数器,用来统计用户打开“上传设置”子项的次数;第 4 行代码是 通过将菜单子项的 ID 传递给 menu.findItem()方法,获取到菜单子项的对象;第 5 行代码是通过 MenuItem 的 setTitle()方法修改菜单标题。 问:onCreateOptionMenu()方法和 onPrepareOptionsMenu()方法有什么区别? 答:onCreateOptionMenu()方法在 Menu 显示之前只调用一次;而 onPrepareOptionsMenu()方法在每次显示 Menu 之 前都会调用,一般用它执行 Menu 的更新操作。 3.onOptionsItemSelected ()方法 onOptionsItemSelected ()方法能够处理菜单选择事件,且该方法在每次单击菜单子项时都会被调用。 下面的代码说明了如何通过菜单子项的 ID 执行不同的操作。 代码清单 5-29 onOptionsItemSelected() @Override public boolean onOptionsItemSelected(MenuItem item){ switch(item.getItemId()){ case MENU_DOWNLOAD: MenuDownlaodCounter++; return true; case MENU_UPLOAD: MenuUploadCounter++; return true; } return false; } onOptionsItemSelected ()的返回值表示是否对菜单的选择事件进行处理,如果已经处理过则返回 true, 否则返回 false;第 3 行的 MenuItem.getItemId()方法可以获取到被选择菜单子项的 ID。 程序运行后,通过单击“菜单键”可以调出程序设计的两个菜单子项,如图 5-22 所示。 图 5-22 运行效果图 ‐ 26 ‐ 5.4.2 子菜单 专业始于专注 卓识源于远见 子菜单是能够显示更加详细信息的菜单子项,如图 5-23 所示。其中,菜单子项使用了浮动窗体的显示 形式,能够更好地适应小屏幕的显示方式。 图 5-23 菜单子项 Android 系统的子菜单使用非常灵活,可以在选项菜单或快捷菜单中使用子菜单,有利于将相同或相 似的菜单子项组织在一起,便于显示和分类。但是,子菜单不支持嵌套。 子菜单的添加使用 addSubMenu()方法实现,代码如代码清单 5-30 所示。 代码清单 5-30 onOptionsItemSelected() SubMenu uploadMenu = (SubMenu) menu.addSubMenu (0,MENU_UPLOAD,1,"上传设置").setIcon(R.drawable.upload); uploadMenu.setHeaderIcon(R.drawable.upload); uploadMenu.setHeaderTitle("上传参数设置"); uploadMenu.add(0,SUB_MENU_UPLOAD_A,0,"上传参数 A"); uploadMenu.add(0,SUB_MENU_UPLOAD_B,0,"上传参数 B"); 第 1 行代码在 onCreateOptionsMenu()方法传递的 menu 对象上调用 addSubMenu()方法,在选项菜单中 添加一个菜单子项,用户单击后可以打开子菜单;addSubMenu()方法与选项菜单中使用过的 add()方法支持 相同的参数,同样可以指定菜单子项的 ID、组 ID 和标题等参数,并且能够通过 setIcon()方法显示菜单的 图标。 第 2 行代码使用 setHeaderIcon ()方法,定义子菜单的图标。 第 3 行定义子菜单的标题,若不规定子菜单的标题,子菜单将显示父菜单子项标题,即第 1 行代码中 “上传设置”。 第 4 行和第 5 行在子菜单中添加了两个菜单子项,菜单子项的更新方法和选择事件处理方法,仍然使 用 onPrepareOptionsMenu()方法和 onOptionsItemSelected ()方法。 以上一小节的代码为基础,将“上传设置”改为子菜单,并在子菜单中添加“上传参数 A”和“上传 参数 B”两个菜单子项。运行结果如图 5-24 所示。 图 5-24 运行效果图 ‐ 27 ‐ 5.4.3 上下文菜单(Context Menu) 专业始于专注 卓识源于远见 快捷菜单同样采用了浮动窗体的显示方式,与子菜单的实现方式相同,但两种菜单的启动方式却截然 不同。  启动方式:快捷菜单类似于普通桌面程序中的“右键菜单”,当用户点击界面元素超过 2 秒后,将 启动注册到该界面元素的快捷菜单。  使 用 方 法 : 与 使 用 选 项 菜 单 的 方 法 非 常 相 似 , 需 要 重 载 onCreateContextMenu() 方 法 和 onContextItemSelected()方法。 1.onCreateContextMenu()方法 onCreateContextMenu()方法主要用来添加快捷菜单所显示的标题、图标和菜单子项等内容,选项菜单 中 的 onCreateOptionsMenu() 方 法 仅 在 选 项 菜 单 第 一 次 启 动 时 被 调 用 一 次 , 而 快 捷 菜 单 的 onCreateContextMenu()方法每次启动时都会被调用一次。 代码清单 5-31 onCreateContextMenu () final static int CONTEXT_MENU_1 = Menu.FIRST; final static int CONTEXT_MENU_2 = Menu.FIRST+1; final static int CONTEXT_MENU_3 = Menu.FIRST+2; @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){ menu.setHeaderTitle("快捷菜单标题"); menu.add(0, CONTEXT_MENU_1, 0,"菜单子项 1"); menu.add(0, CONTEXT_MENU_2, 1,"菜单子项 2"); menu.add(0, CONTEXT_MENU_3, 2,"菜单子项 3"); } ContextMenu 类支持 add()方法(代码第 7 行)和 addSubMenu()方法,可以在快捷菜单中添加菜单子项 和子菜单。 第 5 行代码的 onCreateContextMenu()方法中的参数:第 1 个参数 menu 是需要显示的快捷菜单;第 2 个参数 v 是用户选择的界面元素;第 3 个参数 menuInfo 是所选择界面元素的额外信息。 2.onContextItemSelected ()方法 菜单选择事件的处理需要重载 onContextItemSelected()方法,该方法在用户选择快捷菜单中的菜单子项 后被调用,与 onOptionsItemSelected ()方法的使用方法基本相同。 代码清单 5-32 onContextItemSelected() @Override public 28oolean onContextItemSelected(MenuItem item){ switch(item.getItemId()){ case CONTEXT_MENU_1: LabelView.setText("菜单子项 1"); return true; case CONTEXT_MENU_2: LabelView.setText("菜单子项 2"); return true; case CONTEXT_MENU_3: LabelView.setText("菜单子项 3"); return true; } return false; } 3.registerForContextMenu()方法 使用 registerForContextMenu()方法,将快捷菜单注册到界面控件上(代码清单 5-33 中第 7 行)。这样, 用户在长时间点击该界面控件时,便会启动快捷菜单。 ‐ 28 ‐ 专业始于专注 卓识源于远见 为了能够在界面上直接显示用户所选择快捷菜单的菜单子项,在代码中引用了界面元素 TextView(代 码清单 5-33 中第 6 行),通过更改 TextView 的显示内容,显示用户所选择的菜单子项。 代码清单 5-33 registerForContextMenu () TextView LabelView = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LabelView = (TextView)findViewById(R.id.label); registerForContextMenu(LabelView); } 4.main.xml 代码清单 5-34 main.xml 上述代码为/src/layout/main.xml 文件的部分内容,第 1 行声明了 TextView 的 ID 为 label,在代码清单 5-33 的第 6 行中,通过 R.id.label 将 ID 传递给 findViewById()方法,这样用户便能够引用该界面元素,并 能够修改该界面元素的显示内容。 需要注意的一点,代码清单 5-34 的第 2 行,将 android:layout_width 设置为 match_parent,这样 TextView 将填充满父节点的所有剩余屏幕空间,用户点击屏幕 TextView 下方任何位置都可以启动快捷菜单。如果将 android:layout_width 设置为 wrap_content,则用户必须准确单击 TextView 才能启动快捷菜单。 图 5-25 为快捷菜单的运行效果图。 图 5-25 运行效果图 问:菜单可不可以像界面布局一样在 XML 文件中进行定义? 答:菜单可以像界面布局一样在 XML 文件中进行定义。使用 XML 文件定义界面菜单,将代码与界面设计分类, 有助于简化代码的复杂程度,并且更有利于界面的可视化。 下 面 将 快 捷 菜 单 的 示 例 程 序 MyContextMenu 改 用 XML 实 现 , 新 程 序 的 工 程 名 称 为 MyXLMContoxtMenu。 首先需要创建保存菜单内容的 XML 文件:在/src 目录下建立子目录 menu,并在 menu 下建立 context_menu.xml 文件,代码如代码清单 5-35 所示。 代码清单 5-35 context_menu.xml 在描述菜单的 XML 文件中,必须以标签(代码第 1 行)作为根节点,标签(代码第 2 行)用来描述菜单中的子项,标签可以通过嵌套实现子菜单的功能。 XML 菜单的显示结果如图 5-26 所示。 图 5-26 XML 菜单的显示结果 在 XML 文件中定义菜单后,在 onCreateContextMenu()方法中调用 inflater.inflate()方法,将 XML 资源 文件传递给菜单对象,代码如代码清单 5-36 所示。 代码清单 5-36 onCreateContextMenu() @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){ MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.context_menu, menu); } 第 4 行代码中的 getMenuInflater()为当前的 Activity 返回 MenuInflater;第 5 行代码将 XML 资源文件 R.menu.context_menu,传递给 menu 这个快捷菜单对象。 5.5 界面事件 在 Android 系统中,存在多种界面事件,如点击事件、触摸事件、焦点事件和菜单事件等,在这些界 面事件发生时,Android 界面框架调用界面控件的事件处理方法对事件进行处理。 Android 系统界面事件的传递和处理遵循以下规则。  如果界面控件设置了事件监听器,则事件将先传递给事件监听器。  如果界面控件没有设置事件监听器,界面事件则会直接传递给界面控件的其他事件处理方法。  即使界面控件设置了事件监听器,界面事件也可以再次传递给其他事件处理方法。  是否继续传递事件给其他处理方法是由事件监听器处理方法的返回值决定的。  如果监听器处理方法的返回值为 true,表示该事件已经完成处理过程,不需要其他处理方法参与 处理过程,这样事件就不会再继续进行传递。  如果监听器处理方法的返回值为 false,则表示该事件没有完成处理过程,或需要其他处理方法捕 获到该事件,事件会被传递给其他的事件处理方法。 在 MVC 模型中,控制器根据界面事件(UI Event)类型不同,将事件传递给界面控件不同的事件处 理方法。  按键事件(KeyEvent)将传递给 onKey()方法进行处理。  触摸事件(TouchEvent)将传递给 onTouch()方法进行处理。 5.5.1 按键事件 ‐ 30 ‐ 专业始于专注 卓识源于远见 下面以 EditText 控件中的按键事件为例,说明 Android 系统界面事件传递和处理过程。 假设 EditText 控件已经设置了按键事件监听器,当用户按下键盘上的某个按键时,控制器将产生 KeyEvent 按键事件。Android 系统会首先判断 EditText 控件是否设置了按键事件监听器,因为 EditText 控 件已经设置按键事件监听器 OnKeyListener,所以按键事件先传递到监听器的事件处理方法 onKey()中,事 件能够继续传递给 EditText 控件的其他事件处理方法,完全根据 onKey()方法的返回值来确定:如果 onKey() 方法返回 false,事件将继续传递,这样 EditText 控件就可以捕获到该事件,将按键的内容显示在 EditText 控件中;如果 onKey()方法返回 true,将阻止按键事件的继续传递,这样 EditText 控件就不能够捕获到按键 事件,也就不能够将按键内容显示在 EditText 控件中。 Android 界面框架支持对按键事件的监听,并能够将按键事件的详细信息传递给处理方法。为了处理 控件的按键事件,先需要设置按键事件的监听器,并重载 onKey()方法,示例代码如代码清单 5-37 所示。 代码清单 5-37 设置按键事件的监听器,并重载 onKey()方法 entryText.setOnKeyListener(new OnKeyListener(){ @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { //过程代码…… return true/false; } 第 1 行代码是设置控件的按键事件监听器。 第 3 行代码的 onKey ()方法中的参数:第 1 个参数 View 表示产生按键事件的界面控件;第 2 个参数 keyCode 表示按键代码;第 3 个参数 KeyEvent 则包含了事件的详细信息,如按键的重复次数、硬件编码和 按键标志等。 第 5 行代码是 onKey()方法的返回值:返回 true,阻止事件传递;返回 false,允许继续传递按键事件。 KeyEventDemo 是一个说明如何处理按键事件的示例。 KeyEventDemo 用户界面如图 5-27 所示。 图 5-27 KeyEventDemo 用户界面 从图 5-27 中可以看出,最上方的 EditText 控件是输入字符的区域,中间的 CheckBox 控件用来控制 onKey() 方法的返回值,最下方的 TextView 控件用来显示按键事件的详细信息,包括按键动作、按键代码、按键字 符、UNICODE 编码、重复次数、功能键状态、硬件编码和按键标志。 界面的 XML 文件的代码如代码清单 5-38 所示 代码清单 5-38 界面 XML 文件 在 EditText 中,当任何一个键按下或抬起时,都会引发按键事件。为了能够使 EditText 处理按键事件, 需要使用 setOnKeyListener ()方法在代码中设置按键事件监听器,并在 onKey()方法中添加按键事件的处理 过程,代码如代码清单 5-39 所示。 代码清单 5-39 setOnKeyListener() entryText.setOnKeyListener(new OnKeyListener(){ @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { int metaState = keyEvent.getMetaState(); int unicodeChar = keyEvent.getUnicodeChar(); String msg = ""; msg +="按键动作:" + String.valueOf(keyEvent.getAction())+"\n"; msg +="按键代码:" + String.valueOf(keyCode)+"\n"; msg +="按键字符:" + (char)unicodeChar+"\n"; msg +="UNICODE:" + String.valueOf(unicodeChar)+"\n"; msg +="重复次数:" + String.valueOf(keyEvent.getRepeatCount())+"\n"; msg +="功能键状态:" + String.valueOf(metaState)+"\n"; msg +="硬件编码:" + String.valueOf(keyEvent.getScanCode())+"\n"; msg +="按键标志:" + String.valueOf(keyEvent.getFlags())+"\n"; labelView.setText(msg); if (checkBox.isChecked()) return true; else return false; } 在上述代码中,第 4 行代码用来获取功能键状态。功能键包括左 Alt 键、右 Alt 键和 Shift 键,当这 3 个功能键被按下时,功能键代码 metaState 值分别为 18、34 和 65;但没有功能键被按下时,功能键代码 metaState 值分别为 0。 第 5 行代码获取了按键的 Unicode 值,而在第 9 行中,将 Unicode 转换为了字符,显示在 TextView 中。 第 7 行代码获取了按键动作,0 表示按下按键,1 表示抬起按键。第 7 行代码获取按键的重复次数, 但当按键被长时间按下时,则会产生这个属性值。 第 13 行代码获取了按键的硬件编码,各硬件设备的按键硬件编码都不相同,因此该值一般用于调试。 第 14 行获取了按键事件的标志符。 5.5.2 触摸事件 Android 界面框架支持对触摸事件的监听,并能够将触摸事件的详细信息传递给处理方法,不过需要 设置触摸事件的监听器,并重载 onTouch ()方法。 设置触摸事件的监听器,并重载 onTouch ()方法的代码如代码清单 5-40 所示。 代码清单 5-40 设置触摸事件的监听器,并重载 onTouch ()方法 touchView.setOnTouchListener(new View.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { //过程代码… return true/false; } 在上述代码中,第 1 行代码是设置控件的触摸事件监听器;在第 3 行的 onTouch()方法中,第 1 个参 数 View 表示产生触摸事件的界面控件;第 2 个参数 MontionEvent 表示触摸事件的详细信息,如产生时间、 坐标和触点压力等;第 5 行是 onTouch()方法的返回值。 TouchEventDemo 是一个说明如何处理触摸事件的示例。 TouchEventDemo 用户界面如图 5-28 所示。 ‐ 32 ‐ 专业始于专注 卓识源于远见 图 5-28 TouchEventDemo 用户界面 由图 5-28 可以看出,上半部分的浅蓝色区域是可以接受触摸事件的区域,用户可以在 Android 模拟器 中使用鼠标点击屏幕用以模拟触摸手机屏幕;下方黑色区域是显示区域,用来显示触摸事件类型、相对坐标、 绝对坐标、触点压力、触点尺寸和历史数据量等信息。 在用户界面中使用了线性布局,并加入了 3 个 TextView 控件:第 1 个 TextView(ID 为 touch_area) 用来标识触摸事件的测试区域;第 2 个 TextView(ID 为 history_label)用来显示触摸事件的历史数据量; 第 3 个 TextView(ID 为 event_label)用来显示触摸事件的详细信息,包括类型、相对坐标、绝对坐标、触 点压力和触点尺寸。 XML 文件的代码如代码清单 5-41 所示。 代码清单 5-41 XML 文件 在上述代码中,第 9 行代码定义了 TextView 的背景颜色,#80A0FF 是颜色代码;第 10 行代码定义了 TextView 的字体颜色。 在代码中为了能够引用 XML 文件中声明的界面元素,使用了代码清单 5-42 所示的代码。 代码清单 5-42 在代码中引用 XML 文件中声明的界面元素 TextView labelView = null; labelView = (TextView)findViewById(R.id.event_label); TextView touchView = (TextView)findViewById(R.id.touch_area); final TextView historyView = (TextView)findViewById(R.id.history_label); ‐ 33 ‐ 专业始于专注 卓识源于远见 当手指接触到触摸屏、在触摸屏上移动或离开触摸屏时,分别会引发 ACTION_DOWN、ACTION_UP 和 ACTION_MOVE 触摸事件,而无论是哪种触摸事件,都会调用 onTouch()方法进行处理。事件类型包含 在 onTouch()方法的 MotionEvent 参数中,可以通过 getAction()方法获取到触摸事件的类型,然后根据触摸 事 件 的 不 同 类 型 进 行 不 同 的 处 理 。 为 了 能 够 使 屏 幕 最 上 方 的 TextView 处 理 触 摸 事 件 , 需 要 使 用 setOnTouchListener()方法在代码中设置触摸事件监听器,并在 onTouch()方法添加触摸事件的处理过程。代 码如代码清单 5-43 所示。 代码清单 5-43 onTouch() touchView.setOnTouchListener(new View.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case (MotionEvent.ACTION_DOWN): Display("ACTION_DOWN",event); break; case (MotionEvent.ACTION_UP): int historySize = ProcessHistory(event); historyView.setText("历史数据量:"+historySize); Display("ACTION_UP",event); break; case (MotionEvent.ACTION_MOVE): Display("ACTION_MOVE",event); break; } return true; } }); 第 7 行代码的 Display()是一个自定义方法,主要用来显示触摸事件的详细信息,方法的代码和含义将 在后面进行介绍;第 10 行代码的 ProcessHistory()也是一个自定义方法,用来处理触摸事件的历史数据; 第 11 行代码是使用 TextView 显示历史数据的数量。 MotionEvent 参数中不仅有触摸事件的类型信息,还有触点的坐标信息,获取方式是使用 getX()和 getY() 方法,这两个方法获取到的是触点相对于父界面元素的坐标信息。如果需要获取绝对坐标信息,则可使用 getRawX()和 getRawY()方法。 触点压力是一个介于 0 和 1 之间的浮点数,用来表示用户对触摸屏施加压力的大小,接近 0 表示压力 较小,接近 1 表示压力较大,获取触摸事件触点压力的方式是调用 getPressure()方法。 触点尺寸指用户接触触摸屏的接触点大小,也是一个介于 0 和 1 之间的浮点数,接近 0 表示尺寸较小, 接近 1 表示尺寸较大,可以使用 getSize()方法获取。 Display()将 MotionEvent 参数中的事件信息提取出来,并显示在用户界面上。代码如代码清单 5-44 所 示。 代码清单 5-44 Display() private void Display(String eventType, MotionEvent event){ int x = (int)event.getX(); int y = (int)event.getY(); float pressure = event.getPressure(); float size = event.getSize(); int RawX = (int)event.getRawX(); int RawY = (int)event.getRawY(); String msg = ""; msg += "事件类型:" + eventType + "\n"; msg += "相对坐标:"+String.valueOf(x)+","+String.valueOf(y)+"\n"; msg += "绝对坐标:"+String.valueOf(RawX)+","+String.valueOf(RawY)+"\n"; msg += "触点压力:"+String.valueOf(pressure)+", "; msg += "触点尺寸:"+String.valueOf(size)+"\n"; labelView.setText(msg); ‐ 34 ‐ 专业始于专注 卓识源于远见 } 一般情况下,如果用户将手指放在触摸屏上,但不移动,然后抬起手指,应先后产生 ACTION_DOWN 和 ACTION_UP 两个触摸事件。但如果用户在屏幕上移动手指,然后再抬起手指,则会产生这样的事件序 列:ACTION_DOWN → ACTION_MOVE → ACTION_MOVE → ACTION_MOVE → …→ ACTION_UP。 在手机上运行的应用程序,效率是非常重要的。如果 Android 界面框架不能产生足够多的触摸事件, 则应用程序就不能够很精确地描绘触摸屏上的触摸轨迹。如果 Android 界面框架产生了过多的触摸事件, 虽然能够满足精度的要求,但也降低了应用程序效率。 针对以上问题 Android 界面框架使用了“打包”的解决方法。在触点移动速度较快时会产生大量的数 据,每经过一定的时间间隔便会产生一个 ACTION_MOVE 事件,在这个事件中,除了有当前触点的相关 信息外,还包含这段时间间隔内触点轨迹的历史数据信息,这样既能够保持精度,又不至于产生过多的触 摸事件。 通常情况下,在 ACTION_MOVE 的事件处理方法中,都先处理历史数据,然后再处理当前数据,代 码如代码清单 5-45 所示。 代码清单 5-45 ProcessHistory(MotionEvent event) private int ProcessHistory(MotionEvent event) { int historySize = event.getHistorySize(); for (int i = 0; i < historySize; i++) { long time = event.getHistoricalEventTime(i); float pressure = event.getHistoricalPressure(i); float x = event.getHistoricalX(i); float y = event.getHistoricalY(i); float size = event.getHistoricalSize(i); // 处理过程… } return historySize; } 在上述代码中,第 3 行代码获取了历史数据的数量;然后在第 4 行至 12 行中循环处理这些历史数据; 第 5 行代码获取了历史事件的发生时间;第 6 行代码获取历史事件的触点压力;第 7 行和第 8 行代码获取 历史事件的相对坐标;第 9 行获取历史事件的触点尺寸;在第 14 行返回历史数据的数量,主要是用于界 面显示。 问:Android 模拟器支持触点压力和触点尺寸的模拟吗? 答:Android 模拟器并不支持触点压力和触点尺寸的模拟,所有触点压力恒为 1.0,触点尺寸恒为 0.0。同时,Android 模拟器上也无法产生历史数据,因此历史数据量一直显示为 0。 5.6 自定义样式和主题 Android 也可以像 HTML/CSS 中的 style 一样,使用自定义的 style 样式。Android 一般通过 value 文件 夹下面新建一个 styles.xml 文件来设置自定义样式。这里开发者可以设置高度、填充字体颜色、字体大小、 背景颜色等描述一个 View 或者一个窗口的显示属性。这就像 Web 开发中的 CSS 样式表,使我们的样式独 立于内容进行设计开发。 主题和样式都是通过在 xml 文件中预定义一系列属性值,通过这些属性值来形成统一的显示风格。不 同的是,样式只能应用于某种类型的 View;而主题刚好相反,不能应用于特定的 View,而只能作用于一 个或多个 Activity,或是整个应用。 下面通过代码学习一下如何自定义样式与主题,并在程序中应用。 首先是自定义样式和主题。在项目的 res/values/目录下添加 styles.xml。如代码清单 5-46 所示。 ‐ 35 ‐ 代码清单 5-46 styles.xml 专业始于专注 卓识源于远见 由上述代码可以看出,主题和样式都可以通过在 由上述代码可以看出,主题和样式都可以通过在
Top_arrow
回到顶部
EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。