当前位置:首页 >> 建筑/土木 >>

Android


Android_SDK 开发范例完整版.txt2008 太不正常了,一切都不正常!在这个关键时刻,中国 男足挺身而出,向全世界证明:中国男足还是正常的! 本文由 kingwj6 贡献 pdf 文档可能在 WAP 端浏览体验不佳。建议您优先选择 TXT,或下载源文件到本机查看。 Google Android SDK 开发范例大全 第二章: Android 初体验 Android 操

作系统顶着 Google 与 Open Handset Alliance 的 光环, 让很多程序 员在 Android 手机还没上市之前, 就开始紧追着官方文档、 Early SDK 版 本更新, 这是 IT 业界罕见的现象, 因为大家都抱持着 “不想输在起跑点上” 的心态。 也 这 是手机发展的历史中,第一次有机会让所有的程序员站在同一个 起跑点开始,无论是 Java World 论坛、或是 Google 官方的 Developer Discussion Group 都 见到相当热烈的讨论。 从这一章开始,将对 Android 的程序架构、程序 进入点、编译以及执行, 进行一系列的导 航,而本书规划之初,并非针对 Java 初学者或从未接触过程序 设计的朋友所设计, 故在 这一章简要的 overview 当中, 则以快速掌握开发条件、 开发环境为目的,若你是第一次 接触 Java 程序语言,或从未写过面向对象程序 设计,建议你先学习 Java 语言,打好基础 后, 再回过头来进入本章。 接下来的内容分成五个部分:安装 Android SDK、创建 Android 手机开发项 目 (Project) 了解 Android 程序的生命周期, 、 初探可视化的开发工具, 最后 将 应用程序部署到模拟器和 Android 手机上,为避免过于概念性的论述及篇幅 的浪费,所有 操作练习皆以步骤的方法描述,只要跟着操作即可上手,在体验完 本章的内容之后,你就可 以 开始本书范例的学习之旅了。 在开始之前,你需要先准备以下作业环境以及程序: 必备 项目 Microsoft Windows XP/Microsoft Windows Vista 操作系统 或 Mac OS X 10.4.8 或 更新的版本(硬件必须是 x86 的版本) 或 Linux Android SDK 1.0r2 以上 (本书所有范 例皆以 Android SDK 1.1r1 为开发环境) Java Development Kit(JDK)v6.0 以上 Eclipse 开发 IDE 程序 (本书所有范例皆以 eclipse-jee-ganymede-SR1-win32 版 本为编译环境) 自我检查 (ˇ) 准备就绪之后,就可以前往 Android 官方网站 (http://developer.android.com/)下 载 Android SDK,也可以到( http://androidappdocs.appspot.com)去下载。 ▲图 2-1 到 Android 官方网站下载 SDK 下载后的 Android SDK 为压缩文件,请将它 解压缩到磁盘中,例如下面的文 件夹: D:\SDK\android 而 Eclipse 编译 IDE 环境中,需 安装 ADT(Android Development Tools) plug-in, 此为 Android 的开发工具, 启动 Eclipse 后, 请运行“Help—Software Updates>>Find and Install” ,并按下“Add Site” 输 入 ADT plug-in 网 址 后 ( 如 下 ) 按 下 “ Install ” 便 开 始 自 动 下 载 安 装 : , https://dl-ssl.google.com/android/eclipse/ ▲图 2-2 安装 ADT Plug-in 下载后,安装向导会显示两项必须安装的程序:Android Development Tools 与 Android Editors。 ▲图 2-3 选择安装 Android 开发工具及编辑器 虽然 Android ADT 可以免费下载安装,但在使用上仍然有其局限性,跟一般 应用程序 许可条款的签署类似。 ▲图 2-4 同意使用 Android 软件开发条款 安装 ADT 所需的时间不长,也可以在后台 运行,不会影响现有的操作系统程 序。安装完毕后,需要重新启动 Eclipse,才能使用 Android ADT。 ▲图 2-5 可以在后台运行安装 ADT,并需要重新启动 Eclipse 至此 Android SDK 及 ADT 已安装完毕,所有准备工作都已经就绪,随时可以 开始建立 Android 项目。 这一节将 建立本书第一个 Android 项目,不过,在初次建立 Android 项目之 前,需要设置 Android SDK 的路径,让 Eclipse 可以找到 Android SDK,先执行 “Window—Preferences” 。 ▲图 2-6 运行 Eclipse 的 Preferences 设置功能 点击“Android”的树状列表,单击

“Browse”按钮,选择刚才解压的 Android SDK 的路径,而后按下“Apply”设置让 Eclipse 引用的 SDK 文件夹。 ▲图 2-7 设置 Android Preferences 路径 Android SDK 的引用路径设置完成之后,就 可以开始建立 Hello World 这个 经典范例了;运行“File—New>>Project”建立新项目。 ▲图 2-8 建立新的项目 新建项目向导需要指定一种应用程序类型,故展开“Android” 后,点击 “Android Project” ,按下“Next”按钮继续。 ▲图 2-9 选择建立项目类型为 Android Project 新建 Android 项目需输入项目名称 ( Project name) 、Package name、 Activity name 以及 Application name,最后按下 “Finish”按钮,项目随即建立完成。 ▲图 2-10 输入项目名称及 Package name 建立新项目 到目前为止,新建 Android 项目已经完成,而且 这个新建立的程序也已经 可以运行, 运行的方法是在“Package Explorer”窗口里,点开刚建立好的 “HelloWorld”项目文件夹, 并在项目名称上单击鼠标右键,在功能菜单上运行 “Run As>Android Application”功能。 ▲图 2-11 运行“Run As—Android Application”功能 假若此时尚未将手机与计算机联机,那么 Eclpise 将打开默认的 Android 模 拟器 (Emulator) ,运行画面就如同真的手机开机一样,随着计算机硬件环境的 不同, 运行模拟 器也会有不同的性能表现, 开机之后, 随即打开刚建立好的 Hello World 程序。 ▲图 2-12 没写一行程序的 Hello World 要退出被启动的“Hello World”程序,可以按下手机模拟器上的退格键 (Backspace) 。 ▲图 2-13 按下退格键离开程序,回到手机的桌面 虽然半行代码都没有写,但向导却帮忙写好了程序进入点、布局配置、字符 串常数、应 用程序访问权限等, 除了可作为程序模板之外, 也是 Android 手机程 序的最佳学习范例。让 我们回过头来看看刚才 Android 项目建立向导帮忙做了哪些工作, 以此来 了解 Android 应 用程序的生命周期及其文件组成方式; 首先, “Package Explorer” 展开 窗口里的 “HelloWorld” 项目名称,可以看见如下数据结构的文件: +HelloWorld +-src +-helloworld.irdc.eracom.com.cn +-HelloWorld.java +-R.java +-Android Library +-android.jar - D:\SDK\android +-assets +-res +-drawable +-icon.png +-layout +-main.xml +-values +-strings.xml +-AndroidManifest.xml 在展 开 的 文件 夹 层 中 , “src” “Android Library” “assets” “res” 与“AndroidManifest.xml”同属一 、 、 、 层, 放置在 “\src” 里的为主程序、 程序类 (class) 放置在 ; “\res” 里的为资源文件 (Resource Files) 如程序 ICON 图标、 , 布局文件 (\layout) 与常数 (\values) 以此 Hello World 程 。 序为例,主程序为“HelloWorld.java” ,其内容与一 般 Java 程序格式相类似: package helloworld.irdc.eracom.com.cn; import android.app.Activity; import android.os.Bundle; public class HelloWorld extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 主程 序里可看见 HelloWorld 类继承自 Activity 类, 在类中重写了 onCreate() 方法, 在方法内以 setContentView() 来设置这个 Acvitity 要显示 的布局(R.layout.main) ,使用布局配置“\layout\main.xml” ,布局文件是 以 XML 格式编 写的,内容如下: <?xml version="1.0" encoding="utf-8"?>

<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> 布局配置中设置了一个 TextView TAG,用以配置文本标签 Widget,其内部 设置的 android:text 属性, 则是要显示的文字内容, 引用“@string”里的 hello 字符串常数。 查 看 “ values/strings.xml ” 字 符 串 常 数 设 置 如 下 : <?xml version="1.0" encoding="utf-8"?> <resources> Hello World, HelloWorld HelloWorld </resources> 其 中“hello”字符串变量的内容为“Hello World, HelloWorld” ,这即是 刚才看见的 Hello World 程序显示的文字内容了。 Android 应用程序有以下三种类型: l l l 前端 Activity (Foreground Activities) 后台服务(Background Services) 间隔执行 Activity 。 。 (Intermittent Activities) 。 前端 Activity 就如同这个 Hello World 一样,运行在手机前端程序中;后 台服务可 能是看不见的系统服务(System Service) 、系统 Broadcast(广播信 息)与 Receiver(广 播信息)接收器) ;间隔执行 Activity 则类似如进程 (Threading) 、Notification Manager 等等。 每一个 项目都有一个 “AndroidManifest.xml” 设置文件, 里头包含这个 Android 应 用程序具有哪些 Activity、Service 或者 Receiver,先来看看 Hello World 制作好的 “AndroidManifest.xml” 设置文件的内容描述: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="helloworld.irdc.eracom.com.tw" android:versionCode="1" android:versionName="1.0.0"> <category android:name="android.intent.category.LAUNCHER" /> </manifest> 在 manifest 文 件 中 有 一 个 名 为 HelloWorld 的 Activity , 设 置 其 intent-filter 的 category android:name 为 "android.intent.category.LAUNCHER", 写在 intent-filter 里是指定此 Activity 为默认运行的主要 Activity,除了在 manifest 文件中手动设置之外, 也可在 Eclipse 右击项目名称,运行“Run As—Run Configurations” ,设置 Launch Action: ▲图 2-14 设置项目要执行的 Action 方式或 Activity Activity 类的应用程序有其默认运行的方式, 为了确保应用程序运行的优先 级, 理解 Activity 在手机运行时的生命周期,及其可视性(Visible)周期。 ▲图 2-15 Activity 自 onStop 之后,程序即可被关闭 以 Hello World 程序里继承自 Activitry 类开始,一旦程序被执行,即会照 以上流程 顺序进行,若需要在 Activity 程序里编写程序,默认常见的进入点为 重写 onCreate (Activity)或 onStart(Service) ,重写的方式可通过 Eclipse 来选择,方法为将鼠标光 标停 在继承自 Activity 的空白处,单击鼠标右键展开菜单,点开执行 “Source— Override/Implement Methods”功能。 ▲图 2-16 执行“Source—Override/Implement Methods”功能 在 重 写 于 实 现 方 法 的 对 话 框 中 , 点 开 “ Activity ” 树 状 列 表 , 在 其 中 找 到 了 onCreate(Bundle)方法,勾选前方的选择项(CheckBox) ,再按下“OK”按钮即 可。 ▲图 2-17 选择要重写的方法,向导会自动在主程序中描述重写内容 最后, 比较值得一提的是 onResume() 与 onPause(), 这两个方法为 Activity 在 onCreate 之后运行过程中的生命周期,当程序失去前端焦点、或者被关闭, 就会触发 Activity 的 onPause() 状态;当应用程序被再次唤醒,则会回到 onResume() 状态,故在

