Pro Android 4 第三章 理解Android中的资源

来源:互联网 发布:大学结婚 知乎 编辑:程序博客网 时间:2024/05/22 15:04
     第二章中,我们简要介绍了一些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/colorStrings/res/values/any-file表示字符串资源。除简单字符串外还支持java格式化字符串和纯HTML。其id在R.java里为R.string.*。xml节点为Resources/stringString arrays/res/values/any-file表示字符串数组。其id在R.java里为R.array.*。xml节点为resources/string-arrayPlurals/res/values/any-file表示一定数量的字符串集合。在不同的语言环境下,你写一个句子的方式取决于你引用了0个、1个、一些或者很多对象。其id在R.java里为R.plural.*xml节点为resources/pluralsDimensions/res/values/any-file表示不同view的尺寸大小。支持pixels像素,inches英寸,millimeters毫米,density independent pixel密度无关像素dp和scale independent pixels尺寸无关像素sp。其id在R.java里为R.dimen.*。xml节点为resources/dimenImages/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/*.xmlAndroid支持任意类型的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上来。
原创粉丝点击