计算自定义小费

1. 准备工作

在本 Codelab 中,我们将使用 Codelab 课程“Compose 中的状态简介”中的解决方案代码构建一款交互式小费计算器;在您输入账单金额和小费百分比后,该计算器可以自动计算小费金额并进行取整处理。最终应用将如下图所示:

24370de6d667a700.png

前提条件

  • 已学习“在 Jetpack Compose 中使用状态”Codelab
  • 能够向应用添加 TextTextField 可组合项。
  • 了解 remember 函数、状态、状态提升,以及有状态和无状态可组合函数之间的差异

学习内容

  • 如何向虚拟键盘添加操作按钮。
  • 如何设置键盘操作。
  • 什么是 Switch 可组合项以及如何使用它。
  • 什么是布局检查器。

构建内容

  • 您将构建一款 Tip Time 应用,它会根据用户输入的服务费用和小费百分比计算小费金额。

所需条件

  • Android Studio
  • “在 Jetpack Compose 中使用状态”Codelab 中的解决方案代码

2. 起始应用概览

本 Codelab 将从上一个 Codelab 中的 Tip Time 应用入手,该应用提供了根据固定小费百分比计算小费所需的界面。用户可以通过 Cost of Service 文本框输入服务费用。该应用将计算小费金额,并在 Text 可组合项中显示小费金额。

获取起始代码

首先,请下载起始代码:

或者,您也可以克隆代码的 GitHub 代码库:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout state

您可以在 Tip Calculator GitHub 代码库中浏览该代码。

运行 Tip Time 应用

  1. 在 Android Studio 中打开 Tip Time 项目,并在模拟器或设备上运行应用。
  2. 输入服务费用。应用会自动计算并显示小费金额。

761df483de663721.png

在当前的实现中,小费百分比已硬编码为 15%。在本 Codelab 中,您将使用文本字段扩展该功能,让应用能够根据自定义小费百分比计算小费金额并进行取整处理。

添加必要的字符串资源

  1. Project 标签页中,依次点击 res > values > strings.xml
  2. strings.xml 文件的 <resources> 标记之间,添加以下字符串资源:
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>

strings.xml 文件应如以下代码段所示(其中包含上一个 Codelab 中的字符串):

strings.xml

<resources>
   <string name="app_name">TipTime</string>
   <string name="calculate_tip">Calculate Tip</string>
   <string name="cost_of_service">Cost of Service</string>
   <string name="how_was_the_service">Tip (%)</string>
   <string name="round_up_tip">Round up tip?</string>
   <string name="tip_amount">Tip Amount: %s</string>
</resources>
  1. Cost Of Service 字符串更改为 Bill Amount 字符串。在某些国家/地区,“service”就是“tip”的意思,因此这样更改可以避免混淆。
  2. Cost of Service 字符串中,右键点击该属性的 name cost_of_service,然后依次选择 Refactor > Rename。系统会打开 Rename 对话框。

a2f301b95a8c0e3f.png

  1. Rename 对话框中,将 cost_of _service 替换为 bill_amount,然后点击 Refactor。这会更新项目中出现的所有 cost_of_service 字符串资源,因此您无需手动更改 Compose 代码。

f525a371c2851d08.png

  1. strings.xml 文件中,将字符串值从 Cost of Service 更改为 Bill Amount
<string name="bill_amount">Bill Amount</string>
  1. 找到 MainActivity.kt 文件,然后运行应用。文本框中的标签已更新,如下图所示:

文本字段显示“Bill Amount”,而非“Cost of Service”

3. 添加小费百分比文本字段

用户可能希望根据所提供的服务质量和其他各种原因提高或降低小费。为适应此需求,应用应允许用户计算自定义小费。在本部分中,我们将添加一个文本字段,供用户输入自定义小费百分比,如下图所示:

47b5e8543e5eb754.png

您的应用中已包含 Bill Amount 文本字段可组合项,它属于无状态 EditNumberField() 可组合函数。在上一个 Codelab 中,您已将 amountInput 状态从 EditNumberField() 可组合项提升到 TipTimeScreen() 函数,从而让 EditNumberField() 可组合项变为无状态。

如需添加文本字段,您可以重复使用同一 EditNumberField() 可组合项,但要提供不同的标签。如需进行此项更改,您需要将标签作为形参传入,而不是在 EditNumberField() 可组合函数内对其进行硬编码。

使 EditNumberField() 可组合函数可重复使用:

  1. MainActivity.kt 文件中,将 Int 类型的 label 字符串资源添加到 EditNumberField() 可组合函数的形参中:
@Composable
fun EditNumberField(
   label: Int,
   value: String,
   onValueChange: (String) -> Unit
)
  1. Modifier 类型的 modifier 实参添加到 EditNumberField() 可组合函数中:
@Composable
fun EditNumberField(
   label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
)
  1. 在函数主体中,将硬编码的字符串资源 ID 替换为 label 形参:
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
}
  1. 使用 @StringRes 注解为该函数形参添加注解,指明 label 形参应为字符串资源引用:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
)
  1. 导入以下代码:
import androidx.annotation.StringRes
  1. EditNumberField() 函数的 TextField 可组合项中,将 label 形参传递到 stringResource() 函数。
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
}
  1. TipTimeScreen() 函数的 EditNumberField() 函数调用中,将 label 形参设置为 R.string.bill_amount 字符串资源:
EditNumberField(
   label = R.string.bill_amount,
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. 在“Design”窗格中,点击 2d40b921003ab5eb.png Build & Refresh。应用的界面应如下图所示:

a84cd50c50235a9f.png

  1. TipTimeScreen() 函数中的 EditNumberField() 函数调用后面,再添加一个文本字段,以供用户输入自定义小费百分比。使用以下形参调用 EditNumberField() 可组合函数:
EditNumberField(
   label = R.string.how_was_the_service,
   value = "",
   onValueChange = { }
)

这样即可再添加一个文本框,供用户输入自定义小费百分比。

  1. 在“Design”窗格中,点击 2d40b921003ab5eb.png Build & Refresh。现在,应用预览会显示一个内容为 Tip (%) 的文本字段,如下图所示:

9d2c01d577d077ae.png

  1. TipTimeScreen() 函数的顶部,为已添加的文本字段的状态变量添加一个名为 tipInputvar 属性。使用 mutableStateOf("") 初始化该变量,并使用 remember 函数将调用括起来:
var tipInput by remember { mutableStateOf("") }
  1. 在新的 EditNumberField() 函数调用中,将 value 具名形参设置为 tipInput 变量,然后更新 onValueChange lambda 表达式中的 tipInput 变量:
EditNumberField(
   label = R.string.how_was_the_service,
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. TipTimeScreen() 函数中 tipInput 变量的定义后面,定义一个名为 tipPercentval 变量,以将 tipInput 变量转换为 Double 类型,并使用 elvis 运算符,在值为 null 时返回 0.0
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
  1. TipTimeScreen() 函数中,更新 calculateTip() 函数调用,传入 tipPercent 变量作为第二个形参:
val tip = calculateTip(amount, tipPercent)

现在,TipTimeScreen() 函数的代码应如以下代码段所示:

@Composable
fun TipTimeScreen() {
   var amountInput by remember { mutableStateOf("") }
   var tipInput by remember { mutableStateOf("") }

   val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount, tipPercent)

   Column(
       modifier = Modifier.padding(32.dp),
       verticalArrangement = Arrangement.spacedBy(8.dp)
   ) {
       Text(
           text = stringResource(R.string.calculate_tip),
           fontSize = 24.sp,
           modifier = Modifier.align(Alignment.CenterHorizontally)
       )
       Spacer(Modifier.height(16.dp))
       EditNumberField(
           label = R.string.bill_amount,
           value = amountInput,
           onValueChange = { amountInput = it }
       )
       EditNumberField(
           label = R.string.how_was_the_service,
           value = tipInput,
           onValueChange = { tipInput = it }
       )
       Spacer(Modifier.height(24.dp))
       Text(
           text = stringResource(R.string.tip_amount, tip),
           modifier = Modifier.align(Alignment.CenterHorizontally),
           fontSize = 20.sp,
           fontWeight = FontWeight.Bold
       )
   }
}
  1. 在模拟器或设备上运行应用,然后输入账单金额和小费百分比。应用会正确计算小费金额吗?

bdc482b015472300.png

4. 设置操作按钮

在上一个 Codelab 中,您学习了如何使用 KeyboardOptions 类设置键盘类型。在本部分中,您将学习如何使用相同的 KeyboardOptions 设置键盘操作按钮。键盘操作按钮是指键盘端的按钮。您可以参见下表,了解一些示例:

属性

键盘上的操作按钮

ImeAction.Search:用户想要执行搜索时使用。

ImeAction.Send:用户想要发送输入字段中的文本时使用。

ImeAction.Go:用户想要跳转到输入文本的目的地时使用。

在此任务中,您将为文本框设置两个不同的操作按钮:

  • Bill Amount 文本框设置 Next 操作按钮,用于指示用户已完成当前输入并想移到下一个文本框。
  • Tip % 文本框设置 Done 操作按钮,用于指示用户已完成输入。

包含这些按钮的键盘示例如下面这些图片所示:

添加键盘选项:

  1. EditNumberField() 函数的 TextField() 函数调用中,向 KeyboardOptions 构造函数传递一个值设置为 ImeAction.NextimeAction 具名实参。您可以通过 KeyboardOptions.Default.copy 函数来使用其他默认选项,例如大写和自动更正。
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
           keyboardType = KeyboardType.Number,
           imeAction = ImeAction.Next
       )
   )
}
  1. 在模拟器或设备上运行应用。现在,键盘会显示 Next 操作按钮,如下图所示:

不过,您需要为文本字段设置两个不同的操作按钮。您很快将解决该问题。

  1. 检查 EditNumberField() 函数。TextField() 函数中的 keyboardOptions 形参是硬编码的。若要为文本字段创建不同的操作按钮,您需要将 KeyboardOptions 对象作为实参传入(我们将在下一步中执行该操作)。
// No need to copy, just examine the code.
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
          keyboardType = KeyboardType.Number,
          imeAction = ImeAction.Next
       )
   )
}
  1. EditNumberField() 函数定义中,添加类型为 KeyboardOptionskeyboardOptions 形参。在函数主体中,将其分配给 TextField() 函数的 keyboardOptions 具名形参:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   value: String,
   onValueChange: (String) -> Unit
){
   TextField(
       //...
       keyboardOptions = keyboardOptions
   )
}
  1. TipTimeScreen() 函数中,更新第一个 EditNumberField() 函数调用,为 Bill Amount 文本字段传入 keyboardOptions 具名形参。
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. 在第二个 EditNumberField() 函数调用中,将 Tip % 文本字段的 imeAction 更改为 ImeAction.Done。您的函数应如以下代码段所示:
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. 运行应用。它会显示 NextDone 操作按钮,如下面这些图片所示:

  1. 输入任意账单金额并点击 Next 操作按钮,然后输入任意小费百分比并点击 Done 操作按钮。应用不会执行任何操作,因为您还未向这些按钮添加功能。您将在下一部分中添加这些功能。

5. 设置键盘操作

在本部分中,您将使用 KeyboardActions 类来实现将焦点移到下一个文本框并关闭键盘的功能,以改善用户体验。借助该类,开发者可以指定要触发哪些操作来响应软件键盘上的用户 IME(输入法)操作。举例来说,用户点击 NextDone 操作按钮就属于 IME 操作。

您要实现以下操作:

  • 对于 Next 操作:将焦点移到下一个文本字段(即 Tip % 文本框)。
  • Done 操作:关闭虚拟键盘。
  1. EditNumberField() 函数中,添加名为 focusManagerval 变量,并为其赋予 LocalFocusManager.current 属性作为值:
val focusManager = LocalFocusManager.current

LocalFocusManager 接口用于控制 Compose 中的焦点。您可以使用该变量将焦点移到文本框,以及从文本框清除焦点。

  1. 导入 import androidx.compose.ui.platform.LocalFocusManager
  2. EditNumberField() 函数签名中,再添加一个 KeyboardActions 类型的 keyboardActions 形参:
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   keyboardActions: KeyboardActions,
   value: String,
   onValueChange: (String) -> Unit
) {
   //...
}
  1. EditNumberField() 函数主体中,更新 TextField() 函数调用,将 keyboardActions 形参设置为传入的 keyboardActions 形参。
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardActions = keyboardActions
   )
}

现在,您可以自定义文本字段,并为每个操作按钮设定不同的功能。

  1. TipTimeScreen() 函数调用中,更新第一个 EditNumberField() 函数调用,以添加 keyboardActions 具名形参作为新实参。为该形参分配值,即 KeyboardActions( onNext = { } )
// Bill amount text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onNext = { }
   ),
   //...
)

当用户按键盘上的 Next 操作按钮时,onNext 具名形参的 lambda 表达式便会运行。

  1. 定义 lambda,请求 FocusManager 将焦点向下移到下一个可组合项(即 Tip %)。在该 lambda 表达式中,对 focusManager 对象调用 moveFocus() 函数,然后传入 FocusDirection.Down 实参:
// Bill amount text field
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   keyboardActions = KeyboardActions(
       onNext = { focusManager.moveFocus(FocusDirection.Down) }
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)