编 写 与 User 互 动 的 程 序 过 程 中 , 需 注 意 User 暂 时 离 开 Activity ( 或 前 往 不 同 的 Activity、不同的 Service)前,需要处 理的工作都 会摆在 onPause() 当中执行。以一个 通过网络 FTP 下载 mp3 的 Activity 为例, 暂停下载工作可以写在 onPause 里, 需要接续前一次的下载等处理, 则由 onResume 负 责。 Android 手机有着华丽的机身、 流畅的执行速度, 唯一欠缺的就是 “具有视 觉美感的 UI 设计员” 但自 Android SDK rc20a 一路发展到 1.0_r2, Open Handset Alliance 提供的 , ADT (Android Development Tools) ,终于有了预览界面的功能,只要点开项目的 “res— layout” ,双击 main.xml 运行“Android Layout Editor” ,或在其上 单击鼠标右键展开菜 单单,执行“Open With—Android Layout Editor” 。 ▲图 2-18 单击 XML 布局配置文件,选择以 Android Layout Editor 编辑 Android Editor 的功能虽然阳春,但至少还算齐备,具有文字模式与 UI 配 置模式可 供切换,如下图的“Layout”与“main.xml”页签切换所示。 ▲图 2-19 Android Layout Editor 具有文字模式与 UI 配置模式可供切换编辑 目前的 ADT 版本,虽然提供了预览接口功能,但没有 提供类似 Microsoft Visual Studio 的拖拉界面组件开发工具,所以布局的配置虽然直观,但还是不 够自然,所幸,在 Google 还未完整推出 GUI 的拖拉工具之前,已有网友以 Java 写出 了好用的可视化 GUI 布局拖拉工具程序: DroidDraw。DroidDraw 目前是一个公开的 Google Code,除了可以在线免费下载 (http://code.google .com/p/droiddraw/)使用到计算机端 执行之外,也提供在线直接使用的版本 (http://www .droiddraw.org/) ;DroidDraw 同时 还提供了源代码(Source Code) ,可供程 序员自行参考或修改。 ▲图 2-20 DroidDraw 提供了可视化拖拉组件的方式设计布局期待未来 Google 能将拖拉布局的功 能纳入 Android Editor 当中,如此一来 更能整合 Android SDK 以及开发环境的功能,且 让我们拭目以待吧! 要部署程序在模拟器上运行,在先前 Hello World 的程序已经看过了, 在项 目名称上单击右键执行 Android 应用程序即可,但事实上,要将 Android 程序, 部 署在手机环境中进行测试,方法也是相同的,同样调用“Run As—Android Application”的 方式执行, 不同的是,需要事先安装好 Android 的 USB Driver, 并且通过 USB 联机至手 机,在与手机联机的状况下,就可以让 Eclipse 在运行 Android 程序时,直接将程序部署 于实机环境中执行。 Android USB 驱动程序是随着 Android SDK 所提供的,每一个版本的 SDK 都 可能有不同版本的 USB Driver Version,其存放在以下 Android SDK 解开后的 参 考位置,如: D:\SDK\android\usb_driver\ 安装的步骤是先将手机以 USB 与计算机连接, 操作系统会找到名为 Android Phone 的设备,但是却在装置管理员当中无法正确被识别,如 下所示。 ▲图 2-21 操作系统找到名为 Android Phone 的装置,但无法正确被识别 接着画面会跳出添加硬件向导,选择“从列表或指定位置安装(高级) ”来 自行挑选驱 动程序位置。 ▲图 2-22 选择“从列表或指定位置安装”自己安装驱动程序 在“搜索和安装选项”的画面中,选择“不要搜索,我要自己选择要安装的 驱动程序” 选项,选择“显示所有设备”后,按下“下一步” 。 ▲图 2-23

选择不要搜索系统数据,改以自行挑选硬件的方式 利用浏览按钮选择复制源为 Android USB Driver 程序路径: ▲图 2-24 选择 Android SDK 里所附的 USB Driver 选择驱动程序后,于显示兼容硬件列表中选择“HTC Dream Composite ADB Interface” , 程序将 Android 手机的 USB ADB Interface 安装完成。 ▲图 2-25 安装 Android USB ADB Interface 完成 设备管理器会自动新增一项 ADB Interface 的项目,表示已经顺利安装了 Android 手 机与计算机的联机。 ▲图 2-26 顺利安装了 Android 手机与计算机的联机 安装完 ADB Interface 之后,暂时还无法通过 Eclipse 将 Android 项目程序 部署至 手机上,必须先将手机上的 USB 调试(Debug)模式打开,在手机上执行 “应用程序设置— 开发>>USB 调试” 。 ▲图 2-27 将手机的 USB 调式模式打开 在 Eclipse 执行项目时,若程序发现先前已打开的模拟器与手机同时并存, 那么将会 跳出 Device Chooser 的窗口让开发者选择要部署的设备,下图为选择 Android G1 手机之 后,于 Console 里显示正确执行的 Log 纪录。 ▲图 2-28 上图中的警告为开发使用的是 SDK 1.1 但手机是 SDK 1.0 的警告 部署程序到手机上测试是最适合的方法,因为许多功能皆需要手机才能进行 测试,如 WiFi 驱动程序、平衡感应器、电池剩余计量等等。 看完了本章介绍的内容,相信各位已快 速掌握了 Android SDK 的安装方法、 Eclipse 开发环境的设置、使用可视化布局开发工具, 以及试写了一个 Android Hello World 的应用程序(咳~虽然一行程序都没写~) ,最后将 程序部署于实 机上运行,这是每一位 Android 程序员都必须快速掌握的重点,也是进入本 书范 例必须要具备的基本操作能力。 接下来将是丰富精彩的范例系列, 相信通过边学 边操 作,你将获得更多宝贵的实战经验,祝大家学习愉快! 第三章:用户人机界面 范例说明 前一章写了 Hello World 之后,一直觉得没有写半行代码对不起自己,所以在本章人机 界面一开始,则延续 Hello Wolrd 的气势,进行与 TextView 文字标签的第一次接触。在此 范 例中, 将会在 Layout 中创建 TextView 对象, 并学会定义 res/values/strings.xml 里 的字符串 常数,最后通过 TextView 的 setText 方法,在预加载程序之初,更改 TextView 文字。 运行结果 ▲图 3-1 认识 TextView.setText 更改默认 Layout 里定义的文本字符串 范例程序 src/irdc.ex03_01/EX03_01.java 主程序示范以 setText 方法,输出 String 类型的字符串变量。 package irdc.ex03_01; import android.app.Activity; import android.os.Bundle; /* 必 须 引 用 widget.TextView 才 能 在 程 序 里 声 明 TextView 对 象 */ import android.widget.TextView; public class EX03_01 extends Activity {

/*必须引用 widget.TextView 才能在程序里声明 TextView 对象*/ private TextView mTextView01; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout , 此 时 myTextView01:text 为 str_1 */ setContentView(R.layout.main); /* 使用 findViewBtId 函数,利用 ID 找到该 TextView 对象 */ mTextView01 = (TextView) findViewById(R.id.myTextView01); String str_2 = " 欢 迎 来 到 Android 的 TextView 世 界 … … "; mTextView01.setText(str_2); } } res/layout/main.xml 以 android:id 命名 TextView 的 ID 为 mTextView01; 在较旧的版本写法与 1.0 的不 同, 请特别留意。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/myTextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_1" android:layout_x="61px" android:layout_y="69px" > </TextView> 扩展学习 TextView 里的 setText 方法支持以下多态构造方法: public final void setText(CharSequence text) public final void setText(int resid) public void setText(CharSequence text, TextView.BufferType type) public final void setText(int resid, TextView.BufferType type) public final void setText(char[] text, int start, int len) 在此,以最后 setText(char[] text, int start, int len) 为例,第一个参数为 char 数组作为输 出依 据,第二个参数为从哪一个元素索引开始选取,第三个参数则为要取出多 少个元素,请看以 下的例子: char char_1[] = new char[5]; char_1[0] = 'D'; char_1[1] = 'a'; char_1[2] = 'v'; char_1[3] = 'i'; char_1[4] = 'd'; mTextView01.setText(char_1,1,3); 如上述程序所示,输出的结果是“avi” ,因为从第 1 个元素索引开始,共取 3 个元素; 最 后则要提醒你,TextView.setTextView 不支持 HTML TAG 的输出,所以即便写成这样: mTextView01.setText("戴维 的博客"); 实际 输出时, 也就是纯文本而已,并不会作 HTML TAG 的转换。但若撇开 HTML TAG 之 外(如“<”开头的标记) ,在 TextView 里加上了 android:autoLink="all",那么正文中 若有网 址 (http://) ,是可以被显示的,以下这个范例就交给你自己实现看看。 <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:autoLink="all" android:text=" 请 访 问 戴 维 的 博 客 : http://shop.teac.idv.tw/MyBlog/" /> 范例说明 在之前的范例运行结果,窗口的底色一律是“深黑色” ,这是 SDK 默认的颜色,要更改 Activity 里的窗口底色有许多方法, 最简单的方法就是将颜色色码事先定义在 drawable 当中, 当程序 onCreate 创建的同时, 加载预先定义的画面颜色。 此范例程序的设计方式是在

drawable 里指定 Layout 的背景色(BackGround)为白色, 但这里的“白色” (颜色色码为 #FFFFFFFF)预先定义在 drawable 当中,当程序运行时,背景 就会变为白色。 这是指定 Activity Layout 背景颜色最简单的方法,在范例最末,则将示范如何创建色彩 板(color table) ,让 Android 手机程序可以像使用“常数”般直接取用,并反应在应用程序的 运 行阶段。 运行结果 ▲图 3-2 使用 drawable 设置颜色常数,应用于程序运行时的结果 范例程序 src/irdc.ex03_02/EX03_02.java 程序继承自 Activity 类, 并在重写 onCreate 创建之初, 直接显示 R.layout.main main.xml) ( 这个页面安排的布局配置。 package irdc.ex03_02; import android.app.Activity; import android.os.Bundle; public class EX03_02 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } res/layout/main.xml 在页面布局上,使用了 2 个 TextView 对象,以及 2 个 EditText 对象,关键在于 android: background="@drawable/white" 让 程 序 背 景 变 成 了 白 色 , 而 android:textColor="@drawable/ darkgray" 将 TextView 里 的 文 字 颜 色 ( textColor ) 设 为 灰 色 , 当 中 “@drawable/white”及 “@drawable/darkgray” 的 写 法 则 是 参 考 事 先 于 drawable 里 定 义 好 的 颜 色 常 数 , 将 在 res/values/color.xml 里看见颜色的定义描述。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/widget28" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_id" android:textColor="@drawable/darkgray" android:layout_x="61px" android:layout_y="69px" > </TextView> <TextView android:id="@+id/widget29" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_pwd" android:textColor="@drawable/darkgray" android:layout_x="61px" android:layout_y="158px" > </TextView> <EditText android:id="@+id/widget31" android:layout_width="120dip" android:layout_height="wrap_content" android:textSize="18sp" android:layout_x="114px" android:layout_y="57px" > </EditText> <EditText android:id="@+id/widget30" android:layout_width="120dip" android:layout_height="wrap_content" android:textSize="18sp" android:password="true" android:layout_x="112px" android:layout_y="142px" > </EditText> 扩展学习 事先将 定义好的颜色代码 (color code) drawable 的名称 以 (name) 存放于 resources 当中, 这是开发 Android 程序的好习惯, 正如同字符串常数一样, 颜色也是可 以事先定义 好 的。在本范例中学会了使用 drawable 的 resource 的定义方法: <drawable name=color_name>color_value</drawable> 定义好的 drawable name 常数,必须存放于 res/values 下面,作为资源取用,但定义

好的 背景颜色并非只能当作是“默认”颜色声明使用, 在程序的事件里, 是可以通过程序 来更改的, 如以下程序所示: Resources resources = getBaseContext().getResources(); Drawable HippoDrawable = resources.getDrawable(R.drawable.white); TextView tv = (TextView)findViewByID(R.id.text); tv.setBackground(HippoDrawable); 范例说明 上一个范例通过 Drawable 来定义颜色常数,但实际设计中最常用的方法,则是使用程 序控制 TextView 或其它对象的背景色 (setBackgroundDrawable 方法) 如判断对象被点击 时 , 的背景色亮起、当失去焦点时,又恢复成原来的背景色等等。 以下的范例,将扩展前 一个范例的实现,预先在 Layout 当中设计好两个 TextView,并 在 onCreate 同时, 通过 两种程序描述方法, 实时更改原来 Layout 里 TextView 的背景色以 及 文字颜色, 最后学 会使用 Android 默认的颜色常数(graphics.Color)来更改文字的前景色。 运行结果 ▲ 图 3-3 通 过 setBackgroundDrawable 方 法 更 改 TextView 的 背 景 色 以 及 graphics.Color 更改前景色 范例程序 src/irdc.ex03_03/EX03_03.java 程序里新建两个类成员变量: mTextView01 与 mTextView02,这两个变量在 onCreate 之初,以 findViewById 方法使之初始化为 layout(main.xml)里的 TextView 对象。在当 中 使 用 了 Resource 类 以 及 Drawable 类 , 分 别 创 建 了 resources 对 象 以 及 HippoDrawable 对象, 并 将 前 一 个 范 例 中 所 创 建 的 R.drawable.white 以 getDrawable 方 法 加 载 , 最 后 则 调 用 了 setBackgroundDrawable 来 更 改 mTextView01 的文字底纹。更改 TextView 里的文字,则使用 了 setText 方法。 在 mTextView02 当 中 , 使 用 了 graphics.Color 里 的 颜 色 常 数 , 接 着 , 再 利 用 setTextColor 更改文字的前景色。 package irdc.ex03_03; import android.app.Activity; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.widget.TextView; public class EX03_03 extends Activity { private TextView mTextView01; private TextView mTextView02; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mTextView01 = (TextView) findViewById(R.id.myTextView01); mTextView01.setText("我是应用 Drawable 背景色的戴维文本。"); Resources resources = getBaseContext().getResources(); Drawable HippoDrawable = resources.getDrawable(R.drawable.white); mTextView01.setBackgroundDrawable(HippoDrawable); mTextView02 = (TextView) findViewById(R.id.myTextView02); /* 下 使 用 Color.MAGENTA 指 定 文 本 的 颜 色 为 紫 红 色 */ mTextView02.setTextColor(Color.MAGENTA); } } 扩展学习 Resources resources = getBaseContext().getResources(); Drawable HippoDrawable

= resources.getDrawable(R.drawable.white); 上述这 2 行代码,在前一版本中的写法是这样的: Resources.resources = getDrawable(R.drawable.solid_red); Drawable HippoDrawable = resources.getDrawable(R.drawable.white); 但 是在 1.0 之 后的版本, Resources 不 再支持直接使用 .getDrawable 方 法直接 取 用 drawable , 而 必 须 先 取 得 基 类 的 Context 才 行 。 此 外 , 在 程 序 里 使 用 了 Color.MAGENTA 让 TextView 里的文字变成了粉红色, 事实上, 在 Android 里还有以下 12 种常见的颜色: Color.BLACK Color.BLUE Color.CYAN Color.DKGRAY Color.GRAY Color.GREEN Color.LTGRAY Color.MAGENTA Color.RED Color.TRANSPARENT Color.WHITE Color.YELLOW 这些颜色常数是定义在 android.graphics.Color 里的: 类型 int int int int int int int int int int int int 常数 BLACK BLUE CYAN DKGRAY GRAY GREEN LTGRAY MAGENTA RED TRANSPARENT WHITE YELLOW 值 -16777216 -16776961 -16711681 -12303292 -7829368 -16711936 -3355444 -65281 -65536 0 -1 -256 色 码 0xff000000 0xff0000ff 0xff00ffff 0xff444444 0xff888888 0xff00ff00 0xffcccccc 0xffff00ff 0xffff0000 0x00000000 0xffffffff 0xffffff00 范例说明 从一开始自 Layout 里通过 Resource 初始 化 TextView 的文字,到程序中动态更 改 TextView 文字,但要如何在代码里取得 Resource 的字符串呢?在 Android 里,确实是有 些 方 法可以直接以 R.string.*直接转换 ID 为 String,不过,这样的数据类型转换是非 常规甚至 是不妥的,正确的方法是利用 Context.getString 方法来取得存放在 global 里 的 Resource ID。 以下这个范例将示范如何在程序运行时(runtime) ,通过 CharSequence 依据 Resource ID 取 出字符串,并正确更改 TextView 的文字。 运行结果 ▲图 3-4 通过 java.lang.CharSequence 这个 Interface 来取得存放在 global 里的 Resource ID 范例程序 src/irdc.ex03_04/EX03_04.java 主程序的差异主要是在更改 mTextView02 的文字时(setText 方法)合并了 str_3 与 str_2 , 这两个不同对象,由于 setText 方法同时支持 CharSequence 与 String 类型的 参数,故在此示 范不同数据类型的字符串进行同步输出。 package irdc.ex03_04; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class EX03_04 extends Activity { private TextView mTextView02; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mTextView02 = (TextView) findViewById(R.id.myTextView02); CharSequence str_2 = getString(R.string.str_2); String str_3 = " 我 是 程 序 里 调 用 Resource 的 "; mTextView02.setText(str_3 + str_2); } }

