为 Android 应用创建 XML 布局

1. 准备工作

在此 Codelab 中,您将为一个基本的小费计算器应用构建布局。在此 Codelab 结束时,该应用将拥有一个可以运行的界面,但它实际上还无法计算小费。再完成后面几个 Codelab,就可以让该应用正常使用并且看起来更加专业。

前提条件

  • 能够在 Android Studio 中基于模板创建 Android 应用并运行所创建的应用

学习内容

  • 如何在 Android 中阅读和编写 XML 布局
  • 如何为简易表单构建布局,以便接受用户输入的文本和选择的选项

构建的内容

  • Android 小费计算器应用的界面

所需条件

  • 一台安装了最新的稳定版 Android Studio 的计算机
  • 连接到互联网,以便访问 Android 开发者文档

2. 启动项目

查看 Google 上的小费计算器:https://www.google.com/search?q=tip+calculator

18da3c120daa0759.png

在此在线课程中,您将构建一个简化版的 Android 小费计算器应用。

开发者通常会采取以下开发流程:准备一个简化版的应用,并实现部分功能(即使它看起来不是很好),之后使其能够完全正常运行,并改进其外观。

在此 Codelab 结束时,您的小费计算器应用将如下所示:

bcc5260318477c14.png

您将使用 Android 提供的以下界面元素:

  • EditText - 用于输入和修改文本
  • TextView - 用于显示服务问题和小费金额等文本
  • RadioButton - 与每个小费选项对应的可选择的单选按钮
  • RadioGroup - 用于对单选按钮选项进行分组
  • Switch - 用于选择是否将小费向上取整的切换开关

创建 Empty Activity 项目

  1. 首先,在 Android Studio 中使用 Empty Activity 模板创建一个新的 Kotlin 项目。
  2. 将该应用命名为“Tip Time”,并将最低 API 级别设置为 19 (KitKat)。软件包名称为 com.example.tiptime

4f7619e9faff20e9.png

  1. 点击 Finish 以创建应用。

3. 阅读和理解 XML

您将通过修改描述界面的 XML 来构建应用布局,而不是使用您已经熟悉的布局编辑器。您作为 Android 开发者,一定要学习如何使用 XML 来理解和修改界面布局。

您将查看并修改用于为此应用定义界面布局的 XML 文件。XML 表示“可扩展标记语言”,这是一种使用基于文本的文档描述数据的方式。由于 XML 可扩展且非常灵活,因此它有很多不同的用途,包括定义 Android 应用的界面布局。您可以回顾一下之前的 Codelab,其他资源(例如应用的字符串)也是在名为 strings.xml 的 XML 文件中定义的。

Android 应用的界面是以一种层层包含式层次结构的形式构建的,这种层次结构由组件 (widget) 和这些组件在屏幕上的布局构成。请注意,这些布局本身就是界面组件。

您需要描述屏幕上界面元素的视图层次结构。例如,ConstraintLayout(父级)可以包含 ButtonsTextViewsImageViews 或其他视图(子级)。请注意,ConstraintLayoutViewGroup 的子类。通过这种方式,您可以灵活地确定子视图的位置或大小。

74c7c563d18fffd4.png

Android 应用的层层包含式层次结构

32df120272b2331d.png

每个界面元素由 XML 文件中的一个 XML 元素表示。每个元素都以标记作为开头和结尾,而每个标记都以 < 开头,并以 > 结尾。正如您可以使用布局编辑器(设计视图)为界面元素设置属性一样,XML 元素也可以具有属性。简化过后,上述界面元素的 XML 可能会如下所示:

<ConstraintLayout>
    <TextView
        text="Hello World!">
    </TextView>
</ConstraintLayout>

8dea708333aebabe.png

我们来看一个实际示例。

  1. 打开 activity_main.xml (res > layout > activity_main.xml)。
  2. 您可能会注意到,正如您在之前通过此模板创建的项目中看到的一样,该应用在 ConstraintLayout 内显示了一个包含“Hello World!”的 TextView

4fbdb64c02d62e73.png

  1. 在布局编辑器的右上角,找到 CodeSplit 以及 Design 视图选项。
  2. 选择 Code 视图。

6203bec920791bcc.png

activity_main.xml 中的 XML 应如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

