第6章 Android应用资源
本章要点
- Android 应用的资源及其作用
- Android应用资源的存储方式
- 在XML布局文件中使用资源
- 在Java程序中使用资源
- 使用字符串资源
- 使用颜色资源
- 使用尺寸资源
- 使用数组资源
- 使用图片资源
- 使用各种Drawable资源
- 使用原始XML资源
- 使用布局资源
- 使用菜单资源
- 使用样式和主题资源
- 使用属性资源
- 使用原始资源
- 为Android应用提供国际化资源
- 自适应不同屏幕的资源
经过前面的介绍,相信读者对Android应用已有了大致的了解。如果从物理存在形式来分,Android 应用的源文件大致可分为如下三大类:
- 界面布局文件: XML文件,文件中每个标签都对应于相应的View标签。
- 程序源文件: 应用中的Activity、Service、BroadcastReceiver、ContentProvider四大组件都是由Java或Kotlin源代码来实现的。
- 资源文件 : 主要以各种XML文件为主,还可包括*.png、.jpg、.gif 图片资源。
在传统开发中,初学者很容易犯一个错误:直接在Java或Kotlin源代码中使用如"crazyit.org"、"hello"这样的字符串,或者直接使用123、0.9这样的数值,而且不添加任何注释。过了一段时间后,即使自己再去看原来写的程序代码,一时之间,也无法理解其中"crazyit.org"、"hello"字符串,123、0.9等数值的含义。这种方式就大大增加了程序的维护成本。这种直接在代码中定义的123、0.9等数值,也被称为"魔术数值"(就像表演魔术一样,其他人都搞不懂)。
为了改善这种情况,有经验的开发者会专门定义一个或多个接口或类,然后在其中以常量的形式来定义程序中用到的所有字符串、数值等,这些常量的名称十分明确,如ResultSet.TYPE_FORWARD_ONLY
,相信有经验的读者一看到这个常量就大致明白了它的含义。这样的方式就可以很好地提高程序的可维护性。
使用接口或类的形式来定义程序中用到的字符串、数值,虽然已经部分提高了程序的解耦,但后期维护、进一步开发时,开发人员还得去"代码海"中打捞那些定义字符串常量、数值常量的位置,因此还有可以提高的地方。
Android 应用对这种字符串常量、数值常量的定义做了进一步改进:Android允许把应用中用到的各种资源,如字符串资源、颜色资源、数组资源、菜单资源等都集中放到/res/
目录中定义,应用程序则直接使用这些资源中定义的值。
在Android 应用中,除了/res/
目录用于存放资源之外,assets
目录也用于存放资源。一般来说,assets
目录下存放的资源代表应用无法直接访问的原生资源,应用程序需要通过AssetManager
以二进制流的形式来读取资源。而/res/
目录下的资源,Android SDK会在编译该应用时,自动在R.java
文件中为这些资源创建索引,程序可直接通过R
资源清单类进行访问。
前面介绍的很多示例都是直接将字符串值写在界面布局文件或Activity代码中的,实际上那并不是一种好的方式。只是前面还未详细介绍Android应用的资源,为了避免读者产生畏难心理,并未使用资源文件而已。
6.1 应用资源概述
Android应用资源可分为两大类:
- 无法通过R资源清单类访问的原生资源,保存在
assets
目录下。 - 可通过R资源清单类访问的资源,保存在
/res/
目录下。
大部分时候提到Android应用资源时,往往都是指位于/res/
目录下的应用资源,Android SDK会在编译该应用时在R类中为它们创建对应的索引项。
6.1.1 资源的类型及存储方式
Android要求在/res/
目录下用不同的子目录来保存不同的应用资源,表6.1大致显示了Android不同资源在/res/
目录下的存储方式。
表6.1 Android 应用资源的存储
目录 | 存放的资源 |
---|---|
/res/animation/ |
存放定义属性动画的XML文件 |
/res/anim/ |
存放定义补间动画的XML文件 |
/res/color/ |
存放定义不同状态下颜色列表的XML文件 |
/res/drawable/ |
存放适应不同屏幕分辨率的各种位图文件(如*.png、.9.png、.jpg、*.gif等)。此外,也可能编译成各种Drawable对象的XML文件。 |
/res/mipmap/ |
主要存放适应不同屏幕分辨率的应用程序图标,以及其他系统保留的Drawable资源 |
/res/layout/ |
存放各种用户界面的布局文件 |
/res/menu/ |
存放为应用程序定义各种菜单的资源,包括选项菜单、子菜单、上下文菜单资源 |
/res/raw/ |
存放任意类型的原生资源(比如音频文件、视频文件等)。在Java或Kotlin代码中可通过调用Resources 对象的openRawResource(int id) 方法来获取该资源的二进制输入流。实际上,如果应用程序需要使用原生资源,也可把这些原生资源保存到assets 目录下,然后在应用程序中使用AssetManager 来访问这些资源。 |
/res/values/ |
存放各种简单值的XML文件。这些简单值包括字符串值、整数值、颜色值、数组等。这些资源文件的根元素都是<resources> ,为该<resources> 元素添加不同的子元素则代表不同的资源。 |
/res/xml/ |
存放任意的原生XML文件。这些XML文件可以在Java或Kotlin代码中使用Resources.getXML() 方法进行访问。 |
提示:
/res/drawable
和/res/mipmap
子目录都可针对不同的分辨率建立对应的子目录,比如drawable-ldpi
(低分辨率)、drawable-mdpi
(中等分辨率)、drawable-hdpi
(高分辨率)、drawable-xhdpi
(超高分辨率)、drawable-xxhdpi
(超超高分辨率)等子目录。这种做法可以让系统根据屏幕分辨率来选择对应子目录下的图片。如果开发时为所有分辨率的屏幕提供的是同一张图片,则可直接将该图片放在drawable
目录下。
6.1.2 使用资源
在Android应用中使用资源可分为在Java或Kotlin代码和XML文件中使用资源,其中Java或Kotlin程序用于为Android应用定义四大组件,而XML文件则用于为Android应用定义各种资源。
1. 在源程序中使用资源清单项
由于Android SDK会在编译应用时在R类中为/res/
目录下所有资源创建索引项,因此在Java或Kotlin代码中访问资源主要通过R类来完成。其完整的语法格式为:
[<package_name>.]R.<resource_type>.<resource_name>
<resource_type>
:R类中代表不同资源类型的子类,例如string
代表字符串资源。<resource_name>
:指定资源的名称。该资源名称可能是无后缀的文件名(如图片资源),也可能是XML资源元素中由android:name
属性所指定的名称。<package_name>
:指定R类所在包,实际上就是使用全限定类名。当然,如果在源程序中导入R类所在包,就可以省略包名。
例如如下代码片段:
java
// 从drawable资源中加载图片,并设为该窗口的背景
window.setBackgroundDrawableResource(R.drawable.back);
// 从string资源中获取指定字符串资源,并设置该窗口的标题
window.setTitle(resources.getText(R.string.main_title));
// 获取指定的TextView组件,并设置该组件显示string资源中的指定字符串资源
TextView msg = findViewById(R.id.msg);
msg.setText(R.string.hello_message);
2. 在源代码中访问实际资源
R资源清单类为所有的资源都定义了一个资源清单项,但这个清单项只是一个int类型的值,并不是实际的资源对象。在大部分情况下,Android应用的API允许直接使用int类型的资源清单项代替应用资源。
但有些时候,程序也需要使用实际的Android资源,为了通过资源清单项来获取实际资源,可以借助于Android提供的Resources
类。
提示 :
笔者把Resources
类称为"Android资源访问总管家",Resources
类提供了大量的方法来根据资源清单ID获取实际资源。
Resources
主要提供了如下两类方法:
getXxx(int id)
:根据资源清单ID来获取实际资源。getAssets()
:获取访问/assets/
目录下资源的AssetManager
对象。
Resources
由Context
调用getResources()
方法来获取。
下面的代码片段示范了如何通过Resources
获取实际字符串资源。
java
// 直接调用Activity的getResources()方法来获取Resources对象
Resources res = getResources();
// 获取字符串资源
String mainTitle = res.getText(R.string.main_title).toString();
// 获取Drawable资源
Drawable logo = res.getDrawable(R.drawable.logo);
// 获取数组资源
int[] arr = res.getIntArray(R.array.books);
3. 在XML文件中使用资源
当定义XML资源文件时,其中的XML元素可能需要指定不同的值,这些值就可设置为已定义的资源项。在XML代码中使用资源的完整语法格式为:
@[<package_name>:]<resource_type>/<resource_name>
<package_name>
:指定资源类所在应用的包。如果所引用的资源和当前资源位于同一个包下,则<package_name>
可以省略。<resource_type>
:R类中代表不同资源类型的子类。<resource_name>
:指定资源的名称。该资源名称可能是无后缀的文件名(如图片资源),也可能是XML资源元素中由android:name
属性所指定的名称。
下面将会对各种资源分别进行详细阐述。
6.2 字符串、颜色、尺寸资源
字符串资源、颜色资源、尺寸资源,它们对应的XML文件都将位于/res/values/
目录下,它们默认的文件名以及在R类中对应的内部类如表6.2所示。
表6.2 字符串、颜色、尺寸资源表
资源类型 | 资源文件的默认名 | 对应于R类中的内部类的名称 |
---|---|---|
字符串资源 | /res/values/strings.xml |
R.string |
颜色资源 | /res/values/colors.xml |
R.color |
尺寸资源 | /res/values/dimens.xml |
R.dimen |
6.2.1 颜色值的定义
Android中的颜色值是通过红(Red)、绿(Green)、蓝(Blue)三原色以及一个透明度(Alpha)值来表示的,颜色值总是以井号(#)开头,接下来就是Alpha-Red-Green-Blue的形式。其中Alpha值可以省略,如果省略了Alpha值,那么该颜色默认是完全不透明的。
Android颜色值支持常见的4种形式:
#RGB
:分别指定红、绿、蓝三原色的值(只支持0~f这16级颜色)来代表颜色。#ARGB
:分别指定红、绿、蓝三原色的值(只支持0~f这16级颜色)及透明度(只支持0~f这16级透明度)来代表颜色。#RRGGBB
:分别指定红、绿、蓝三原色的值(支持00~ff这256级颜色)来代表颜色。#AARRGGBB
:分别指定红、绿、蓝三原色的值(支持00~ff这256级颜色)以及透明度(支持00~ff这256级透明度)来代表颜色。
在这四种形式中,A、R、G、B都代表一个十六进制的数,其中A代表透明度,R代表红色数值,G代表绿色数值,B代表蓝色数值。
6.2.2 定义字符串、颜色、尺寸资源文件
字符串资源文件 位于/res/values/
目录下,字符串资源文件的根元素是<resources>
,该元素里每个<string>
子元素定义一个字符串常量,其中<string>
元素的name
属性指定该常量的名称,<string>
元素开始标签和结束标签之间的内容代表字符串值,如以下代码所示:
xml
<string name="hello">Hello World, ValuesResTest!</string>
如下文件是该示例的字符串资源文件:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">字符串、颜色、尺寸资源</string>
<string name="c1">F00</string>
<string name="c2">0F0</string>
<string name="c3">00F</string>
<string name="c4">0FF</string>
<string name="c5">F0F</string>
<string name="c6">FF0</string>
<string name="c7">07F</string>
<string name="c8">70F</string>
<string name="c9">F70</string>
</resources>
颜色资源文件 位于/res/values/
目录下,颜色资源文件的根元素是<resources>
,该元素里每个<color>
子元素定义一个颜色常量,其中<color>
元素的name
属性指定该颜色的名称,<color>
元素开始标签和结束标签之间的内容代表颜色值,如以下代码所示:
xml
<!--定义一个颜色,名称为c1,颜色为红色-->
<color name="c1">#F00</color>
如下文件是该示例的颜色资源文件:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="c1">#F00</color>
<color name="c2">#0F0</color>
<color name="c3">#00F</color>
<color name="c4">#0FF</color>
<color name="c5">#F0F</color>
<color name="c6">#FF0</color>
<color name="c7">#07F</color>
<color name="c8">#70F</color>
<color name="c9">#F70</color>
</resources>
尺寸资源文件 位于/res/values/
目录下,尺寸资源文件的根元素是<resources>
,该元素里每个<dimen>
子元素定义一个尺寸常量,其中<dimen>
元素的name
属性指定该尺寸的名称,<dimen>
元素开始标签和结束标签之间的内容代表尺寸值,如以下代码所示:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 定义 Gridview组件中每个单元格的宽度、高度 -->
<dimen name="spacing">8dp</dimen>
<dimen name="cell_width">60dp</dimen>
<dimen name="cell_height">66dp</dimen>
<dimen name="title_font_size">18sp</dimen>
</resources>
上面三份资源文件分别定义了字符串、颜色、尺寸资源,应用程序接下来既可在XML文件中使用这些资源,也可在Java或Kotlin代码中使用这些资源。
6.2.3 使用字符串、颜色、尺寸资源
正如前面所介绍的,在XML文件中使用资源按如下语法格式:
@[<package_name>:]<resource_type>/<resource_name>
下面程序的界面布局中大量使用了前面定义的资源:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<!-- 使用字符串资源、尺寸资源 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/app_name"
android:textSize="@dimen/title_font_size" />
<!-- 定义一个GridView组件,使用尺寸资源中定义的长度来指定水平间距、垂直间距 -->
<GridView
android:id="@+id/grid01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:numColumns="3"
android:horizontalSpacing="@dimen/spacing"
android:verticalSpacing="@dimen/spacing" />
</LinearLayout>
上面程序中的代码就是使用字符串资源、尺寸资源的代码。
在Java或Kotlin代码中使用资源按如下语法格式:
[<package_name>.]R.<resource_type>.<resource_name>
下面的Activity代码同时使用了上面定义的三种资源:
java
public class MainActivity extends Activity {
// 使用字符串资源
int[] textIds = new int[]{R.string.c1, R.string.c2, R.string.c3, R.string.c4, R.string.c5, R.string.c6, R.string.c7, R.string.c8, R.string.c9};
// 使用颜色资源
int[] colorIds = new int[]{R.color.c1, R.color.c2, R.color.c3, R.color.c4, R.color.c5, R.color.c6, R.color.c7, R.color.c8, R.color.c9};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridView grid = findViewById(R.id.grid01);
// 创建一个BaseAdapter对象
BaseAdapter ba = new BaseAdapter() {
// 重写该方法,该方法返回的View 将作为GridView的每个格子
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv;
if (convertView == null) {
tv = new TextView(MainActivity.this);
} else {
tv = (TextView) convertView;
}
Resources res = MainActivity.this.getResources();
// 使用尺寸资源来设置文本框的高度、宽度
tv.setWidth((int) res.getDimension(R.dimen.cell_width));
tv.setHeight((int) res.getDimension(R.dimen.cell_height));
// 使用字符串资源设置文本框的内容
tv.setText(textIds[position]);
// 使用颜色资源来设置文本框的背景色
tv.setBackgroundResource(colorIds[position]);
tv.setTextSize
(res.getInteger(R.integer.font_size));
return tv;
}
};
// 为GridView 设置Adapter
grid.setAdapter(ba);
}
}
上面程序中的代码分别使用了前面定义的字符串资源、颜色资源和尺寸资源。运行上面的程序,将可以看到如图6.1所示的界面。
图6.1 使用字符串、颜色、尺寸资源
与定义字符串资源类似的是,Android也允许使用资源文件来定义boolean常量。例如,在/res/values/
目录下增加一个bools.xml
文件,该文件的根元素也是<resources>
,根元素内通过<bool>
子元素来定义boolean常量,示例如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="is_male">true</bool>
<bool name="is_big">false</bool>
</resources>
一旦在资源文件中定义了如上所示的资源文件之后,在Java或Kotlin代码中按如下语法格式访问:
[<package_name>.]R.bool.<bool_name>
在XML文件中按如下格式即可访问资源:
@[<package_name>:]bool/<bool_name>
例如,为了在Java代码中获取指定boolean变量的值,可通过如下代码来实现:
java
Resources res = getResources();
boolean isMale = res.getBoolean(R.bool.is_male);
与定义字符串资源类似,Android也允许使用资源文件来定义整型常量。例如,在/res/values/
目录下增加一个integers.xml
文件,该文件的根元素也是<resources>
,根元素内通过<integer>
子元素来定义整型常量,示例如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="my_size">32</integer>
<integer name="book_numbers">12</integer>
</resources>
在Java或Kotlin代码中按如下语法格式访问:
[<package_name>.]R.integer.<integer_name>
在XML文件中按如下格式即可访问资源:
@[<package_name>:]integer/<integer_name>
例如,为了在Java代码中获取指定整型变量的值,可通过如下代码来实现:
java
Resources res = getResources();
int mySize = res.getInteger(R.integer.my_size);
6.3 数组(Array)资源
在之前的示例中,数组是在Java源代码中定义的。然而,Android并不推荐在程序源代码中定义数组,因为Android允许通过资源文件来定义数组资源。
Android采用位于/res/values/
目录下的arrays.xml
文件来定义数组资源。定义数组时,XML资源文件的根元素是<resources>
,该元素内可包含如下三种子元素:
<array>
:定义普通类型的数组,例如Drawable数组。<string-array>
:定义字符串数组。<integer-array>
:定义整型数组。
在资源文件中定义了数组资源后,可以在Java或Kotlin程序中通过如下形式来访问资源:
[<package_name>.]R.array.<array_name>
在XML文件中则可通过如下形式进行访问:
@[<package_name>:]array/<array_name>
为了在Java或Kotlin程序中访问到实际数组,Resources
类提供了如下方法:
String[] getStringArray(int id)
:根据资源文件中字符串数组资源的名称来获取实际的字符串数组。int[] getIntArray(int id)
:根据资源文件中整型数组资源的名称来获取实际的整型数组。TypedArray obtainTypedArray(int id)
:根据资源文件中普通数组资源的名称来获取实际的普通数组。TypedArray
代表一个通用类型的数组,该类提供了getXxx(int index)
方法来获取指定索引处的数组元素。
以下是示例的数组资源文件:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 定义一个Drawable数组 -->
<array name="plain_arr">
<item>@color/c1</item>
<item>@color/c2</item>
<item>@color/c3</item>
<item>@color/c4</item>
<item>@color/c5</item>
<item>@color/c6</item>
<item>@color/c7</item>
<item>@color/c8</item>
<item>@color/c9</item>
</array>
<!-- 定义字符串数组 -->
<string-array name="string_arr">
<item>@string/c1</item>
<item>@string/c2</item>
<item>@string/c3</item>
<item>@string/c4</item>
<item>@string/c5</item>
<item>@string/c6</item>
<item>@string/c7</item>
<item>@string/c8</item>
<item>@string/c9</item>
</string-array>
<!-- 定义书籍字符串数组 -->
<string-array name="books">
<item>疯狂Java讲义</item>
<item>疯狂前端开发讲义</item>
<item>疯狂Android讲义</item>
</string-array>
</resources>
在定义了上面的数组资源之后,既可在XML文件中使用这些数组资源,也可在Java程序中使用这些数组资源。例如,如下界面布局文件中定义了一个ListView
组件,并将android:entries
属性值指定为一个数组。
界面布局文件代码如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<!--省略其他组件定义 -->
<!-- 定义ListView组件,使用了数组资源 -->
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:entries="@array/books" />
</LinearLayout>
接下来在Java程序中使用资源文件中定义的数组,程序代码如下:
java
public class MainActivity extends Activity {
// 获取系统定义的数组资源
private String[] texts;
private TypedArray icons;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
texts = getResources().getStringArray(R.array.string_arr);
icons = getResources().obtainTypedArray(R.array.plain_arr);
// 创建一个BaseAdapter对象
BaseAdapter ba = new BaseAdapter() {
@Override
public int getCount() {
return texts.length;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
// 重写该方法,该方法返回的View将作为GridView的每个格子
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv;
if (convertView == null) {
tv = new TextView(MainActivity.this);
} else {
tv = (TextView) convertView;
}
Resources res = MainActivity.this.getResources();
// 使用尺寸资源来设置文本框的高度、宽度
tv.setWidth((int) res.getDimension(R.dimen.cell_width));
tv.setHeight((int) res.getDimension(R.dimen.cell_height));
// 使用字符串资源设置文本框的内容
tv.setText(texts[position]);
// 使用颜色资源来设置文本框的背景色
tv.setBackground(icons.getDrawable(position));
tv.setTextSize(20f);
return tv;
}
};
GridView grid = findViewById(R.id.grid01);
// 为GridView设置Adapter
grid.setAdapter(ba);
}
}
上面程序中的代码就是使用数组资源的关键代码。运行上面的程序,将看到如图6.2所示的结果。
图6.2 使用数组资源
6.4 使用Drawable资源
Drawable资源是Android应用中使用最广泛且灵活的资源之一。它不仅可以直接使用诸如.png
、.jpg
、.gif
、.9.png
等图片格式,还可以通过多种XML文件定义资源。这些资源文件会被系统编译成Drawable
类的对象,因此它们也被称为Drawable资源。
通常,Drawable资源保存在/res/drawable
目录下,或者针对不同的屏幕分辨率保存在/res/drawable-ldpi
、/res/drawable-mdpi
、/res/drawable-hdpi
、/res/drawable-xhdpi
等子目录下。
6.4.1 图片资源
图片资源是最简单的Drawable资源。将.png
、.jpg
、.gif
等格式的图片放入/res/drawable-xxx
目录下后,Android SDK会在编译应用时自动加载这些图片,并在R资源清单类中生成对应的资源索引。
注意: Android要求图片资源的文件名必须符合Java或Kotlin标识符的命名规则,否则Android SDK无法为该图片在R类中生成资源索引。
在Activity类中使用图片资源时,可以通过如下语法格式来访问该资源:
[<package_name>.]R.drawable.<file_name>
在XML文件中则按如下语法格式来访问该资源:
@[<package_name>:]drawable/<file_name>
要在程序中获得实际的Drawable对象,可以使用Resources
类提供的getDrawable(int id)
方法,根据资源的ID获取实际的Drawable对象。
6.4.2 StateListDrawable资源
StateListDrawable
用于组织多个Drawable对象。当使用StateListDrawable
作为目标组件的背景或前景图片时,StateListDrawable
对象所显示的Drawable对象会根据目标组件的状态自动切换。
定义StateListDrawable
对象的XML文件的根元素为<selector>
,该元素可以包含多个<item>
元素,这些元素可指定如下属性:
android:color
或android:drawable
:指定颜色或Drawable对象。android:state_xxx
:指定一个特定状态。
以下是一个简单的StateListDrawable
XML文件示例:
xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 指定获得焦点时的颜色 -->
<item android:state_focused="true" android:color="#144" />
<!-- 指定失去焦点时的颜色 -->
<item android:state_focused="false" android:color="#ccf" />
</selector>
StateListDrawable
的<item>
元素支持多种状态,常见的状态如表6.3所示。
实例:高亮显示正在输入的文本框
在这个实例中,我们将使用StateListDrawable
资源来动态改变文本框的文字颜色。通过定义一个Drawable资源文件,可以在文本框获取焦点时显示一种颜色,失去焦点时显示另一种颜色。
定义的Drawable资源文件如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 指定获得焦点时的颜色 -->
<item android:state_focused="true" android:color="#144" />
<!-- 指定失去焦点时的颜色 -->
<item android:state_focused="false" android:color="#ccf" />
</selector>
接下来,在界面布局文件中使用该Drawable资源:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 使用 StateListDrawable资源 -->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@drawable/my_image" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@drawable/my_image" />
</LinearLayout>
这个程序运行时,当用户在文本框中输入内容时,文字颜色会动态改变。
通过使用StateListDrawable
,不仅可以让文本框里的文字颜色随文本框状态的改变而切换,还可以让按钮的背景图片随按钮状态的改变而切换。StateListDrawable
的功能非常灵活,它可以让各种组件的背景、前景随状态的改变而切换。
6.4.3 LayerDrawable资源
LayerDrawable
类似于StateListDrawable
,它也可以包含一个Drawable
数组。系统会按照这些Drawable
对象的数组顺序绘制它们,索引最大的Drawable
对象将会被绘制在最上面。
定义LayerDrawable的XML文件
定义LayerDrawable
对象的XML文件的根元素为<layer-list>
,该元素可以包含多个<item>
元素,这些元素可指定如下属性:
android:drawable
:指定作为LayerDrawable
元素之一的Drawable
对象。android:id
:为该Drawable
对象指定一个标识。android:bottom
|android:top
|android:left
|android:right
:用于指定一个长度值,用于将该Drawable
对象绘制到目标组件的指定位置。
以下是一个简单的LayerDrawable
XML文件示例:
xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 指定一个Drawable元素 -->
<item android:id="@android:id/background"
android:drawable="@drawable/grow" />
</layer-list>
实例:定制拖动条的外观
在这个实例中,我们将使用LayerDrawable
来定制SeekBar
的外观。通过定义一个LayerDrawable
资源文件,可以改变SeekBar
轨道的外观及其已完成部分的Drawable对象。
定义如下的Drawable资源文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义轨道的背景 -->
<item android:id="@android:id/background"
android:drawable="@drawable/grow" />
<!-- 定义轨道上已完成部分的外观 -->
<item android:id="@android:id/progress"
android:drawable="@drawable/ok" />
</layer-list>
另一个LayerDrawable
对象定义如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="center"
android:src="@drawable/ic_logo" />
</item>
<item android:left="40dp" android:top="40dp">
<bitmap android:gravity="center"
android:src="@drawable/ic_logo" />
</item>
<item android:left="80dp" android:top="80dp">
<bitmap android:gravity="center"
android:src="@drawable/ic_logo" />
</item>
</layer-list>
上面的代码定义了三个"层叠"在一起的Drawable
对象。接下来,在界面布局中使用my_bar.xml
定义的Drawable
对象来改变SeekBar
的外观,并通过ImageView
显示layer_logo
的Drawable
组件。
布局文件如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 定义一个拖动条,并改变轨道外观 -->
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progressDrawable="@drawable/my_bar" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/layer_logo" />
</LinearLayout>
该程序的代码无需任何改变,只需加载并显示上述界面布局文件即可。运行该程序,将看到如图6.3所示的界面。
图6.3 使用LayerDrawable资源
6.4.4 ShapeDrawable资源
ShapeDrawable
用于定义一个基本的几何图形(如矩形、圆形、线条等)。定义 ShapeDrawable
的 XML 文件的根元素是 <shape>
元素,该元素可以指定以下属性:
android:shape=["rectangle" | "oval" | "line" | "ring"]
:指定定义哪种类型的几何图形。
定义 ShapeDrawable 对象的完整语法格式如下:
xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape=["rectangle" | "oval" | "line" | "ring"]>
<!-- 定义几何图形的四个角的弧度 -->
<corners
android:radius="integer"
android:topLeftRadius="integer"
android:topRightRadius="integer"
android:bottomLeftRadius="integer"
android:bottomRightRadius="integer" />
<!-- 定义使用渐变色填充 -->
<gradient
android:centerX="integer"
android:angle="integer"
android:centerY="integer"
android:centerColor="integer"
android:endColor="color"
android:gradientRadius="integer"
android:startColor="color"
android:type=["linear" | "radial" | "sweep"]
android:usesLevel=["true" | "false"] />
<!-- 定义几何形状的内边距 -->
<padding
android:left="integer"
android:top="integer"
android:right="integer"
android:bottom="integer" />
<!-- 定义几何形状的大小 -->
<size
android:width="integer"
android:height="integer" />
<!-- 定义使用单种颜色填充 -->
<solid
android:color="color" />
<!-- 定义为几何形状绘制边框 -->
<stroke
android:width="integer"
android:color="color"
android:dashWidth="integer"
android:dashGap="integer" />
</shape>
实例:椭圆形、渐变背景的文本框
在这个实例中,我们将使用 ShapeDrawable
资源为文本框指定背景,并实现各种形状和渐变效果。
首先,定义一个简单的矩形 ShapeDrawable
资源文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 设置填充颜色 -->
<solid android:color="#fff" />
<!-- 设置四周的内边距 -->
<padding android:left="7dp"
android:top="7dp"
android:right="7dp"
android:bottom="7dp" />
<!-- 设置边框 -->
<stroke android:width="3dp" android:color="#ff0" />
</shape>
接下来定义一个带有渐变效果的 ShapeDrawable
资源:
xml
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 定义填充渐变颜色 -->
<gradient
android:startColor="#FFFF0000"
android:endColor="#80FF00FF"
android:angle="45" />
<!-- 设置内填充 -->
<padding android:left="7dp"
android:top="7dp"
android:right="7dp"
android:bottom="7dp" />
<!-- 设置圆角矩形 -->
<corners android:radius="8dp" />
</shape>
最后,定义一个椭圆形的 ShapeDrawable
资源:
xml
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<!-- 定义填充渐变颜色 -->
<gradient
android:startColor="#f00"
android:endColor="#00f"
android:type="sweep"
android:angle="45" />
<!-- 设置内填充 -->
<padding android:left="7dp"
android:right="7dp"
android:top="7dp"
android:bottom="7dp" />
</shape>
在定义了上面的 ShapeDrawable
资源之后,接下来在界面布局文件中使用这些资源作为文本框的背景:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/my_shape_1" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/my_shape_2" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/my_shape_3" />
</LinearLayout>
运行程序,将看到如图6.4所示的界面,显示了带有不同 ShapeDrawable
背景的文本框。
图6.4 使用 ShapeDrawable资源
6.4.5 ClipDrawable资源
ClipDrawable
代表从其他位图上截取的一个"图片片段"。在 XML 文件中定义 ClipDrawable
对象使用 <clip>
元素,该元素的语法格式如下:
xml
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="drawable_resource"
android:clipOrientation="horizontal_or_vertical"
android:gravity="alignment" />
上面的语法格式中可指定如下三个属性:
android:drawable
:指定截取的源 Drawable 对象。android:clipOrientation
:指定截取方向,可以设置为水平截取或垂直截取。android:gravity
:指定截取时的对齐方式。
使用 ClipDrawable
对象时可以调用 setLevel(int level)
方法来设置截取的区域大小。当 level
为 0 时,截取的图片片段为空;当 level
为 10000 时,截取整张图片。
实例:徐徐展开的风景
本实例展示了如何通过 ClipDrawable
对象实现图片徐徐展开的效果。程序先定义如下 ClipDrawable
对象:
xml
<?xml version="1.0" encoding="UTF-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:clipOrientation="horizontal"
android:drawable="@drawable/shuangta"
android:gravity="center" />
上面的程序控制从中间开始截取图片,截取方向为水平截取。接下来通过一个定时器来定期修改 ClipDrawable
对象的 level
,实现图片徐徐展开的效果。相关代码如下:
java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.image);
final ClipDrawable drawable = (ClipDrawable) imageView.getDrawable();
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x1233) {
drawable.setLevel(drawable.getLevel() + 200);
}
}
}
final Handler handler = new MyHandler();
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Message msg = new Message();
msg.what = 0x1233;
handler.sendMessage(msg);
if (drawable.getLevel() >= 10000) {
timer.cancel();
}
}
}, 0, 300);
}
}
运行上面的程序,将看到如图6.5所示的效果,图片将从中间向两侧徐徐展开,类似于一个图片进度条的效果。
图6.5 使用 ClipDrawable资源
通过这种 ClipDrawable
实现的图片进度条,可以在用户界面中实现视觉效果更好的进度指示功能。
6.4.6 AnimationDrawable 资源
AnimationDrawable
代表一个逐帧动画,这种动画通过逐帧切换不同的图片来实现动态效果。Android 既支持传统的逐帧动画,也支持补间动画(通过平移、缩放、旋转等变换实现)。本节将介绍如何定义和使用 AnimationDrawable
资源。
定义 AnimationDrawable 资源
在 Android 中,可以使用 XML 文件定义一个逐帧动画,其根元素为 <animation-list>
,该元素可以包含多个 <item>
元素,每个 <item>
元素指定一帧图片及其显示时间。定义的动画资源文件应该放在 /res/drawable
目录下。
XML 资源文件示例:
xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/frame1" android:duration="50"/>
<item android:drawable="@drawable/frame2" android:duration="50"/>
<item android:drawable="@drawable/frame3" android:duration="50"/>
<item android:drawable="@drawable/frame4" android:duration="50"/>
</animation-list>
上面的资源文件定义了一个逐帧动画,其中 android:oneshot
属性决定动画是否只播放一次。android:drawable
属性指定每一帧的图片资源,android:duration
属性指定每帧显示的时间(单位为毫秒)。
在 Java 代码中使用 AnimationDrawable
在 Java 或 Kotlin 代码中,可以通过以下步骤来使用 AnimationDrawable
资源:
- 在布局文件中定义一个
ImageView
,用于显示动画。 - 在代码中使用
ImageView
的getDrawable()
方法获取AnimationDrawable
对象。 - 调用
start()
方法开始播放动画,调用stop()
方法停止动画。
示例代码:
java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.image);
imageView.setBackgroundResource(R.drawable.my_animation);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();
}
}
在上面的代码中,首先通过 setBackgroundResource()
方法将定义的动画资源设置为 ImageView
的背景,然后获取该背景资源并强制转换为 AnimationDrawable
类型,最后通过 start()
方法开始播放动画。
补间动画
补间动画是通过平移、缩放、旋转、渐变等变换来实现动画效果的。可以通过在 XML 中定义 <set>
元素以及 <alpha>
、<scale>
、<translate>
、<rotate>
等子元素来设置动画效果。
定义补间动画
定义补间动画的 XML 文件如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:duration="5000">
<scale
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="true"
android:duration="2000"/>
<translate
android:fromXDelta="10"
android:toXDelta="130"
android:fromYDelta="30"
android:toYDelta="-80"
android:duration="2000"/>
</set>
上面的 XML 文件定义了一个动画资源,其中包含了缩放和位移两种变换。通过设置 android:fillAfter="true"
,动画结束后将保留最终的状态。
在 Java 代码中使用上述定义的动画资源:
java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = findViewById(R.id.image);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.my_anim);
imageView.startAnimation(animation);
}
}
上述代码中,通过 AnimationUtils.loadAnimation()
方法加载动画资源,并调用 startAnimation()
方法来启动动画。
结果展示
运行以上代码,ImageView 中的图片将根据定义的补间动画效果进行缩放和平移,直到动画结束。补间动画可用于实现丰富的动画效果,提升用户界面的交互性和动态表现。
总结
AnimationDrawable
和补间动画是 Android 中两种重要的动画资源类型,它们可以在用户界面中实现动态的视觉效果。逐帧动画适用于简单的图片切换,而补间动画则可以用来实现复杂的图形变换。通过合理使用这些动画资源,可以为 Android 应用程序提供更加生动和吸引人的用户体验。
本来Android的API文档中说明可以在<alpha.../>、<scale.../> 、<translate.../> 、<rotate.../>等元素中指定android:fillAfter为true来实现这个效果,但实际上要为<set.../>设置android:fillAfter为true 才可以。详始品类图6.6 使用AnimationDrawable资源
6.5 属性动画 (Property Animation) 资源
Animator
代表一个属性动画,是 Android 动画系统的一部分。它主要通过操作对象的属性来实现动画效果。Animator
本身是一个抽象类,常用的子类包括 AnimatorSet
、ValueAnimator
、ObjectAnimator
、TimeAnimator
。在本节中,我们将介绍如何定义属性动画资源,并通过简单的实例展示如何使用这些资源。
定义属性动画的 XML 文件
在 Android 中,可以使用 XML 文件来定义属性动画,这些文件通常位于 /res/animator
目录下。定义属性动画的 XML 文件可以以以下三个元素中的任意一个作为根元素:
<set.../>
: 定义一个AnimatorSet
对象,用于组合多个动画。<objectAnimator.../>
: 定义一个ObjectAnimator
动画,直接操作对象的属性。<animator.../>
: 定义一个ValueAnimator
动画,允许开发者自定义动画的属性变化过程。
下面是属性动画的 XML 文件的一般语法格式:
xml
<?xml version="1.0" encoding="utf-8"?>
<set android:ordering="together | sequentially">
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode="repeat | reverse"
android:valueType="intType | floatType"
android:interpolator="[@package:]anim/interpolator"/>
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode="repeat | reverse"
android:valueType="intType | floatType"
android:interpolator="[@package:]anim/interpolator"/>
</set>
示例:不断渐变的背景色
这个示例将使用属性动画控制组件的背景色不断渐变。以下是定义的属性动画 XML 文件,它位于 /res/animator
目录下:
color_anim.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="backgroundColor"
android:duration="3000"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="#FF8080"
android:valueTo="#8080FF"
android:valueType="intType" />
在上面的 XML 文件中,<objectAnimator>
定义了一个 ObjectAnimator
对象,用于控制组件的 backgroundColor
属性,动画持续时间为 3000 毫秒,并且会无限次重复,重复模式为反向播放 (reverse
),即颜色从 valueFrom
渐变到 valueTo
后,再从 valueTo
渐变回 valueFrom
。
在 Java 代码中使用属性动画
接下来,我们在 Activity 中加载并应用这个动画资源:
MainActivity.java
java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout container = findViewById(R.id.container);
container.addView(new MyAnimationView(this));
}
public class MyAnimationView extends View {
public MyAnimationView(Context context) {
super(context);
// 加载动画资源
ObjectAnimator colorAnim = (ObjectAnimator) AnimatorInflater.loadAnimator(
MainActivity.this, R.animator.color_anim);
colorAnim.setEvaluator(new ArgbEvaluator());
// 对该 View 本身应用属性动画
colorAnim.setTarget(this);
// 开始动画
colorAnim.start();
}
}
}
在上面的代码中:
MyAnimationView
是一个自定义的View
,在onCreate()
方法中被添加到LinearLayout
中。- 使用
AnimatorInflater.loadAnimator()
方法加载定义好的动画资源文件,并转换为ObjectAnimator
对象。 - 通过
setEvaluator()
方法设置ArgbEvaluator
,该评估器将颜色值从valueFrom
逐渐变为valueTo
。 - 使用
setTarget()
方法将动画应用于MyAnimationView
本身。 - 最后,通过
start()
方法启动动画。
运行该程序后,你将看到背景颜色在两个定义的颜色之间不断渐变,带来动态的视觉效果。
总结
属性动画是 Android 提供的一个强大而灵活的动画系统,它通过改变对象的属性值来创建动画效果。通过 XML 文件定义和 Java 代码加载,开发者可以方便地在应用中实现复杂的动画效果。这个系统不仅可以改变视图的属性,还可以应用于任何对象,使得 Android 应用程序的界面更加生动和互动。
6.6 使用原始XML资源
在某些时候,Android应用有一些初始化的配置信息、应用相关的数据资源需要保存,一般推荐使用XML文件来保存它们,这种资源就被称为原始XML资源。下面介绍如何定义、获取原始XML资源。
6.6.1 定义原始XML资源
原始XML资源一般保存在 /res/xml/
路径下,当使用Android Studio创建Android应用时,/res/
目录下并没有包含xml子目录,开发者应该自行手动创建xml子目录。
接下来Android应用对原始XML资源没有任何特殊的要求,只要它是一份格式良好的XML文档即可。
一旦成功地定义了原始XML资源,接下来在XML文件中就可通过如下语法格式来访问它。
@[<package_name>:] xml/file_name
在Java 或Kotlin代码中则按如下语法格式来访问。
[<package_name>.]R.xml.<file_name>
为了在Java 或Kotlin程序中获取实际的XML文档,可以通过Resources的如下两个方法来实现。
XmlResourceParser getXml(int id)
: 获取XML文档,并使用一个XmlPullParser来解析该XML文档,该方法返回一个解析器对象(XmlResourceParser是XmlPullParser的子类)。InputStream openRawResource(int id)
: 获取XML文档对应的输入流。
大部分时候都可以直接调用getXml(int id)
方法来获取XML文档,并对该文档进行解析。
Android 默认使用内置的Pull解析器来解析XML文件。
6.6.2 使用原始XML文件
下面为示例程序添加一个原始的XML文件,将该XML文件放到 /res/xml
目录下。该XML文件的内容很简单,如下所示。
books.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book price="109.0" 出版日期="2008年">疯狂Java讲义</book>
<book price="108.0" 出版日期="2009年">轻量级 Java EE企业应用实战</book>
<book price="79.0" 出版日期="2009年">疯狂前端开发讲义</book>
</books>
接下来就可以在Activity中获取该XML资源,并解析该XML资源中的信息了。Activity程序如下。
MainActivity.java
java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取按钮,并为其设置点击事件监听器
Button bn = findViewById(R.id.bn);
bn.setOnClickListener(view -> {
// 获取XML资源的解析器
XmlResourceParser xrp = getResources().getXml(R.xml.books);
StringBuilder sb = new StringBuilder();
try {
// 解析XML文档
while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
// 遇到开始标签
if (xrp.getEventType() == XmlResourceParser.START_TAG) {
String tagName = xrp.getName();
// 处理 <book> 标签
if ("book".equals(tagName)) {
String price = xrp.getAttributeValue(null, "price");
String pubDate = xrp.getAttributeValue(null, "出版日期");
sb.append("价格: ").append(price).append(" 出版日期: ").append(pubDate).append(" 书名: ");
sb.append(xrp.nextText()).append("\n");
}
}
// 获取下一个解析事件
xrp.next();
}
// 显示解析结果
TextView show = findViewById(R.id.show);
show.setText(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
上面的程序中包含一个按钮和一个文本框,当用户单击该按钮时,程序将会解析指定XML文档,并把文档中的内容显示出来。
6.7 使用布局 (Layout) 资源
从我们学习第一个Android应用开始,已经开始接触Android的Layout资源了,因此此处不会详述Android的Layout资源的知识,只是对Layout资源进行简单的归纳。
Layout资源文件应放在 /res/layout/
目录下,Layout资源文件的根元素通常是各种布局管理器,比如 LinearLayout
、TableLayout
、FrameLayout
等,接着在该布局管理器中定义各种View组件即可。
一旦在Android项目中定义了Layout资源,接下来在XML文件中就可通过如下语法格式来访问它。
@[<package_name>:]layout/<file_name>
在Java 或Kotlin代码中则按如下语法格式来访问。
[<package_name>.]R.layout.<file_name>
6.8 使用菜单 (Menu) 资源
前面已经介绍过Android的菜单支持,并分别介绍了如何使用Java代码来实现菜单和使用XML资源文件来定义菜单。
实际上,Android 推荐使用XML资源文件来定义菜单,这样将会提供更好的解耦。由于前面介绍过如何使用XML资源文件定义菜单,因此此处不再详细介绍菜单资源文件的内容,只是对其进行简单的归纳。
Android 菜单资源文件放在 /res/menu
目录下,菜单资源的根元素通常是 <menu>
元素,<menu>
元素无须指定任何属性。
一旦在Android项目中定义了菜单资源,接下来在XML文件中就可通过如下语法格式来访问它。
@[<package_name>:]menu/<file_name>
在Java 或Kotlin代码中则按如下语法格式来访问。
[<package_name>.]R.menu.<file_name>
6.9 使用样式 (Style) 和主题 (Theme) 资源
样式和主题资源都用于对Android应用进行"美化",只要充分利用Android应用的样式和主题资源,开发者就可以开发出各种风格的Android应用。
6.9.1 样式资源
在Android开发中,经常需要对某个类型的组件指定大致相似的格式,比如字体、颜色、背景色等。如果每次都为View组件重复指定这些属性,无疑会有大量的工作量,而且不利于项目后期的维护。为了提高效率,Android提供了样式资源的功能。
样式资源文件放在 /res/values/
目录下,样式资源文件的根元素是 <resources>
元素,该元素内可包含多个 <style>
子元素,每个 <style>
子元素定义一个样式。 <style>
元素指定如下两个属性:
name
:指定样式的名称。parent
:指定该样式所继承的父样式。当继承某个父样式时,该样式将会获得父样式中定义的全部格式。当然,当前样式也可以覆盖父样式中指定的格式。
在 <style>
元素内可包含多个 <item>
子元素,每个 <item>
子元素定义一个格式项。例如:
xml
<resources>
<style name="style1">
<item name="android:textSize">20sp</item>
<item name="android:textColor">#00d</item>
</style>
<style name="style2" parent="style1">
<item name="android:background">#fee6</item>
<item name="android:padding">8dp</item>
<item name="android:textColor">#000</item>
</style>
</resources>
在上面的样式资源中定义了两个样式,其中第二个样式继承了第一个样式,并且覆盖了父样式中的 textColor
属性。
一旦定义了样式资源之后,可以在XML资源中按如下语法格式来使用样式:
xml
@[<package name>:]style/<file name>
6.9.2 主题资源
与样式资源非常相似,主题资源的XML文件通常也放在 /res/values/
目录下,主题资源的XML文件同样以 <resources>
元素作为根元素,同样使用 <style>
元素来定义主题。
主题与样式的区别主要体现在:
- 主题不能作用于单个的View组件,而是对整个应用中的所有Activity起作用,或对指定的Activity起作用。
- 主题定义的格式应该是改变窗口外观的格式,例如窗口标题、窗口边框等。
例如,定义如下主题资源:
xml
<style name="CrazyTheme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowFrame">@drawable/window_border</item>
<item name="android:windowBackground">@drawable/star</item>
</style>
在定义了主题之后,可以在Java代码中使用该主题,例如:
java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(R.style.CrazyTheme);
setContentView(R.layout.activity_main);
}
大部分时候,在 AndroidManifest.xml
文件中为指定应用或指定Activity应用主题更加简单。如果想让应用中全部窗口使用该主题,只需要为 <application>
元素添加 android:theme
属性即可:
xml
<application android:theme="@style/CrazyTheme">
</application>
如果只想让某个Activity拥有这个主题,可以修改 <activity>
元素,通过 android:theme
指定主题即可。
Android中还提供了几种内置的主题资源,这些主题可以通过查询 Android.R.style
类来查看。例如,如果希望某个Activity使用对话框风格的窗口,可以这样定义:
xml
<activity android:theme="android:Theme.Material.Dialog">
</activity>
与样式类似,Android主题同样支持继承。例如:
xml
<style name="CrazyTheme" parent="android:Theme.Material.Dialog">
</style>
上面定义的 CrazyTheme
主题继承了 android:Theme.Material.Dialog
主题,在此基础上可以添加或覆盖某些属性来实现自定义主题。
6.10 属性 (Attribute) 资源
在开发自定义View组件时,如果需要让组件支持在XML布局文件中指定属性,那么可以通过属性资源来实现。这种属性资源的定义和管理能够帮助开发者更方便地控制自定义View组件的外观和行为。
属性资源文件也放在 /res/values/
目录下,文件的根元素是 <resources>
,该元素包含以下两个子元素:
<attr>
:用于定义一个属性。<declare-styleable>
:用于定义一个可声明的对象,每个styleable
对象是一组attr
属性的集合。
在定义属性资源后,开发者可以在自定义组件的构造函数中通过 AttributeSet
对象来获取这些属性。
例如,假设开发了一个默认带动画效果的图片组件,该组件需要一个额外的 duration
属性来控制动画的持续时间。首先需要在属性资源文件中定义该属性:
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 定义一个属性 -->
<attr name="duration" format="integer"/>
<!-- 定义一个 styleable 对象来组合多个属性 -->
<declare-styleable name="AlphaImageView">
<attr name="duration"/>
</declare-styleable>
</resources>
在定义了上述属性资源后,可以在自定义的 AlphaImageView
组件中获取并使用该属性,例如:
java
public class AlphaImageView extends ImageView {
private static final int SPEED = 300;
private int alphaDelta;
private int curAlpha = 0;
private Timer timer;
public AlphaImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AlphaImageView);
int duration = typedArray.getInt(R.styleable.AlphaImageView_duration, 0);
typedArray.recycle();
alphaDelta = 255 * SPEED / duration;
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
curAlpha += alphaDelta;
if (curAlpha >= 255) {
curAlpha = 255;
timer.cancel();
}
postInvalidate();
}
}, 0, SPEED);
}
@Override
protected void onDraw(Canvas canvas) {
this.setImageAlpha(curAlpha);
super.onDraw(canvas);
}
}
在上面的代码中,通过获取 AlphaImageView
的 duration
属性,计算了图片透明度的变化幅度,并通过定时器动态改变图片的透明度。
在使用自定义组件 AlphaImageView
时,可以在XML布局文件中为它指定 duration
属性,代码如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:crazyit="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 使用自定义组件,并指定属性资源文件中定义的属性 -->
<org.crazyit.res.AlphaImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/javaee"
crazyit:duration="8000" />
</LinearLayout>
在运行该程序时,可以看到图片从透明逐渐变为完全显示的效果,这就是通过 duration
属性实现的。
6.11 使用原始资源
除了前面介绍的各种XML文件和图片文件外,Android应用可能还需要用到其他类型的资源,如声音文件等。声音资源对于许多应用来说非常重要,选择合适的音效可以大大提升应用的用户体验。
Android的原始资源可以放在以下两个地方:
- /res/raw/ 目录下:Android SDK会处理该目录下的资源,并在R清单类中为这些资源生成索引项。
- /assets/ 目录下:该目录下的资源是更彻底的原始资源,应用程序需要通过
AssetManager
来管理这些资源。
访问原始资源:
-
对于放在
/res/raw/
目录下的资源,Android SDK会在R清单类中生成一个索引项。可以在XML文件中通过以下语法格式访问这些资源:xml@[<package_name>:]raw/file_name
在Java或Kotlin代码中,可以通过以下语法格式访问:
java[<package_name>.]R.raw.<file_name>
这样,Android应用可以非常方便地访问
/res/raw/
目录下的原始资源。 -
对于放在
/assets/
目录下的资源,Android应用需要使用AssetManager
来访问。AssetManager
提供了以下两种常用方法:InputStream open(String fileName)
: 根据文件名获取原始资源的输入流。AssetFileDescriptor openFd(String fileName)
: 根据文件名获取原始资源的AssetFileDescriptor
,它代表了原始资源的描述,应用程序可以通过AssetFileDescriptor
来获取原始资源。
示例:
以下是一个播放声音文件的示例程序。程序中定义了两个按钮:一个用于播放 /res/raw/
目录下的声音文件,另一个用于播放 /assets/
目录下的声音文件。
首先,将 bomb.mp3
文件放入 /res/raw/
目录,并将 shot.mp3
文件放入 /assets/
目录。Android SDK会自动处理 /res/raw/
目录下的资源,并在R清单类中为它生成一个索引项 R.raw.bomb
。
java
public class MainActivity extends Activity {
private MediaPlayer mediaPlayer1;
private MediaPlayer mediaPlayer2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 直接根据声音文件的ID来创建MediaPlayer
mediaPlayer1 = MediaPlayer.create(this, R.raw.bomb);
// 获取应用的AssetManager
AssetManager am = getAssets();
try {
// 获取指定文件对应的AssetFileDescriptor
AssetFileDescriptor afd = am.openFd("shot.mp3");
mediaPlayer2 = new MediaPlayer();
// 使用MediaPlayer加载指定的声音文件
mediaPlayer2.setDataSource(afd.getFileDescriptor());
mediaPlayer2.prepare();
} catch (IOException e) {
e.printStackTrace();
}
// 获取第一个按钮,并为它绑定事件监听器
Button playRaw = findViewById(R.id.playRaw);
playRaw.setOnClickListener(view -> mediaPlayer1.start()); // 播放声音
// 获取第二个按钮,并为它绑定事件监听器
Button playAsset = findViewById(R.id.playAsset);
playAsset.setOnClickListener(view -> mediaPlayer2.start()); // 播放声音
}
}
在这个示例中,mediaPlayer1
用于播放 /res/raw/
目录下的声音文件,而 mediaPlayer2
则利用 AssetManager
来播放 /assets/
目录下的声音文件。通过这种方式,可以灵活地处理和播放不同位置的声音文件。
6.12 国际化
引入国际化的目的是为了提供自适应、更友好的用户界面。程序国际化指的是同一个应用在不同语言、国家环境下,可以自动呈现出对应的语言等用户界面,从而提供更好的用户体验。
6.12.1 为 Android 应用提供国际化资源
为 Android 程序提供国际化资源非常方便,因为 Android 本身采用了 XML 资源文件来管理所有字符串消息。只要为各消息提供不同国家、语言对应的内容即可。通过前面的介绍我们知道,Android 应用使用 res/values/
目录下的资源文件来保存程序中用到的字符串消息。为了给这些消息提供不同国家、语言的版本,开发者需要为 values
目录添加几个不同的语言国家版本。不同 values
文件夹的命名方式为:
values-语言代码-r国家代码
例如,如果希望应用支持简体中文和美式英语两种环境,则需要在 res/
目录下添加 values-zh-rCN
和 values-en-rUS
两个目录。
如果希望应用程序的图片也能随国家、语言环境改变,那么还需要为 drawable
目录添加几个不同的语言国家版本。不同 drawable
文件夹的命名方式为:
drawable-语言代码-r国家代码
如果还需要为 drawable
目录按分辨率提供文件夹,则可以在后面追加分辨率后缀,例如:
drawable-zh-rCN-mdpi
drawable-zh-rCN-hdpi
drawable-zh-rCN-xhdpi
drawable-en-rUS-mdpi
drawable-en-rUS-hdpi
drawable-en-rUS-xhdpi
接下来,可以分别在 values-en-rUS
和 values-zh-rCN
目录下创建 strings.xml
文件。这些文件将包含不同语言的字符串资源。
例如:
\res\values-en-rUS\strings.xml(美式英语):
xml
<resources>
<string name="cancel">Cancel</string>
<string name="ok">OK</string>
<string name="msg">Hello, Android!</string>
</resources>
\res\values-zh-rCN\strings.xml(简体中文):
xml
<resources>
<string name="ok">确定</string>
<string name="cancel">取消</string>
<string name="msg">你好啊,可爱的小机器人!</string>
</resources>
在不同语言的国际化资源文件中,所有消息的 key
是相同的,只是在不同国家、语言环境下,消息资源 key
对应的 value
不同。
6.12.2 国际化 Android 应用
Android 的设计本身就是国际化的,当开发者在 XML 界面布局文件、Java 代码中加载字符串资源时,Android 的国际化机制就已经在起作用了。
例如,下面是一个界面布局文件:
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:lines="2" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/logo" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal">
<!-- 两个按钮的文本都是通过消息资源指定的 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ok" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel" />
</LinearLayout>
</LinearLayout>
在上面的布局文件中,字符串内容并没有硬编码在布局文件中,而是通过资源文件中的字符串值加载,这时 Android 的国际化机制就会起作用。如果系统环境是简体中文,则加载 res/values-zh-rCN/strings.xml
文件中的字符串资源;如果是美式英语环境,则加载 res/values-en-rUS/strings.xml
文件中的字符串资源。
同样,在 Java 或 Kotlin 代码中也可以根据资源 ID 设置字符串内容,而不是以硬编码的方式设置为固定的字符串内容。例如:
java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvShow = findViewById(R.id.show);
// 设置文本框所显示的文本
tvShow.setText(R.string.msg);
}
}
如果将手机设置为美式英语环境(通过 Android 系统的 Settings → System → Language & input → Select language → English (United States) 设置),运行程序将看到美式英语环境下的界面。
如果将手机设置为简体中文环境(通过 Android 系统的 Settings → System → Language & input → Language → 中文(简体) 设置),运行程序将看到简体中文环境下的界面。
通过国际化机制,Android 应用可以根据系统的语言环境自动加载相应的资源文件,从而实现界面语言和图片的自动切换。如果需要对程序标题等内容也进行国际化处理,只需为相应的字符串资源提供不同语言版本即可。
6.13 自适应不同屏幕的资源
开发Android应用的一个挑战是不同设备的屏幕尺寸和分辨率差异很大,而开发者希望应用能够在所有设备上运行良好。因此,开发Android应用时必须考虑如何让应用自适应不同的屏幕。
提示:相比之下,iOS设备的屏幕尺寸和分辨率较为固定,因此开发iOS应用时所需考虑的设备更少。
前面提到,Android默认将 drawable
目录(存放图片等Drawable资源的目录)分为 drawable-ldpi
、drawable-mdpi
、drawable-hdpi
、drawable-xhdpi
、drawable-xxhdpi
等子目录,正是为了适应不同分辨率的屏幕。
通常来说,屏幕资源需要考虑以下几个方面:
- 屏幕尺寸 :可分为
small
(小屏幕)、normal
(中等屏幕)、large
(大屏幕)、xlarge
(超大屏幕)四种。 - 屏幕分辨率 :可分为
ldpi
(低分辨率)、mdpi
(中等分辨率)、hdpi
(高分辨率)、xhdpi
(超高分辨率)、xxhdpi
(超超高分辨率)五种。 - 屏幕方向 :可分为
land
(横屏)和port
(竖屏)两种。
图6.13展示了不同屏幕尺寸、不同分辨率的通用说法。
为不同屏幕适配资源
为不同尺寸的屏幕设置用户界面时,每种用户界面总有一个最低的屏幕尺寸要求。上面这些通用说法中屏幕尺寸的最低分辨率是以 dp
为单位的。因此,定义界面布局时应尽量使用 dp
作为单位。
下面是不同屏幕尺寸所需的最低尺寸:
- xlarge 屏幕:至少需要
960dp x 720dp
。 - large 屏幕:至少需要
640dp x 480dp
。 - normal 屏幕:至少需要
470dp x 320dp
。 - small 屏幕:至少需要
426dp x 320dp
。
为了提供自适应不同屏幕的资源,可以采取以下措施:
- 屏幕分辨率 :为
drawable
目录增加后缀ldpi
(低分辨率)、mdpi
(中等分辨率)、hdpi
(高分辨率)、xhdpi
(超高分辨率)、xxhdpi
(超超高分辨率),分别为不同分辨率的屏幕提供资源。 - 屏幕尺寸 :为
layout
、values
等目录增加后缀small
、normal
、large
和xlarge
,分别为不同尺寸的屏幕提供相应资源。
从Android 3.2开始,Android建议直接使用真实的屏幕尺寸来定义屏幕尺寸,例如:
sw<N>dp
:屏幕尺寸至少宽N
个dp
才能使用该资源,例如layout-sw600dp
表示设备屏幕的宽度大于或等于600dp
时使用该目录下的布局资源。w<N>dp
:屏幕尺寸可用宽度为N
个dp
可使用该资源。h<N>dp
:屏幕尺寸可用高度为N
个dp
才能使用该资源。
提示:还可以为 layout
、values
等目录增加后缀 land
和 port
,分别为横屏和竖屏提供相应的资源。
示例:适配不同屏幕的布局
以下是一个示例,在 layout
目录下定义了一个界面布局文件 activity_main.xml
:
xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:src="@drawable/a"/>
</RelativeLayout>
在 xhdpi
设备上(例如 768x1280
、320dpi
),系统将会使用 res/drawable-xhdpi/
目录下的图片。
在 xxhdpi
设备上(例如 1080x1920
、420dpi
),系统将会使用 res/drawable-xxhdpi/
目录下的图片。
示例:适配不同屏幕尺寸的布局
提供了两份布局文件,一份放在 layout-normal
目录下,一份放在 layout-large
目录下。
layout-normal 目录下的 activity_main.xml
:
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="第一个按钮"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="第二个按钮"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="第三个按钮"/>
</LinearLayout>
layout-large 目录下的 activity_main.xml
:
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一个按钮"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第二个按钮"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第三个按钮"/>
</LinearLayout>
在正常尺寸的屏幕(如 768x1280
、320dpi
)上,系统将加载 layout-normal
目录下的 activity_main.xml
布局文件。
在大尺寸的屏幕(如 720x1280
、240dpi
)上,系统将加载 layout-large
目录下的 activity_main.xml
布局文件。
这表明,屏幕大小不仅与分辨率有关,还与 dpi
有关。dpi
越高,屏幕越小,图像显示效果越细腻。因此,720x1280
、240dpi
的屏幕比 768x1280
、320dpi
的屏幕更大,故而Android在 720x1280
、240dpi
的屏幕上会选择 layout-large
目录下的 activity_main.xml
布局文件。
6.14 本章小结
通过使用资源文件,Android 应用可以将各种字符串、图片、颜色、界面布局等内容交由 XML 文件配置管理,从而避免在 Java 或 Kotlin 代码中直接定义这些内容,减少硬编码带来的问题。本章介绍了 Android 应用资源的存储方式与使用方式,同时详细讲解了字符串资源、颜色资源、尺寸资源、数组资源、图片资源、各种 Drawable 资源、原始 XML 资源、布局资源、菜单资源、样式和主题资源、属性资源、原始资源等多种资源文件的使用。
此外,本章还讨论了 Android 应用的国际化支持及其对不同分辨率屏幕的自适应处理。这些内容在实际开发中至关重要,读者需要认真掌握。
总之,Android 应用资源是实现应用高解耦设计的关键,学习和掌握这些内容将为开发灵活、高效的 Android 应用奠定坚实的基础。