res/layout/main.xml 为了作 为对比,在 main.xml 里创建了两个 TextView,并采 LinearLayout 的方式配 置, 一上一下,在运行结果中 id 为 myTextView01 的 TextView 并没有任何文字的更改, 维持一 开始的 str_1 参考字符串常数里的文字) 但在程序运行后, 为 myTextView02 的 TextView ( , id 则作了文字的实时更改。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/myTextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_1" android:layout_x="30px" android:layout_y="50px" > </TextView> <TextView android:id="@+id/myTextView02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/str_2" android:layout_x="30px" android:layout_y="70px" > </TextView> 扩展学习 虽然在 values/strings.xml 里定义了默认的字符串常数,需留意若遭遇如 “?”“'” 、 、 “\”等 符号时,必须使用转义字符(\) ,如下: \? \' \\ 范例说明 在开发手机应用程序时, 除了底层对 API 的掌握度之外, 最重要的仍是对屏幕分辨率 的 概念, 因各家手机厂商所采用的屏幕尺寸不同, user UI 接口呈现及布局自然也各异。 尽 管 Android 可设置为随着窗口大小调整缩放比 例, 但即便如此, 手机程序设计人员 还是必 须知道手机屏幕的边界, 以避免缩放造成的布局 (Layout) 变形问题。 这个范例非常 的简短, 只需几行程序即可取 得手机的分辨率,当中的关键则是 DisplayMetrics 类的应用。 运行结果 ▲图 3-5 取得 Android 手机的实际屏幕分辨率 范例程序 src/irdc.ex03_05/EX03_05.java 在 android.util 底下的 DisplayMetrics 对象,记录了一些常用的信息,包含了显示 信息、 大小、维度、字体等等;在使用时,请记得引用 android.util.DisplayMetrics。值 得一提的是 DisplayMetrics 对象里的 widthPixels 及 heightPixels 字段为整数类型, 在 以下的程序当中, 并没有对其作字符串类型的转换,因为字符串连接运算符的缘故,所以输 出 strOpt 为字符 串。 package irdc.ex03_05; import android.app.Activity; import android.os.Bundle; import android.util.DisplayMetrics; import android.widget.TextView; public class EX03_05 extends Activity { private TextView mTextView01; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 必 须 引 用 android.util.DisplayMetrics */ DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); String strOpt = "手机屏幕分辨率为: + dm.widthPixels + " × " + dm.heightPixels; " mTextView01 = (TextView) findViewById(R.id.myTextView01); mTextView01.setText(strOpt); }

} 扩展学习 程序一开始所创建的 DisplayMetrics 对 象 (程序中的 dm) , 不需要传递任何参数 (构 造时) ,但在调用 getWindowManager()之后,会取得现有的 Activity 的 Handler,此时, 调 用 getDefaultDisplay 方法将取得的宽高维度存放于 DisplayMetrics 对象 dm 中,而 取得的宽 高维度是 以像素为单位(Pixel) , “像素”所指的是“绝对像素”而非“相对像 素” 。 范例说明 老是要一个个指定文字的大小、颜色也不是办法,有没 有类似 CSS 样式的方法可用来 指定颜色、大小呢?事实上是有的,在 Android 程序开发过程中,也可以通过样式(Style) 的方式,初始化 TextView 的文本颜色、大小;当然这个范例只是抛砖引玉,在 Layout 当 中 的任何对象(以 XML 定义)都可以用样式化的方式来更改其外观。 在以下的范例中,将 创建两个 TextView 作为对比,使其呈现两种不同的样式差异作为 练习,而 Style 的写法 与先前介绍到的颜色常数 (color.xml) 相同, 同样是定义在 res/values 下面, 但其 XML 定 义的方式不同来看看以下这个范例练习。 运行结果 ▲图 3-6 利用 Style 来初始化 TextView 范例程序 src/irdc.ex03_06/EX03_06.java 主程序看起来非常干净,只有加载 R.layout.main 定义布局内容而已,但由于定义在 main.xml 里的语句不同,自然也有不同的样貌呈现。 package irdc.ex03_06; import android.app.Activity; import android.os.Bundle; public class EX03_06 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } res/layout/main.xml 诚如先前所述,初始化 TextView 时,指定 Style 属性,使其应用 style.xml 里事先 定义好 的样式。 <?xml version="1.0" encoding="utf-8"?> android:layout_height="fill_parent" > <TextView style="@style/DavidStyleText1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal" android:text="@string/str_text_view1" /> <TextView style="@style/DavidStyleText2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal" android:text="@string/str_text_view2" /> res/values/style.xml 在此的 style.xml 就是这个范例的关键之处了,当中定义了两个样式名称,分别 为 DavidStyleText1 与 DavidStyleText2;留意 于 </resources> 扩展学习 style 与 color 的 XML 语法相类似,皆需要先声明 xml 的版本以及 encoding 为 UTF-8,但其内的 resources 则需要以 stylename 作为样式名称, 在最内层才是以 item 定

义样式的范 围,其语法如下: 范例说明 按钮在许多 Windows 窗口应用程序中,是最常见到的控件( Controls) ,此控件也常 在 网页设计里出现,诸如网页注册窗体、应用程序里的“确定”等等。 而按钮所触发的事 件 处理,我 们称为 Event Handler ,只不过 在 Android 当 中,按钮 事件 是由系统的 Button.OnClickListener 所控制, 熟悉 Java 程序设计的读者对 OnXxxListener 应 该不陌 生。以下的范例将示范如何在 Activity 里布局一个按钮(Button) ,并设计这个按钮的 事件处理函数,当点击 按钮的同时,更改 TextView 里的文字。 运行结果 ▲图 3-7 Android 手机的实际画面运行结果 范例程序 src/irdc.ex03_07/Ex03_07.java 一开始,必须先在 Layout 当中布局一个 Button 及一个 TextView 对象,找不到这两 个组 件的话,系统会无法运行下去,在开发阶段会造成编译错误。 其次在 主程序中,请留 意 onCreate 里创建的 Button.OnClickListener 事件, 这也是触发 按 钮 时 会 运 行 的 程 序 段 落 ; 但 由 于 Eclipse 无 法 自 动 加 载 默 认 的 传 递 参 数 ( new Button.OnClickListener()) ,所以,在编写程序描述时,必须自行键入新创建的按钮所需 的 OnClickListener() 事件,如下所示: package irdc.ex03_07; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class EX03_07 extends Activity { private Button mButton1; private TextView mTextView1; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mButton1 =(Button) findViewById(R.id.myButton1); mTextView1 = (TextView) findViewById(R.id.myTextView1); mButton1.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub mTextView1.setText("Hi, Everyone!!"); } }); } } 扩展学习 本范例中只有一个按钮,但在 Activity 里,其实可以布局数个按钮,只需要在 Layout 里多配置一个按钮对象,以下的程序将创建两个按钮事件作为示范: package irdc.ex03_07; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class EX03_07 extends Activity { private Button mButton1; private Button mButton2; private TextView mTextView1; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mButton1 =(Button) findViewById(R.id.myButton1); mButton2 =(Button) findViewById(R.id.myButton2); mTextView1 = (TextView) findViewById(R.id.myTextView1);

mButton1.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub mTextView1.setText("Hi, Everyone!!"); } }); mButton2.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub mTextView1.setText("Hi, David!!"); } }); } } 最后来谈谈按钮事件里被重写的 onClick(View v) 函数, 此函数唯一的参数是 View 类型 的变量 v,这个变量所指的是来自父层(parent)的 ContentView,亦即通过“v.*” 可以更改其 父层的 View 状态或属性。 范例说明 在网页的世界里, 想要在两个网页间做转换, 只要利用 超链接 (HyperLink) 就可以实 现, 但在手机的世界里, 要如何实现手机页面之间的转换呢?最简单的方式就是改变 Activity 的 Layout! 这个范例里头, 在 将布局两个 Layout, 分别为 Layout1 (main.xml) Layout2 与 (mylayout.xml) ,默认载入的 Layout 为 main.xml,且在 Layout1 当中创建一个按钮, 当点 击按钮时,显示第二个 Layout(mylayout.xml) ;同样地,在 Layout2 里也设计一 个按钮, 当点击第二个 Layout 的按钮之后,则显示回原来的 Layout1,现在就来示范如何 在两个页面 之间互相切换。 运行结果 ▲图 3-8 手机页面 Layout 间的切换 范例程序 src/irdc.ex03_08/EX03_08.java 主程序 中, 预加载的 Layout 是 main.xml, 屏幕上显示的是黑色背景的 “This is Layout 1!! ” 在 第 一 个 Layout 上 的 按 钮 被 点 击 的 同 时 , 改 变 Activity 的 Layout 为 , mylayout.xml, 屏 幕上显示变为白色背景的 “This is Layout 2!!” ,并利用 Button 点击时,调用方法 的不同做两 个 Layout 间的切换。 package irdc.ex03_08; /* import 相 关 class */ import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class EX03_08 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b1 = (Button) findViewById(R.id.button1); b1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { jumpToLayout2(); } }); } /* method jumpToLayout2:将 layout 由 main.xml 切换成 mylayout.xml */ public void jumpToLayout2() { /* 将 layout 改 成 mylayout.xml */ setContentView(R.layout.mylayout); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b2 = (Button) findViewById(R.id.button2); b2.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { jumpToLayout1(); } }); } /* method jumpToLayout1:将 layout 由 mylayout.xml 切换成 main.xml */ public

void jumpToLayout1() { /* 将 layout 改成 main.xml */ setContentView(R.layout.main); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b1 = (Button) findViewById(R.id.button1); b1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { jumpToLayout2(); } }); } } res/layout/main.xml 为了凸显 Layout 间切换的效果, 特别改变两个 Layout 的背景色及输出文字。 main.xml 在 中定义其背景色为黑色,输出文字为“This is Layout 1!!” 。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text1" android:textSize="24sp" android:layout_width="186px" android:layout_height="29px" android:layout_x="70px" android:layout_y="32px" android:text="@string/layout1" > </TextView> res/layout/mylayout.xml 在 mylayout.xml 中定义其背景色为白色,输出文字为“This is Layout 2!!” 。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text2" android:textSize="24sp" android:layout_width="186px" android:layout_height="29px" android:layout_x="70px" android:layout_y="32px" android:textColor="@drawable/black" android:text="@string/layout2" > </TextView> 扩展学习 运用改变 Activity Layout 这个技巧,就可做出手机页面转换的效果,当然亦可搭配之 前 介绍过的 Style 与 Theme 的设置,进行更加灵活的布局配置运用,例如,让用户自行决 定要 使用的系统样式、背景及文字颜色等,接着直接应用来改变布局。 再者, 利用 setContentView 来置换页面,还有一个特别的优点,即所有程序里的变量 皆存在相同的状 态,无论是类成员变量、类函数等等,皆可以在一个 Activity 的状态中直接 取得,并没有 参数传递的问题。打个比喻:Layout1 收集了用户输入的信用卡卡号等付款信 息,在“下一 步” 显示 Layout2 使之显示订单信息, 让用户进行确认, 并在点击按钮后, 调用 Layout3 进 行刷卡 Gateway 的授权操作, 这当中皆没有需要传递的变量, 其手法是将所需 要的字段数 据,以类成员变量作如下声明: public class EX03_08 extends Activity { public String colVar1; public String colVar2; public String colVar3; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { /* 下面的程序略 */ 范例说明 前一个范例介绍了如何运用切换 Layout 的方式, 进行手机页面间的转换。 如果要转换 的页面并不单只是背景、颜色或文字内容的不同,而是 Activity 的置换,那就不是单单改 变 Layout 就能完成的, 尤其是需要传递的变量不像网页可以通过 Cookie 或 Session, 在 程序 里要移交主控权到另外一个 Activity,光靠先前的 Layout 技巧是办 不到的。 那要 如何解决 Activity 控制权的移交呢?在 Android 的程序设计中,可在主程序里使用 startActivity() 这个方法来调用另一个 Activity(主程序本身即是一个 Activity) ,但 当中的关 键并不在 startActivity 这个方法, 而是 Intent 这个特有的对象, Intent 就如 同其英文字义, 是“想要”或“意图”之意,在主 Activity 当中,告诉程序自己是什么, 并想要前往哪 里, 这就 是 Intent 对象所处理的事了。 本范例并没有特别的 Layout 布局, 而是直接在主 Activity (Activity1)当中部署一个按钮, 当点击按钮的同时,告诉主 Activity 前往 Activity2, 在 Activity2 里创建一个回到 Activity1 的按钮, 本范例 并

将利用此简易 的程序描述, 示范如何 在一个 Activity 中调用另一个 Activity 的手法。 运行结果 ▲图 3-9 在两个 Activity 间做切换 范例程序 src/irdc.ex03_09/EX03_09.java 主程序 中加载的 Layout 为 main.xml,屏幕上显示的是黑色背景的 “This is Activity 1!!” , 在 Button 被点击时调用另一个 Activity (EX03_09_1, 参考 AndroidManifest.xml 里 的说明) , 并将主 Activity 关闭 finish(),接着将主控权交给下一个 Activity,即 Activity2。 package irdc.ex03_09; /* import 相 关 class */ import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.content.Intent; public class EX03_09 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入 main.xml Layout */ setContentView(R.layout.main); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b1 = (Button) findViewById(R.id.button1); b1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* new 一个 Intent 对象,并指定要启动的 class */ Intent intent = new Intent(); intent.setClass(EX03_09.this, EX03_09_1.class); /* 调用一个新的 Activity */ startActivity(intent); /* 关闭原本的 Activity */ EX03_09.this.finish(); } }); } } src/irdc.ex03_09/EX03_09_1.java EX03_09_1.java 程 序 是 第 二 个 Activity 的 主 程 序 , 其 加 载 的 Layout 为 mylayout.xml, 屏幕上所显示的是白色背景的 “This is Activity 2!!” ,当主 Activity (Activity1) 调用这个 Activity Activity2) 同样为 Button 添加 onClickListener(), ( 后, 使 Button 被点击时,重新 调用 Activity1(EX03_09) ,并将 Activity2(EX03_09_1) 关闭(finish()) 。 package irdc.ex03_09; /* import 相 关 class */ import android.app.Activity; import android.os.Bundle; import android.content.Intent; import android.view.View; import android.widget.Button; public class EX03_09_1 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 mylayout.xml Layout */ setContentView(R.layout.mylayout); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b2 = (Button) findViewById(R.id.button2); b2.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* new 一个 Intent 对象, 并 指 定 要 启 动 的 class */ Intent intent = new Intent(); intent.setClass(EX03_09_1.this, EX03_09.class); /* 调用一个新的 Activity */ startActivity(intent); /* 关闭原本的 Activity */ EX03_09_1.this.finish(); } }); } } res/layout/main.xml

为了凸显 Activity 间切换的效果,特别将两个 Layout 的背景及输出文字有所区别。 在 main.xml 中定义其背景色为黑色,输出文字为“This is Activity 1!!” 。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text1" android:textSize="24sp" android:layout_width="186px" android:layout_height="29px" android:layout_x="70px" android:layout_y="32px" android:text="@string/act1" > </TextView> res/layout/mylayout.xml 在 mylayout.xml 中定义其背景色为白色,输出文字为“This is Activity 2!!” 。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text2" android:textSize="24sp" android:layout_width="186px" android:layout_height="29px" android:layout_x="70px" android:layout_y="32px" android:textColor="@drawable/black" android:text="@string/act2" > </TextView> AndroidManifest.xml 由于本范例中添加了一个 Activity,所以必须在 AndroidManifest.xml 中定义一个新 的 activity,并给予名称 name,否则程序将无法编译运行。 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="irdc.ex03_09" android:versionCode="1" android:versionName="1.0.0"> <category android:name="android.intent.category.LAUNCHER" /> </manifest> 扩展学习 当 系 统 中 新 添 加 Activity 时 , 必 须 在 AndroidManifest.xml 里 定 义 一 个 新 的 activity: 否则系统将会因为找不到 Activity 而发生编译错误。 另外, 当程序中出现两个以上的 Activity 时 , 系统 要如何 决 定主 程序 是哪 一 支( entry point )呢 ?以本 范 例来 说, AndroidManifest.xml 中 Activity EX03_09 的定义如下: <category android:name="android.intent.category.LAUNCHER" /> 其中有一行为 <category android:name="android.intent.category.LAUNCHER" />,这 就代 表程序启动时,会先运行 EX03_09 这个 Activity,而非 EX03_09_1。需注意的是,这 个参数 必须要被定义,如果 xml 中没有一支 Activity 有设置这个参数,则程序将不会被 运行。 此外,在两支 Java 程序中的最后一行都调用了 finish() 这个方法,它代表这个 Activity 已运作完毕,当系统接收到这个命令时,即会关闭此 Activity,所以此时点击模 拟器的返回 (Back)键,并不 会回到上一个 Activity 的画面,如果要让模拟器的返回键 有回上一页的 效果, 可以将此行程序注释掉。 同理, 当两个 Activity 在切换时, 并非 真的 只有两个 Activity 在切换,而是在点击按钮时,再重新调用起一个新的 Activity。 范例说明 在上一个范例里,介绍了如何在 Activity 中调 用另一个 Activity,但若需要在调用 另外 一个 Activity 的同时传递数据,那么就需要利用 android.os.Bundle 对象封装数据 的能 力, 将欲传递的数据或参数,通过 Bundle 来传递不同 Intent 之间的数据。 本范例 的设计为一个简易表单的范例, Activity1 中收集 User 输入的数据, 在 在离开 Activity1 的同时, User 选择的结果传递至下一个 Activity2, 将 以一个简单 BMI“标准体重计 算 器”示范如何传递数据到下一个 Activity 里。 运行结果 ▲图 3-10 在两个 Activity 间做数据的传递