这与简化的示例相比要复杂得多,但 Android Studio 会执行一些操作来帮助确保 XML 更易于阅读,就像它针对 Kotlin 代码的处理方式一样。

  1. 注意缩进。Android Studio 会自动执行此操作,以显示元素的层次结构。TextView 之所以缩进显示,是因为它包含在 ConstraintLayout 中。ConstraintLayout 是父级,而 TextView 是子级。每个元素的属性都会缩进显示,以表示它们属于该元素。
  2. 注意颜色标识 - 有些代码以蓝色显示,有些以绿色显示,等等。该文件的类似部分会以相同的颜色绘制,以帮助您将它们匹配起来。需要特别注意的是,Android Studio 会以相同颜色绘制元素标记的开头和结尾。(注意:Codelab 中使用的颜色可能与您在 Android Studio 中看到的颜色不一致。)

XML 标记、元素和属性

以下是 TextView 元素的简化版本,方便您查看一些重要部分:

<TextView
    android:text="Hello World!"
/>

包含 <TextView 的代码行是该标记的开头,而包含 /> 的代码行是该标记的结尾。包含 android:text="Hello World!" 的代码行是该标记的一个属性。它表示将由 TextView 显示的文本。这 3 行代码是一种常用的简写形式,称为“空元素标记”。这与您按如下所示使用单独的开始标记和结束标记编写的代码意义相同:

<TextView
    android:text="Hello World!"
></TextView>

通常,我们会尽量减少空元素标记的代码行数,并将该标记的结尾与它前面的代码行相结合。因此,您可能会在两行代码(或者,如果没有属性,甚至会是一行)中看到一个空元素标记。

<!-- with attributes, two lines -->
<TextView
    android:text="Hello World!" />

ConstraintLayout 元素是使用单独的开始标记和结束标记编写的,因为它需要能在内部存储其他元素。下面是包含 TextView 元素的 ConstraintLayout 元素的简化版本:

<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:text="Hello World!" />
</androidx.constraintlayout.widget.ConstraintLayout>

如果您想要再添加一个 View 作为 ConstraintLayout 的子级(例如 TextView 下面的 Button),则可将其置于 TextView 标记 /> 之后和 ConstraintLayout 的结束标记之前,如下所示:

<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:text="Hello World!" />
    <Button
        android:text="Calculate" />
</androidx.constraintlayout.widget.ConstraintLayout>

有关用于创建布局的 XML 的更多信息

  1. 查看 ConstraintLayout 的标记,您会注意到它显示的是 androidx.constraintlayout.widget.ConstraintLayout,而不是像 TextView 一样仅显示 ConstraintLayout。这是因为 ConstraintLayout 属于 Android Jetpack,而后者包含会在 Android 核心平台之上提供其他功能的代码库。Jetpack 包含诸多实用功能,您可以借此更加轻松地构建应用。您会发现此界面组件是 Jetpack 的一部分,因为它以“androidx”开头。
  2. 您可能已经注意到以 xmlns: 开头,后跟 androidapptools 的代码行。
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"

xmlns 表示 XML 命名空间,并且每行代码都定义了一个架构,或者与这些字词相关的属性的词汇。例如,android: 命名空间标记了由 Android 系统定义的属性。布局 XML 中的所有属性均以其中一个命名空间开头。

  1. 在 XML 元素之间增加空白字符并不会改变其对计算机的含义,但这有助于用户更加轻松地阅读 XML。

Android Studio 会自动添加一些空白字符并进行缩进,以提升易读性。您稍后将了解如何使 Android Studio 确保您的 XML 遵循编码样式规范。

  1. 您可以像为 Kotlin 代码添加注释一样为 XML 添加注释。注释应以 <!-- 开头,以 --> 结束。
<!-- this is a comment in XML -->

<!-- this is a
multi-line
Comment.
And another
Multi-line comment -->
  1. 请注意文件的第一行:
<?xml version="1.0" encoding="utf-8"?>

这表示该文件是一个 XML 文件,但并非每个 XML 文件都包含此行代码。

4. 使用 XML 构建布局

  1. 还是在 activity_main.xml 中,切换到 Split 屏幕视图,以查看 Design Editor 旁边的 XML。在 Design Editor 中,您可以预览界面布局。

