1. 准备工作
在此 Codelab 中,您将为一个基本的小费计算器应用构建布局。在此 Codelab 结束时,该应用将拥有一个可以运行的界面,但它实际上还无法计算小费。再完成后面几个 Codelab,就可以让该应用正常使用并且看起来更加专业。
前提条件
- 能够在 Android Studio 中基于模板创建 Android 应用并运行所创建的应用
学习内容
- 如何在 Android 中阅读和编写 XML 布局
- 如何为简易表单构建布局,以便接受用户输入的文本和选择的选项
构建的内容
- Android 小费计算器应用的界面
所需条件
- 一台安装了最新的稳定版 Android Studio 的计算机
- 连接到互联网,以便访问 Android 开发者文档
2. 启动项目
查看 Google 上的小费计算器:https://www.google.com/search?q=tip+calculator
在此在线课程中,您将构建一个简化版的 Android 小费计算器应用。
开发者通常会采取以下开发流程:准备一个简化版的应用,并实现部分功能(即使它看起来不是很好),之后使其能够完全正常运行,并改进其外观。
在此 Codelab 结束时,您的小费计算器应用将如下所示:
您将使用 Android 提供的以下界面元素:
EditText
- 用于输入和修改文本TextView
- 用于显示服务问题和小费金额等文本RadioButton
- 与每个小费选项对应的可选择的单选按钮RadioGroup
- 用于对单选按钮选项进行分组Switch
- 用于选择是否将小费向上取整的切换开关
创建 Empty Activity 项目
- 首先,在 Android Studio 中使用 Empty Activity 模板创建一个新的 Kotlin 项目。
- 将该应用命名为“Tip Time”,并将最低 API 级别设置为 19 (KitKat)。软件包名称为 com.example.tiptime。
- 点击 Finish 以创建应用。
3. 阅读和理解 XML
您将通过修改描述界面的 XML 来构建应用布局,而不是使用您已经熟悉的布局编辑器。您作为 Android 开发者,一定要学习如何使用 XML 来理解和修改界面布局。
您将查看并修改用于为此应用定义界面布局的 XML 文件。XML 表示“可扩展标记语言”,这是一种使用基于文本的文档描述数据的方式。由于 XML 可扩展且非常灵活,因此它有很多不同的用途,包括定义 Android 应用的界面布局。您可以回顾一下之前的 Codelab,其他资源(例如应用的字符串)也是在名为 strings.xml
的 XML 文件中定义的。
Android 应用的界面是以一种层层包含式层次结构的形式构建的,这种层次结构由组件 (widget) 和这些组件在屏幕上的布局构成。请注意,这些布局本身就是界面组件。
您需要描述屏幕上界面元素的视图层次结构。例如,ConstraintLayout
(父级)可以包含 Buttons
、TextViews
、ImageViews
或其他视图(子级)。请注意,ConstraintLayout
是 ViewGroup
的子类。通过这种方式,您可以灵活地确定子视图的位置或大小。
Android 应用的层层包含式层次结构
每个界面元素由 XML 文件中的一个 XML 元素表示。每个元素都以标记作为开头和结尾,而每个标记都以 <
开头,并以 >
结尾。正如您可以使用布局编辑器(设计视图)为界面元素设置属性一样,XML 元素也可以具有属性。简化过后,上述界面元素的 XML 可能会如下所示:
<ConstraintLayout>
<TextView
text="Hello World!">
</TextView>
</ConstraintLayout>
我们来看一个实际示例。
- 打开
activity_main.xml
(res > layout > activity_main.xml)。 - 您可能会注意到,正如您在之前通过此模板创建的项目中看到的一样,该应用在
ConstraintLayout
内显示了一个包含“Hello World!”的TextView
。
- 在布局编辑器的右上角,找到 Code、Split 以及 Design 视图选项。
- 选择 Code 视图。
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 代码的处理方式一样。
- 注意缩进。Android Studio 会自动执行此操作,以显示元素的层次结构。
TextView
之所以缩进显示,是因为它包含在ConstraintLayout
中。ConstraintLayout
是父级,而TextView
是子级。每个元素的属性都会缩进显示,以表示它们属于该元素。 - 注意颜色标识 - 有些代码以蓝色显示,有些以绿色显示,等等。该文件的类似部分会以相同的颜色绘制,以帮助您将它们匹配起来。需要特别注意的是,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 的更多信息
- 查看
ConstraintLayout
的标记,您会注意到它显示的是androidx.constraintlayout.widget.ConstraintLayout
,而不是像TextView
一样仅显示ConstraintLayout
。这是因为ConstraintLayout
属于 Android Jetpack,而后者包含会在 Android 核心平台之上提供其他功能的代码库。Jetpack 包含诸多实用功能,您可以借此更加轻松地构建应用。您会发现此界面组件是 Jetpack 的一部分,因为它以“androidx”开头。 - 您可能已经注意到以
xmlns:
开头,后跟android
、app
和tools
的代码行。
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 中的所有属性均以其中一个命名空间开头。
- 在 XML 元素之间增加空白字符并不会改变其对计算机的含义,但这有助于用户更加轻松地阅读 XML。
Android Studio 会自动添加一些空白字符并进行缩进,以提升易读性。您稍后将了解如何使 Android Studio 确保您的 XML 遵循编码样式规范。
- 您可以像为 Kotlin 代码添加注释一样为 XML 添加注释。注释应以
<!--
开头,以-->
结束。
<!-- this is a comment in XML -->
<!-- this is a
multi-line
Comment.
And another
Multi-line comment -->
- 请注意文件的第一行:
<?xml version="1.0" encoding="utf-8"?>
这表示该文件是一个 XML 文件,但并非每个 XML 文件都包含此行代码。
4. 使用 XML 构建布局
- 还是在
activity_main.xml
中,切换到 Split 屏幕视图,以查看 Design Editor 旁边的 XML。在 Design Editor 中,您可以预览界面布局。
- 您可以根据个人偏好来决定使用哪种视图,但对于此 Codelab,请使用 Split 视图,这样您既可以查看自己修改的 XML,也可以在 Design Editor 中看到这些修改造成的变化。
- 尝试点击不同的代码行,比如先点击
ConstraintLayout
下的某行代码,然后再点击TextView
下的某行代码,您会注意到 Design Editor 中的相应视图会被选中。反之亦然,例如,如果您在 Design Editor 中点击TextView
,系统会突出显示相应的 XML。
删除 TextView
- 您现在不需要
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>
- 向
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
元素,该元素使用户能够在应用中输入或修改文本。
- 查看
EditText
文档,并检查示例 XML。 - 找到
ConstraintLayout
的起始标记和结束标记之间的空白区域。 - 从文档中复制 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>
您可能还不完全理解上述代码,我们会在以下步骤中进行介绍。
- 请注意,
EditText
带有红色下划线。 - 将鼠标指针悬停在此处,您会看到系统显示“view is not constrained”(视图未进行约束)错误,这与您在之前的 Codelab 中看到的类似。前面已经提到,
ConstraintLayout
的子级需要进行约束,以便布局知道如何整理它们。
- 将这些约束条件添加到
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
属性,确保它能够在应用中有效运行。
- 找到设置为
@+id/plain_text_input
的id
属性。 - 将
id
属性更改为更合适的名称@+id/cost_of_service
。
- 查看
layout_height
属性。此属性设置为wrap_content
,这意味着它将与其中的内容一样高。此项设置没有问题,因为这里只有 1 行文本。 - 查看
layout_width
属性。此属性设置为match_parent
,但您不能针对ConstraintLayout
的子级设置match_parent
。此外,文本字段不需要那么宽。将其设置为160dp
这一固定宽度,这应该足够用户输入服务费用了。
- 注意
inputType
属性,它是一项新的属性。该属性的值为"text"
,这意味着用户可以在屏幕上的字段中输入任意文本字符(字母字符、符号等)。
android:inputType="text"
但是,您需要用户在 EditText
中仅输入数字,因为该字段表示货币价值。
- 清除
text
这个单词,但保留英文引号。 - 开始在该位置输入
number
。输入“n”后,Android Studio 会显示包含“n”的一系列可能的补全项。
- 选择
numberDecimal
,这会将输入内容限制为带小数点的数字。
android:inputType="numberDecimal"
如需查看其他输入类型选项,请参阅开发者文档中的指定输入法类型。
还需要进行另外一项更改,因为显示一些提示有助于用户确定应该在这个字段中输入什么内容。
- 将
hint
属性添加到EditText
中,用于描述用户应在此字段中输入什么内容。
android:hint="Cost of Service"
您会看到 Design Editor 也相应进行了更新。
- 在模拟器中运行应用。显示的内容应如下所示:
非常棒!它的作用还不是很大,但是您已经有一个良好的开端,并且对 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 提供的建议可能会对您有所帮助。
- 在
EditText
标记的结尾/>
后,新增一行代码并开始输入<TextView
。 - 从建议中选择
TextView
,然后 Android Studio 将自动为TextView
添加layout_width
和layout_height
属性。 - 对于这两种属性,都选择
wrap_content
,因为您只需要TextView
与其中的文本内容大小一致。 - 添加
text
属性,将其设置为"How was the service?"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="How was the service?"
- 以
/>
结束标记。 - 请注意,在 Design Editor 中,
TextView
与EditText
重叠了。
这样看起来不对,因此您接下来需要针对 TextView
添加约束条件。思考您需要哪些约束条件。TextView
在水平和垂直方向上应该位于何处?您可以通过查看应用屏幕截图来获取帮助。
在垂直方向上,您需要 TextView
低于“Cost of Service”文本字段。在水平方向上,您需要 TextView
与父视图的起始边缘对齐。
- 为
TextView
添加水平约束条件,将它的起始边缘约束为父视图的起始边缘。
app:layout_constraintStart_toStartOf="parent"
- 为
TextView
添加垂直约束条件,将TextView
的顶部边缘约束为服务费用View
的底部边缘。
app:layout_constraintTop_toBottomOf="@id/cost_of_service"
请注意,@id/cost_of_service
中没有半角加号,因为相应 ID 已定义。
这个应用看起来并不完美,但目前先别担心。您只需确保所有必要的组件都显示在屏幕上,并且功能正常即可。您将在后续 Codelab 中修改其外观。
- 在
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 上搜索。开发者在遇到困难时可以使用这个功能强大的工具。
- 在 Google 上搜索
radio button android
。排名最靠前的搜索结果来自 Android 开发者网站,提供了一个有关如何使用单选按钮的指南,太棒了!
- 浏览单选按钮指南。
通过阅读说明,您能够确定您可以在布局中针对自己需要的每个单选按钮使用一个 RadioButton
界面元素。此外,您还需要对 RadioGroup
中的单选按钮进行分组,因为一次只能选择一个选项。
有一些 XML 看起来似乎符合您的需求。请仔细阅读,了解 RadioGroup
如何成为父视图,而 RadioButtons
成为其中的子视图。
- 返回您在 Android Studio 中的布局,将
RadioGroup
和RadioButton
添加到应用中。 - 在
TextView
元素之后(但仍在ConstraintLayout
内),开始输入<RadioGroup
。Android Studio 会提供一些实用建议来帮助您完成对 XML 的修改。 - 将
RadioGroup
的layout_width
和layout_height
设置为wrap_content
。 - 添加一个资源 ID,并将其设置为
@+id/tip_options
。 - 以
>
结束开始标记。 - Android Studio 将会添加
</RadioGroup>
。与ConstraintLayout
一样,RadioGroup
元素中也会包含其他元素,因此您可能需要将其移动到单独的一行。 - 将
RadioGroup
约束为位于服务问题的下方(在垂直方向),以及父视图的起始位置(在水平方向)。 - 将
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
- 在
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>
- 将
layout_width
和layout_height
设置为wrap_content
。 - 为
RadioButton
分配资源 ID@+id/option_twenty_percent
。 - 将文本设置为
Amazing (20%)
。 - 以
/>
结束标记。
<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>
现在,您已经添加了一个 RadioButton
,接下来,您可以修改 XML,为 Good (18%)
和 Okay (15%)
选项再添加 2 个单选按钮吗?
RadioGroup
和 RadioButtons
的 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>
添加默认选择
目前,没有任何小费选项处于选中状态。最好默认选择一个单选按钮选项。
RadioGroup
上有一个属性,通过该属性,您可以指定最初选择的应该是哪个按钮。该属性称为 checkedButton
,您需要将其设置为您想要选择的那个单选按钮的资源 ID。
- 在
RadioGroup
中,将android:checkedButton
属性设置为@id/option_twenty_percent
。
<RadioGroup
android:id="@+id/tip_options"
android:checkedButton="@id/option_twenty_percent"
...
请注意,在 Design Editor 中,布局已相应更新。系统默认选中了 20% 的小费选项,真棒!现在,它开始看起来有点像小费计算器了!
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
,用于显示小费金额。
添加开关以将小费向上取整
接下来,您需要使用 Switch
微件,让用户能够选择是否将小费向上取整。
您希望 Switch
与父级一样宽,因此不妨考虑将宽度设置为 match_parent
。如前所述,您无法针对 ConstraintLayout
中的界面元素设置 match_parent
。相反,您需要约束该视图的起始和结束边缘,并将宽度设置为 0dp
。如果将宽度设置为 0dp
,就表示告知系统不计算宽度,只尝试匹配针对视图设置的约束条件即可。
- 在
RadioGroup
的 XML 之后添加一个Switch
元素。 - 如上所述,将
layout_width
设置为0dp
。 - 将
layout_height
设置为wrap_content
。这会使Switch
视图与其中的内容一样高。 - 将
id
属性设置为@+id/round_up_switch
。 - 将
text
属性设置为Round up tip?
。这将用作Switch
的标签。 - 将
Switch
的起始边缘约束为tip_options
的起始边缘,并将其结束边缘约束为父级的结束边缘。 - 将
Switch
的顶部约束为tip_options
的底部。 - 以
/>
结束标记。
最好能使该开关默认处于开启状态,这时就要用到 android:checked
这个属性,它可能的值为 true
(开启)或 false
(关闭)。
- 将
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" />
添加“Calculate”按钮
接下来,您需要添加一个 Button
,以便用户能够请求计算小费。您希望按钮与父级一样宽,以便其水平约束条件和宽度与 Switch
的一样。
- 在
Switch
后添加一个Button
。 - 按照为
Switch
设置宽度相同的方式将宽度设置为0dp
。 - 将高度设置为
wrap_content
。 - 为其提供资源 ID
@+id/calculate_button
以及文本"Calculate"
。 - 将
Button
的顶部边缘约束为 Round up tip?Switch
的底部边缘。 - 将起始边缘约束为父级的起始边缘,并将结束边缘约束为父级的结束边缘。
- 以
/>
结束标记。
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" />
添加小费计算结果
您的布局马上就要完成了!在这一步中,您需要为小费计算结果添加一个 TextView
,将其置于 Calculate 按钮下,并使其与结束边缘对齐,而不是像其他界面元素一样与起始边缘对齐。
- 添加一个资源 ID 名为
tip_result
且文本为Tip Amount
的TextView
。 - 将
TextView
的结束边缘约束为父级的结束边缘。 - 将顶部边缘约束为 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" />
- 运行应用。显示内容应如下面的屏幕截图所示。
太棒了!如果这是您首次使用 XML,简直棒极了!
请注意,该应用可能看起来与屏幕截图不完全相同,因为模板可能在 Android Studio 的后期版本中发生了变化。Calculate 按钮目前尚不会执行任何操作,但您可以输入费用、选择小费百分比,然后切换选项以选择是否将小费向上取整。在下一个 Codelab 中,您将执行操作,让 Calculate 按钮正常运行,因此,一定要回过头来回顾这些内容!
7. 采用规范的编码做法
提取字符串
您可能已经注意到有关硬编码字符串的警告。回顾之前的 Codelab 可知,通过将字符串提取到资源文件中,可更加轻松地将您的应用翻译成其他语言,还可以重复使用字符串。浏览 activity_main.xml
并提取所有字符串资源。
- 点击某个字符串;将鼠标悬停在出现的黄色灯泡图标上,然后点击它旁边的三角形;选择 Extract String Resource。字符串资源的默认名称没有问题。如果需要,对于小费选项,您可以使用
amazing_service
、good_service
和ok_service
使名称更具描述性。
现在,验证您刚刚添加的字符串资源。
- 如果未显示 Project 窗口,请点击窗口左侧的 Project 标签页。
- 依次打开 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 提供了各种工具来整理代码,并确保其遵循建议的编码规范。
- 在
activity_main.xml
中,依次选择 Edit > Select All。 - 依次选择 Code > Reformat Code。
这样可以确保缩进量是一致的,并且它可能会对界面元素的某些 XML 重新排序,以便进行分组(例如,将某个元素的所有 android:
属性放到一起)。
8. 解决方案代码
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. 自行练习
请进行以下练习:
- 创建一个不同的计算器应用,例如烹饪计量单位转换器,用于在毫升和液量盎司之间来回转换、在克和杯之间来回转换,等等。您需要哪些字段?