范例程序 src/irdc.ex03_10/EX03_10.java 在 第一 个 Activity1 主 程序中,定义了 “性 别”选 项的 RadioGroup 以 及输入 身高的 “EditText” ,并运用 Intent 及 Bundle 对象, 在调用 Activity2(EX03_10_1) 时, 同时将数据 传入。 关于 EditText 对象的使用在此仅供参考, 详细的应用以及属性方法, 将会在未来 讨 论控件时,再详细解说。 package irdc.ex03_10; /* import 相 关 class */ import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RadioButton; public class EX03_10 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b1 = (Button) findViewById(R.id.button1); b1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /*取得输入的身高*/ EditText et = (EditText) findViewById(R.id.height); double height=Double.parseDouble(et.getText().toString()); /* 取 得 选 择 的 性 别 */ String sex=""; RadioButton rb1 = (RadioButton) findViewById(R.id.sex1); if(rb1.isChecked()) { sex="M"; } else { sex="F"; } /*new 一个 Intent 对象, 并指定 class*/ Intent intent = new Intent(); intent.setClass(EX03_10.this,EX03_10_1.class); /*new 一个 Bundle 对象,并将要传递的数据传入*/ Bundle bundle = new Bundle(); bundle.putDouble("height",height); bundle.putString("sex",sex); /*将 Bundle 对象 assign 给 Intent*/ intent.putExtras(bundle); /*调用 Activity EX03_10_1*/ startActivity(intent); } }); } } src/irdc.ex03_10/EX03_10_1.java 那么, 在 Activity2(EX03_10_1)要如何接收来自 Activity1(EX03_10)传递来的数 据呢?试想,在 Activity1 是以 Bundle 封装对象,自然在 Activity2 亦是以 Bundle 的 方式解 开封装的数据;程序中以 getIntent().getExtras() 方法取得随着 Bundle 对象传 递过来的性别与 身高,经过计算之后,显示在屏幕上。 package irdc.ex03_10; /* import 相关 class */ import java.text.DecimalFormat; import java.text.NumberFormat; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class EX03_10_1 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 加载 main.xml Layout */ setContentView(R.layout.myalyout); /* 取 得 Intent 中 的 Bundle 对 象 */ Bundle bunde = this.getIntent().getExtras(); /* 取得 Bundle 对象中的数据 */ String sex = bunde.getString("sex"); double height = bunde.getDouble("height"); /* 判断性别 */ String sexText=""; if(sex.equals("M")) { sexText="男性"; } else { sexText="女性"; }

/* 取得标准体重 */ String weight=this.getWeight(sex, height); /* 设 置 输 出 文 字 */ TextView tv1=(TextView) findViewById(R.id.text1); tv1.setText("你是一位"+sexText+"\n 你的身高是" +height+"厘米\n 你的标准体重是 "+weight+"公斤"); } /* 四 舍 五 入 的 method */ private String format(double num) { NumberFormat formatter = new DecimalFormat("0.00"); String s=formatter.format(num); return s; } /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ private String getWeight(String sex,double height) { String weight=""; if(sex.equals("M")) { weight=format((height-80)*0.7); } else { weight=format((height-70)*0.6); } return weight; } } res/layout/mylayout.xml mylayout.xml 为(EX03_10_1)的 Layout,定义了显示计算结果的 TextView。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:layout_x="50px" android:layout_y="72px" > </TextView> AndroidManifest.xml 由于本范例中有两个 Activity, 所以文件中必须有两个 activity 的声明, 否则系统将 无法 运行,请看以下的描述。 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="irdc.ex03_10" android:versionCode="1" android:versionName="1.0.0"> <category android:name="android.intent.category.LAUNCHER" /> </manifest> 扩展学习 Bundle 对象针对了不同的数据类型提供了许多的方法, 例如, 此范例中传递 String 类型 的数据,使用的方法为 Bundle.putString(stringName,stringValue): bundle.putDouble("sex",sex); 而 要 传 递 Double 类 型 的 数 Bundle.putDouble(doubleName,doubleValue),如 下: 据 , 使 用 的 方 法 为 bundle.putString("height",height); 反 之 , 若 要 由 Bundle 对 象 中 取 出 数 据 , 则 使 用 Bundle.getString(stringName) 、 Bundle.getDouble(doubleName) 等相对应的方法即可。 除了上述简单的传递类型之外, 尚有 String[] 与 ArrayList 等封装的方式可供使 用 参 考。 范例说明 上一个范例中, 好不容易将数据从 Activity1 传递至 Activity2, 如果要再回到 Activity1,数据该不会要再封装一次吧?而且前一个 Activity1 早就被程序 destroy 了, 倘若 在 Activity1 最后以 finish() 结束程序, 再通过 Activity2 将数据采用 Bundle 的 方式通过新打开 Activity1 传递参数,这样的做法虽然也可以恢复 User 输入的数据, 但 是并不符合我们的期待,尤其 是 User 曾经输入过的数据,如果不小心点击回到上一页,数 据就消失不见,这就不妙了。 有鉴于科技始终来自于人性,如果要在次页面加上一个 “回 上页”的按钮,而非通过模 拟器的回复键,且回上页后又能保留之前输入的相关信息,那么 就必须使 用 startActivityForResult()来 唤起一个 Activity。利用这个方法,前一个 Activity1 便会有一个等 待次 Activity2 的返回,而返回的数据就可以达到我们想要的结

果。 运行结果 图 3-11 将数据返回到前一个 Activity 范例程序 src/irdc.ex03_11/EX03_11.java 在 Activity1 主 程 序 中 调 用 Activity 的 方 法 更 改 成 startActivityForResult(intent,0), 其中 0 为下一个 Activity 要返回值的依据,可指 定为自行定义的参考标识符 Identifier) 。 ( 程序覆 盖了 onActivityResult() 这个方法, 令程序在收到 result 后,再重新加载写回原本输入的值。 package irdc.ex03_11; /* import 相 关 class */ import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RadioButton; public class EX03_11 extends Activity { private EditText et; private RadioButton rb1; private RadioButton rb2; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b1 = (Button) findViewById(R.id.button1); b1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /*取得输入的身高*/ et = (EditText) findViewById(R.id.height); double height=Double.parseDouble(et.getText().toString()); /* 取 得 选 择 的 性 别 */ String sex=""; rb1 = (RadioButton) findViewById(R.id.sex1); rb2 = (RadioButton) findViewById(R.id.sex2); if(rb1.isChecked()) { sex="M"; } else { sex="F"; } /*new 一 个 Intent 对 象 , 并 指 定 class*/ Intent intent = new Intent(); intent.setClass(EX03_11.this,EX03_11_1.class); /*new 一个 Bundle 对象,并将要传递的数据传入*/ Bundle bundle = new Bundle(); bundle.putDouble("height",height); bundle.putString("sex",sex); /*将 Bundle 对象 assign 给 Intent*/ intent.putExtras(bundle); /*调用 Activity EX03_11_1*/ startActivityForResult(intent,0); } }); } /* 覆 盖 onActivityResult()*/ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (resultCode) { case RESULT_OK: /* 取得来自 Activity2 的 数 据 , 并 显 示 于 画 面 上 */ Bundle bunde = data.getExtras(); String sex = bunde.getString("sex"); double height = bunde.getDouble("height"); et.setText(""+height); if(sex.equals("M")) { rb1.setChecked(true); } else { rb2.setChecked(true); } break; default: break; } } } src/irdc.ex03_11/EX03_11_1.java 在 Activity2 的主程序中, 设计当 Button 被点击时, Bundle 对象与结果返回给前 将 一个 Activity1。 package irdc.ex03_11; /* import 相关 class */ import java.text.DecimalFormat;

import java.text.NumberFormat; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class EX03_11_1 extends Activity { Bundle bunde; Intent intent; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入 mylayout.xml Layout */ setContentView(R.layout.myalyout); /* 取 得 Intent 中 的 Bundle 对 象 */ intent=this.getIntent(); bunde = intent.getExtras(); /* 取得 Bundle 对象中的数据 */ String sex = bunde.getString("sex"); double height = bunde.getDouble("height"); /* 判断性别 */ String sexText=""; if(sex.equals("M")) { sexText="男性"; } else { sexText="女性"; } /* 取得标准体重 */ String weight=this.getWeight(sex, height); /* 设 置 输 出 文 字 */ TextView tv1=(TextView) findViewById(R.id.text1); tv1.setText("你是一位"+sexText+"\n 你的身高是"+height+ "厘米\n 你的标准体重是 "+weight+"公斤"); /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ Button b1 = (Button) findViewById(R.id.button1); b1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /* 返回 result 回上一个 activity */ EX03_11_1.this.setResult(RESULT_OK, intent); /* 结束这个 activity */ EX03_11_1.this.finish(); } }); } /* 四舍五入的 method */ private String format(double num) { NumberFormat formatter = new DecimalFormat("0.00"); String s=formatter.format(num); return s; } /* 以 findViewById()取得 Button 对象,并添加 onClickListener */ private String getWeight(String sex,double height) { String weight=""; if(sex.equals("M")) { weight=format((height-80)*0.7); } else { weight=format((height-70)*0.6); } return weight; } } res/layout/mylayout.xml mylayout.xml 为 Activity2 (EX03_11_1) Layout, 的 其中定义了显示计算结果的 TextView 与回上一页的 Button 按钮。 <?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:layout_x="50px" android:layout_y="72px" > </TextView> AndroidManifest.xml 范例中有两个 Activity,所以 AndroidManifest.xml 里必须有这两个 activity 的声 明,否 则系统将无法运行。 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="irdc.ex03_11" android:versionCode="1" android:versionName="1.0.0"> <category android:name="android.intent.category.LAUNCHER" /> </manifest> 扩展学习 范例中为了在回到上一页时,能够显示之前所输入的数 据,故将原本传递次 Activity