a03bcf5beacb4b45.png

  1. 您可以根据个人偏好来决定使用哪种视图,但对于此 Codelab,请使用 Split 视图,这样您既可以查看自己修改的 XML,也可以在 Design Editor 中看到这些修改造成的变化。
  2. 尝试点击不同的代码行,比如先点击 ConstraintLayout 下的某行代码,然后再点击 TextView 下的某行代码,您会注意到 Design Editor 中的相应视图会被选中。反之亦然,例如,如果您在 Design Editor 中点击 TextView,系统会突出显示相应的 XML。

1abc54a646c39f66.png

删除 TextView

  1. 您现在不需要 TextView,因此请将其删除。请务必删除从 <TextView 到结束标记 /> 之间的所有内容。
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

文件中剩余的所有内容便是 ConstraintLayout

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</androidx.constraintlayout.widget.ConstraintLayout>
  1. ConstraintLayout 添加 16dp 的内边距,避免界面被挤到屏幕的边缘。

内边距与外边距类似,但它会向 ConstraintLayout 的内侧增加空间,而不是在外侧增加空间。

<androidx.constraintlayout.widget.ConstraintLayout
    ...
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

添加“Cost of Service”文本字段

在这一步中,您将添加界面元素,以便用户在应用中输入服务费用。您将使用 EditText 元素,该元素使用户能够在应用中输入或修改文本。

7746dedb0d79923f.png

  1. 查看 EditText 文档,并检查示例 XML。
  2. 找到 ConstraintLayout 的起始标记和结束标记之间的空白区域。
  3. 从文档中复制 XML,并将其粘贴到您在 Android Studio 中的布局内的相应空白区域。

此时,您的布局文件应如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/plain_text_input"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:inputType="text"/>

</androidx.constraintlayout.widget.ConstraintLayout>

您可能还不完全理解上述代码,我们会在以下步骤中进行介绍。

  1. 请注意,EditText 带有红色下划线。
  2. 将鼠标指针悬停在此处,您会看到系统显示“view is not constrained”(视图未进行约束)错误,这与您在之前的 Codelab 中看到的类似。前面已经提到,ConstraintLayout 的子级需要进行约束,以便布局知道如何整理它们。

40c17058bd6786f.png

  1. 将这些约束条件添加到 EditText,以将其锚定到父级的左上角。
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

如果您使用英语或其他从左到右 (LTR) 书写的语言编写代码,起始边缘位于左侧。但某些语言(例如阿拉伯语)是从右到左 (RTL) 书写的,因此起始边缘位于右侧。这就是约束条件使用“start”的原因,这样它才能与 LTR 或 RTL 语言搭配使用。同样,约束条件使用“end”,而不是“right”。

添加新的约束条件后,EditText 元素将如下所示:

<EditText
    android:id="@+id/plain_text_input"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:inputType="text"/>

检查 EditText 属性

请仔细检查您粘贴进来的所有 EditText 属性,确保它能够在应用中有效运行。

  1. 找到设置为 @+id/plain_text_inputid 属性。
  2. id 属性更改为更合适的名称 @+id/cost_of_service
  1. 查看 layout_height 属性。此属性设置为 wrap_content,这意味着它将与其中的内容一样高。此项设置没有问题,因为这里只有 1 行文本。
  2. 查看 layout_width 属性。此属性设置为 match_parent,但您不能针对 ConstraintLayout 的子级设置 match_parent。此外,文本字段不需要那么宽。将其设置为 160dp 这一固定宽度,这应该足够用户输入服务费用了。

1f82a5e86ae94fd2.png

  1. 注意 inputType 属性,它是一项新的属性。该属性的值为 "text",这意味着用户可以在屏幕上的字段中输入任意文本字符(字母字符、符号等)。
android:inputType="text"

但是,您需要用户在 EditText 中仅输入数字,因为该字段表示货币价值。

  1. 清除 text 这个单词,但保留英文引号。
  2. 开始在该位置输入 number。输入“n”后,Android Studio 会显示包含“n”的一系列可能的补全项。

99b04cbd21e74693.gif

  1. 选择 numberDecimal,这会将输入内容限制为带小数点的数字。
android:inputType="numberDecimal"

如需查看其他输入类型选项,请参阅开发者文档中的指定输入法类型

还需要进行另外一项更改,因为显示一些提示有助于用户确定应该在这个字段中输入什么内容。

  1. hint 属性添加到 EditText 中,用于描述用户应在此字段中输入什么内容。