moveFocus() 函数将焦点沿指定方向移动,在本示例中,就是向下移到 Tip % 文本字段。

  1. 导入以下代码:
import androidx.compose.ui.focus.FocusDirection
  1. Tip % 文本字段添加类似实现。不同之处在于您需要定义一个 onDone 具名形参,而不是 onNext
// Tip% text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onDone = { }
   ),
   //...
)
  1. 用户输入自定义小费后,在键盘上执行 Done 操作应该会清除焦点,进而关闭键盘。定义 lambda,请求 FocusManager 清除焦点。在 lambda 表达式中,对 focusManager 对象调用 clearFocus() 函数:
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   keyboardActions = KeyboardActions(
       onDone = { focusManager.clearFocus() }),
   value = tipInput,
   onValueChange = { tipInput = it }
)

clearFocus() 函数会从获得焦点的组件中清除焦点。

  1. 运行应用。现在,键盘操作会更改获得焦点的组件,如下面的 GIF 所示:

3164e7a2f39a2d7b.gif

6. 添加开关

开关可用来开启或关闭单个项的状态。切换开关具有两种状态,可让用户在两个选项之间进行选择。切换开关由滑块和滑道组成,如下面这些图片所示:

1. 滑块
2. 滑道

开关属于选择控件,可用于输入决策或声明偏好设置,例如下图所示的设置:

a90c4e22e48b30e0.png

用户可以前后拖动滑块来选择所选选项,或者直接点按开关进行切换。下面的 GIF 显示了另一个切换开关示例,其中的 Visual options(视觉选项) 设置切换为 Dark mode(深色模式)

91b7bd7a6e02e5ff.gif

如需详细了解开关,请参阅开关文档。

您将使用 Switch 可组合项,以便用户选择是否将小费向上取整为最接近的整数,如下图所示:

cf89a61484296bab.png

TextSwitch 可组合项添加一个代码行:

  1. EditNumberField() 函数后面,添加一个 RoundTheTipRow() 可组合函数,然后将默认的 Modifier 作为实参传入,类似于 EditNumberField() 函数:
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
  1. 实现 RoundTheTipRow() 函数,添加一个具有以下 modifierRow 布局可组合项,以将子元素的宽度设置为屏幕上的最大值,居中对齐,并确保尺寸为 48 dp
Row(
   modifier = Modifier
       .fillMaxWidth()
       .size(48.dp),
   verticalAlignment = Alignment.CenterVertically
) {
}
  1. 导入以下代码:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Size
  1. Row 布局可组合项的 lambda 代码块中,添加一个使用 R.string.round_up_tip 字符串资源来显示 Round up tip? 字符串的 Text 可组合项:
Text(text = stringResource(R.string.round_up_tip))
  1. Text 可组合项后面,添加一个 Switch 可组合项,然后传递一个设置为 roundUpchecked 具名形参和一个设置为 onRoundUpChangedonCheckedChange 具名形参。
Switch(
    checked = roundUp,
    onCheckedChange = onRoundUpChanged,
)

下表列出了这些形参(与您为 RoundTheTipRow() 函数定义的形参相同)的相关信息:

形参

说明

checked

指示开关是否处于选中状态。这是 Switch 可组合项的状态。

onCheckedChange

点击开关时要调用的回调。

  1. 导入以下代码:
import androidx.compose.material.Switch
  1. RoundTipRow() 函数中,添加一个类型为 BooleanroundUp 形参和一个接受 Boolean 且不返回任何内容的 onRoundUpChanged lambda 函数:
@Composable
fun RoundTheTipRow(
   roundUp: Boolean,
   onRoundUpChanged: (Boolean) -> Unit,
   modifier: Modifier = Modifier
)

这会提升开关的状态。

  1. Switch 可组合项中,添加以下 modifier,以将 Switch 可组合项与屏幕末端对齐:
       Switch(
           modifier = modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           //...
       )
  1. 导入以下代码:
import androidx.compose.foundation.layout.wrapContentWidth
  1. TipTimeScreen() 函数中,为 Switch 可组合项的状态添加一个 var 变量。创建一个名为 roundUpvar 变量,将其设置为 mutableStateOf(),并以 false 为默认实参。使用 remember { } 括住调用。
fun TipTimeScreen() {
   //...
   var roundUp by remember { mutableStateOf(false) }

   //...
   Column(
       ...
   ) {
     //...
  }
}

这就是 Switch 可组合项状态的变量,默认状态为 false。

  1. TipTimeScreen() 函数的 Column 代码块的 Tip % 文本字段后面,使用以下实参调用 RoundTheTipRow() 函数:一个设置为 roundUproundUp 具名形参,以及一个设置为 lambda 回调的 onRoundUpChanged 具名形参(用于更新 roundUp 值):
@Composable
fun TipTimeScreen() {
   //...

   Column(
       ...
   ) {
       Text(
           ...
       )
       Spacer(...)
       EditNumberField(
           ...
       )
       EditNumberField(
           ...
       )
       RoundTheTipRow(roundUp = roundUp, onRoundUpChanged = { roundUp = it })
       Spacer(...)
       Text(
           ...
       )
   }
}

这会显示 Round up tip(小费向上取整) 行。

  1. 运行应用。应用会显示 Round up tip? 切换开关,但是该切换开关的滑块不太起眼,如下图所示:

处于未选中状态和处于选中状态的开关,带有标识其 2 个元素和相关状态的数字1. 滑块
2. 滑轨

在后续步骤中,您可以通过将滑块变成深灰色来提高其可见性。

  1. RoundTheTipRow() 函数的 Switch() 可组合项中,添加 colors 具名形参。
  2. colors 具名形参设置为 SwitchDefaults.colors() 函数,该函数接受设为 Color.DarkGray 实参的 uncheckedThumbColor 具名形参。
Switch(
   //...
   colors = SwitchDefaults.colors(
       uncheckedThumbColor = Color.DarkGray
   )
)
  1. 导入以下代码:
import androidx.compose.material.SwitchDefaults
import androidx.compose.ui.graphics.Color

现在,RoundTheTipRow() 可组合函数应如以下代码段所示:

@Composable
fun RoundTheTipRow(roundUp: Boolean, onRoundUpChanged: (Boolean) -> Unit) {
   Row(
       modifier = Modifier
           .fillMaxWidth()
           .size(48.dp),
       verticalAlignment = Alignment.CenterVertically
   ) {
       Text(stringResource(R.string.round_up_tip))
       Switch(
           modifier = Modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           checked = roundUp,
           onCheckedChange = onRoundUpChanged,
           colors = SwitchDefaults.colors(
               uncheckedThumbColor = Color.DarkGray
           )
       )
   }
}
  1. 运行应用。开关的滑块颜色变得不一样了,如下图所示:

24370de6d667a700.png

  1. 输入账单金额和小费百分比,然后让 Round up tip? 切换开关处于开启状态。小费金额并没有取整,这是因为您还需要更新 calculateTip() 函数,您将在下一部分中执行此操作。

更新 calculateTip() 函数以对小费进行取整

修改 calculateTip() 函数以接受 Boolean 变量,从而将小费向上取整为最接近的整数:

  1. calculateTip() 函数需要知道开关的状态(该状态为 Boolean 值)才能对小费进行向上取整。在 calculateTip() 函数中,添加类型为 BooleanroundUp 形参:
private fun calculateTip(
   amount: Double,
   tipPercent: Double = 15.0,
   roundUp: Boolean
): String {
   //...
}
  1. calculateTip() 函数中的 return 语句前面,添加一个 if() 条件来检查 roundUp 值。如果 roundUptrue,则定义一个 tip 变量并设置为 kotlin.math.ceil() 函数,然后将函数 tip 作为实参进行传递:
if (roundUp)
   tip = kotlin.math.ceil(tip)

完成后的 calculateTip() 函数应如以下代码段所示:

private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
   var tip = tipPercent / 100 * amount
   if (roundUp)
       tip = kotlin.math.ceil(tip)
   return NumberFormat.getCurrencyInstance().format(tip)
}
  1. TipTimeScreen() 函数中,更新 calculateTip() 函数调用,然后传入 roundUp 形参:
val tip = calculateTip(amount, tipPercent, roundUp)
  1. 运行应用。现在,它会将小费金额向上取整,如下面这些图片所示:

7. 获取解决方案代码

如需下载完成后的 Codelab 代码,您可以使用以下 Git 命令:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git

或者,您也可以下载 ZIP 文件形式的代码库,将其解压缩并在 Android Studio 中打开。

如果您想查看解决方案代码,请前往 GitHub 查看

8. 总结

恭喜!您向 Tip Time 应用添加了自定义小费功能。现在,您的应用可让用户输入自定义小费百分比并对小费金额进行向上取整了。欢迎使用 #AndroidBasics 标签在社交媒体上分享您的作品!

了解更多内容