的 Intent (里面包含了有数据的 Bundle 对象) 再重新返回给主 Activity1。 如果要在 次 Activity2 中返回其它的数据, 例如, 经过计算后的结果、 数据, 此时只需将要返回 的数据再放入 Bundle 对象中即可达成。 此外, 以本范例 而言, 其实使用 startActivity() 也可达成同样的结果,仅需在主 Activity 被 create 时去判断 Intent 内有没有数据,有 的话, 就将数据带入;没有的话,就带入空值 (null) 。但程序还需要再做有无值的比较, 较为繁琐,既然 Android API 中有提供更好用的 方法,何来不用的道理?更何况如果系统 不是只有几行代码,而是几十行、几百行代码, 那 时头可就大了! 第四章:史上超豪华的手机控件 范例说明 EditText Widget 的设计是为了等待 User 输入而准备的,那么在 User 输入的同时, 又该 如何拦截所输入的文字呢?Android 的多数 Widget 都有 setOnKeyListener 事件, 以此 Listener 捕捉 User 输入的键盘事件。 接着, 本范例将以 EditText 与 TextView 示 范如何在捕捉 User 键盘输入文字的同时,实 时取得文字,同步显示于 TextView,类似手 机版的 Ajax 效果,实时输入实时输出。 运行结果 ▲图 4-1 在 EditText 输入的数据,立即出现在 TextView 里面 范例程序 src/irdc.ex04_01/EX04_01.java 主程序 中唯一也是关键之处, 便是利用 EditText.OnKeyListener 来拦截 EditText 的 键盘 输入事件, 仅需在其中重写 onKey() 方法, onKey() 方法中, EditText.getText() 在 将 取出 的文字,显示于 TextView 当中,是一个简单易懂的范例练习。 package irdc.ex04_01; import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import android.widget.EditText; import android.widget.TextView; public class EX04_01 extends Activity { /*声明 TextView、EditText 对象*/ private TextView mTextView01; private EditText mEditText01; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 取 得 TextView 、 EditText*/ mTextView01 = (TextView)findViewById(R.id.myTextView); mEditText01 = (EditText)findViewById(R.id.myEditText); /* 设 置 EditText 用 OnKeyListener 事 件 来 启 动 */ mEditText01.setOnKeyListener(new EditText.OnKeyListener() { @Override public boolean onKey(View arg0, int arg1, KeyEvent arg2) { // TODO Auto-generated method stub /* 设 置 TextView 显 示 EditText 所 输 入 的 内 容 */ mTextView01.setText(mEditText01.getText()); return false; } }); } } 延伸学习 这个实时输入实时显示的效果可以扩展在许多手机应用 程序中,可以试着 在 OnKeyListener() 里做实时文字过滤效果, 例如: User 输入不雅的文字时, 当 可以提示 User 不接受部分关键字,以输入 Shit 为例,在 TextView 就会出 现:Sh*t,此种做法可以过滤 掉 不雅文字的出现。 此外,不仅是 Widget 才有 setOnKeyListener 方法可以重写,事实 上,在 View 里也有 View.setOnKeyListener,也就是捕捉 User 点击键盘时的 事件处理,

但请特别注意, 需拦截 这个事件, View 要取得焦点 即 (Focus) 才能触发 onKeyDown 事件。 最后提醒你,旧版本 当中的 View.setKeyListener 类已经被删除,1.0r2 版之后,已经改 用 View.setOnKeyListener() 方法替换。 范例说明 延续前一章按钮事件的应用范例,重新设计一个具有背景图的按钮,让按钮有美观的背 景图片, 只是这次不使用先前的 Button Widget, 而是改以 ImageButton Widget 来显示。 将 按钮背景图预先 Import 至 Drawable 里(*.png 图形文件) ,利用这些图片,作为 ImageButton 的背景图,为了做对照,在 Layout 配置一个“一般按钮” ,运行结果画面中, 可 以明显看出图片按钮与一般按钮在外观上的差异。 要 设 置 ImageButton 背 景 图 有 许 多 方 法 , 此 程 序 使 用 的 方 法 是 ImageButton.setImageResource(),需要传 递的参数即是 res/drawable/ 下面的 Resource ID, 除 了设置背景图片的方法外,程序需 要用到 onFocusChange 与 onClick 等按钮事件作为按钮事 件点击之后的处理,最后通过 TextView 来显示目前图片按钮的状态为 onClick、onFocus, 或 offFocus,并且同步更新 按钮的背景图,让 User 有动态交互的感 觉。 运行结果 ▲图 4-2 随着 Focus 与 Click 动作,画面上的图片与文字会告知你目前图片 按钮的 状态 范例程序 src/irdc.ex04_02/EX04_02.java 主 程 序 构 造 三 个 对 象 ImageButton 、 Button 与 TextView , 并 在 ImageButton 上 设 置 onFocusChangeListener 与 onClickListener , 并 实 现 Image Button 图片的置换。ImageButton.setOnFocusChangeListener() 是处理 User 点击图片按 钮之后需要处理的关 键, 当点击图片按钮的瞬间, ImageButton.setImageResource() 来 以 更换背景图片。 package irdc.EX04_02; import android.app.Activity; import android.os.Bundle; import android.view.View; /*使用 OnClickListener 与 OnFocusChangeListener 来区分 按 钮 的 状 态 */ import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; public class EX04_02 extends Activity { /* 声 明 三 个 对 象 变 量 ( 图 片 按 钮 , 按 钮 , 与 TextView)*/ private ImageButton mImageButton1; private Button mButton1; private TextView mTextView1; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 通 过 findViewById 构 造 三 个 对 象 */ mImageButton1 =(ImageButton) findViewById(R.id.myImageButton1); mButton1=(Button)findViewById(R.id.myButton1); mTextView1 = (TextView) findViewById(R.id.myTextView1); /* 通 过 OnFocusChangeListener 来 响 应 ImageButton 的 onFous 事 件 */ mImageButton1.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View arg0, boolean isFocused) { // TODO Auto-generated method stub /*若 ImageButton 状态为 onFocus 改变 ImageButton 的图片 * 并改变 textView 的 文字*/ if (isFocused==true) { mTextView1.setText(" 图 片 按 钮 状 态 为 :Got Focus"); mImageButton1.setImageResource(R.drawable.iconfull); } /* 若 ImageButton 状 态 为

offFocus 改 变 ImageButton 的 图 片 * 并 改 变 textView 的 文 字 */ else { mTextView1.setText(" 图 片 按 钮 状 态 为 :Lost Focus"); mImageButton1.setImageResource(R.drawable.iconempty); } } }); /* 通 过 onClickListener 来 响 应 ImageButton 的 onClick 事 件 */ mImageButton1.setOnClickListener(new OnClickListener() { public void onClick(View v) { // TODO Auto-generated method stub /* 若 ImageButton 状 态 为 onClick 改 变 ImageButton 的图片 * 并改变 textView 的文字*/ mTextView1.setText("图片按钮状态 为:Got Click"); mImageButton1.setImageResource(R.drawable.iconfull); } }); /* 通 过 onClickListener 来 响 应 Button 的 onClick 事 件 */ mButton1.setOnClickListener(new OnClickListener() { public void onClick(View v) { // TODO Auto-generated method stub /*若 Button 状态为 onClick 改变 ImageButton 的图 片 * 并改变 textView 的文字*/ mTextView1.setText("图片按钮状态为:Lost Focus"); mImageButton1.setImageResource(R.drawable.iconempty); } }); } } 扩展学习 除了在运行时用 onFocus() 与 onClick() 事件来设置按钮背景图片外,Android 的 MVC 设计理念, 可以让程序运行之初就以 xml 定义的方式来初始化 ImageButton 的背景图, 仅需 先将图片导入 res/drawable。设 置 方 法 为 在 res/drawable 下 自 行 定 义 一 个 xml , 主 要 针 对 按 钮 的 state_focused 、 state_pressed 与 drawable 属性作 设置,如下所示: drawable/advancedbutton.xml <?xml version="1.0" encoding="utf-8"?> 然后, main.xml 中将 advancedbutton 赋值给 Button 组件中 background 的属性。 在 layout/main.xml 如此一来,即可达到如同本范例程序所展示的效果。 范例说明 Toast 是 Android 专属的提示小对象,它的 使用方式相当简单,但用途却很广泛,基 本 上,Toast 就是一个简短的小信息,将要告诉用户的信息,以一个浮动在最上层的 View 显示, 显示 Toast 之 后,静待几秒后便会自动消失,最常见的应用就是音量大小的调整, 当点击 音量调整钮之后, 会看见跳出的音量指示 Toast 对象, 等待调整完之后便会消失。通 过 Toast 的特性,可以在不影响用户通话或聆听音乐情况下,显示要给 User 的信息。 对 于程序员来说, 它也是一个非常好用的 debug 工具, 可以在任何程序运行时, 通过 Toast 的 方式, 显示运行变量或手机环境的概况。本范例使用一个 EditText 控件来接受用户输入 的 文 字 , 以 及 配 置 Button 按 钮 Widget , 点 击 按 钮 时 , 将 EditText 里 的 文 字 , 以 Toast.makeText()的方法让文字显示于 Toast 对象中, 这段文字会在显示一段时间后自动 消失,读者可借此体验一下 Toast 对象的使用与显示。 运行结果 ▲图 4-3 在 EditText 字段中填写文字,点击按钮送出后,会发出 Toast 信息 范例程序 src/irdc.ex04_03/EX04_03.java 主程序需要构建两个控件 EditText 与 Button,在 Button 的 onClick() 方法中使用 Toast 对象的 makeText() 方法来显示输入的文字。 package irdc.EX04_03; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.view.View; import

android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class EX04_03 extends Activity { /** Called when the activity is first created. */ /*声明两个对象变量(按钮与 编辑文字)*/ private Button mButton; private EditText mEditText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过 findViewById()取得对象 */ mButton=(Button)findViewById(R.id.myButton); mEditText=(EditText)findViewById(R.id.myEditText); /* 设 置 onClickListener 给 Button 对 象 聆 听 onClick 事 件 */ mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub /* 声 明 字 符 串 变 量 并 取 得 用 户 输 入 的 EditText 字 符 串 */ Editable Str; Str=mEditText.getText(); /* 使 用 系 统 标 准 的 makeText() 方 式 来 产 生 Toast 信 息 */ Toast.makeText( EX04_03.this, "你的愿望 "+Str.toString()+"已送达耶诞老人信箱", Toast.LENGTH_LONG).show(); /*清空 EditText*/ mEditText.setText(""); } }); } } 扩展学习 Toast 显示后会在一定时间内消失, Toast 构造参数中的第二个参数为显示的时间常 在 数,可设置为 LENGTH_LONG 或 LENGTH_SHORT,前者提示时间较长,后者较短,作为 传递 makeText() 方法的参数使用。 当然,你也可以使用重写 Toast 对象的方法,自定义 Toast 显示的 Layout,以不同于系 统内置的方式显示客制化的 Toast 对象,如要在 Toast 里显 示图片(Drawable) ,方式如下: Toast mToast01 = new Toast(this); ImageView mView01 = new ImageView(this); mView01.setImageResource(R.drawable.icon); mToast01.setView(mView01); mToast01.show(); 或显示自定义的 Layout Widget(如 TextView) ,则写法如下: Toast mToast01 = new Toast(this); TextView mView01=new TextView(this); mView01.setText("ToastWords"); mToast01.setView(mView01); mToast01.show(); 或者通过 AlertDialog.Builder 来创建类似 Toast 的信息对象,读者可以实现看看, 比较 两者有何不同: AlertDialog mADialog01 =new AlertDialog.Builder(this) mADialog01.setTitle("Android 提示"); mADialog01.setMessage("this is a message"); mADialog01.show(); 范例说明 所有的网络服务在 User 使用之前, 都需要签署同意条款, 在手机应用程序、 手机游戏 的 设 计 经 验 中 , 常 看 见 CheckBox 在 同 意 条 款 情 境 的 使 用 , 其 选 取 的 状 态 有 两 种 isChecked=true 与 isChecked=false。以下范例将设计一个 TextView 放 入 条 款 文 字 , 在 下 方 配 置 一 个 CheckBox Widget 作 为 选 取 项 , 通 过 Button.onClickListener 按 钮 事 件 处 理 , 取 得 User 同 意 条 款 的 状 态 。 当 CheckBox.isChecked 为 true,更改 TextView 的文字内容为“你已接受同意!!,当未 选 ” 取 CheckBox 时,Button 是不可以选择的(被 Disabled) 。 运行结果

▲图 4-4 未勾选“我同意”时, “确定”按钮是不可以按的 范例程序 src/irdc.ex04_04/EX04_04.java 利用 CheckBox.OnClickListener 里的事件来判断 Button 该不该显示,其方法就是判 断 Button.Enabled 的值; 在一开始时, 默认参数为 false, 当有点击 CheckBox 时, Button 参数 就修改为 true。 package irdc.ex04_04; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; public class EX04_04 extends Activity { /** Called when the activity is first created. */ /*声明 TextView、CheckBox、Button 对象*/ public TextView myTextView1; public TextView myTextView2; public CheckBox myCheckBox; public Button myButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 取 得 TextView 、 CheckBox 、 Button*/ myTextView1 = (TextView) findViewById(R.id.myTextView1); myTextView2 = (TextView) findViewById(R.id.myTextView2); myCheckBox = (CheckBox) findViewById(R.id.myCheckBox); myButton = (Button) findViewById(R.id.myButton); /* 将 CheckBox 、 Button 默 认 为 未 选 择 状 态 */ myCheckBox.setChecked(false); myButton.setEnabled(false); myCheckBox.setOnClickListener(new CheckBox.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(myCheckBox.isChecked()) { /* 设 置 Button 为 不 能 选 择 对 象 */ myButton.setEnabled(true); myTextView2.setText(""); } else { /*设置 Button 为可以 选择对象*/ myButton.setEnabled(false); myTextView1.setText(R.string.text1); /*在 TextView2 里显示出"请勾选我同意"*/ myTextView2.setText(R.string.no); } } }); myButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(myCheckBox.isChecked()) { myTextView1.setText(R.string.ok); }else { } } }); } } 扩展学习 CheckBox 在默认内容为空白时(没有任何默认的提示文字下) ,可设置提示 User 的 文 字, 其调用的方法为 CheckBox.setHint() 方法; 在扩展学习的范例练习, 是抓取 R.string.hello 这个字符串常数,其与默认 CheckBox 文字的结果是相同的,试试看: myTextView1 = (TextView) findViewById(R.id.myTextView1); myTextView2 = (TextView) findViewById(R.id.myTextView2); myCheckBox = (CheckBox) findViewById(R.id.myCheckBox); myButton = (Button) findViewById(R.id.myButton); myCheckBox.setChecked(false); /* 利 用 setHIT 抓 取 strings 里 面 的 值 */ CharSequence hint = getString(R.string.hello); myCheckBox.setHint(hint); /* 设 置 文 字 颜 色 */ myCheckBox.setHintTextColor(Color.RED); 范例说明 你使用消费券了吗?消费券只有 3600 元,但是想要 买的东西却是无穷多(∞) 。这

个 范 例 程 序 要 示 范 的 是 CheckBox.setOnCheckedChangeListener , 在 程 序 中 设 计 三 个 CheckBox 核取项,分别表示三种物品列表,当 User 勾选其中一个物品,就在 TextView 里 显示已选择 的物品列表。 程 序 的 关 键 在 同 时 聆 听 三 个 CheckBox.OnCheckedChangeListener 的 状 态 , 并 在 CheckBox.onChecked() 方法中, 重组所有被勾选的物品文字。 运行结果 ▲图 4-5 勾选不同的 CheckBox, CheckBox 的文字会在 TextView 中显示出来 该 范例程序 src/irdc.ex04_05/EX04_05.java 主 程 序 的 重 点 在 于 构 造 三 个 CheckBox 的 对 象 ,以 及 一 个 TextView 对 象 , 并 通 过 setOnCheckedChangeListener 实现 onCheckedChanged() 方法来更新 TextView 文字。 package irdc.EX04_05; import android.app.Activity; import android.os.Bundle; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.TextView; public class EX04_05 extends Activity { /*声明对象变量*/ private TextView mTextView1; private CheckBox mCheckBox1; private CheckBox mCheckBox2; private CheckBox mCheckBox3; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /*通过 findViewById 取得 TextView 对象并调整文字内容*/ mTextView1 = (TextView) findViewById(R.id.myTextView1); mTextView1.setText("你所选择的项目有: "); /* 通 过 findViewById 取 得 三 个 CheckBox 对 象 */ mCheckBox1=(CheckBox)findViewById(R.id.myCheckBox1); mCheckBox2=(CheckBox)findViewById(R.id.myCheckBox2); mCheckBox3=(CheckBox)findViewById(R.id.myCheckBox3); /* 设 置 OnCheckedChangeListener 给 三 个 CheckBox 对 象 */ mCheckBox1.setOnCheckedChangeListener(mCheckBoxChanged); mCheckBox2.setOnCheckedChangeListener(mCheckBoxChanged); mCheckBox3.setOnCheckedChangeListener(mCheckBoxChanged); } /* 声 明 并 构 造 onCheckedChangeListener 对 象 */ private CheckBox.OnCheckedChangeListener mCheckBoxChanged = new CheckBox.OnCheckedChangeListener() { /*implement onCheckedChanged 方法*/ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub /*通过 getString()取 得 CheckBox 的 文 字 字 符 串 */ String str0=" 所 选 的 项 目 为 : "; String str1=getString(R.string.str_checkbox1); String str2=getString(R.string.str_checkbox2); String str3=getString(R.string.str_checkbox3); String plus=";"; String result="但是超过 预算啰!!"; String result2="还可以再多买几本喔!!"; /*任一 CheckBox 被勾选后,该 CheckBox 的文字会改变 TextView 的文字内 容 * 三 个 对 象 总 共 八 种 情 境 */ if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str1+plus+str2+plus+str3+result); } else