android:hint="Cost of Service"

您会看到 Design Editor 也相应进行了更新。

824454d2a316efb1.png

  1. 在模拟器中运行应用。显示的内容应如下所示:

c9d413de53b0853d.png

非常棒!它的作用还不是很大,但是您已经有一个良好的开端,并且对 XML 进行了一些修改。您用于创建布局的 XML 应如下所示。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:hint="Cost of Service"
        android:inputType="numberDecimal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

添加服务问题

在这一步中,您将为问题提示“How was the service?”添加一个 TextView。尝试输入此内容,而不是复制/粘贴。Android Studio 提供的建议可能会对您有所帮助。

  1. EditText 标记的结尾 /> 后,新增一行代码并开始输入 <TextView
  2. 从建议中选择 TextView,然后 Android Studio 将自动为 TextView 添加 layout_widthlayout_height 属性。
  3. 对于这两种属性,都选择 wrap_content,因为您只需要 TextView 与其中的文本内容大小一致。fee18cc43df85294.png
  4. 添加 text 属性,将其设置为 "How was the service?"
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="How was the service?"
  1. /> 结束标记。
  2. 请注意,在 Design Editor 中,TextViewEditText 重叠了。

ac09d5cae6ae2455.png

这样看起来不对,因此您接下来需要针对 TextView 添加约束条件。思考您需要哪些约束条件。TextView 在水平和垂直方向上应该位于何处?您可以通过查看应用屏幕截图来获取帮助。

bcc5260318477c14.png

在垂直方向上,您需要 TextView 低于“Cost of Service”文本字段。在水平方向上,您需要 TextView 与父视图的起始边缘对齐。

  1. TextView 添加水平约束条件,将它的起始边缘约束为父视图的起始边缘。
app:layout_constraintStart_toStartOf="parent"
  1. TextView 添加垂直约束条件,将 TextView 的顶部边缘约束为服务费用 View 的底部边缘。
app:layout_constraintTop_toBottomOf="@id/cost_of_service"

请注意,@id/cost_of_service 中没有半角加号,因为相应 ID 已定义。

3822136f7ed815f2.png

这个应用看起来并不完美,但目前先别担心。您只需确保所有必要的组件都显示在屏幕上,并且功能正常即可。您将在后续 Codelab 中修改其外观。

  1. TextView 上添加资源 ID。稍后,当您添加更多视图,并使其互相约束时,会需要引用此视图。
android:id="@+id/service_question"

此时,您的 XML 应如下所示。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:hint="Cost of Service"
        android:layout_height="wrap_content"
        android:layout_width="160dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:inputType="numberDecimal"/>

    <TextView
        android:id="@+id/service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="How was the service?"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service"/>

</androidx.constraintlayout.widget.ConstraintLayout>

5. 添加小费选项

接下来,您将添加单选按钮,供用户选择不同的小费选项。

应提供以下三种选项:

  • 太棒了 (20%)
  • 很好 (18%)
  • 不错 (15%)

如果您不确定如何执行此操作,可以在 Google 上搜索。开发者在遇到困难时可以使用这个功能强大的工具。

  1. 在 Google 上搜索 radio button android。排名最靠前的搜索结果来自 Android 开发者网站,提供了一个有关如何使用单选按钮的指南,太棒了!

f5f1c6778ae7a5d.png

  1. 浏览单选按钮指南

通过阅读说明,您能够确定您可以在布局中针对自己需要的每个单选按钮使用一个 RadioButton 界面元素。此外,您还需要对 RadioGroup 中的单选按钮进行分组,因为一次只能选择一个选项。

有一些 XML 看起来似乎符合您的需求。请仔细阅读,了解 RadioGroup 如何成为父视图,而 RadioButtons 成为其中的子视图。

  1. 返回您在 Android Studio 中的布局,将 RadioGroupRadioButton 添加到应用中。
  2. TextView 元素之后(但仍在 ConstraintLayout 内),开始输入 <RadioGroup。Android Studio 会提供一些实用建议来帮助您完成对 XML 的修改。aee75ba409dc51aa.png
  3. RadioGrouplayout_widthlayout_height 设置为 wrap_content
  4. 添加一个资源 ID,并将其设置为 @+id/tip_options
  5. > 结束开始标记。
  6. Android Studio 将会添加 </RadioGroup>。与 ConstraintLayout 一样,RadioGroup 元素中也会包含其他元素,因此您可能需要将其移动到单独的一行。正在将“layout_width”和“layout_height”都设为“wrap_content”
  7. RadioGroup 约束为位于服务问题的下方(在垂直方向),以及父视图的起始位置(在水平方向)。
  8. android:orientation 属性设置为 vertical。如果您要在某行添加 RadioButtons,则应将屏幕方向设置为 horizontal

