# 五、用户界面小部件

在本章中，我们将调查安卓的基本用户界面小部件:按钮、文本字段、复选框、滑块、微调器和选择器。了解如何配置和查询这些组件使您能够收集或向用户显示几乎任何类型的信息，这是任何高质量安卓应用程序的基础。我们还将简要讨论如何改变这些 UI 元素的外观以及 Android 的内置主题。

本章旨在介绍安卓提供的许多用户界面组件。您应该对哪些组件可用以及如何在现实应用程序中开始使用它们有一个基本的了解。本章包含提供更多信息的官方文档链接。

本章中的所有示例都可以在本书示例代码中包含的 *UserInterfaceWidgets* 项目中找到。这个项目的主要活动提供了几个其他活动的链接，每个活动都包含特定类型的小部件的具体示例。

## 影像

可以使用[图像视图](http://developer.android.com/reference/android/widget/ImageView.html)对象显示图像。一个`ImageView`可以显示任何一种位图，它照顾到基本的对齐和缩放功能。您可以在`activity_image.xml`布局文件中找到各种`ImageView`配置的几个示例:

![](img/image046.jpg)

图 45:本章示例应用程序中定义的图像活动

要在一个 XML 布局文件中包含一个`ImageView`，您可以开发以下代码:

```java
    <ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="5dp"
    android:src="@drawable/syncfusion_icon" />

```

一个`ImageView`最重要的属性就是`android:src`。这定义了它将显示的图像。在上述情况下，`@drawable/syncfusion_icon`指的是应用程序源代码中包含的图像文件。请注意，这与字符串资源的格式相似(例如，`@string/button_title`)。然而，`@drawable`前缀指的是一个**可绘制资源**，它是任何一种图形资产。

### 添加可抽取资源

当然，为了让上面的代码片段起作用，我们需要向 Eclipse 项目中添加一个名为`syncfusion_icon`的图像文件。*用户界面小部件*示例项目已经包含了这个文件，但是添加您自己的图形资源就像从硬盘上复制它们并粘贴到 Eclipse 包资源管理器中的一个或多个`res/drawable`目录一样简单。请记住，`hdpi`、`ldpi`和其他后缀对应于不同的屏幕分辨率，因此为您计划支持的每个分辨率包含替代图像是一个很好的做法。

![](img/image047.jpg)

图 46:向 Eclipse 项目添加图形资产

请注意，可绘制的资源可以是 PNG、GIF 或 JPG 文件，文件扩展名将被自动推断。

### 缩放图像

您可以通过在`ImageView`元素上定义`layout_width`和`layout_height`属性来缩放图像。`ImageView`提供几种类型的[缩放行为](http://developer.android.com/reference/android/widget/ImageView.html#attr_android:scaleType)。`ImageView``android:scaleType`属性的值决定了它是将图像拉伸到指定的尺寸(`fitXY`)、按比例缩放以显示整个图像(`centerInside`)、将未缩放的图像在指定的尺寸内居中(`center`)还是以其他几种预定义的方式将图像尺寸映射到`layout_width`和`layout_height`值。

```java
    <ImageView
    android:layout_width="150dp"
    android:layout_height="100dp"
    android:scaleType="fitXY"
    android:src="@drawable/syncfusion_icon" />

```

例如，上面的代码将把`syncfusion_icon`图像拉伸到 150x100 个独立于设备的像素。

### 以编程方式定义图像源

如果您想动态设置一个`ImageView`实例的源图像，您可以将一个可绘制的资源传递给它的`setImageDrawable()`方法。以下代码片段可以在`ImageActivity.java`中找到，演示了如何将名为`syncfusion_alt_icon.png`的图像加载到在 XML 布局文件中定义的`ImageView`中。

```java
    // Dynamically load an image into an ImageView
    ImageView imageView = (ImageView) findViewById(R.id.dynamicImage);
    Resources resources = getResources();
    Drawable image = resources.getDrawable(R.drawable.syncfusion_alt_icon);
    imageView.setImageDrawable(image);

```

在安卓框架中，**资源包**包含所有的 XML 布局文件、字符串资源和可绘制资源。应用程序的资源包由[资源](http://developer.android.com/reference/android/content/res/Resources.html)类表示，活动包可以通过全局`getResources()`函数获取。`Resources`类定义了几个有用的方法，将资源标识转化为有用的对象。在这种情况下，`getDrawable()`方法让我们将`res/drawable`文件夹中的一个图像文件转换为[可绘制的](http://developer.android.com/reference/android/graphics/drawable/Drawable.html)对象，该对象可以由`ImageView`对象显示。请注意，图像文件的标识是通过`R.drawable`访问的，很像一个 XML 布局文件是通过`R.layout`访问的。

`setImageDrawable()`方法是一种非常灵活的显示图像的方式，因为可以从远程 URL 或用户定义的位置从资源包(如上所示)加载位图。

## 按钮

在这本书里，我们一直在研究`Button`视图，但是有一些常见的修改值得研究。到目前为止，我们使用的按钮完全是基于文本的，但是也可以创建基于图标的按钮，以及将图标和文本相结合的按钮。包含在包含的示例项目中的`activity_button.xml`布局文件包含基于文本、基于图标和组合的按钮。

![](img/image048.jpg)

图 47:本章示例应用程序中定义的按钮活动

我们已经看到了几个基于文本的按钮的例子，所以我们将直接跳到图标按钮。图标按钮由一个名为`ImageButton`的专用类创建。这类按钮不显示文本标题，它们使用与上一节中的`ImageView`相同的`android:src`属性。例如，以下代码片段使用名为`edit_button_icon.png`的图像文件作为其图标:

```java
    <ImageButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="20dp"
    android:src="@drawable/edit_button_icon" />

```

与`ImageView`一样，`android:src`属性必须指向您已经添加到项目中的可绘制资源。

将图标和文本组合在一个按钮中有点复杂。我们必须回到熟悉的`Button`类，并使用以下属性之一告诉它应该在哪里显示与其文本标题相关的图标:

*   `android:drawableBottom`–在文本下添加图标
*   `android:drawableEnd`–在文本后添加图标(根据文本方向而变化)
*   `android:drawableLeft`–在文本左侧添加图标
*   `android:drawableRight`–在文本右侧添加图标
*   `android:drawableStart`–在文本前添加图标(根据文本方向而变化)
*   `android:drawableTop`–在文本上方添加图标

每一个都以一个可抽取的资源作为值，就像`ImageView`的`android:src`属性一样。例如，如果您想在文本标题的左侧显示图像文件`edit_button_icon.png`，您可以使用以下内容:

```java
    <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="20dp"
    android:text="Edit"
    android:drawableLeft="@drawable/edit_button_icon"
    android:drawablePadding="10dp" />

```

还要注意`android:drawablePadding`属性，它允许您定义图标和文本之间的间距。

所有这些按钮类型都可以使用`android:onClick`属性来定义当它们被点击时要调用的方法，但是如果您以编程方式创建它们，这个 XML 属性是不可用的。相反，您需要设置按钮的`onClickListener`属性，就像我们在上一章中检测`ListView`和`GridView`项目的点击一样。例如，如果您想在本章示例应用程序的`ButtonActivity`中以编程方式定义`textImageButton`的行为，您可以使用以下内容:

```java
    Button button = (Button) findViewById(R.id.textImageButton);
    button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View sender) {
    Button senderAsButton = (Button) sender;
    String title = senderAsButton.getText().toString();
    Log.d(TAG, String.format("You clicked the '%s' button", title));
    }
    });

```

这种定义按钮行为的方法比静态的`android:onClick` XML 属性提供了更多的可能性。它在动态生成按钮时是必要的，并且它使得根据上下文改变按钮的行为成为可能。

## 文本字段

静态文本字段(即标签)在本书前面已经介绍过了。在本节中，我们将学习如何更改它们的颜色、大小和其他属性。我们还将学习如何通过可编辑文本字段接受用户输入，这是收集用户输入的最基本方式之一。本节中所有代码的具体示例可以在*用户界面小部件*项目的`activity_text_field.xml`布局中找到。在开发人员指南的[输入控件](http://developer.android.com/guide/topics/ui/controls/text.html)部分以及[文本视图](http://developer.android.com/reference/android/widget/TextView.html)类文档中可以找到更详细的文本字段概述。

### 设置文本字段样式

请记住，可以使用`<TextView>`元素将文本字段添加到布局中，并且可以使用`android:text`属性定义它们的文本。特定文本字段的外观可以通过在其上定义其他属性来更改，下面列出了其中最常见的属性:

*   `android:textColor`–文本字段的颜色，以`#AARRGGBB`的形式指定为十六进制数。
*   `android:textSize`–文本的大小。缩放后的像素(`sp`)是首选的使用单位。
*   `android:textStyle`–类型的样式。必须是`normal`、`bold`或`italic`。
*   `android:typeface`–要使用的字体。值必须是`normal`、`sans`、`serif`或`monospace`。
*   `android:textIsSelectable`–用户是否可以选择文本。必须是`true`或`false`。

下面的`<TextView>`演示了所有这些属性。请注意，缩放后的像素单位(`sp`)基于用户首选的字体大小，这意味着它将根据周围的文本适当缩放。当使用像素(`px`)、独立于设备的像素(`dp`)或英寸(`in`)来明确定义文本大小时，情况并非如此。

```java
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Look, a big red serif font"
    android:textColor="#ffff0000"
    android:textSize="20sp"
    android:textStyle="italic"
    android:typeface="serif"
    android:textIsSelectable="true" />

```

这将创建一个如下所示的标签:

![](img/image049.jpg)

图 48:具有定制外观的文本视图

并且，由于`android:textIsSelectable`设置为真，用户可以点击并按住文本进行选择和复制。

![](img/image050.jpg)

图 49:选择可选文本视图的一部分

### 可编辑文本字段

可编辑文本字段具有不同于静态文本字段的外观和行为。他们使用下划线和提示向用户表明这是为了收集输入，当用户点击一个可编辑的文本字段时，屏幕上的键盘——也称为软键盘(与硬*软件键盘相反)——出现。*

 *![](img/image051.jpg)

图 50:用软键盘编辑文本字段

使用`<EditText>`元素创建可编辑文本视图，而不是`<TextView>`元素。例如，上面截图中的文本视图是由以下 XML 创建的:

```java
    <EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:hint="Tap here to edit this text"
    android:inputType="text" />

```

可编辑文本字段提示是要设置的最重要属性之一。它们起到标签的作用，告诉用户期望什么样的输入。

`android:inputType`属性允许您指定您期望的输入类型。这可能会对可用性产生巨大影响，因为它决定了软键盘的显示类型。例如，如果您只想收集一个电话号码，您可以使用`phone`作为该值。这将使安卓显示器成为一个拨号盘，而不是一个完整的键盘，从而更容易输入所需的输入。

![](img/image052.jpg)

图 51:使用 android:inputType 显示数字键盘，而不是全键盘

有太多的内置输入类型，其中许多可以组合起来，让应用程序开发人员更好地控制用户体验。下面列出了一些最常见的`android:inputType`值(所有可用选项参见[安卓:输入类型文档](http://developer.android.com/reference/android/widget/TextView.html#attr_android:inputType)):

*   `text`–使用普通文本键盘。
*   `textEmailAddress`–使用带有`@`字符的文本键盘。
*   `textUri`–使用带有`/`字符的文本键盘。
*   `number`–使用不带传统拨号键盘字母的数字键盘。
*   `phone`–使用带有传统拨号键盘字母的数字键盘(例如， *2* 键也显示 *ABC* )。
*   `textCapWords`–大写用户键入的每个单词。
*   `textCapSentences`–将每个句子的第一个字母大写。
*   `TextAutoCorrect`–使用安卓内置词典自动纠正拼错的单词。
*   `textPassword`–输入字符后隐藏字符。
*   `datetime`–使用带有/字符的数字键盘。

其中一些值可以使用按位运算符`(|)`进行组合。例如，如果您希望`<EditText>`元素使用文本输入、大写句子和自动纠正拼写错误的单词，您可以使用以下内容:

```java
    android:inputType="text|textCapSentences|textAutoCorrect"

```

自定义软键盘的另一种方法是使用`android:imeOptions`属性。这定义了什么是“完成”按钮。例如，如果您想在用户完成输入后显示*发送*作为最终动作，您可以在`<EditText>`元素中添加以下行:

```java
    android:imeOptions="actionSend"

```

生成的键盘如下图所示。请注意前面示例中的“完成”按钮是如何变成“发送”按钮的。

![](img/image053.jpg)

图 52:更改 android:imeOptions 属性以显示发送按钮

`android:imeOptions`最常见的值有:`actionDone`、`actionSend`、`actionSearch`和`actionNext`，这些都是不言而喻的。完整列表请访问[安卓:imeOptions 文档](http://developer.android.com/reference/android/widget/TextView.html#attr_android:imeOptions)。

#### 收集文本输入

当然，如果您正在使用一个`<EditText>`元素，您可能会想要在用户完成输入之后对输入做一些事情。这个过程类似于用`OnClickListener`收听按钮点击。在`TextFieldActivity.java`中，你会发现一个最小的例子，它告诉你如何收集输入。

让我们从需要导入的类开始，这样我们的例子才能工作:

```java
    import android.view.KeyEvent;
    import android.view.inputmethod.EditorInfo;
    import android.widget.TextView;
    import android.widget.TextView.OnEditorActionListener;
    import android.widget.EditText;
    import android.util.Log;

```

[键事件](http://developer.android.com/reference/android/view/KeyEvent.html)类包含按下哪个键的信息。我们不会将此用于我们的示例，但它在使用硬件键盘时非常有用。 [EditorInfo](http://developer.android.com/reference/android/view/inputmethod/EditorInfo.html) 类定义了几个常量，让我们检查`<EditText>`元素正在收集哪种文本。当然，我们将需要`TextView`和`EditText`类，以及 [OnEditorActionLister](http://developer.android.com/reference/android/widget/TextView.OnEditorActionListener.html) 类，这是让我们倾听“完成”动作的方法。

要想知道用户何时按下了“完成”按钮，首先需要找到有问题的文本视图，然后设置其`onEditorActionListener`属性，如下所示:

```java
    EditText text = (EditText) findViewById(R.id.textField);
    text.setOnEditorActionListener(new OnEditorActionListener() {
    public boolean onEditorAction(TextView textView,
    int actionId,
    KeyEvent event) {
    if (actionId == EditorInfo.IME_ACTION_SEND) {
    String input = textView.getText().toString();
    Log.d(TAG, String.format("Processing input: %s", input));
    }
    return false;
    }
    });

```

每当用户编辑完指定的`<EditText>`(在这种情况下，标识为`textField`的那个)时，就会调用`onEditorAction()`功能。这是您应该以您认为合适的方式处理输入的地方。如果您想检查发送了哪种动作，您可以对照`EditorInfo`中的一个常量检查其`actionId`参数。在这里，我们确定这是一个*发送*动作，然后我们简单地将输入记录到 LogCat 中。

`onEditorAction()`的返回值很重要。如果它返回`true`，则意味着您的代码已经处理了与收集输入相关的所有事情，包括在必要时隐藏屏幕键盘。但是，如果它返回`false`，则意味着应该执行默认的处理行为，这通常会隐藏屏幕键盘。

上面的片段返回`false`，这样当用户完成时，键盘就被隐藏了，不管用户输入了什么。如果您想根据输入手动关闭键盘(例如，如果用户输入了无效值，您想保持键盘显示)，您可以使用[输入方法管理器](http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html)来完成，如下所示:

```java
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(textView.getWindowToken(), 0);

```

您还需要另外两个导入才能工作:

```java
    import android.content.Context;
    import android.view.inputmethod.InputMethodManager;

```

现在，您应该了解如何在 XML 布局中包含可编辑的文本字段，配置它们的键盘，并在用户完成输入后收集这些数据。这是您在自己的安卓应用程序中开始收集文本输入所需了解的一切。

## 复选框

复选框旨在让用户同时选择多个布尔项目。它们可以与单选按钮形成对比，单选按钮只允许用户从组中选择一个项目。由于复选框可以单独选中，因此使用它们与使用按钮非常相似。

它们由 [CheckBox](http://developer.android.com/reference/android/widget/CheckBox.html) 类表示，该类实现了实际的复选框和复选框，以及相应的文本标签。复选框使用与按钮相同的`android:onClick`属性，这使得用户很容易确定它们何时被切换。

![](img/image054.jpg)

图 53:使用几个复选框来改变文本视图的外观

示例项目中的`activity_check_box.xml`布局文件和`CheckBoxActivity.java`类展示了复选框最常见的特征(以及一些程序化的`TextView`操作)。它使用三个复选框来更改文本字段的颜色、样式和大小。完整的例子可以在上面的截图中看到。让我们从查看其中一个复选框的 XML 开始:

```java
    <CheckBox
    android:id="@+id/checkBoxGreen"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Green text"
    android:onClick="checkBoxClicked" />

```

这将创建一个以*绿色文本*为标签的复选框，只要点击它，它就会调用`CheckBoxActivity`上的`checkBoxClicked()`方法。该示例项目包含另一个用于切换文本字段是正常还是粗体的按钮(*粗体文本*)，以及第三个用于在`18sp`和`30sp`文本大小之间切换的按钮(*大文本*)。

处理此点击的活动将比前面的示例稍微复杂一点。在`CheckBoxActivity.java`中，您会发现三个私有实例变量用于存储文本字段的状态:

```java
    private boolean isGreen;
    private boolean isBold;
    private boolean isBig;

```

`onCreate()`方法初始化这些变量，然后调用我们稍后将定义的两个方法:

```java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_check_box);
    this.isGreen = true;
    this.isBold = false;
    this.isBig = false;
    synchronizeCheckBoxes();
    synchronizeTextView();
    }

```

这些同步方法将用于使复选框和`TextView`分别匹配活动内部状态的逻辑分开。对于具有许多互连的用户界面小部件的活动，以这种方式组织它们的交互可以使项目更易于维护。`synchronizeCheckBoxes()`方法获取每个复选框的标识，并使用`CheckBox``setChecked()`方法使它们匹配相应的属性:

```java
    private void synchronizeCheckBoxes() {
    CheckBox green = (CheckBox) findViewById(R.id.checkBoxGreen);
    CheckBox bold = (CheckBox) findViewById(R.id.checkBoxBold);
    CheckBox big = (CheckBox) findViewById(R.id.checkBoxBig);
    green.setChecked(this.isGreen);
    bold.setChecked(this.isBold);
    big.setChecked(this.isBig);
    }

```

`synchronizeTextView()`方法使用私有实例变量来切换文本视图的外观:

```java
    private void synchronizeTextView() {
    TextView text = (TextView) findViewById(R.id.checkBoxText);
    if (this.isGreen) {
    text.setTextColor(Color.parseColor("#FF009900"));
    } else {
    text.setTextColor(Color.parseColor("#FF000000"));
    }
    if (this.isBold) {
    text.setTypeface(Typeface.create("default", Typeface.BOLD));
    } else {
    text.setTypeface(Typeface.create("default", Typeface.NORMAL));
    }
    if (this.isBig) {
    text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30);
    } else {
    text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
    }
    }

```

最后，我们有复选框的点击方法，它使用被点击的复选框的标识和值来更新内部状态:

```java
    public void checkBoxClicked(View view) {
    CheckBox checkbox = (CheckBox) view;
    boolean isChecked = checkbox.isChecked();
    switch (view.getId()) {
    case R.id.checkBoxGreen:
    this.isGreen = isChecked;
    break;
    case R.id.checkBoxBold:
    this.isBold = isChecked;
    break;
    case R.id.checkBoxBig:
    this.isBig = isChecked;
    break;
    }
    synchronizeTextView();
    }

```

这三种方法提供了非常容易维护的清晰数据流，即使对于较大的活动也是如此:同步方法使视图与活动的内部状态相匹配，`checkBoxClicked()`收集用户输入以改变该状态。

## 单选按钮

从 UI 的角度来看，一组单选按钮就像一组复选框；但是，一次只允许选择一个项目。从开发人员的角度来看，这种行为使得他们的 API 不同于复选框。您不必单独管理每个项目，而是必须将单选按钮封装在一个组中，这样系统就知道一次只能选择其中一个。

按钮由 [RadioButton](http://developer.android.com/reference/android/widget/RadioButton.html) 类表示，您可以使用 [RadioGroup](http://developer.android.com/reference/android/widget/RadioGroup.html) 类对它们进行分组。与按钮和复选框一样，当用户进行选择时，可以使用按钮上的`android:onClick`属性调用方法。

![](img/image055.jpg)

图 54:使用单选按钮设置文本视图的字体

本章示例项目中的`activity_radio_button.xml`布局和`RadioButtonActivity.java`类提供了单选按钮和单选按钮组的简单演示。它使用三个单选按钮让用户选择`TextView`的字体。上一节的示例让用户切换几个独立的属性，`sans serif`、`serif`和`monospace`字体值是互斥的，因此单选按钮是呈现这些选项的合适选择。

无线电的 XML 是一个由`<RadioGroup>`元素包围的`<RadioButton>`元素列表。除了确保只有一个项目是选择之外，`RadioGroup`的工作是以水平或垂直格式排列单选按钮。它实际上是`LinearLayout`的子类，所以您可以使用相同的`android:orientation`属性来设置单选按钮的方向。每个包含的`<RadioButton>`元素本质上都与我们一直在使用的按钮和复选框相同:

```java
    <RadioGroup
    android:id="@+id/radioGroup"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <RadioButton
    android:id="@+id/radioButtonSans"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Sans Serif"
    android:onClick="radioButtonClicked"/>
    <RadioButton
    android:id="@+id/radioButtonSerif"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Serif"
    android:onClick="radioButtonClicked"/>
    <RadioButton
    android:id="@+id/radioButtonMonospace"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Monospace"
    android:onClick="radioButtonClicked"/>
    </RadioGroup>

```

在`RadioButtonActivity.java`中，你会在`onCreate()`方法中找到一些代码来设置初始选择，以及一个`radioButtonClicked()`方法来在用户改变选择时更新`TextView`。我们没有像上一节那样费心抽象出太多的功能，因为只有一个属性被修改了:

```java
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_radio_button);

    // Set the initial selection
    RadioButton serif = (RadioButton) findViewById(R.id.radioButtonSerif);
    serif.setChecked(true);
    radioButtonClicked(null);
    }

    public void radioButtonClicked(View view) {
    // Use the radio group to find the selected button
    RadioGroup group = (RadioGroup) findViewById(R.id.radioGroup);
    String typeface;
    switch (group.getCheckedRadioButtonId()) {
    case R.id.radioButtonSans:
    typeface = "sans";
    break;
    case R.id.radioButtonSerif:
    typeface = "serif";
    break;
    case R.id.radioButtonMonospace:
    typeface = "monospace";
    break;
    default:
    typeface = "default";
    }
    // Update the TextView accordingly
    TextView text = (TextView) findViewById(R.id.radioButtonText);
    text.setTypeface(Typeface.create(typeface, Typeface.NORMAL));
    }

```

这里唯一的新方法是`RadioGroup`的`getCheckedRadioButtonId()`，它返回选中的`radio button`的 ID。这让你不用查询每个按钮就能知道选择了哪个项目(注意`radioButtonClicked()`完全不需要使用视图参数)。`Radio button` s 可以使用与复选框相同的`setChecked()`方法以编程方式选择，我们使用该方法设置初始选择。如果确实需要单独检查每个单选按钮，可以使用相应的`getChecked()`方法。

另外值得注意的是，可以用`RadioGroup`的`clearCheck()`方法清除单选按钮选择。

## 喷丝头

微调器是下拉菜单，允许用户从一组选项中选择一个项目。它们提供了与单选按钮类似的功能，但它们在屏幕上占用的空间更少，并且更容易看到选定的项目。由于这些原因，如果您为单个字段提供了四个或五个以上的选项，建议使用微调器而不是单选按钮。

虽然它们可能提供与单选按钮类似的功能，但是微调器需要完全不同的应用编程接口。他们的项目使用[适配器](http://developer.android.com/reference/android/widget/Adapter.html)填充，这使得使用它们更像使用列表视图和网格视图，而不是单选按钮或复选框。

![](img/image056.jpg)

图 55:使用微调器改变文本视图的颜色

在本节中，我们将学习如何创建一个[微调器](http://developer.android.com/reference/android/widget/Spinner.html)，用[数组编辑器](http://developer.android.com/reference/android/widget/ArrayAdapter.html)填充它，并用[OnemSelectedListener](http://developer.android.com/reference/android/widget/AdapterView.OnItemSelectedListener.html)处理用户输入。这几乎就是本书前面我们用来配置列表视图和网格视图的模式。`activity_spinner.xml`布局文件和`SpinnerActivity.java`类演示了如何用`Spinner`设置`TextView`的颜色。

由于微调器需要以编程方式填充，因此向布局中添加微调器的 XML 非常简单:

```java
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".SpinnerActivity" >

    <TextView
    android:id="@+id/spinnerText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginRight="10dp"
    android:text="Hello, World!"
    android:textSize="18sp" />

    <Spinner
    android:id="@+id/colorSpinner"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />

    </LinearLayout>

```

`SpinnerActivity.java`显示如何填充这个微调器。首先，我们创建一个`ArrayList`来表示选项:

```java
    ArrayList<String> colors = new ArrayList<String>();
    colors.add("Red");
    colors.add("Orange");
    colors.add("Yellow");
    colors.add("Green");
    colors.add("Blue");
    colors.add("Violet");

```

然后，我们用这个`ArrayList`创建一个`ArrayAdapter`。请记住，在我们的列表视图工作中，`ArrayAdapter`将数据项转换为`View`对象，供微调器显示。安卓提供了一个内置的微调项目资源，可通过`android.R.simple_spinner_item`访问。但是，由于微调器是下拉小部件，我们还需要设置适配器的`dropDownViewResource`属性。内置的`android.R.simple_spinner_dropdown_item`是用于此目的的首选资源:

```java
    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
    android.R.layout.simple_spinner_item,
    colors);
    adapter.setDropDownViewResource(
    android.R.layout.simple_spinner_dropdown_item);

```

然后，我们可以将适配器给我们在布局文件中定义的`Spinner`:

```java
    Spinner spinner = (Spinner) findViewById(R.id.colorSpinner);
    spinner.setAdapter(adapter);

```

如果此时编译项目，您应该能够在微调器中看到选定的项目，并且能够点击它来查看下拉菜单。但是，为了让它真正*做*的事情，我们需要实现一个选择处理程序。重要的是，不要将微调器所需的[OnemSelectedListener](http://developer.android.com/reference/android/widget/AdapterView.OnItemSelectedListener.html)类与我们在上一章中与`ListView`和`GridView`一起使用的[OnemColliclistener](http://developer.android.com/reference/android/widget/AdapterView.OnItemClickListener.html)类混淆。前者有一个我们需要实现的额外方法(尽管它不一定需要做任何事情):

```java
    spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
    public void onItemSelected(AdapterView<?> parent,
    View v,
    int position,
    long id) {
    String selectedColor = (String) parent.getItemAtPosition(position);
    setTextColor(selectedColor);
    }

    public void onNothingSelected(AdapterView<?> parent) {
    // Called when the selection disappears
    }
    });

```

`onItemSelected()`方法是处理点击的地方。上面的代码使用适配器的`getItemAtPosition()`方法获取选定的`String`。然后，我们将其传递给一个名为`setTextColor()`的方法，如下所示:

```java
    private void setTextColor(String color) {
    String hexColor = "#FF000000";
    if (color.equals("Red")) {
    hexColor = "#FFAA0000";
    } else if (color.equals("Orange")) {
    hexColor = "#FFCC6600";
    } else if (color.equals("Yellow")) {
    hexColor = "#FFCCAA00";
    } else if (color.equals("Green")) {
    hexColor = "#FF00AA00";
    } else if (color.equals("Blue")) {
    hexColor = "#FF0000AA";
    } else if (color.equals("Violet")) {
    hexColor = "#FF6600AA";
    }

    TextView text = (TextView) findViewById(R.id.spinnerText);
    text.setTextColor(Color.parseColor(hexColor));
    }

```

这将采用微调器中显示的`String`值，将其转换为十六进制值，并相应地更新`TextView`的颜色。这就是让我们的纺纱机运转的全部要求。

如果您不喜欢以编程方式定义列表项，可以将它们放在一个 XML 资源文件中，并将其动态加载到适配器中。这是比在活动类中硬编码值更好的做法，因为它将所有文本值保存在 XML 文件中。由于很容易根据设备和用户的区域设置加载不同的资源文件，这使得将您的应用翻译成其他语言变得轻而易举

首先我们需要在`strings.xml`中定义一个字符串数组:

```java
    <string-array name="spinnerColors">
    <item>Red</item>
    <item>Orange</item>
    <item>Yellow</item>
    <item>Green</item>
    <item>Blue</item>
    <item>Violet</item>
    </string-array>

```

要将这些项目加载到微调器中，您所要做的就是用从资源文件创建的项目替换当前的`ArrayAdapter`:

```java
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
    R.array.spinnerColors,
    android.R.layout.simple_spinner_item);

```

`R.array.spinnerColors`是我们刚刚创建的字符串数组的 ID，静态的`ArrayAdapter.createFromResource()`方法为我们打理其他的一切。这将具有与硬编码`ArrayList`完全相同的效果。

## 日期/时间选择器

Android 提供了用于选择日期和时间的内置 UI 组件。通常，这是通过对话而不是直接在活动中完成的。[日期选择日志](http://developer.android.com/reference/android/app/DatePickerDialog.html)和[时间选择日志](http://developer.android.com/reference/android/app/TimePickerDialog.html)类提供了可重用的界面，确保用户选择有效的日期/时间。它们还确保了跨应用程序的一致用户界面。

![](img/image057.jpg)

图 56:用日期选择器对话框选择日期

本节解释从用户处收集日期或时间输入所需的最低要求。创建日期选择器需要三件事:

1.  一个[DatePickerdiolog](http://developer.android.com/reference/android/app/DatePickerDialog.html)对象定义了对话框的外观。
2.  一个[对话片段](http://developer.android.com/reference/android/app/DialogFragment.html)对象托管`DatePickerDialog`并管理对话生命周期。
3.  一个 [OnDateSetListener](http://developer.android.com/reference/android/app/DatePickerDialog.OnDateSetListener.html) 实现处理用户输入。

第一个组件是安卓平台提供的，所以我们需要做的就是实例化它。

`DialogFragment`是实际对话的轻量级包装器，它确保对话被正确打开/关闭，并且任何中断都被正确处理。我们所需要做的就是子类化`DialogFragment`来返回一个`DatePickerDialog`对象作为它的宿主对话框。[片段](http://developer.android.com/guide/components/fragments.html)将在下一章中介绍，但就目前而言，只要说它们是模块化 UI 组件就足够了。您可以将它们视为可重用的视图，可以嵌入到不同的活动或对话框中。

为了收集输入，我们需要实现`OnDateSetListener`接口，该接口定义了一个名为`onDateSet()`的方法，每当用户关闭对话框时都会调用该方法。因为您可能想要在主机`Activity`中处理输入，所以我们将在这里定义`onDateSet()`。

首先，让我们从示例的 XML 开始，它只是一个按钮，允许用户选择日期:

```java
    <Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:text="Pick Date"
    android:onClick="showDatePickerDialog" />

```

`showDatePickerDialog()`方法是打开对话框的方法，但是在此之前，让我们先配置对话框本身。这通常是通过子类化`DialogFragment`并覆盖其`onCreateDialog()`方法来返回您想要显示的任何对话框来完成的。因此，我们需要创建一个名为`DatePickerFragment`的新类，它将如下所示:

```java
    import android.os.Bundle;
    import android.support.v4.app.DialogFragment;
    import android.app.Dialog;
    import android.app.DatePickerDialog;
    import java.util.Calendar;

    public class DatePickerFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Create and return the date picker dialog
    final Calendar c = Calendar.getInstance();
    int year = c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH);
    int day = c.get(Calendar.DAY_OF_MONTH);

    PickerActivity context = (PickerActivity) getActivity();
    return new DatePickerDialog(context, context, year, month, day);
    }

    }

```

首先，我们使用[日历](http://developer.android.com/reference/java/util/Calendar.html)类来获取当前日期，然后我们提取年、月和日的组成部分来提供给日期选取器。然后，我们需要做的就是实例化`DatePickerDialog`。第一个参数应该是主机`Activity`，第二个参数是监听器对象，应该实现`OnDateSetListener`接口。因为我们希望宿主活动也是侦听器，所以我们将它用作第二个参数(注意，这将需要更改`PickerActivity`类声明)。

现在，我们可以在`PickerActivity.java`中定义`showDatePickerDialog()`方法来显示这个对话框。打开选取器需要创建承载日期选取器(`DatePickerFragment`)的`DialogFragment`对象，然后调用其`show()`方法，如下所示:

```java
    public void showDatePickerDialog(View view) {
    DialogFragment picker = new DatePickerFragment();
    picker.show(getSupportFragmentManager(), "datePicker");
    }

```

`getSupportFragmentManager()`方法是一种向后兼容的显示片段的方式(片段是在安卓 3.0 中添加的，但可以使用`getSupportFragmentManager()`支持回到安卓 1.6)。该方法在[片段活动](http://developer.android.com/reference/android/support/v4/app/FragmentActivity.html)中定义，这意味着`PickerActivity`必须子类化该方法，而不是通常的`Activity`。请记住`PickerActivity`也被用作侦听器对象，因此它的类声明应该如下所示:

```java
    public class PickerActivity extends FragmentActivity
    implements DatePickerDialog.OnDateSetListener

```

最后，要处理选择的日期，我们需要在`PickerActivity.java`中定义`onDateSet()`。在这种情况下，我们将只在文本字段中显示它:

```java
    @Override
    public void onDateSet(DatePicker view, int year, int month, int day) {
    // Process the selected date (month is zero-indexed)
    TextView text = (TextView) findViewById(R.id.pickerText);
    String message = String.format("Selected date: %d/%d/%d",
    month+1, day, year);
    text.setText(message);
    }

```

请注意，`month`参数总是零索引的，以便与`Calendar`类兼容。

## 总结

本章简要介绍了创建安卓用户界面的一些最重要的小部件。我们学习了如何使用图像视图、按钮、文本字段、复选框、单选按钮、微调器和日期选择器来显示和收集来自用户的信息。还有一些其他有用的 UI 组件我们没有讨论，包括[切换按钮](http://developer.android.com/guide/topics/ui/controls/togglebutton.html)和[动作栏](http://developer.android.com/guide/topics/ui/actionbar.html)，但是我们会把这些留给你自己去探索。

到目前为止，你应该有能力创建自己的多屏安卓应用程序，并构建几种不同类型的布局。在下一节中，我们将研究用户界面开发的一个更高级的方面，称为片段。片段是模块化框架的一部分，用于在几个不同的活动中重用行为和视图。这将为你的安卓应用打开几种新的导航可能性。*