if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str2+plus+str3+result); } else if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str1+plus+str3+result); } else if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==false) { mTextView1.setText(str0+str1+plus+str2+result); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==true) { mTextView1.setText(str0+str3+plus+result2); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==true & mCheckBox3.isChecked()==false) { mTextView1.setText(str0+str2); } else if(mCheckBox1.isChecked()==true & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==false) { mTextView1.setText(str0+str1); } else if(mCheckBox1.isChecked()==false & mCheckBox2.isChecked()==false & mCheckBox3.isChecked()==false) { mTextView1.setText(str0); } } }; } 扩展学习 读者可以将 OnCheckedChangeListener 改为 OnTouchListener(屏幕触控事件) ,方 法如 下: private CheckBox.OnTouchListener mCheckBoxTouch = new CheckBox.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub /* 判断在触控笔指压此控件时的状态 */ if(mCheckBox1.isChecked()==false) { /* 当 触 控 笔 放 开 后 的 动 作 */ } else if(mCheckBox1.isChecked()==true) { /*当触控笔压下后的动作*/ } return false; } }; 请试着比较 OnCheckedChangeListener 与 OnTouchListener 在使用上的差异。 范例说明 接下来要介绍的是 RadioGroup 的组事件。 RadioGroup 可将各自不同的 RadioButton 设 限于同一个 Radio 按钮组, 同属一个 RadioGroup 组里的按钮, 只能做出单一选择 (单 选题),虽然前一章曾经介绍过 RadioGroup 与 RadioButton,但当时使用的是 Button 事 件, 在此要示 范“点击”的同时就运行事件 处理,不再需要按钮(Button)的辅助了。 先 设 计 一个 TextView Widget , 以及 一 个 RadioGroup , 并于 该 RadioGroup 内 放 置两个 RadioButton,默认为都不选择,在程序运行阶段,利用 onCheckedChanged 作为启动事件装 置,让 User 选择其中一个按钮时,显示被选择的内容,最后将 RadioButton 的选项文字显 示 于 TextView 当中。 运行结果 ▲图 4-6 点击帅哥或美女按钮的同时,会立即显示事件结果 范例程序 src/irdc.ex04_06/EX04_06.java 利 用 OnCheckedChangeListener 来 启 动 RadioGroup 的 事 件 , 随 后 将 被 勾 选 的 RadioButton (mRadio1.getText())的文字显示于 TextView。 package irdc.ex04_06; import android.app.Activity; import android.os.Bundle; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.TextView; public class EX04_06 extends Activity { public TextView mTextView1; public RadioGroup mRadioGroup1; public RadioButton

mRadio1,mRadio2; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 取 得 TextView 、 RadioGroup 、 RadioButton 对 象 */ mTextView1 = (TextView) findViewById(R.id.myTextView); mRadioGroup1 = (RadioGroup) findViewById(R.id.myRadioGroup); mRadio1 = (RadioButton) findViewById(R.id.myRadioButton1); mRadio2 = (RadioButton) findViewById(R.id.myRadioButton2); /*RadioGroup 用 OnCheckedChangeListener 来 运 行 */ mRadioGroup1.setOnCheckedChangeListener(mChangeRadio); } private RadioGroup.OnCheckedChangeListener mChangeRadio = new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { // TODO Auto-generated method stub if(checkedId==mRadio1.getId()) { /*把 mRadio1 的 内 容 传 到 mTextView1*/ mTextView1.setText(mRadio1.getText()); } else if(checkedId==mRadio2.getId()) { /* 把 mRadio2 的 内 容 传 到 mTextView1*/ mTextView1.setText(mRadio2.getText()); } } }; } 扩展学习 在扩展学习里, 请加上两个 Button 在其中, 一个为回答, 另一个为清除 RadioButton 的 选择状态。 程序有随机设置的答案选项, User 点击回答按钮时, 当 比较答案是否正确, 正 若 确,则以 AlertDialog 对话窗口显示答案结果。 answerButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { new AlertDialog.Builder(TEST_56.this) .setIcon(R.drawable.icon) .setTitle(R.string.a bout_dialog_title) .setPositiveButton(R.string.about_dialog_ok, null) .setMessage(R.string.about_dialog_thanks) .create(); { } }).show(); } 在清除 Button.onClickListener 的事件处理中,只需将被选择的 RadioButton 取消 掉,回 到等待回答的状态。 mRadioGroup1.clearCheck(); 范例说明 在设计此范例之前,必须先准备三张图片(两张外框 图、一张内框图) ,将这三张图 片 放在 res/drawable 下面,在此使用的图片为 PNG 图形文件,而图案大小最好是调整成 手机 屏幕大小,或者依据手机 的分辨率,动态调整 ImageView 的大小。稍后的范例将介绍 如何 调整 ImageView 的大小, 这里就不赘述了。准 备 好 之 后 ,开 始 做 这 个 酷 炫 的 专 业 相 框 应 用 程 序 , 在 Layout 当 中 创 建 了 两 个 ImageView,且以绝对 坐标的方式“堆栈”在一起,在其下方放上两个按钮(Button) ,按钮的 目的是为了要用 来切换图片,创建完成后,要在 Button 事件里处理置换图片的动作。 程序目的为点击 Button1,ImageView1 会出现 right 的图片,点击 Button2,ImageView1 会出现 left 的 图片,而 ImageView2 皆为固定不动(文件名叫 oa) ,这个范例并不难,很快 就会知道葫 芦里卖的是什么药了,先来看看范例运行结果。 运行结果 ▲图 4-7 点击按钮后,会更换 ImageView(外框)图片 范例程序

src/irdc.ex04_07/EX04_07.java 此程序的关键地方在于 getResources() 这个方法,这个方法负责访问 Resource ID, 无 论 是 访 问 资 源 里 的 图 文 件 、 文 字 都 要 用 到 getResources() ; 在 此 使 用 getResources().getDrawable() 来 载 入 res/drawable 里 的 图 文 件 , 并 将 图 片 放 置 在 ImageView 当中。 package irdc.ex04_07; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ImageView; public class EX04_07 extends Activity { /* 声 明 Button 、ImageView 对 象*/ private ImageView mImageView01; private ImageView mImageView02; private Button mButton01; private Button mButton02; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 取 得 Button 、 ImageView 对 象 */ mImageView01 = (ImageView)findViewById(R.id.myImageView1); mImageView02 = (ImageView)findViewById(R.id.myImageView2); mButton01 = (Button) findViewById(R.id.myButton1); mButton02 = (Button) findViewById(R.id.myButton2); /* 设 置 ImageView 背 景 图 */ mImageView01.setImageDrawable(getResources(). getDrawable(R.drawable.right)); mImageView02.setImageDrawable(getResources(). getDrawable(R.drawable.oa)); /* 用 OnClickListener 事 件 来 启 动 */ mButton01.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { /*当启动后, ImageView 立 刻 换 背 景 图 */ mImageView01.setImageDrawable(getResources(). getDrawable(R.drawable.right)); } }); mButton02.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { mImageView01.setImageDrawable(getResources(). getDrawable(R.drawable.left)); } }); } } res/layout/main.xml 创建两个 ImageView, 一个为外框、 另一个为内框, 需注意的是, 图片需要做一个排序 堆 栈顺序, 将前景图放在上方 (以 AbsoluteLayout) , 将背景图放在前景图的下方, 这是最 简 单的堆栈顺序方法。 <?xml version="1.0" encoding="utf-8"?> 延伸学习 学会 ImageView 之后, 在延伸学习里, 便可试着将两个 ImageButton Widget 堆栈在 一起, 如此一来,不但有背景图,还有按钮事件。 接着,你就 可以自由发挥了。ImageButton 的使用方法已经介绍过,而堆栈的技巧可参 考这个范例程序, 比较不同的地方就是只要点击图片, 即可直接做换图的动 作, 不需要再 点 击面的 Button 做更换, 需要注意的是图片大小要作调整, 不然可能会与 ImageButton 不合 喔! 范例说明 Spinner 就是下拉菜单,也等于 swing 的 combo box、html 的 ,由于手机画面有 限, 要在有限的范围选择项目,下拉菜单是唯一、也是较好的选择。 Android 提供的 Spinner Widget 的下拉菜单已经非常好用了,样式也还适用。但本范例 的示范重点在于自定义下拉