RadioGroup 的 XML 应如下所示:

<RadioGroup
    android:id="@+id/tip_options"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/service_question">

</RadioGroup>

添加 RadioButton

  1. RadioGroup 的最后一个属性的后面,但在 </RadioGroup> 结束标记的前面,添加一个 RadioButton
<RadioGroup
    android:id="@+id/tip_options"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@id/service_question">

    <!-- add RadioButtons here -->

</RadioGroup>
  1. layout_widthlayout_height 设置为 wrap_content
  2. RadioButton 分配资源 ID @+id/option_twenty_percent
  3. 将文本设置为 Amazing (20%)
  4. /> 结束标记。
<RadioGroup
   android:id="@+id/tip_options"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:layout_constraintTop_toBottomOf="@id/service_question"
   app:layout_constraintStart_toStartOf="parent"
   android:orientation="vertical">

   <RadioButton
       android:id="@+id/option_twenty_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Amazing (20%)" />

</RadioGroup>

53cb416b368e9612.png

现在,您已经添加了一个 RadioButton,接下来,您可以修改 XML,为 Good (18%)Okay (15%) 选项再添加 2 个单选按钮吗?

RadioGroupRadioButtons 的 XML 应如下所示:

<RadioGroup
   android:id="@+id/tip_options"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:layout_constraintTop_toBottomOf="@id/service_question"
   app:layout_constraintStart_toStartOf="parent"
   android:orientation="vertical">

   <RadioButton
       android:id="@+id/option_twenty_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Amazing (20%)" />

   <RadioButton
       android:id="@+id/option_eighteen_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Good (18%)" />

   <RadioButton
       android:id="@+id/option_fifteen_percent"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Okay (15%)" />

</RadioGroup>

bab25b6a35d4ce52.png

添加默认选择

目前,没有任何小费选项处于选中状态。最好默认选择一个单选按钮选项。

RadioGroup 上有一个属性,通过该属性,您可以指定最初选择的应该是哪个按钮。该属性称为 checkedButton,您需要将其设置为您想要选择的那个单选按钮的资源 ID。

  1. RadioGroup 中,将 android:checkedButton 属性设置为 @id/option_twenty_percent
<RadioGroup
   android:id="@+id/tip_options"
   android:checkedButton="@id/option_twenty_percent"
   ...

请注意,在 Design Editor 中,布局已相应更新。系统默认选中了 20% 的小费选项,真棒!现在,它开始看起来有点像小费计算器了!

c412e7f16590cd33.png

XML 目前应该会如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:hint="Cost of Service"
        android:layout_height="wrap_content"
        android:layout_width="160dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:inputType="numberDecimal"/>

    <TextView
        android:id="@+id/service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="How was the service?"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service"
        app:layout_constraintStart_toStartOf="parent" />

    <RadioGroup
        android:id="@+id/tip_options"
        android:checkedButton="@id/option_twenty_percent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/service_question"
        app:layout_constraintStart_toStartOf="parent"
        android:orientation="vertical">

        <RadioButton
            android:id="@+id/option_twenty_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Amazing (20%)" />

        <RadioButton
            android:id="@+id/option_eighteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Good (18%)" />

        <RadioButton
            android:id="@+id/option_fifteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Okay (15%)" />
    </RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

6. 完成布局的其余部分

您现在需要处理布局的最后一部分。您需要添加一个 Switch、一个 Button 和一个 TextView,用于显示小费金额。

bcc5260318477c14.png

添加开关以将小费向上取整

接下来,您需要使用 Switch 微件,让用户能够选择是否将小费向上取整。

