1. Antes de começar
Neste codelab, você vai usar o código da solução do codelab Introdução ao estado no Compose para criar uma calculadora interativa de gorjetas que pode calcular e arredondar um valor ao inserir a conta e a porcentagem da gorjeta. Confira o app final nesta imagem:

Pré-requisitos
- Já ter feito o codelab "Usar estado no Jetpack Compose".
- Saber adicionar os elementos combináveis
TexteTextFielda um app. - Conhecimento sobre a função
remember, estado, elevação de estado e diferença entre funções combináveis com e sem estado.
O que você vai aprender
- Como adicionar um botão de ação a um teclado virtual.
- Como configurar as ações do teclado.
- O que é um elemento combinável
Switche como usá-lo. - O que é o Layout Inspector.
O que você vai criar
- Um app Tip Time que calcula valores com base no custo de serviço inserido pelo usuário e na porcentagem da gorjeta.
O que é necessário
- Android Studio
- O código da solução do codelab "Usar estado no Jetpack Compose".
2. Visão geral do app inicial
Este codelab começa com o app Tip Time do codelab anterior, que fornece a interface do usuário necessária para calcular uma gorjeta com uma porcentagem fixa. A caixa de texto Cost of service (custo do serviço) permite que o usuário insira o preço do serviço. O app calcula e mostra o valor da gorjeta em um elemento combinável Text.
|
|
Acessar o código inicial
Para começar, faça o download do código inicial:
Como alternativa, é possível clonar o repositório do 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
Procure o código no repositório do GitHub do Tip Calculator (link em inglês).
Executar o app Tip Time
- Abra o projeto Tip Time no Android Studio e execute o app em um emulador ou dispositivo.
- Insira o custo do serviço. O app calcula e mostra automaticamente o valor da gorjeta.

Na implementação atual, a porcentagem da gorjeta está fixada no código como 15%. Neste codelab, você vai estender esse recurso com um campo de texto que permite ao app calcular uma porcentagem personalizada e arredondar o valor da gorjeta.
Adicionar os recursos de string necessários
- Na guia Project, clique em res > values > strings.xml.
- Entre as tags
<resources>do arquivostrings.xml, adicione estes recursos de string:
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>
O arquivo strings.xml vai ser parecido com o snippet de código abaixo, que inclui as strings do codelab anterior:
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>
- Mude a string
Cost Of ServiceparaBill Amount. Em alguns países, serviço significa gorjeta, por isso essa mudança evita confusão. - Na string
Cost of Service, clique com o botão direito do mouse emcost_of_service, onamedo atributo, e selecione Refactor > Rename. Uma caixa de diálogo Rename é aberta.

- Na caixa de diálogo Rename, substitua
cost_of _serviceporbill_amounte clique em Refactor. Isso atualiza todas as ocorrências do recurso de stringcost_of_serviceno projeto. Assim, não é necessário mudar o código do Compose manualmente.

- No arquivo
strings.xml, mude o valor da string deCost of ServiceparaBill Amount:
<string name="bill_amount">Bill Amount</string>
- Navegue até o arquivo
MainActivity.kte execute o app. O identificador vai ser atualizado na caixa de texto, como mostra esta imagem:

3. Adicionar um campo de texto de porcentagem da gorjeta
É possível que um cliente queira aumentar ou diminuir a gorjeta com base na qualidade do serviço prestado e por vários outros motivos. Para acomodar isso, o app precisa permitir que o usuário calcule uma gorjeta personalizada. Nesta seção, você vai adicionar um campo de texto para que o usuário insira uma porcentagem da gorjeta, como mostrado nesta imagem:

Você já tem um campo de texto Bill amount (valor da conta) no app, que é a função combinável EditNumberField() sem estado. No codelab anterior, você elevou o estado amountInput do elemento combinável EditNumberField() para a função TipTimeScreen(), deixando o elemento EditNumberField() sem estado.
Para adicionar um campo de texto, reutilize o mesmo elemento combinável EditNumberField(), mas com um rótulo diferente. Para fazer essa mudança, você precisa transmitir o rótulo como um parâmetro em vez de fixá-lo na função combinável EditNumberField().
Torne a função combinável EditNumberField() reutilizável:
- No arquivo
MainActivity.ktnos parâmetros da função combinávelEditNumberField(), adicione um recurso de stringlabeldo tipoInt:
@Composable
fun EditNumberField(
label: Int,
value: String,
onValueChange: (String) -> Unit
)
- Adicione um argumento
modifierdo tipoModifierà função combinávelEditNumberField():
@Composable
fun EditNumberField(
label: Int,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
)
- No corpo da função, substitua o ID do recurso de string fixado no código pelo parâmetro
label:
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
label = { Text(stringResource(label)) },
//...
)
}
- Para indicar que o parâmetro
labelé uma referência de recurso de string, faça a anotação@StringResno parâmetro da função:
@Composable
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
)
- Importe o seguinte:
import androidx.annotation.StringRes
- No elemento combinável
TextFieldda funçãoEditNumberField(), transmita o parâmetrolabelà funçãostringResource().
@Composable
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
//...
label = { Text(stringResource(label)) },
//...
)
}
- Na chamada
EditNumberField()da funçãoTipTimeScreen(), defina o parâmetrolabelcomo o recurso de stringR.string.bill_amount:
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChange = { amountInput = it }
)
- No painel "Design", clique em
Build & Refresh. A interface do app vai ficar assim:

- Na função
TipTimeScreen()após a chamadaEditNumberField(), adicione outro campo de texto para a porcentagem de gorjeta personalizada. Chame a função combinávelEditNumberField()com estes parâmetros:
EditNumberField(
label = R.string.how_was_the_service,
value = "",
onValueChange = { }
)
Isso adiciona outra caixa de texto para a porcentagem de gorjeta personalizada.
- No painel "Design", clique em
Build & Refresh. A visualização do app agora mostra um campo de texto Tip (%) (porcentagem da gorjeta) como nesta imagem:

- Na parte de cima da função
TipTimeScreen(), adicione uma propriedadevarcom o nometipInputpara a variável de estado do campo de texto adicionado. UsemutableStateOf("")para inicializar a variável e cercar a chamada com a funçãoremember:
var tipInput by remember { mutableStateOf("") }
- Na nova chamada de função
EditNumberField(), defina o parâmetro com nomevaluecomo a variáveltipInpute atualize a variáveltipInputna expressão lambdaonValueChange:
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChange = { tipInput = it }
)
- Na função
TipTimeScreen()após a definição da variáveltipInput, defina uma variávelvalcom o nometipPercentque converte a variáveltipInputem um tipoDouble, use um operador elvis e retorne0.0se o valor fornull:
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
- Na função
TipTimeScreen(), atualize a chamadacalculateTip()e transmita a variáveltipPercentcomo o segundo parâmetro:
val tip = calculateTip(amount, tipPercent)
O código da função TipTimeScreen() vai ficar parecido com este snippet de código:
@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
)
}
}
- Execute o app em um emulador ou dispositivo e insira o valor da conta e a porcentagem da gorjeta. O app calcula o valor da gorjeta corretamente?

4. Definir um botão de ação
No codelab anterior, você explorou como usar a classe KeyboardOptions para definir o tipo do teclado. Nesta seção, você vai aprender a definir o botão de ação do teclado com as mesmas KeyboardOptions. Um botão de ação do teclado é um botão no final dele. Confira alguns exemplos nesta tabela:
Propriedade | Botão de ação no teclado |
|
|
|
|
|
|
Nesta tarefa, você vai definir dois botões de ação diferentes para as caixas de texto:
- Um botão de ação Next (próximo) para a caixa de texto Bill Amount (Valor da conta), que indica que o usuário terminou a entrada atual e quer passar para a próxima caixa de texto.
- Um botão de ação Done para a caixa de texto Tip %, que indica que o usuário terminou de digitar a gorjeta.
Você pode ver exemplos de teclados com esses botões de ação nestas imagens:
|
|
Adicione opções de teclado:
- Na chamada
TextField()da funçãoEditNumberField(), transmita ao construtorKeyboardOptionsum argumento com o nomeimeActiondefinido como um valorImeAction.Next. Use a funçãoKeyboardOptions.Default.copypara aplicar as outras opções padrão, como letras maiúsculas e correção automática.
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
)
)
}
- Execute o app em um emulador ou dispositivo. O teclado agora exibe o botão de ação Next, como você pode ver nesta imagem:
|
|
No entanto, é recomendável ter dois botões de ação diferentes nos campos de texto. Esse problema vai ser corrigido em breve.
- Examine a função
EditNumberField(). O parâmetrokeyboardOptionsna funçãoTextField()está fixado no código. Para criar botões de ação diferentes para os campos de texto, transmita o objetoKeyboardOptionscomo um argumento. Isso vai ser feito na próxima etapa.
// 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
)
)
}
- Na definição da função
EditNumberField(), adicione um parâmetrokeyboardOptionsdo tipoKeyboardOptions. No corpo da função, atribua-o ao parâmetrokeyboardOptionsda funçãoTextField():
@Composable
fun EditNumberField(
@StringRes label: Int,
keyboardOptions: KeyboardOptions,
value: String,
onValueChange: (String) -> Unit
){
TextField(
//...
keyboardOptions = keyboardOptions
)
}
- Na função
TipTimeScreen(), atualize a primeira chamada de funçãoEditNumberField()e transmita o parâmetrokeyboardOptionspara o campo de texto Bill Amount.
EditNumberField(
label = R.string.bill_amount,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
value = amountInput,
onValueChange = { amountInput = it }
)
- Na segunda chamada de função
EditNumberField(), mude aimeActiondo campo de texto Tip % paraImeAction.Done. A função vai ser semelhante a este snippet de código:
EditNumberField(
label = R.string.how_was_the_service,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
value = tipInput,
onValueChange = { tipInput = it }
)
- Execute o app. Ele mostra os botões de ação Next e Done, como você pode ver nestas imagens:
|
|
- Digite qualquer valor para a conta e clique no botão de ação Next. Insira qualquer porcentagem de gorjeta e clique no botão de ação Done. Nada acontece porque você ainda não adicionou nenhuma funcionalidade aos botões. Vamos fazer isso na próxima seção.
5. Definir ações do teclado
Nesta seção, você vai implementar a função que move o foco para o próximo campo de texto e, para melhorar a experiência do usuário, fecha o teclado com a classe KeyboardActions, que permite aos desenvolvedores especificar ações que são acionadas em resposta à ação do IME (editor de método de entrada, na sigla em inglês) dos usuários no teclado de software. Uma ação do IME ocorre, por exemplo, quando o usuário clica no botão de ação Next ou Done.
Implemente o seguinte:
- Na ação Next, mova o foco para o próximo campo de texto (a caixa de texto Tip %).
- Na ação Done, feche o teclado virtual.
- Na função
EditNumberField(), adicione uma variávelvalcom o nomefocusManagere atribua a ela um valor da propriedadeLocalFocusManager.current:
val focusManager = LocalFocusManager.current
A interface LocalFocusManager é usada para controlar o foco no Compose. Use essa variável para mover o foco até as caixas de texto e o remover.
- Importe
import androidx.compose.ui.platform.LocalFocusManager. - Na assinatura da função
EditNumberField(), adicione outro parâmetrokeyboardActionsdo tipoKeyboardActions:
@Composable
fun EditNumberField(
@StringRes label: Int,
keyboardOptions: KeyboardOptions,
keyboardActions: KeyboardActions,
value: String,
onValueChange: (String) -> Unit
) {
//...
}
- No corpo da função
EditNumberField(), atualize a chamadaTextField()e defina o parâmetrokeyboardActionscomo o parâmetro transmitidokeyboardActions.
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
keyboardActions = keyboardActions
)
}
Agora você pode personalizar os campos de texto com funções diferentes para cada botão de ação.
- Na chamada de função
TipTimeScreen(), atualize a primeira chamadaEditNumberField()para incluir um parâmetrokeyboardActionscomo um novo argumento. Atribua um valor a ele,KeyboardActions( onNext ={ }):
// Bill amount text field
EditNumberField(
//...
keyboardActions = KeyboardActions(
onNext = { }
),
//...
)
A expressão lambda do parâmetro onNext é executada quando o usuário pressiona o botão de ação Next no teclado.
- Defina a lambda. Peça que o
FocusManagermova o foco para baixo até o próximo elemento combinável, Tip %. Na expressão lambda, chame a funçãomoveFocus()no objetofocusManagere transmita o argumentoFocusDirection.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 }
)
A função moveFocus() move o foco na direção especificada, que é o campo de texto Tip % nesse caso.
- Importe estas informações:
import androidx.compose.ui.focus.FocusDirection
- Adicione uma implementação semelhante ao campo de texto Tip %. A diferença é que você precisa definir um parâmetro
onDoneem vez deonNext.
// Tip% text field
EditNumberField(
//...
keyboardActions = KeyboardActions(
onDone = { }
),
//...
)
- Depois que o usuário digita a gorjeta personalizada, a ação "Done" no teclado retira o foco, o que fecha o teclado. Defina a lambda e peça para o
FocusManagerretirar o foco. Na expressão lambda, chame a funçãoclearFocus()no objetofocusManager:
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 }
)
A função clearFocus() retira o foco do componente que está em foco.
- Execute o app. As ações do teclado agora mudam o componente em foco, como você pode ver neste GIF:

6. Adicionar uma chave
Uma chave ativa ou desativa o estado de um único item. Há dois estados em um botão de ativação que permitem ao usuário selecionar entre duas opções. Um botão de alternância consiste em um círculo e uma faixa, conforme mostrado nestas imagens:
|
|
Uma chave é um controle de seleção que pode ser usado para inserir decisões ou declarar preferências, por exemplo, configurações, como você pode ver nesta imagem:

O usuário pode arrastar o círculo para frente e para trás a fim de escolher a opção selecionada ou simplesmente tocar na chave para alternar. Você pode ver outro exemplo de um botão de ativação neste GIF, em que a configuração Visual options (opções visuais) muda para Dark mode (modo escuro):

Para saber mais, consulte a documentação sobre chaves.
Use o elemento combinável Switch para que o usuário arredonde a gorjeta para o número inteiro mais próximo, como você pode ver nesta imagem:

Adicione uma linha para os elementos combináveis Text e Switch.
- Depois da função
EditNumberField(), adicione uma função combinávelRoundTheTipRow()e transmita umModifierpadrão, como argumentos semelhantes à funçãoEditNumberField():
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
- Implemente a função
RoundTheTipRow(), adicione um elemento combinável de layoutRowcom omodifierabaixo para definir a largura dos elementos filhos como o máximo na tela, centralize o alinhamento e garanta um tamanho de48dp:
Row(
modifier = Modifier
.fillMaxWidth()
.size(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
}
- Importe estas informações:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Size
- No bloco lambda do elemento combinável do layout
Row, adicione um elementoTextque use o recurso de stringR.string.round_up_tippara mostrar uma stringRound up tip?:
Text(text = stringResource(R.string.round_up_tip))
- Depois do elemento combinável
Text, adicione um elementoSwitche transmita um parâmetrocheckeddefinido comoroundUpe um parâmetroonCheckedChangedefinido comoonRoundUpChanged.
Switch(
checked = roundUp,
onCheckedChange = onRoundUpChanged,
)
Esta tabela contém informações sobre esses parâmetros, que são os mesmos definidos para a função RoundTheTipRow():
Parâmetro | Descrição |
| Se a chave está marcada ou não. Esse é o estado do elemento combinável |
| O callback que é chamado quando a chave recebe um clique. |
- Importe o seguinte:
import androidx.compose.material.Switch
- Na função
RoundTipRow(), adicione um parâmetroroundUpdo tipoBooleane uma função lambdaonRoundUpChangedque usa umBooleane não retorna nada:
@Composable
fun RoundTheTipRow(
roundUp: Boolean,
onRoundUpChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier
)
Isso eleva o estado da chave.
- No elemento combinável
Switch, adicione estemodifierpara alinhar o elementoSwitchao final da tela:
Switch(
modifier = modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
//...
)
- Importe o seguinte:
import androidx.compose.foundation.layout.wrapContentWidth
- Na função
TipTimeScreen(), adicione uma variável var para o estado do elemento combinávelSwitch. Crie uma variávelvarcom o nomeroundUpe a defina comomutableStateOf(), comfalsecomo o argumento padrão. Envolva a chamada comremember { }.
fun TipTimeScreen() {
//...
var roundUp by remember { mutableStateOf(false) }
//...
Column(
...
) {
//...
}
}
Essa é a variável do estado do elemento combinável Switch e "false" vai ser o estado padrão.
- No bloco
Columnda funçãoTipTimeScreen()depois do campo de texto Tip %, chame a funçãoRoundTheTipRow()com estes argumentos: parâmetroroundUpdefinido comoroundUpe um parâmetro nomeadoonRoundUpChangeddefinido como um callback lambda que atualiza o valorroundUp:
@Composable
fun TipTimeScreen() {
//...
Column(
...
) {
Text(
...
)
Spacer(...)
EditNumberField(
...
)
EditNumberField(
...
)
RoundTheTipRow(roundUp = roundUp, onRoundUpChanged = { roundUp = it })
Spacer(...)
Text(
...
)
}
}
A linha Round up tip (arredondar gorjeta) vai aparecer.
- Execute o app. Ele mostra o botão de ativação Round up tip? (Arredondar gorjeta?), mas o círculo do botão quase não está visível, como você pode ver nesta imagem:
|
|
Você vai melhorar a visibilidade do círculo nas próximas etapas alterando a cor dele para cinza escuro.
- No elemento combinável
Switch()da funçãoRoundTheTipRow(), adicione um parâmetro com o nomecolors. - Defina o parâmetro
colorscomo uma funçãoSwitchDefaults.colors()que aceita um parâmetrouncheckedThumbColordefinido como um argumentoColor.DarkGray.
Switch(
//...
colors = SwitchDefaults.colors(
uncheckedThumbColor = Color.DarkGray
)
)
- Importe o seguinte:
import androidx.compose.material.SwitchDefaults
import androidx.compose.ui.graphics.Color
A função combinável RoundTheTipRow() agora vai ficar assim:
@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
)
)
}
}
- Execute o app. A cor do círculo da chave é diferente, como você pode ver nesta imagem:

- Insira o valor da conta e a porcentagem da gorjeta e selecione o botão de alternância Round up tip?. O valor da gorjeta não é arredondado porque você ainda precisa atualizar a função
calculateTip(), o que vai ser feito na próxima seção.
Atualizar a função calculateTip() para arredondar a gorjeta
Modifique a função calculateTip() para aceitar uma variável Boolean e arredondar (link em inglês) a gorjeta para o número inteiro mais próximo:
- Para arredondar a gorjeta, a função
calculateTip()precisa saber o estado da chave, que é umBoolean. Na funçãocalculateTip(), adicione um parâmetroroundUpdo tipoBoolean:
private fun calculateTip(
amount: Double,
tipPercent: Double = 15.0,
roundUp: Boolean
): String {
//...
}
- Na função
calculateTip(), antes da instruçãoreturn, adicione uma condiçãoif()que verifica o valorroundUp. SeroundUpfortrue, defina uma variáveltipcomo a funçãokotlin.math.ceil()e, em seguida, transmita a funçãotipcomo argumento:
if (roundUp)
tip = kotlin.math.ceil(tip)
A função calculateTip() concluída vai ficar parecida com este snippet de código:
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)
}
- Na função
TipTimeScreen(), atualize a chamadacalculateTip()e transmita um parâmetroroundUp:
val tip = calculateTip(amount, tipPercent, roundUp)
- Execute o app. Agora, o valor da gorjeta será arredondado, como mostrado nestas imagens:
|
|
7. Acessar o código da solução
Para fazer o download do código do codelab concluído, use este comando git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Se você quiser conferir o código da solução, acesse o GitHub (link em inglês).
8. Conclusão
Parabéns! Você adicionou a função de gorjeta personalizada ao app Tip Time. Agora, o app permite que os usuários digitem uma porcentagem de gorjeta personalizada e arredondem o valor da gorjeta. Compartilhe seu trabalho nas redes sociais usando a hashtag #AndroidBasics.











1. Círculo
1. Círculo