菜单里的样式, 其关键在于调用 setDropDownViewResource 方 法, XML 的方式定义下拉 以 菜单要显示的模样。本范例除了自定义下拉菜单,还用程序设 计了一段动画, User 以触控 的方式点击这个自定义的 Spinner 时, 当 会以一段动画提示 User。 运行结果 ▲图 4-8 自定义 Spinner 的下拉菜单模式,具有圆角的效果 范例程序 src/irdc.ex04_08/EX04_08.java 在 new ArrayAdapter 中 , 我 们 使 用 ArrayAdapter(Context context, int textViewResourceId, T[] objects) 这个 Constructor, textViewResourceId 使用 Android 提供的 ResourceID,objects 为必须传递的字符串数 组(String Array) 。 Adapter 的 setDropDownViewResource 可以设置下拉菜单的显示方式, 将该 xml 定义在 res/layout 目 录 下 面 , 可 针 对 下 拉 菜 单 中 的 TextView 进 行 设 置 , 如 同 本 程 序 里 的 R.layout.myspinner_dropdown 即为自定义的下拉菜单 TextView 样式。除了改变 下拉 菜单样 式外,也对 Spinner 做了一点动态效果,点击 Spinner 时,晃动 Spinner 再出现 下拉菜单 (myAnimation) 。 package irdc.ex04_08; import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; import android.widget.ListView; import android.widget.Spinner; public class EX04_08 extends Activity { private static final String[] countriesStr = { "北京市", "上海市", "天津市", "重庆市" }; private TextView myTextView; private Spinner mySpinner; private ArrayAdapter adapter; Animation myAnimation; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入 main.xml Layout */ setContentView(R.layout.main); /* 以 findViewById() 取 得 对 象 */ myTextView = (TextView) findViewById(R.id.myTextView); mySpinner = (Spinner) findViewById(R.id.mySpinner); adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, countriesStr); /* myspinner_dropdown 为自定义下拉菜单样式定义在 res/layout 目录下 */ adapter.setDropDownViewResource(R.layout.myspinner_dropdown); /* 将 ArrayAdapter 添 加 Spinner 对 象 中 */ mySpinner.setAdapter(adapter); /* 将 mySpinner 添加 OnItemSelectedListener */ mySpinner.setOnItemSelectedListener (new Spinner.OnItemSelectedListener() { @Override public void onItemSelected (AdapterView<?> arg0, View arg1, int arg2, long arg3) { /* 将所选 mySpinner 的值 带入 myTextView 中 */ myTextView.setText("选择的是" + countriesStr[arg2]); /* 将 mySpinner 显示 */ arg0.setVisibility(View.VISIBLE); } @Override public void onNothingSelected(AdapterView<?> arg0) { // TODO Auto-generated method stub } }); /* 取 得 Animation 定 义在 res/anim 目 录 下 */ myAnimation = AnimationUtils.loadAnimation(this, R.anim.my_anim); /* 将 mySpinner 添加 OnTouchListener */ mySpinner.setOnTouchListener(new Spinner.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { /* 将 mySpinner 运 行 Animation */ v.startAnimation(myAnimation); /* 将 mySpinner 隐 藏 */ v.setVisibility(View.INVISIBLE); return false; } });

mySpinner.setOnFocusChangeListener(new Spinner.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { // TODO Auto-generated method stub } }); } } res/layout/myspinner_dropdown.xml 改变下拉菜单样子的 xml,里面所使用的组件为 TextView。 <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="24sp" android:singleLine="true" style="?android:attr/spinnerDropDownItemStyle" /> res/anim/my_anim.xml Android 的动画(animation)由四种类型(type)所组成:alpha、scale、translate, 以及 rotate,以下的自定义动画将使用当中的两种。 <?xml version="1.0" encoding="utf-8"?> 扩展学习 Animation 主要有两种动态方式, 一种是 tweened animation (渐变动画) 另一种是 frame , by frame animation(画面转换动画) 。tweened animation 则有以下四种基本 转换方式: l l l l AlphaAnimation (transparency changes): 透明度转换 RotateAnimation (rotations): 旋 转 转 换 ScaleAnimation (growing or shrinking) : 缩 放 转 换 TranslateAnimation (position changes):位置转换 定义好你想要的动画 xml 后, AnimationUtils.loadAnimation 将动画加载, 用 在想要 加上 动态效果的组件中使用 startAnimation 方法。 范例说明 前面的范例对 Spinner 的自定义菜单、 交互事件已大致掌握了设计方法, 但在 Android 的 Spinner 里的元素, 若要动态增减 Spinner 下拉菜单的选项, 就必须利用 ArrayList 的 依赖 性来完成。 以下范例将设计一个 EditText,当 User 输 入了新的文字,在点击“添 加” 按钮的同时, 就会将输入的值添加 Spinner (至下拉菜单的最后一项) , 接着 Spinner 会停留在刚添加好的 选项上;当 点击“删除”按钮,则删除选择的 Spinner 选项,常应用 于未知 Spinner 选项数量 的 To-Do List、或添加维护市县数据等等。 运行结果 ▲图 4-9 随 User 的输入文字,可动态添加/删除的 Spinner 菜单 范例程序 src/irdc.ex04_09/EX04_09.java Spinner 添加了 OnItemSelectedListener 事件, 当点击下拉菜单后, 将值带到上方 的 TextView。上一个范例在 new adapter 时传入 String 数组,这次因为要添加及删除 adapter, 所以要传入的是 ArrayList,否则,在添加删除时会出现错误。 package irdc.ex04_09; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class EX04_09 extends Activity { private static final String[] countriesStr = { "

北京市", "上海市", "天津市", "重庆市" }; private TextView myTextView; private EditText myEditText; private Button myButton_add; private Button myButton_remove; private Spinner mySpinner; private ArrayAdapter adapter; private List allCountries; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); allCountries = new ArrayList(); for (int i = 0; i < countriesStr.length; i++) { allCountries.add(countriesStr[i]); } /* new ArrayAdapter 对 象 并 将 allCountries 传 入 */ adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, allCountries); adapter .setDropDownViewResource (android.R.layout.simple_spinner_dropdown_item); /* 以 findViewById() 取 得 对 象 */ myTextView = (TextView) findViewById(R.id.myTextView); myEditText = (EditText) findViewById(R.id.myEditText); myButton_add = (Button) findViewById(R.id.myButton_add); myButton_remove = (Button) findViewById(R.id.myButton_remove); mySpinner = (Spinner) findViewById(R.id.mySpinner); /* 将 ArrayAdapter 添 加 Spinner 对 象 中 */ mySpinner.setAdapter(adapter); /* 将 myButton_add 添 加 OnClickListener */ myButton_add.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { String newCountry = myEditText.getText().toString(); /* 先比较添加的值是否已存在,不存在才可添加 */ for (int i = 0; i < adapter.getCount(); i++) { if (newCountry.equals(adapter.getItem(i))) { return; } } if (!newCountry.equals("")) { /* 将值添加至 adapter */ adapter.add(newCountry); /* 取得添加的值的位置 */ int position = adapter.getPosition(newCountry); /* 将 Spinner 选择在添加的值的位置 */ mySpinner.setSelection(position); /* 将 myEditText 清 空 */ myEditText.setText(""); } } }); /* 将 myButton_remove 添 加 OnClickListener */ myButton_remove.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { if (mySpinner.getSelectedItem() != null) { /* 删 除 mySpinner 的值 */ adapter.remove(mySpinner.getSelectedItem().toString()); /* 将 myEditText 清空 */ myEditText.setText(""); if (adapter.getCount() == 0) { /* 将 myTextView 清 空 */ myTextView.setText(""); } } } }); /* 将 mySpinner 添 加 OnItemSelectedListener */ mySpinner.setOnItemSelectedListener (new Spinner.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { /* 将所选 mySpinner 的 值 带 入 myTextView 中 */ myTextView.setText(arg0.getSelectedItem().toString()); } @Override public void onNothingSelected(AdapterView<?> arg0) { } }); } } 扩展学习 setDropDownViewResource 主要是设置 User 点击 Spinner 后出现的下拉菜单样式, 除 了 前一个范例使用自设方式改变 TextView 内容之外,android 亦提供两种基本的样式: l android.R.layout.simple_spinner_item:TextView 的下拉菜单。

l android.R.layout.simple_spinner_dropdown_item: 除了有 TextView, 右边有 radio 的下拉菜单。 查看 Android 源代码中的 simple_spinner_dropdown_item.xml,内容如下: <?xml version="1.0" encoding="utf-8"?> <TextView xmlns: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:singleLine="true" style="?android:attr/spinnerDropDownItemStyle" /> 以下为自定义修改后,适用于 spinner 的 Layout: <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/text1" android:layout_width="fill_parent" android:layout_height="12sp" android:singleLine="true" style="?android:attr/spinnerDropDownItemStyle" android:textSize="10sp" /> 范例说明 还记得在第三章 “简单的 Gallery 相片画廊” 例, 范 为了简化问题, 使用了 Android 默 认 的 Icon 作为 Gallery 显示的内容吗?现在, 将数张 PNG 图片导入 Drawable 当中, 并 于 onCreate 的同时,载入于 Gallery Widget 中,试着再添加一个 OnItemClick 的事件, 以取得 图片的 ID 编号来响应用户点击图片时的状态,完成 Gallery 的高级使用。本范例 的 另一个 重点,就是如何设置 Gallery 图片的宽高以及放置图片 Layout 的大小,在此我们 改 写 一 个 继 承 自 BaseAdapter 的 ImageAdapter 容 器 来 存 放 图 片 , 通 过 ImageView.setScaleType() 的方法 来改变图片的显示, 再通过 setLayoutParams() 方法来 改变 Layout 的宽高。 运行结果 ▲图 4-10 程序启动后会显示 res/drawable 中的图片, 点击任一图片会 Toast 该图片 的编号 范例程序 src/irdc.ex04_10/EX04_10.java 主程序有两大重点, 第一、 ImageAdapter 继承 BaseAdapter class 的未实现方法的 重写 是 构造,第二、则是 Gallery 的 OnItemClick() 方法与图片及 Layout 宽高设置。 package irdc.EX04_10; import android.app.Activity; import android.os.Bundle; /* 本 范 例 需 使 用 到 的 class */ import android.content.Context; import android.content.res.TypedArray; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Gallery; import android.widget.ImageView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class EX04_10 extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 通 过 findViewById 取得*/ Gallery g = (Gallery) findViewById(R.id.mygallery); /* 添加一 ImageAdapter 并设置给 Gallery 对象 */ g.setAdapter(new ImageAdapter(this)); /* 设 置 一 个 itemclickListener 并 Toast 被 点 击 图 片 的 位 置 */

g.setOnItemClickListener(new OnItemClickListener() { public void onItemClick (AdapterView<?> parent, View v, int position, long id) { Toast.makeText (EX04_10.this, getString(R.string.my_gallery_text_pre) + position+ getString(R.string.my_gallery_text_post), Toast.LENGTH_SHORT).show(); } }); } /* 改写 BaseAdapter 自定义一 ImageAdapter class */ public class ImageAdapter extends BaseAdapter { /*声明变量*/ int mGalleryItemBackground; private Context mContext; /*ImageAdapter 的构造器*/ public ImageAdapter(Context c) { mContext = c; /* 使用在 res/values/attrs.xml 中的<declare-styleable>定义 * 的 Gallery 属 性 .*/ TypedArray a = obtainStyledAttributes(R.styleable.Gallery); /* 取 得 Gallery 属 性 的 Index id*/ mGalleryItemBackground = a.getResourceId (R.styleable.Gallery_android_galleryItemBackground, 0); /*让对象的 styleable 属性能够反复使用*/ a.recycle(); } /* 重 写 的 方 法 getCount, 返 回 图 片 数 目 */ public int getCount() { return myImageIds.length; } /* 重 写 的 方 法 getItemId, 返 回 图 像 的 数 组 id */ public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } /* 重写的方法 getView,返回一 View 对象 */ public View getView (int position, View convertView, ViewGroup parent) { /*产生 ImageView 对象*/ ImageView i = new ImageView(mContext); /* 设 置 图 片 给 imageView 对 象 */ i.setImageResource(myImageIds[position]); /* 重 新 设 置 图 片 的 宽 高 */ i.setScaleType(ImageView.ScaleType.FIT_XY); /* 重 新 设 置 Layout 的 宽 高 */ i.setLayoutParams(new Gallery.LayoutParams(136, 88)); /* 设 置 Gallery 背 景 图 */ i.setBackgroundResource(mGalleryItemBackground); /*返回 imageView 对象*/ return i; } /*构建一 Integer array 并取得预加载 Drawable 的图片 id*/ private Integer[] myImageIds = { R.drawable.photo1, R.drawable.photo2, R.drawable.photo3, R.drawable.photo4, R.drawable.photo5, R.drawable.photo6, }; } } res/values/attrs.xml 定义 layout 外部 resource 的 xml 文件,用来改变 layout 的背景图。 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="Gallery"> </declare-styleable> </resources> 扩展学习 在 Android:ScaleType 中定义了下列常数可供使用, 通过 “ObjectView.ScaleType 常 数名称” 的方式,就可以改变图片的显示方式。 常数名称 Matrix fitXY fitStart fitCenter 值 0 1 2 3 fitEnd center centerCrop centerInside 4 5 6 7 另外,在主程序中,我们使用了下面这一段写法:

TypedArray a = obtainStyledAttributes(R.styleable.Gallery); 这是一个引用自制 layout 元素的用法, 必须在 res/values 下面添加一个 attrs.xml, 并在 其 中 定 义 <declare-styleable> 标 签 TAG, 目 的 是 自 定 义 layout 的 背 景 风 格 , 并 且 通过 TypeArray 的特性, 让相同的 Layout 元素可以重复用于每一张图 片。 范例说明 大家都应该用过操作系统中的文件搜索功能吧! 它可以 快速帮我们找到想要的文件。如 果要在手机制作一个文件搜索的功能, 又该如何实现呢?其实这个功能并不困难, Java I/O 的 API 中提供了 java.io.File 对象,只要利用 File 对象的方法,再搭配 Android 的 EditText、 TextView 等对象, 就可以轻松做出一个手机的文件搜索引擎。 本范例中使用 EditText、Button 与 TextView 三种对象来实现此功能,用户将要搜索的 文件名称或关键 字输入 EditText 中, 点击 Button 后, 程序会在根目录中寻找符合的文件, 并 将搜索 结果显示于 TextView 中;如果找不到符合的文件,则显示找不到文件。 运行结果 ▲图 4-11 快速搜索手机根目录中的文件 范例程序 src/irdc.ex04_11/EX04_11.java 范例中以 java.io.File 对象来取得根目录下的文件,经过比较后,将符合结果的文件 路径 写入 TextView 中,若要在 TextView 中换行,需使用 \n 来作换行符号。 package irdc.ex04_11; /* import 相 关 class */ import java.io.File; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class EX04_11 extends Activity { /*声明对象变量*/ private Button mButton; private EditText mKeyword; private TextView mResult; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); /* 初 始 化 对 象 */ mKeyword=(EditText)findViewById(R.id.mKeyword); mButton=(Button)findViewById(R.id.mButton); mResult=(TextView) findViewById(R.id.mResult); /* 将 mButton 添 加 onClickListener */ mButton.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { /*取得输入的关键字*/ String keyword = mKeyword.getText().toString(); if(keyword.equals("")) { mResult.setText(" 请 勿 输 入 空 白 的 关 键 字 !!"); } else { mResult.setText(searchFile(keyword)); } } }); } /* 搜索文件的 method */ private String searchFile(String keyword) { String result=""; File[] files=new File("/").listFiles(); for( File f : files ) { if(f.getName().indexOf(keyword)>=0) { result+=f.getPath()+"\n"; } } if(result.equals("")) result="找不到文件!!"; return result; } } 扩展学习 在本范例中, searchFile(String keyword) 这个方法的功用为搜索根目录下符合关键字

的 文件,在搜索文件的过程中,只搜索根目录中的文件,并没有再对子目录下的文件作进 一步 的比较, 如果要再强化这个 文件搜索的功能, 让它也能搜索包含子目录下的所有文件, 可 以在程序中利用 File.isDirectory() 这个方法来判断其是否为目录。 如果是的话, 就继 续往下 一层寻找;不是的话,就终止向下寻找的动作。这个做法在后面的范例中会有详细的 示范, 要注意的是手机 硬件环境是否能负荷程序做大规模的文件搜索,毕竟手机的硬件配 备 (处 理器、 内存) 是比不上一般计算机的。 另外, 在范例中仅针对关键字做了空白检查, 在比较文 件名称时也没有忽略大小写 (要 大小写完全符合才搜索的出来) ,如果要让这 个搜索功能更完备,当然可以多做一点变化, 例如,检查关键字是否包含特殊的字符、 可 选择比较文件时是否忽略大小写、 可选择要搜 索的文件类型, 甚至是可自己指定要搜索的目 录等等,就看各位要如何运用了。 范例说明 Android 默 认 的 按 钮 通 常 都 是 方 方 正 正 的 , 也 许 前 面 的 范 例 已 经 看 过 如 何 通 过 ImageButton 来置换按钮背景图片了, 但本范例的练习则是在两个按 钮之间的交互,点击 A 按钮,恢复 B 按钮图片;点击 B 按钮,恢复 A 按钮的图片。使用 的方法为点击的瞬间置换 图片,置换的图片方式与先前介绍的相同 (ImageButton.setImageDrawable) 。 程序项目预先 import 了三张图 片: p1.png、 p2.png 以及 p3.png, 而在 onCreate() 时,画面 Layout 上的两个 ImageButton 各自显示 p1.png 以及 p2.png,当点击其一按钮,则改变 自己的图片为 p3.png,并还原另 一按钮的为默认 的图文件,以按钮事件及图文件置换的方 式来提醒 User 现在所“选择”的图像按钮为何。 运行结果 ▲图 4-12 被点击的 ImageButton 因变换图片而被 Hightlight 出来 范例程序 src/irdc.ex04_12/EX04_12.java 范 例 程 序 主 要 通 过 setImageDrawable 方 法 来 改 变 按 钮 的 图 片 , 图 片 是 放 在 res/drawable 目录下面。而 ImageButton.setOnClickListener() 的角色则是本程序的关 键,通过同时换两个 按钮的图片来表示被选择的图片,是一种类似被选择的假象手法,常应 用于切换开关(Turn On/Turn Off)之用。 package irdc.ex04_12; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ImageButton; import android.widget.TextView; public class EX04_12 extends Activity { TextView myTextView; ImageButton myImageButton_1; ImageButton myImageButton_2; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入 main.xml Layout */ setContentView(R.layout.main); /* 以 findViewById() 取 得 TextView 及 ImageButton 对 象 */ myTextView = (TextView) findViewById(R.id.myTextView); myImageButton_1=(ImageButton)findViewById(R.id.myImageButton_1); myImageButton_2=(ImageButton)findViewById(R.id.myImageButton_2); /* myImageButton_1 添 加 OnClickListener */ myImageButton_1.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { myTextView.setText("你点 击的是 myImageButton_1"); /* 点击 myImageButton_1 时将 myImageButton_1 图片置换 成 p3 图 片 */

myImageButton_1.setImageDrawable(getResources().getDrawable( R.drawable.p3)); /* 点 击 myImageButton_1 时 将 myImageButton_2 图 片 置 换 成 p2 图 片 */ myImageButton_2.setImageDrawable(getResources().getDrawable( R.drawable.p2)); } }); /* myImageButton_2 添 加 OnClickListener */ myImageButton_2.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { myTextView.setText("你点击的是 myImageButton_2"); /* 点 击 myImageButton_2 时 将 myImageButton_1 图 片 置 换 成 p1 图 片 */ myImageButton_1.setImageDrawable(getResources().getDrawable( R.drawable.p1)); /* 点 击 myImageButton_2 时 将 myImageButton_2 图 片 置 换 成 p3 图 片 */ myImageButton_2.setImageDrawable(getResources().getDrawable( R.drawable.p3)); } }); } } 扩展学习 除了自己在 res/drawable 放置图片方式外, 也可以用系统 Android 操作系统默认的 图片, 如打电话、简短提示的图标等等,只要修改 main.xml 里 ImageButton 的属性: android:src="@drawable/p1" 将其改成 android:src="@android:drawable/sym_action_call" 看到了吗?标识“@android:”就表示是引用 android 所提供,而非自行导入的。 范例说明 通过 Google 上网搜索时,只要输入几个文字,就会显示可能的关键字让你挑选,这种 效果在 Android 中是非常容易达到的。事实上,Android 的 AutoCompleteTextView Widget, 只要搭配 ArrayAdapter 就能设计出类似 Google 搜索提示的效果。 本范例先在 Layout 当中布局一个 AutoCompleteTextView Widget,然后通过预先设置好 的 字 符 串 数 组 , 将 此 字 符 串 数 组 放 入 ArrayAdapter , 最 后 利 用 AutoCompleteTextView.setAdapter 方法, 就可以让 AutoCompleteTextView Widget 具有 自动完 成提示的功能。例如,只要输入 ab,就会自动带出包含 ab 的所有字符串列表。 运行结果 ▲图 4-13 只要输入 ab,就会自动带出所有包含 ab 的字符串列表 范例程序 src/irdc.ex04_13/EX04_13.java 本范例 程序主要示范 AutoCompleteTextView 用法,再次使用到 ArrayAdapter,只要 是 有 下 拉 菜 单 的 项 目 , 都 必 须 使 用 到 ArrayAdapter 对 象 。 此 外 , 将 ArrayAdapter 添 加 AutoCompleteTextView 对象中, 所使用的方法为 setAdapter, 当中传 输唯一的参数类型即为 字符串类型的 ArrayAdapter。 package irdc.ex04_13; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; public class EX04_13 extends Activity { private static final String[] autoStr = new String[] { "a", "abc", "abcd", "abcde" }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); /* new ArrayAdapter 对象并将 autoStr 字符串数组传 入 */ ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, autoStr); /* 以 findViewById()取得

AutoCompleteTextView 对 象 */ AutoCompleteTextView myAutoCompleteTextView = (AutoCompleteTextView) findViewById(R.id.myAutoCompleteTextView); /* 将 ArrayAdapter 添 加 AutoCompleteTextView 对 象 中 */ myAutoCompleteTextView.setAdapter(adapter); } } 扩展学习 有个类 似 AutoCompleteTextView 的对象, 称为 MultiAutoCompleteTextView, 它继承 了 AutoCompleteTextView,差别在于它可以在输入框一直增加新的选择值,其编写方式也 有些 不同, 一定要 setTokenizer, 否则会 出现错误, 以下范例是传入 CommaTokenizer 类, 结果 会将原本选择框里的值往后加逗号及空白。 package irdc.ex04_13; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.MultiAutoCompleteTextView; public class EX04_13 extends Activity { private static final String[] autoStr = new String[] { "a", "abc", "abcd", "abcde" }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* 载入 main.xml Layout */ setContentView(R.layout.main_1); /* new ArrayAdapter 对象并将 autoStr 字 符 串 数 组 传 入 */ ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, autoStr); /* 以 findViewById()取得 MultiAutoCompleteTextView 对象 */ MultiAutoCompleteTextView myAutoCompleteTextView = (MultiAutoCompleteTextView) findViewById(R.id.myAutoCompleteTextView); /* 将 ArrayAdapter 添 加 AutoCompleteTextView 对 象 中 */ myAutoCompleteTextView.setAdapter(adapter); myAutoCompleteTextView.setTokenizer (new MultiAutoCompleteTextView.CommaTokenizer()); } } 范例说明 Android 里的 AnalogClock Widget 是一个时钟对象,本范例将配置一个小时钟,并在 其 下放置一个 TextView,为了做对照,上面放置的为模拟时钟,下面的 TextView 则模拟 电 子 时 钟 , 将 AnalogClock 的 时 间 以 数 字 钟 形 式 显 示 。 本 范 例 的 重 点 , 在 在 android.os.Handler、java.lang.Thread 以及 android.os.Message 三对 象的整合应用, 通过产生 Thread 对象,在进程内同步调用 System.currentTimeMillis() 取得 系统时间, 并通过 Message 对象来通知 Handler 对象,Handler 则扮演联系 Activity 与 Thread 之 间的桥梁, 在收到 Message 对象后, 将时间变量的值, 显示于 TextView 当中, 产生数字 时 钟的外观与功能。 运行结果 ▲图 4-14 示数字钟 程序启动后,除了 AnalogClock 会显示目前系统时间外,下方会显 范例程序 src/irdc.ex04_14/EX04_14.java 主程序需要另外加载 Java 的 Calendar 与 Thread 对象,在 onCreate() 中构造 Handler 与 Thread 两对象,并实现 handelMessage() 与 run() 两个方法,如下所述: package irdc.EX04_14; import android.app.Activity; import android.os.Bundle; /* 这里我们需要使用 Handler 类与 Message 类来处理进程*/ import android.os.Handler; import android.os.Message; import android.widget.AnalogClock; import