您希望 Switch 与父级一样宽,因此不妨考虑将宽度设置为 match_parent。如前所述,您无法针对 ConstraintLayout 中的界面元素设置 match_parent。相反,您需要约束该视图的起始和结束边缘,并将宽度设置为 0dp。如果将宽度设置为 0dp,就表示告知系统不计算宽度,只尝试匹配针对视图设置的约束条件即可。

  1. RadioGroup 的 XML 之后添加一个 Switch 元素。
  2. 如上所述,将 layout_width 设置为 0dp
  3. layout_height 设置为 wrap_content。这会使 Switch 视图与其中的内容一样高。
  4. id 属性设置为 @+id/round_up_switch
  5. text 属性设置为 Round up tip?。这将用作 Switch 的标签。
  6. Switch 的起始边缘约束为 tip_options 的起始边缘,并将其结束边缘约束为父级的结束边缘。
  7. Switch 的顶部约束为 tip_options 的底部。
  8. /> 结束标记。

最好能使该开关默认处于开启状态,这时就要用到 android:checked 这个属性,它可能的值为 true(开启)或 false(关闭)。

  1. android:checked 属性设置为 true

综上所述,Switch 元素的 XML 应如下所示:

<Switch
    android:id="@+id/round_up_switch"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:checked="true"
    android:text="Round up tip?"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="@id/tip_options"
    app:layout_constraintTop_toBottomOf="@id/tip_options" />

d374fab984650296.png

添加“Calculate”按钮

接下来,您需要添加一个 Button,以便用户能够请求计算小费。您希望按钮与父级一样宽,以便其水平约束条件和宽度与 Switch 的一样。

  1. Switch 后添加一个 Button
  2. 按照为 Switch 设置宽度相同的方式将宽度设置为 0dp
  3. 将高度设置为 wrap_content
  4. 为其提供资源 ID @+id/calculate_button 以及文本 "Calculate"
  5. Button 的顶部边缘约束为 Round up tip? Switch 的底部边缘。
  6. 将起始边缘约束为父级的起始边缘,并将结束边缘约束为父级的结束边缘。
  7. /> 结束标记。

Calculate Button 的 XML 应如下所示:

<Button
   android:id="@+id/calculate_button"
   android:layout_width="0dp"
   android:layout_height="wrap_content"
   android:text="Calculate"
   app:layout_constraintTop_toBottomOf="@id/round_up_switch"
   app:layout_constraintStart_toStartOf="parent"
   app:layout_constraintEnd_toEndOf="parent" />

5338cf87c61d15c9.png

添加小费计算结果

您的布局马上就要完成了!在这一步中,您需要为小费计算结果添加一个 TextView,将其置于 Calculate 按钮下,并使其与结束边缘对齐,而不是像其他界面元素一样与起始边缘对齐。

  1. 添加一个资源 ID 名为 tip_result 且文本为 Tip AmountTextView
  2. TextView 的结束边缘约束为父级的结束边缘。
  3. 将顶部边缘约束为 Calculate 按钮的底部边缘。
<TextView
    android:id="@+id/tip_result"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintTop_toBottomOf="@id/calculate_button"
    android:text="Tip Amount" />

9644bcdabbd8d7d1.png

  1. 运行应用。显示内容应如下面的屏幕截图所示。

e4ed552fa9fbe4ce.png

太棒了!如果这是您首次使用 XML,简直棒极了!

请注意,该应用可能看起来与屏幕截图不完全相同,因为模板可能在 Android Studio 的后期版本中发生了变化。Calculate 按钮目前尚不会执行任何操作,但您可以输入费用、选择小费百分比,然后切换选项以选择是否将小费向上取整。在下一个 Codelab 中,您将执行操作,让 Calculate 按钮正常运行,因此,一定要回过头来回顾这些内容!

7. 采用规范的编码做法

提取字符串

您可能已经注意到有关硬编码字符串的警告。回顾之前的 Codelab 可知,通过将字符串提取到资源文件中,可更加轻松地将您的应用翻译成其他语言,还可以重复使用字符串。浏览 activity_main.xml 并提取所有字符串资源。

  1. 点击某个字符串;将鼠标悬停在出现的黄色灯泡图标上,然后点击它旁边的三角形;选择 Extract String Resource。字符串资源的默认名称没有问题。如果需要,对于小费选项,您可以使用 amazing_servicegood_serviceok_service 使名称更具描述性。

现在,验证您刚刚添加的字符串资源。

  1. 如果未显示 Project 窗口,请点击窗口左侧的 Project 标签页。
  2. 依次打开 app > res > values > strings.xml,以查看所有的界面字符串资源。
<resources>
    <string name="app_name">Tip Time</string>
    <string name="cost_of_service">Cost of Service</string>
    <string name="how_was_the_service">How was the service?</string>
    <string name="amazing_service">Amazing (20%)</string>
    <string name="good_service">Good (18%)</string>
    <string name="ok_service">Okay (15%)</string>
    <string name="round_up_tip">Round up tip?</string>
    <string name="calculate">Calculate</string>
    <string name="tip_amount">Tip Amount</string>
</resources>

重新设置 XML 的格式

Android Studio 提供了各种工具来整理代码,并确保其遵循建议的编码规范。

  1. activity_main.xml 中,依次选择 Edit > Select All
  2. 依次选择 Code > Reformat Code

这样可以确保缩进量是一致的,并且它可能会对界面元素的某些 XML 重新排序,以便进行分组(例如,将某个元素的所有 android: 属性放到一起)。

8. 解决方案代码

bcc5260318477c14.png

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/cost_of_service"
        android:layout_width="160dp"
        android:layout_height="wrap_content"
        android:hint="@string/cost_of_service"
        android:inputType="numberDecimal"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/service_question"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/how_was_the_service"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/cost_of_service" />

    <RadioGroup
        android:id="@+id/tip_options"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checkedButton="@id/option_twenty_percent"
        android:orientation="vertical"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/service_question">

        <RadioButton
            android:id="@+id/option_twenty_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/amazing_service" />

        <RadioButton
            android:id="@+id/option_eighteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/good_service" />

        <RadioButton
            android:id="@+id/option_fifteen_percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/ok_service" />
    </RadioGroup>

    <Switch
        android:id="@+id/round_up_switch"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="@string/round_up_tip"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/tip_options"
        app:layout_constraintTop_toBottomOf="@id/tip_options" />

    <Button
        android:id="@+id/calculate_button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/calculate"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/round_up_switch" />

    <TextView
        android:id="@+id/tip_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tip_amount"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/calculate_button" />
</androidx.constraintlayout.widget.ConstraintLayout>

res/values/strings.xml

<resources>
   <string name="app_name">Tip Time</string>
   <string name="cost_of_service">Cost of Service</string>
   <string name="how_was_the_service">How was the service?</string>
   <string name="amazing_service">Amazing (20%)</string>
   <string name="good_service">Good (18%)</string>
   <string name="ok_service">Okay (15%)</string>
   <string name="round_up_tip">Round up tip?</string>
   <string name="calculate">Calculate</string>
   <string name="tip_amount">Tip Amount</string>
</resources>

9. 总结

  • XML(可扩展标记语言)是一种整理文本的方式,由标记、元素和属性组成。
  • 使用 XML 可定义 Android 应用的布局。
  • 使用 EditText 可支持用户输入或修改文本。
  • EditText 会生成一条提示,告知用户应在相应字段内输入什么内容。
  • 指定 android:inputType 属性可限制用户在 EditText 字段输入哪种类型的文本。
  • 使用 RadioButtons 可创建一个互斥选项列表,并使用 RadioGroup 进行分组。
  • RadioGroup 可以垂直排列,也可以水平排列,并且您可以指定最初选中的应该是哪个 RadioButton
  • 使用 Switch 可让用户在两个选项之间切换。
  • 您可以在不使用单独的 TextView 的情况下为 Switch 添加标签。
  • ConstraintLayout 的每个子级都必须具有垂直和水平约束条件。
  • 使用“start”和“end”约束条件可处理从左到右 (LTR) 和从右到左 (RTL) 书写的语言。
  • 约束条件属性的名称应遵循 layout_constraint<Source>_to<Target>Of 格式。
  • 如需将 View 设置为与它所在的 ConstraintLayout 一样宽,请将其起始边缘约束为父级的起始边缘,将结束边缘约束为父级的结束边缘,并将宽度设置为 0dp。

10. 了解更多内容

访问下列链接即可查看有关所涵盖主题的更多文档。您可以访问 developer.android.com,找到所有有关 Android 开发的文档。此外,如果遇到困难,别忘了可在 Google 上搜索。

11. 自行练习

请进行以下练习

  • 创建一个不同的计算器应用,例如烹饪计量单位转换器,用于在毫升和液量盎司之间来回转换、在克和杯之间来回转换,等等。您需要哪些字段?