计算自定义小费

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

1. 准备工作

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

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 标签在社交媒体上分享您的作品!

了解详情