android.widget.TextView; /*需要使用 Java 的 Calendar 与 Thread 类来取得系统时间 */ import java.util.Calendar; import java.lang.Thread; public class EX04_14 extends Activity { /* 声 明 一 常 数 作 为 判 别 信 息 用 */ protected static final int GUINOTIFIER = 0x1234; /*声明两个 widget 对象变量*/ private TextView mTextView; public AnalogClock mAnalogClock; /*声明与时间相关的变量*/ public Calendar mCalendar; public int mMinutes; public int mHour; /*声明关键 Handler 与 Thread 变量*/ public Handler mHandler; private Thread mClockThread; /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); /* 通 过 findViewById 取 得 两 个 widget 对 象 */ mTextView=(TextView)findViewById(R.id.myTextView); mAnalogClock=(AnalogClock)findViewById(R.id.myAnalogClock); /*通过 Handler 来接收进程所传递的信息并更新 TextView*/ mHandler = new Handler() { public void handleMessage(Message msg) { /*这里是处理信息的方法*/ switch (msg.what) { case EX04_14.GUINOTIFIER: /* 在 这 处 理 要 TextView 对 象 Show 时 间 的 事 件 */ mTextView.setText(mHour+" : "+mMinutes); break; } super.handleMessage(msg); } }; /*通过进程来持续取得系统时间*/ mClockThread=new LooperThread(); mClockThread.start(); } /*改写一个 Thread Class 用来持续取得系统时间*/ class LooperThread extends Thread { public void run() { super.run(); try { do { /*取得系统时间*/ long time = System.currentTimeMillis(); /*通过 Calendar 对象来取得小时与分钟*/ final Calendar mCalendar = Calendar.getInstance(); mCalendar.setTimeInMillis(time); mHour = mCalendar.get(Calendar.HOUR); mMinutes = mCalendar.get(Calendar.MINUTE); /*让进程休息一秒*/ Thread.sleep(1000); /*重要关键程序:取得时间后发出信息给 Handler*/ Message m = new Message(); m.what = EX04_14.GUINOTIFIER; EX04_14.this.mHandler.sendMessage(m); }while(EX04_14.LooperThread.interrupted()= =false); /* 当 系 统 发 出 中 断 信 息 时 停 止 本 循 环 */ } catch(Exception e) { e.printStackTrace(); } } } } 扩展学习 其实要达到本范例效果的代码应该只有两行, 也就是将笔者对于 TextView 与进程 Thread 的处理,改为使用 widget.DigitalClock 的方式,写法如下: import android.widget.AnalogClock mDigitalClock =(DigitalClock)findViewById(R.id.myDigitalClock); 而本程序使用 TextView 来模拟 DigitalClock 的做法, 实际上, 也是参考 AnalogClock 与 DigitalClock 这两个 widget 的程序代码所做的练习, 对于将来实现 Timer 相关的小对 象, 会 有所帮助。 Android 提供了 System.currentTimeMillis()、 uptimeMillis()、 elapsedRealtime() 这 三 种 不 同 特 性 的 System Clock 给 开 发 者 使 用 。 本 范 例 使 用 System.currentTimeMillis() 就是标准的 Clock 用法,需要搭配真实的日期与时间使用, 另外两者则是适用于 interval 与 elapse time 来控制程序与 UI 之用, 读者可以自己练习

看看。 范例说明 许多的网络服务, 例如网络订票、 网络订房、 网络订旅游行程、 或在注册会员输入基本 数 据时,都会需要用户输入日期与时间格式的数据,有些网站会提供贴心的小功能:直接由 万 年历上来选择日期与时间,选择完毕,就会自动将日期与时间带入需要填写的字段中。 Android 有没有提供类似的组件可以实现这样的功能呢?答案是肯定的!在这个范例中,将 示范以 Android API 中提供的 DatePicker 与 TimePicker 两种对象来实现动态输入日期 与时 间的功能。 范例中使用了 DatePicker、 TimePicker 与 TextView 三种对象,以 TextView 来 显 示 日 期 与 时 间 , 默 认 带 入 目 前 系 统 的 日 期 与 时 间 , DatePicker 与 TimePicker 可 让 用 户 动 态 调 整 日 期 与 时 间 , 当 用 户 调 整 了 DatePicker 的 日 期 或 TimePicker 时间时,则 TextView 中所显示的 日 期与时间亦会跟着改变。 运行结果 ▲图 4-15 由手机屏幕上动态输入日期与时间 范例程序 src/irdc.ex04_15/EX04_15.java 程 序 中 以 updateDisplay() 这 个 方 法 来 设 置 TextView 中 所 显 示 的 日 期 时 间 , 以 java.util.Calendar 对象来取得目前的系统时间,并预先带入 TextView 中。 当用户 更改了 DatePicker 里的年、 日时, 月、 将触发 DatePicker 的 onDateChange() 事 件,运行 updateDisplay() 来重新设置 TextView 中显示的日期;同样的原理,当用户 更改了 TimePicker 里 的 时 间 , 会 触 发 TimePicker 的 onTimeChange() 事 件 , 运 行 updateDisplay() 来 重新设置 TextView 中显示的时间。 package irdc.ex04_15; /* import 相关 class */ import java.util.Calendar; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import android.widget.DatePicker; import android.widget.TimePicker; public class EX04_15 extends Activity { /*声明日期及时间变量*/ private int mYear; private int mMonth; private int mDay; private int mHour; private int mMinute; /*声明对象变量 */ TextView tv; TimePicker tp; DatePicker dp; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { /*取得目前日期与 时 间 */ Calendar c=Calendar.getInstance(); mYear=c.get(Calendar.YEAR); mMonth=c.get(Calendar.MONTH); mDay=c.get(Calendar.DAY_OF_MONTH); mHour=c.get(Calendar.HOUR_OF_DAY); mMinute=c.get(Calendar.MINUTE); super.onCreate(savedInstanceState); /* 载 入 main.xml Layout */ setContentView(R.layout.main); /*取得 TextView 对象,并调用 updateDisplay() 来设置显示的初始日期时间*/ tv= (TextView) findViewById(R.id.showTime); updateDisplay(); /*取 得 DatePicker 对 象 , 以 init() 设 置初 始 值 与 onDateChangeListener() */ dp=(DatePicker)findViewById(R.id.dPicker); dp.init(mYear,mMonth,mDay,new DatePicker.OnDateChangedListener() { @Override public void onDateChanged(DatePicker view,int year, int monthOfYear,int dayOfMonth) { mYear=year; mMonth= monthOfYear; mDay=dayOfMonth; /* 调用 updateDisplay()来改变显示日期*/ updateDisplay(); } });

/* 取 得 TimePicker 对 象 , 并 设 置 为 24 小 时 制 显 示 */ tp=(TimePicker)findViewById(R.id.tPicker); tp.setIs24HourView(true); /*setOnTimeChangedListener , 并 重 写 onTimeChanged event*/ tp.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() { @Override public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { mHour=hourOfDay; mMinute=minute; /*调用 updateDisplay()来改变显 示时间*/ updateDisplay(); } }); } /*设置显示日期时间的方法*/ private void updateDisplay() { tv.setText( new StringBuilder().append(mYear).append("/") .append(format(mMonth + 1)).append("/") .append(format(mDay)).append(" ") .append(format(mHour)).append(":") .append(format(mMinute)) ); } /*日期时间显示两位数的方法*/ private String format(int x) { String s=""+x; if(s.length()==1) s="0"+s; return s; } } 扩展学习 本范例的重点在于学习如何使用 DatePicker 与 TimePicker 对象来达成动态调整日期 与 时 间 的 功 能 , 眼 尖 的 读 者 应 该 发 现 , 在 范 例 中 , DatePicker 实 现 OnDateChangedListener() 的 方法与 TimePicker 实现 OnTimeChangedListener() 的方法 是不太相同的。DatePicker 对象以 init() 这个方法来指定 DatePicker 初始的年、月、日 及 OnDateChangedListener() 的 事 件 ; 而 TimePicker 对 象 则 是 直 接 以 setOnTimeChangedListener() 事件来处理时间改变时程序要做的 操作。 在 旧 版 的 Android SDK ( 1.0r2 版 以 前 的 SDK 版 本 ) 中 , DatePicker 对 象 有 提 供 setOnDateChangedListener() 这个方法, 但是在新版的 SDK (1.0r2) , 这个方法被删除了, 所 以 要 实 现 OnDateChangedListener() 时 , 必 须 以 init() 方 式 来 重 写 OnDateChangedListener(); 而 TimePicker 则直接以 setOnTimeChangedListener() 来实 现 即 可 。 Android API 另 外 提 供 了 其 它 的 对 象 来 实 现 动 态 修 改 日 期 时 间 的 功 能 : DatePickerDialog 与 TimePickerDialog。这两种类型的对象最大 的差别在于 DatePicker 与 TimePicker 是直接 显示在屏幕画面上,DatePickerDialog 与 TimePickerDialog 对象 则是以跳出 Dialog 的方式 而 来显示,如下图所示。 ▲图 4-16 DatePickerDialog 与 TimePickerDialog DatePickerDialog 与 TimePickerDialog 的实现方式又为何?以下提供简单的范例供 各位 参考: /* 取 得 更 改 日 期 的 Button , 添 加 onClickListener */ Button dButton=(Button)findViewById(R.id.dPicker); dButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { /* onClick 时 跳 出 DatePickerDialog */ new DatePickerDialog(EX04_15_1.this, new DatePickerDialog.OnDateSetListener() { public void onDateSet(DatePicker view,int year, int monthOfYear,int dayOfMonth) { /* 这 里 放 更 新 日 期 的 方 法 */ } },mYear,mMonth,mDay).show(); } }); /* 取 得 更 改 时 间 的 Button , 添 加 onClickListener */ Button tButton=(Button)findViewById(R.id.tPicker); tButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { /* onClick 时 跳 出 TimePickerDialog */ new TimePickerDialog(EX04_15_1.this, new TimePickerDialog.OnTimeSetListener() { public

void onTimeSet(TimePicker view,int hourOfDay, int minute) { /* 这里放更新时间的方 法 */ } },mHour,mMinute,true).show(); } }); 不论是 DatePicker、TimePicker,或 DatePickerDialog、TimePickerDialog,都可以 实现动 态更改日期时间的功能,要用哪一种方式来实现,端视各位的应用程序需要啰!


相关文章:
Android-复习重点
home 键 Android 程序的隐藏 , 当你按下手机的 Home 键的时候 , 系统会默认调用程序栈中最上层 Activity 的 stop()方法,然后整个应用程序都会被隐藏起来,当你...
基于Android平台的个人理财软件的设计与实现_图文
本科毕业论文(设计) 基于 Android 平台的个人理财软件的设计 与实现 二级学院 专班业级 医药信息工程学院 计算机科学与技术 医学智能 2011 级(1)班 林彬健 ...
Android编程基础教学大纲
Android编程基础教学大纲_工学_高等教育_教育专区。Android编程基础教学大纲 《Android 编程初级》教学大纲 2013-11-21 修订版 一、 课程教学目标: (一) 知识目标...
ANDROID:控件属性(很全)
控件属性: android 属性 Android 功能强大,界面华丽,但是众多的布局属性就害苦了开发者,下面这 篇文章结合了网上不少资料, 第一类:属性值为 true 或 false ...
自己整理最全Android笔试大全
自己整理最全Android笔试大全_求职/面试_求职/职场_应用文书。自己整理的Android笔试宝典,出去面试做笔试题再也不用担心了,而且会喜欢上做Android面试题。制胜宝典!1...
毕业设计:基于Android平台的音乐播放器设计与开发_图文
毕业设计:基于Android平台的音乐播放器设计与开发_工学_高等教育_教育专区。东北大学秦皇岛分校 毕业设计 (论文) 第 1 页 毕业设计基于安卓平台的音乐播放器 (论文...
如何导入一个android源码并且运行
如何导入一个android源码并且运行_计算机软件及应用_IT/计算机_专业资料。如何导入一个android源码并且运行 小编初学 android 的时候也不知道如何导入源码,但经过半天...
android开发关键技术
Android 开发技术研究 学 号:110085208027 学生所在学院:信息工程学院 学生姓名 :陈烨 任课教师 :杨词慧 教师所在学院:信息工程学院 2012 年 6 月 Android 开发...
Android应用开发试题
Android应用开发试题_IT/计算机_专业资料。Android 应用开发试题一、选择题(每题 2 分,共 30 分) 1. 下列不是手机操作系统的是?()D B. Window Mobile C. ...
Android实训心得
Android 实训心得 刚开始接触 Android 感觉到它很有意思,在界面开发上和 web 也可以形成了相通 的架构,更加方便,视觉上也是非常的酷,在前期我通过的大量的 ...
更多相关标签: