第二章中,我们简要介绍了一些android应用程序的结构和基本概念。同时,你也已经了解了Android SDK、Eclipse的开发工具ADT,如何使用虚拟机运行android程序。
在本章和接下来的几章,我们会深入了解一下Android SDK的基础模块。这些模块包括资源(resources)、content providers和intents。
Android依赖资源对UI控件进行描述性的定义。这种定义与HTML使用tag定义UI没有什么不同。基于这种思想,Android对UI设计方法进行了改进。Android运行资源模式化、局部化。本章将介绍各种Android中的资源。
理解资源
资源在Android架构中扮演了重要角色。Android中的资源是与可运行应用所绑定的文件(类似于音乐文件或者描述窗口部局的文件)或者值(例如对话框的标题)。这些文件与值的绑定方式可以允许在改变时不必重新编译应用。
资源的一些常见例子包括字符串(Strings)、颜色(colors)、位图(bitmaps)和布局(layouts)。资源中的字符串允许你使用其资源id,而不是在代码中进行硬编码。这种间接使用资源的方法可以使你只改变资源而不用改变代码。
Android中有相当多的资源。我们就以最常见的资源String为例进行介绍。
字符串资源
Android允许你在一个或者多个xml资源文件中定义字符串。这些包含字符串的资源文件位于res/values目录下。虽然你常见的是名字为strings.xml的文件,但是事实上其名称可以是任意的。列表3-1给出了一个字符串资源的例子:
Listing 3-1:string.xml
<?xml version = "1.0" encoding="utf-8"?>
<resources>
<string name="hello">hello</string>
<string name="app_name">hello appname</string>
</resorces>
注:在一些eclipse版本中,<resources>需要xmlns进行限制。只要有xmlns描述就可以,而不必管xmlns到底指向哪里。例如下面的两个例子都可以:
<resources xmlns="http://schmas.android.com/apk/res/android">
<resources xmlns="default namespaces">
甚至连第一行(指明这是一个xml文件,其编码格式是什么)都可以忽略,Android依然可以很好的运行。
当该文件创建或者进行更新时,Android的ADT工具会自动在根目录的包中生成一个R.java文件,其中不同的字符串有着不同的id号。注意下面例子里R.java的位置。这是一个应用的高层面的结构:
注:无论有多少资源文件,只要一个R.java文件生成。
对于列表3-1中的资源,在R.java中对应的结构如下:
请注意android是如何定义顶层类的:public final class R。其内部类为public static final类型,名为string。R.java文件通过创建这个内部类作为string的命名空间,来统一管理string资源。
其中的两个static final的int数据是与之相对应的字符串资源的id。你可以通过下面的形式在任何地方使用资源id:
R.string.hello
这些自动生成的id指向的是int类型,而不是string类型。大多数方法使用字符串时,以这些int型的id作为输入,android会在需要时将这些int数据转换为string。
将所有的字符串定义在strings.xml里仅仅是一个约定俗成的规范。任何文件,只要其内部结构类似于list 3-1,且位于res/values目录下,android都可以进行处理。
这个文件的结构很容易学习。你会有一个根节点<resources>,里面有一系列<string>子元素。每一个<string>子元素里都有一个name属性,与R.java里的id相对应。
为了演示android允许多个字符串资源文件存在,你可以加入另一个资源文件string1.xml,放在同样的目录下,其内容见list 3-3
android ADT插件会在编译时检查id的唯一性,然后把这些id作为新的常量加入到R.java里去,例如:R.string.hello1和R.string.app_name1。
布局资源
在Android中,屏幕上显示的图像往往从xml文件中加载而来,而xml文件则视为资源。这与HTML文件描述网页的内容与布局十分相似。这些xml文件称为布局资源。布局文件是Android UI编程中的关键性文件。以list 3-4展示的代码为例:
setContentView(R.layout.main)这一行可以看出,存在着一个静态的类R.layout,其内部有一个常量main(int 类型)指向了一个由布局资源文件所定义的视图。该布局文件的名称为main.xml,位于res/layout目录下。换句话说,这个表达式需要程序员创建一个文件res/layout/main.xml,并在文件中定义必要的布局。main.xml的内容可以如List3-5所示:
文中定义了一个名为LinearLayout的根节点,其中包含着一个TextView和一个Button。LinearLayout可以以水平或者垂直的方式排列子布局。本例为垂直方式。
你需要为每个屏幕(或activity)定义一个单独的布局文件。更确切的说,每一个布局需要对应一个专门的文件。如果你打算画两个屏幕,你很可能需要两个布局文件:res/layout/screen1_layout.xml和res/layout/screen2_layout.xml。
注:res/layout目录下的每一个文件都会根据其文件名(不包括后缀)生成一个独一无二的常量。对于layout而言,布局文件的数量是关键,而对于string资源,单独的string资源的数量才是关键所在。
例如你在res/layout有两个layout文件,分别为file1.xml和file2.xml。这样在R.java里会生成如List3-6所示的结构:
资源文件中的具体视图,例如TextView等可以通过R.java中自动生成的资源id进行引用,如
TextView tv = (TextView) findViewById(R.id.text1);
tv.setText("Try this text instead");
本例中,你通过Activity类中的findViewById方法来定位到TextView。其中常量R.id.text1与TextView中定义的id相对应。如:
<TextView id="@+id/text1">
...
</TextView>
其中,属性id的值,也就是常量text1被用来唯一标识该TextView,和Activity中的其它View区别开来。“+”表示如果id “text1”不存在的话则创建一个。下面你会看到更多的关于id属性的内容。
资源引用符(Resources Reference Syntax)
先不考虑各种资源的类型(目前为止我们已经接触到了string和layout两种),Android的java代码中是通过其id来引用资源的。在xml文件中,你所使用的将id与资源向相联系的符号被称作Resources reference syntax。这种语法并不局限于通过id来进行资源定位,它是一种定义string、layout和image等资源的方法。
这种通用的方法是如何实现资源定位的呢?通过其用法可以看出,id其实就是一个能追踪类似string等资源的数字。想象一下,你的工程里有一桶数字,你可以从中挑选一个来使之与某个控件相关联。
让我们进一步发掘一下这种资源引用的结构。这种资源引用有如下结构:
@[package:]type/name
type与R.java中的资源类型的命名空间相对应。例如:
R.drawable
R.id
R.layout
R.string
R.attr
R.plural
R.array
而与之相对应的资源引用符号如下:
drawable
id
layout
string
attr
plurals
string-array
而name则是这些资源的名称。(例如list3-5中text1)同时,在R.java中它也代表着一个int型常量。
如果不指定package名字,则type/name会基于本地资源以及本地生成的R.java文件。
如果其形式为android:type/name,则引用会基于android包以及android.R.java。你可以使用任何java的包名来作为package,这样与之相对应的R.java文件会处理相关引用。
现在让我们在回头看看List3-5中id是如何与一个空间相关联的。id被视为资源,通过这个逻辑,当你写下:
<TextView android:id="@+id/text1 ... ./>
就代表“这个资源的类型为id,名称为text1,将text1作为TextView实例的一个引用”。其中“+”表示:如果text1还没有定义为一个资源,则用一个与众不同的数字进行定义。
基于上述信息,我们来分析一些id的例子。通过List3-7我们可以看出,“android:id”并非该语法的一部分,它只是将id与控件(如TextView)连接的一种方法。
Listing3-7:
<TextView android:id="text">
//编译错误,因为id不能被赋值为一个字符串。更重要的是它不是一个资源引用符号
<TextView android:id="@text">
// 语法错误。@text还缺少类型名。应该写为@id/text,或@+id/text,或@string/text。不过string在此处并不合法,尽管这是一个合法的资源引用符号。这是因为左边需要的是一个id,而不是string。所以你会得到一个错误信息“No Resource type specified”
<TextView android:id="@id/text">
//错误,因为无法找到与text相匹配的资源。当然,除非你之前已经定义了名为text的资源
<TextView android:id="@android:id/text">
//错误,该资源为非public资源。表明在android.R.id中无此id。当然,假设在Android.R.java中定义了text,则为合法
<TextView android:id="@+id/text">
//成功。在本地包中R.java里创建一个名为text的id
在“@+id/text”中,“+”拥有特殊的含义。它告诉Android 名为text的id不一定存在,如果果真如此,则创建一个名为text的id。除了在id中,在一般的资源引用符号里我们不会使用“+”。这是很有道理的,因为你无法想象一个场景,string资源没有被显示定义就被创建。系统无法自动创建,因为它必须是一个独一无二的数。
这种资源与id直接的连接关系常常是造成困扰的源头。为了解决这个问题,请记住一件事:id就是对资源的一个引用。
定义你自己的资源id以便日后使用
配置id的一般形式是创建一个新的id或者使用Android包里的id。其实,也可以自己先创建id,然后在自己的包里使用id。这一切都基于id就是资源的事实。如果id是资源,那么就可以被预先定义好,并在以后使用。
List3-7中<TextView android:id="@+id/text">表明:一个名为text的id,如果已经存在,则可以被使用。如果不存在,则会创建一个。所以,当text已经存在时,会复用它吗?
你可能倾向于在R.java中放一个如R.id.text的常量,但是R.java是不可编辑的。即使可以编辑,该文件也会在res目录下的文件发生变化、增加或删除时重新生成。
解决办法是使用一个名为item的标签来定义id,而不必将id与任何资源绑定。List3-8就是一例:
Listing 3-8 预定义id
<resources>
<item type="id" name="text">
</resources>
本例中,type为id类型。一旦这个id存在,则Listing3-9就正确了。
Listing3-9 复用一个id
<TextView android:id="@id/text">
...
</TextView>
编译与反编译Android的资源
Android主要有两种资源文件:xml文件和raw文件(如音频、视频和图片)。你已经看到,在某些文件资源被定义为xml文件中的value(如string),而某些文件本身就是一种资源(如layout文件)。
xml文件之间更为显著的区别是:一种文件被编译成为二进制文件,而另一种被原封不动的拷贝到设备里。目前你所看到的了两个例子(string资源文件和layout资源文件)都是先编译成二进制文件,然后打包到安装包里。这些xml文件已经预定义好了格式,xml的节点被转化为id。
你也可以选择使一些xml文件拥有自定义的格式,这些文件不会被解释,但是可以生成id。(资源类型为xml)。然而,你确实需要将其编译为二进制文件,并享有易于定位的便捷。为了这个目的,你可以讲文件放在/res/xml目录下。这样,你就可以使用Android提供的xml阅读器来读取xml节点。
但是,如果你将文件放在res/raws目录下,包括xml文件在内都不会被编译成二进制文件。然而,其毕竟是资源文件,因此Android在R.java中生成一个id。其资源类型为raw,所以你可以通过R.raw.name(name去掉扩展名)来进行访问。你必须显式的使用基于流的api对这些raw文件进行读写,音频和视频文件正属于这个类型。
注:由于raw目录也是res/目录的一部分,因此音频和视频文件和其它资源文件一样也可以通过id进行定位。
正如我们在第二章的表2-1所示:资源文件根据其类型放在不同的子目录下。下面是一些重要的子目录及其所放的文件类型:
anim:已经编译好的动画文件。
drawable:位图
layout:ui和view的定义
values:Arrays、color、dimensions、strings和styles
xml:编译好的xml文件
raw:不经编译的文件。
Android Asset Packing Tool(AAPT)中的资源编译器将除了raw目录下的资源文件编译到最终的.apk文件中。该文件包含了Android应用的代码和资源,以及相关联的jar包。(apk表示Android package)最终,这个.apk文件会被安装到设备中。
注:尽管xml资源解析器允许资源名为hello-string的形式,但是在R.java中会有编译时错误。你可以通过把破折号改为下划线来解决此问题,如hello_string。
列举一些Android的关键资源
现在你已经了解了一些基本的资源文件。我们将进一步了解一下其他的Android支持的资源文件,包括其xml表示形式已经如何在代码中使用。(当你编写资源文件时,可以用本节内容作为快速参考手册)。让我们先快速浏览一下资源类型及其类型,如表3-1所示:
Table3-1:资源类型
资源类型 |
位置 |
描述 |
Colors |
/res/values/any-file |
颜色标示符,其id在R.java里表示为R.color.*。xml节点为Resources/color |
Strings |
/res/values/any-file |
表示字符串资源。除简单字符串外还支持java格式化字符串和纯HTML。其id在R.java里为R.string.*。xml节点为Resources/string |
String arrays |
/res/values/any-file |
表示字符串数组。其id在R.java里为R.array.*。xml节点为resources/string-array |
Plurals |
/res/values/any-file |
表示一定数量的字符串集合。在不同的语言环境下,你写一个句子的方式取决于你引用了0个、1个、一些或者很多对象。其id在R.java里为R.plural.*xml节点为resources/plurals |
Dimensions |
/res/values/any-file |
表示不同view的尺寸大小。支持pixels像素,inches英寸,millimeters毫米,density independent pixel密度无关像素dp和scale independent pixels尺寸无关像素sp。其id在R.java里为R.dimen.*。xml节点为resources/dimen |
Images |
/res/drawable/multiple-files |
表示图像资源。支持图片格式为jpg/png/gif等。每个图像是一个单独的文件,其id取决于图片名称。其id在R.java里为R.drawable.*。支持的图像类型还包括可伸缩的图像:其图像一部分可以伸缩,而另一部分保持不变。可伸缩文件就是常说的.9.png文件 |
Color drawables |
/res/values/any-file /res/drawable/multiple-files |
表示常备用来作为背景的矩形颜色或一般的位图。其可以代替用作背景的单一颜色的位图。在java中,其等价于创建一个带颜色的矩形并设置为背景。 在values子目录下的<drawable>值标签支持该类型。其id在R.java里为R.drawable.*。xml节点为resources/drawable。 Android也支持圆角或渐变的矩形,这些文件以<shape>标签作为根标签,放在res/drawable目录下。其id在R.java里也为R.drawable.*。其文件名被转换为独一无二的id名。 |
任意xml 文件 |
/res/xml/*.xml |
Android支持任意类型的xml。这些xml被AAPT编译为二进制文件。其id在R.java里为R.xml.*。 |
任意raw资源 |
/res/raw/*.* |
Android允许将不会再被编译的二进制文件或文本文件放在此目录下。每个文件都对应唯一的id。其id在R.java里为R.raw.*。 |
任意raw assets |
/assets/*.*/*.* |
Android允许将任意类型的文件房子啊assets目录下任意层级的子目录下。这些并不是真正的资源,仅仅是纯文件。该目录与res目录不同,允许任意深度的子目录。这些文件不会产生相应的id。你只能用一个相对路径名(相对于/assets,但不包含改路径) |
表中的所有资源都会在后面的章节中用xml文件和java代码片段详细描述。
注:通过观察id的生成方式,我们可以看到(尽管没有任何官方说明):res/values目录之外的资源文件的id都是根据文件名生成的,而res/values目录下的资源id生成,则需要进入到资源文件里面根据其内容生成。
String Arrays
你可以设置一个字符串数组作为资源,将其置于res/values目录下的任意文件中。你需要使用string-array作为xml的节点。这个节点是xml节点的一个子节点,类似于string节点。Listing3-10是一个在资源文件中定义字符串数组的例子:
Listing3-10 定义字符串数组
<resources ...>
.......Other resources
<string-array name="test_array">
<item>one</item>
<item>two</item>
<item>three</item>
</string-array>
......other resources
</resources>
一旦你定义了这样一个字符串数组,就可以在java文件中如Listing3-11这样使用:
Listing3-11 使用字符串数组
//从Activity中获取资源对象
Resources res = your-acitivity.getResources();
String[] strings = res.getStringArray(R.array.test_array);
// 打印字符串
for(String s : strings) {
log.d("example", s);
}
复数
plurals资源是一个字符串集合。这些字符串根据某个数量的不同有不同的表示形式。以一个窝里有多少鸡蛋为例:
There is 1 egg;
There is 2 eggs;
There is 0 eggs;
There is 100 eggs;
注意,当数字为0、1、100时这几个句子的不同之处。其中,当数量为1时,其句子有所不同。Android通过plurals资源来支持这种变化。Listing3-12将告诉你如何使用plurals来实现字符串根据数量不同而发生改变:
Listing3-12:应用复数
<resources ...>
<plurals name="eggs_in_a_nest_test">
<item quantity="one">There is 1 egg</item>
<item quantity="other">There are %d eggs</item>
</plurals>
</resoureces>
两种不同表达式在同一个plurals里。现在你可以在java代码里通过赋予一个数量值来使用这个plurals资源。如Listing3-13所示。getQuantityString()的第一个参数时plural资源的id。第二个参数选择使用哪个字符串。当其值为1时,你使用的是该字符串本身。当字符串不是1的时候,你必须制定第三个参数来替换%d.如果你在plurals里使用格式化字符串,则你应该至少传入3个参数。第二个参数可能造成混淆,其实其关键在于该值是否等于1.
Listing3-13
Resources res = your-acitivity.getResoureces();
String s1 = res.getQuantityString(R.plurals.eggs_in_a_nest_test, 0, 0);
String s2 = res.getQuantityStirng(R.plurals.eggs_in_a_nest_test, 1, 1);
String s3 = res.getQuantityStirng(R.plurals.eggs_in_a_nest_test, 10, 10);
通过上述代码,会根据传入的值不同,而返回合适的字符串。
然而,如果还有其他的数量属性该如何处理呢?我们强烈推荐你阅读一下Android的Resource.java和Plurals.java源码来真正理解这个问题。本章末尾关于Resourecs的连接中给出了这两个文件的地址。
其底线在于,对于英文环境,只有两种环境“one”和“others”。这对于其他大多数语音也是如此,但是对于cs(Czech)语言来说就不是这样了,捷克语有三种情况“one”(1个),“few”(2-4个)和“others”(剩余情况)。
更多关于字符串数组的内容
前面几节你已经简要了解了字符串数组的相关内容。我们重新审视一下这些内容并给出它们之间的细微差别:包括HTML字符串以及如何替代字符串资源中的变量。
注:大多数UI框架都支持字符串资源。然而,Android允许通过资源id来对字符串进行索引,因此更为简单。
Listing3-14将告诉你如何在xml资源文件中定义普通字符串、带引号字符串、HTML字符串和可替换字符串:
Listing3-14:利用xml语法定义字符串
<resources>
<string name="simple_string>simple string</string>
<string name="quoted_string>"quoted 'xyz' string"</string>
<string name="double_quoted_string">\"double quotes\"</string>
<string name="java_format_string"> hello %2$s Java format string. %1$s again </string>
<string name="tagged_string">Hello <b><i>Slanted Android</i></b>, You are bold.</string>
</resources>
xml字符串资源文件需要放在res/values目录下,其文件名可以任意。
带引号字符串需要通过转义字符输出双引号。字符串还允许以java格式化字符串的形式进行定义。
Android还允许在string节点内部使用xml的子节点,如<b>><i>及其他简单的HTML字体标记。你可以通过复合的html字符串来定义的字符的样式以便在textview中输出。
Listing3-15表示如何在java代码中使用这些字符串:
Lisint3-15.在java代码中使用字符串资源
// 使用简单的字符串,并在textview中显示
String simpleString = activity.getString(R.stirng.simple_string);
textView.setText(simpleString);
// 读取一个带引号的字符串,并在textview中显示
String quotedString = acitivity.getString(R.string.quoted_string);
textView.setText(quotedString);
// 读取一个带双引号的字符串,并在textview中显示
String doubleQuotedString = activity.getString(R.string.double_quoted_string);
textView.setText(doubleQuotedString);
// 读取一个java格式化字符串
String javaFormatString = acitivity.getString(R.string.java_format_string);
// 通过传入参数格式化字符串
String substitutedString = String.format(javaFormatString, "hello", "Android");
textView.setText(substitutedString);
// 读取一个html字符串,并显示
String htmlString = activity.getString(R.string.tagged_string);
// 将其转换为span,这样就可以显示在textview
// android.text.html类支持html字符串,这仅仅是一个android的内部类,不支持所有的html标签
Spanned textSpan = android.text.html.fromHtml(R.string.tagged_string);
textView.setText(textSpan);
一旦你定义好一个字符串资源,就可以在xml的layout文件里直接使用,例如在TextView里直接设置。Listing3-16列出如何在TextView里直接使用html字符串。
Listing3-16:xml文件中使用字符串资源
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizental"
android:text="@string/tagged_string"/>
TextView自动识别出该字符串是html字符串,并依此进行格式化。你可以直接在layout中的View中快速设置漂亮的文字格式是件非常棒的事情。
颜色资源Color Resources
和字符串资源一样,你也可以使用资源引用符来间接的引用颜色资源。这使得Android可以讲颜色资源本地化,并且可以提供主题。一旦你在资源文件中定义了颜色资源标示符,你就可以在java代码中通过其id引用颜色资源。对比字符串资源,其id位于<你的包名>.R.string命名空间中,颜色资源位于<你的包名>.R.color命名空间中。
Android在其自带的资源文件中也定义了一套基本的颜色。这些id位于android.R.color命名空间中。通过下面地址可以进一步学习android.R.color中可以获取的颜色常量:
http://code.google.com/android/reference/android/R.color.html
Listing3-17列出了一些在xml资源文件中定义color资源的例子:
Listing3-17:定义color资源的xml语法
<resources>
<color name="red">#f00</color>
<color name="blue">#0000ff</color>
<color name="green">#f0f0</color>
<color name="main_back_ground_color">#ffffff00</color>
</resources>
Listing3-17的内容需要为与res/values目录下。其文件名可以任意选取。Android会扫描所有文件,从中选取<resources>和<color>节点来计算出其id。
Listing3-18列出如何在java代码中使用color资源。
Listing3-18:
int mainBackGroundColor = activity.getResources().getColor(R.color.main_back_ground_color);
Lisint3-19列出如何在定义View时使用color资源。
Listing3-19:
<TextView android:layout_widht="fill_parent"
android:layout_height="wrap_content"
android:textColor="@color/red"
android:text="Sample text to show red color"/>
尺寸资源dimension resources
像素、英寸、点都是尺寸的一种,均可以在xml文件和java文件中使用。你可以用这些尺寸资源样式化和本地化Android的UI而不必修改java代码。
Listing3-20列出如何在xml文件中使用尺寸资源
Listing3-20:
<resources>
<dimen name="mysize_in_pixels">1px</dimen>
<dimen name="mysize_in_dp">1dp</dimen>
<dimen name="medium_size">100sp</dimen>
</resources>
你可以用以下任意单位定义尺寸:
px:像素
in:英寸
mm:毫米
pt:点
dp:像素无关像素。基于dpi(每英寸像素)为160的屏幕而定义。尺寸根据屏幕密度而动态变化。
sp:缩放无关像素.允许用户调整大小的尺寸。主要用于字符。
在java中,你需要实例化一个资源对象来获取尺寸。你可以在activity中调用getResourece()方法,然后根据该资源实例和尺寸资源id来获取实际尺寸值。
Listing3-21:在java代码中获取尺寸值
float dimen = activity.getResources().getDimension(R.dimen.mysize_in_pixels);
注:在java中需要调用Dimension的全称,而在R.java命名空间中选取的是其缩写dimen。
相对于java代码,在xml中使用尺寸资源,需要使用简写dimen,见Listing3-22
Listing 3-22:在xml文件中使用尺寸
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/medium_size"/>
图像资源Image Resources
Android会为存储在res/values目录下的图像资源生成资源id号。支持的图像类型为.gif、.png和jpg。每个图像资源会根据其名称生成独特的id号。如果一个图像的文件名称为sample_image.jpg,则生成的id号为R.drawable.sample_image。
注意:如果有两个文件的名称相同会得到错误提示。res/drawable下面的子目录下的图像会被忽略。该目录下的任何文件都不会读取。
你可以在其他的xml文件中引用res/drawable的图像资源,如Listing3-23所示:
Listing 3-23在xml中使用图像资源
<Button
android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="fail"
android:background="@drawable/simple_image"
你可以通过ava代码来使用图像资源,将其设置到为一个UI对象里,如Button:
Listing 3-24
// 调用getDrawable来获取图像
BitmapDrawable d = activity.getResource().getDrawable(R.drawable.sample_image);
// 然后使用drawable设置背景
button.setBackgroundDrawable(d);
// 或者直接通过资源id使用
button.setBackgroundResource(R.drawable.sample_image);
注:这些背景相关的办法属于View类。因此绝大多数UI空间都可以使用。
Android同时支持一种特殊的图像:可伸缩图像。这种.pnd文件部分区域可以设置为静态不变的,部分可以设置为可伸缩的。Android提供了一个名为Draw 9-patch的工具来绘制这些区域(可以参考下面网址获取更多内容:http://developer.android.com/guide/developing/tools/draw9patch.html)。
一旦制作好这种png图像,就可以像普通图像一样去使用。当我们为一个按钮设置背景时,该按钮的大小又会随着文字发生变化,这种图像就非常方便了。
颜色图片资源Color Drawable Resources
在Android中,图像时drawable资源的一种。Android还支持其它的drawable资源,比如color-drawable资源。它本质上是一个带颜色的矩形。
注:Android的说明文档似乎说会支持圆角矩形,但是我们并没有成功。我们采用了一个替换方案。文档还指出返回的java实例是PaintDrawable,但是代码返回的是Color-Drawable。
要想定义一个带颜色的矩形,需要在res/values目录下的xml文件里通过名为drawable的节点来标识。Listing3-25是一些Color-Drawable的例子:
Listing3-25:定义Color-Drawable 资源的语法
<resources>
<drawable name="red_rectangle">#f00</drawable>
<drawable name="blue_rectangle">#0000ff</drawable>
<drawable name="green_rectangle>#f0f0</drawable>
</resources>
Listing3-26和Listing3-27分别演示了如何在java代码和xml文件中使用color-drawable资源:
Listing3-26:在java代码中使用color-drawable资源
ColorDrawable redDrawable = (ColorDrawable) activity.getResource().getDrawable(R.drawable.red_rectangle);
textView.setBackgroundDrawable(redDrawable);
Listing3-27:在xml文件中使用color-drawable资源
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAlign="center"
android:background="@drawable/red_rectangle"/>
要实现圆角,你可以使用现在代码中并无文档的<shape>标签。不过,这个标签需要在res/drawable目录下使用。Listing3-28演示了如何在res/drawable/my_rounded_rectangle.xml文件中定义一个圆角矩形。
Listing3-28:定义圆角矩形
<shape xmlns:android="http://schemas.android.com/apk/res/android"
<solid android:color="#f0600000"/>
<stroke android:width="3dp" color="#ffff8080"/>
<corners android:radius="13dp"/>
<padding android:left="10dp" android:top="10dp"
android:right="10dp" android:bottom="10dp"/>
</shape>
你可以将这个图像用作前面的textview的背景,如Listing3-29所示:
Listing3-29:在java代码中使用drawable
// 获取drawable
GradientDrawable roundedRectangle = (GradientDrawable) activity.getResources().getDrawable(R.drawable.my_rounded_rectangle);
// 设置drawable为TextView的背景
textView.setBackgroundDrawable(roundedRectangle);
注:并不一定要吧返回的Drawable基类转为GradientDrawable,这么做只是为了展示<shape>标签会转换为GradientDrawable。这个信息很重要,因为你可以查阅一下该标签定义的类的API文档。
最后在说明一点:drawable子目录下的文件对应着一个BitmapDrawable类。一个drawable资源值,如Listing3-25中的矩形对应着ColorDrawable。而包含<shape>标签的xml文件对应着GradientDrawable。
使用任意xml资源文件
除了之前介绍的结构化的资源,Android还允许将任意的xml文件作为资源。这种方法将对资源的使用扩展到了对任意xml文件的使用。首先,这种方法通过使用以这些文件为基础而生成的资源id来很方便的使用它们。第二,这种方法允许你能本地化这些xml资源文件。第三,你可以有效的编译和在设备上存储这些xml文件。
允许通过这种方式读取的xml文件存储在res/xml目录下。Listing3-3-列出了一个名为res/xml/test.xml的例子:
Listing 3-30:xml文件举例
<rootelem1>
<subelem1>
Hello World from a xml sub element
</subelem1>
</rootelem1>
正如其他xml资源文件一样,AAPT先将该文件编译,然后再放到应用包里。如果你想解析这些文件,你需要一个XmlPullParser实例。你可以使用Listing3-31中的代码获取一个XmlPullParser的实例(可以在任意Context下使用):
Listing 3-31:读取一个xml文件
Resources res = activity.getResources();
XmlResourceParser xpp = res.getXml(R.xml.test);
返回的XmlResourceParser是XmlPullParser的一个实例,它还实现了java.util.AttributeSet接口。Listing3-32列举了更多的读取text.xml文件的代码片段:
Listing3-32:使用XmlPullParser
private String getEventFromAnXmlFile(Activity activity) throws XmlPullParserException, IOException {
StringBuffer sb = new StringBuffer();
Resources res = activity.getResources();
XmlResoucesParser xpp = res.getXml(R.xml.test);
xpp.next();
int eventType = xpp.getEventType();
while(eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_DOCUMENT) {
sb.append("******start document");
} else if (eventType == XmlPullParser.START_TAG) {
sb.append("\nStart tag "+xpp.getName());
} else if(eventType == XmlPullParser.END_TAG) {
sb.append("\nEnd tag "+xpp.getName());
} else if(eventType == XmlPullParser.TEXT) {
sb.append("\nText "+xpp.getText());
}
eventType == xpp.next();
} // eof-while
sb.append("\n******End document");
return sb.toString();
} // eof-function
从Listing3-32,你可以知道如何得到一个XmlPullParser,如何使用XmlPullParser来遍历xml文件中的元素,如何使用XmlPullParser中的方法来获取元素的细节信息。如果你想运行上述代码,你需要创建一个之前介绍的xml文件,然后通过菜单或按钮调用getEventFromAnXmlFile方法。你可以通过Lod.d调试方法将返回的字符串打印出来。
使用Raw资源
除了xml文件外,Android还允许使用raw文件。这些资源位于res/raw目录下。这些raw文件可以是音频、视频或txt文档。它们都可以通过资源id来进行本地化或者引用。与在res/xml目录下的xml文件不同,这些raw文件不会被编译,但是会打包到应用包里对应的位置。然而,每个文件都会在R.java文件中生成相应的标识符。如果你创建了一个res/raw/test.txt的文件,那么可以使用Listing3-33中的代码读取该文件。
Listing3-33:读取raw资源
String getStringFromRawFile(Activity activity) throws IOException{
Resources r = activity.getResources();
InputStream is = r.openRawResource(R.raw.test);
String myText = convertStreamToString(is);
is.close();
return myText;
}
String convertStreamToString(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputSteam();
int i = is.read();
while(i != -1) {
baos.write(i);
i = is.read();
}
return baos.toString();
}
注:如果文件名称是通过复制自动产生的,则ADT会产生一个编译错误。这是所有基于文件名称产生资源id的情况共有的问题。
使用Assets
Android还提供了其它的一些目录用来存放文件,如/assets。其与res属于同一级别目录。也就是说assets并不是res下的子目录。在assets目录下的文件不会再R.java中生成资源id。你必须指定路径来读取这些文件。文件路径是基于/assets的相对路径。如Listing3-34所示,你可以使用AssetManager来读取文件:
Listing3-34:读取asset
//注:本断代码未进行异常处理
String getStringFromAssetFile(Activity activity) {
AssetManager am = activity.getAssets();
InputStream is = am.open("test.txt");
String s = convertStreamToString(is);
is.close;
return s;
}
复习一下资源目录的结构
Listing3-35展示了所有资源的目录结构:
Listing3-35:资源目录
/res/values/strings.xml
/colors.xml
/dimens.xml
/attrs.xml
/styles.xml
/drawable/*.png
/*.jpg
/*.gif
/*.9.png
/anim/*.xml
/layout/*.xml
/raw/*.*
/xml/*.xml
/assets/*.*/*.*
注:由于没有位于res目录下,只有asset下面的目录可以有任意层次的子目录。其他所有的目录只能包含文件,而不能有子目录。这也与R.java为这些资源生成id的方式相符。
资源及配置
资源有利于本地化。例如:你可以设置不同的语音环境来得到不同的字符串。Android资源可以根据不同的配置发生变化,而语言仅仅是其中的一种。另外一种配置的变化是设备由垂直状态变为水平状态。垂直模式被称为portrait模式,而水平模式被称为landspace模式。
Android允许你通过同一个id来引用不同布局模式的布局。这可以通过为不同的配置设置不同字母来实现。Listing3-36就是一例:
Listing3-36:可替换的资源目录
res/layout/main_layout.xml
res/layout-port/main_layout.xml
res/layout_land/main_layout.xml
即使有三个不同的文件,也只会在R.java中生成一个id。其id形如:
R.layout.main_layout
然而,当你使用这个布局id时,当配置不同你会得到相对于的布局。
本例中扩展的-port和-land被称为配置限定词(configuration qualifiers)。这些限定词是大小写敏感的,并且用“-”与资源隔开。通过这种限定词定义的资源被称为可替换资源。(alternate resources)而res目录下没有configuration qualifiers的资源被称为默认资源。
下面列举了绝大多数configuration qualifiers。注意新的qualifier可能加入到新的APIs中。可以通过参考部分的网址来查看最新的资源qualifier。
mccAAA:AAA代表mobile country code.
mncAAA:AAA代表carrier/network code。
en-rUS:语言和地区
sw<N>dp,w<N>dp,h<N>dp:分别表示smallest width,available width,available height。(API 13)
small、normal、large、xlarge:屏幕尺寸
long、nolong:屏幕类型
port、land:portrait和landspace。
car、desk:docking的类别
night、notnight:夜间或白天
ldpi、mdpi、hdpi、xhdpi、nodpi、tvdpi:屏幕密度
notouch、stylus、finger:屏幕种类
keysexposed、keyssoft、keyshiden:键盘种类
nokeys、qwerty、12key:按键数量
navexposed、navehidden:导航键隐藏或显示。
nonav、dpad、trackball、wheel:导航设备类型
v3、v4、v7:API级别
有了这些qualifiers,你可以像Listing3-37所示定义资源目录:
Listing 3-37:
res/layout-mcc312-mnc222-en-rUS
res/layout-ldpi
res/layout-hdpi
res/layout-car
你可以通过Costom locale 应用来查看你当前的语言环境。该应用的路径为:Home -- List of Applications -- Costom locale
当给定一个资源id好,Android会用一个特定的算法来获取相应的资源。你可以通过参考“Reference URLs”章节来查找相应的地址来获取更多的内容,不过现在我们先看一下其中的一部分规则。
主要原则是最先列出的qualifier拥有最高的优先级。考虑一下Listing3-38列出的路径:
Listing 3–38. Layout File Variations
\res\layout\main_layout.xml
\res\layout-port\main_layout.xml
\res\layout-en\main_layout.xml
在列表中main_layout.xml有两个不同的变种。一个是语言相关而另一个是方向相关。现在我们检验一下,当手机处于垂直状态时,会选择哪个layout。结果是即使手机处于垂直状态,系统扔选择了语言相关的layout。这是因为语言的qualifier的优先级高于方向的qualifier。本章中“Reference URLs”章节的连接中列出了所有的qualifier和优先级。
我们通过几个字符串资源做实验来进一步探讨一下这种优先级规则。注意字符串资源是基于独立的id号而layout资源是基于文件的。为了测试字符串资源的configuration qualifier优先级,我们使用5个资源id分别对应于5个变种:default、en、en_us、port和en_port。这5个id资源是:
teststring_all:这个id在所有的资源目录变种中都存在,包括默认的资源。
testport_port:这个id只在default和-port目录下。
t1_enport:这个id在default、-en和-port目录下。
t1_1_en_port:这个id在default和-en-port目录下。
t2:只在default目录下。
Listing3-39列出了value目录所有的变种:
Listing 3–39. String Variations Based on Configuration
// values/strings.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">teststring in root</string>
<string name="testport_port">testport-port</string>
<string name="t1_enport">t1 in root</string>
<string name="t1_1_en_port">t1_1 in root</string>
<string name="t2">t2 in root</string>
</resources>
// values-en/strings_en.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">teststring-en</string>
<string name="t1_enport">t1_en</string>
<string name="t1_1_en_port">t1_1_en</string>
</resources>
// values-en-rUS/strings_en_us.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">test-en-us</string>
</resources>
// values-port/strings_port.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">test-en-us-port</string>
<string name="testport_port">testport-port</string>
<string name="t1_enport">t1_port</string>
<string name="t1_1_en_port">t1_1_port</string>
</resources>
// values-en-port/strings_en_port.xml
<resources xmlns="http://schemas.android.com/apk/res/android">
<string name="teststring_all">test-en-port</string>
<string name="t1_1_en_port">t1_1_en_port</string>
</resources>
Listing3-40列出了R.java中生成的id:
Listing 3–40. R.java to Support String Variations
public static final class string {
public static final int teststring_all=0x7f050000;
public static final int testport_port=0x7f050004;
public static final int t1_enport=0x7f050001;
public static final int t1_1_en_port=0x7f050002;
public static final int t2=0x7f050003;
}
马上你就会看到,即使你定义了无数的String,但是只有5个string资源id生成。如果你使用这些字符串资源,每个字符串资源的具体行为如下:(我们测试的配置是:en_US和portrait)
teststring_all: 这个id有5个不同值。因为在5个目录下均存在,最终会选择values-en-rUS/目录下的值。基于这种优先级规则,特定的语言优先级高于default、en、port和en-port。
testport_port : 这个id只在default和-port目录下。因为没有在任何以-en开始的目录下,-port目录优先级高于default,所有-port目录下值被选取。如果其在-en目录下,则会选取-en目录下的值。
t1_enport : 这个id在3个目录下:default、-en和-port。由于同时在-en和-port下,所以会取-en下的值。
t1_1_en_port:这个id在4个目录下:default、-en、-port和-en-port。因为可以在-en-port目录下获取,所以忽略-en、-port和default。
t2:只在default目录下,所以从default目录取值。
Android SDK还有一个更为细致的算法规则,你可以仔细研究。然而,这个实验已经告诉了你它的本质。其关键在于不同变种的优先级。接下来的部分提供了一些URL。其中包括一个地址来下载本章的一个移植的工程。你可以通过这个工程来检测不同配置。
Reference URLs
当你学习Android的资源时,你可能需要将下面的参考网址保存起来。除了列出网址外,我们还标明了相应网址的内容:
http://developer.android.com/guide/topics/resources/index.html:
资源文件相关文档的索引。
http://developer.android.com/guide/topics/resources/available-resources.html
Android关于资源类型的文档。
http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources :
最新Android SDK中configuration qualifier列表。
http://developer.android.com/guide/practices/screens_support.html:
关于如何适应各种尺寸的屏幕的指导文档。
http://developer.android.com/reference/android/content/res/Resources.html:
读取资源的各种方法。
http://developer.android.com/reference/android/R.html :
Android核心平台的资源定义
www.androidbook.com/item/3542 :
我们关于复数plural、字符串数组string array和可变资源alternative resources的研究,也包括一些其它资源的链接。
androidbook.com/proandroid4/projects:
本章中涉及到各种资源的工程下载。其名为ProAndroid4_Ch03_TestResources.zip.
概要
我们简要总结一下本章学到的内容:
你知道了Android支持的资源类型。
你知道如何在xml文件中创建资源。
你知道了资源id是如何生成的,并且知道如何在java代码中使用资源id。
你知道如何使用xml 资源、raw资源和assets。
简单地接触到了可变资源。
你知道如何定义plural和string array
你学会了资源引用语法。
复习问题
你可以用下面的问题作为知道来巩固你所学到的知识:
1.你能说出多少种类型的资源。
2.什么是R.java
3.为什么R.java为使用资源提供了便利。
4.资源定位符resource-reference syntax和为控制UI分配的id之间的联系什么
5.文件的扩展名是否会被用来生成资源id?
6.如果两个基于文件名的资源在一起,而两种的区别仅在于扩展名,那么会发生什么情况?
7.什么是raw资源和xml资源,其与assets之间的区别是什么?
8.你能定位到xml资源吗?
9.你能定位到assets吗?
10.你能写出并解释resource-reference syntax吗?
11.你能提前定义资源id以便使用吗?如果可以,请问为什么?
12.创建一个id需要什么xml节点。
13.如果你将文件放在xml或者raw目录下,会在R.java中生成相应的id吗?
14.Android会为assets目录下的文件生成id吗?
15.plurals中的one和others代表什么。
16.你能在字符串中使用HTML字符串吗?
17.你如何在一个textview中显示HTML字符串
18.你如何将一个矩形定义为drawable
19.你如何使用一个shape drawable?
20.你需要使用哪个类来读取res/xml目录下的xml文件。
21.在Android中,主要用来处理xml文件的类是哪个?
22.什么是AssetManager,你如何得到它?
23.什么是资源类,你如何得到其实例?
24.你能在assets目录下有任意层级的子目录吗?
25.你能在res/xml目录下设置子目录吗?
26.什么是resources configuration qualifiers?
学会了这些,现在我们将把注意力转到下一张的content provider上来。