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
- Ter feito o codelab Introdução ao estado no Compose.
- Saber adicionar os elementos combináveis
Text
eTextField
a 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.
- O que é um elemento combinável
Switch
e como usá-lo. - Adicionar ícones principais aos campos de texto.
O que você vai criar
- Um app Tip Time que calcula valores de gorjetas com base no valor da conta inserido pelo usuário e na porcentagem da gorjeta.
O que é necessário
- A versão mais recente do Android Studio.
- O código da solução do codelab Introdução ao estado no Compose.
2. Acessar o código inicial
Para começar, faça o download do código inicial:
Outra opção é 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 Time
(link em inglês).
3. Visão geral do app inicial
Este codelab começa com o app Tip Time do codelab anterior, Introdução ao estado no Compose, que fornece a interface do usuário necessária para calcular uma gorjeta com uma porcentagem fixa. A caixa de texto Bill amount (valor da conta) 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
.
Executar o app Tip Time
- Abra o projeto Tip Time no Android Studio e execute o app em um emulador ou dispositivo.
- Insira o valor da conta. 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 Percentage</string>
<string name="round_up_tip">Round up tip?</string>
O arquivo strings.xml
será parecido com o snippet de código abaixo, que inclui as strings do codelab anterior:
strings.xml
<resources>
<string name="app_name">Tip Time</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="bill_amount">Bill Amount</string>
<string name="how_was_the_service">Tip Percentage</string>
<string name="round_up_tip">Round up tip?</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>
4. 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 o combinável TipTimeLayout()
, 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 codificá-lo na função combinável EditNumberField()
.
Torne a função combinável EditNumberField()
reutilizável:
- No arquivo
MainActivity.kt
nos parâmetros da função combinávelEditNumberField()
, adicione um recurso de stringlabel
do tipoInt
:
@Composable
fun EditNumberField(
label: Int,
value: String,
onValueChanged: (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@StringRes
no parâmetro da função:
@Composable
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
)
- Importe o seguinte:
import androidx.annotation.StringRes
- Na chamada
EditNumberField()
da função combinávelTipTimeLayout()
, defina o parâmetrolabel
como o recurso de stringR.string.bill_amount
:
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChanged = { amountInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth()
)
- No painel Preview, não deve haver nenhuma mudança visual.
- Na função combinável
TipTimeLayout()
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 = "",
onValueChanged = { },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth()
)
Isso adiciona outra caixa de texto para a porcentagem de gorjeta personalizada.
- A visualização do app agora mostra um campo de texto Tip Percentage (porcentagem da gorjeta), como nesta imagem:
- Na parte de cima da função combinável
TipTimeLayout()
, adicione uma propriedadevar
com o nometipInput
para 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 nomevalue
como a variáveltipInput
e atualize a variáveltipInput
na expressão lambdaonValueChanged
:
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChanged = { tipInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth()
)
- Na função
TipTimeLayout()
, depois da definição da variáveltipInput
. Defina umval
com o nometipPercent
que converte a variáveltipInput
em um tipoDouble
. Use um operador Elvis e retorne0
se o valor fornull
. Esse valor poderá sernull
se o campo de texto estiver vazio.
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
- Na função
TipTimeLayout()
, atualize a chamadacalculateTip()
e transmita a variáveltipPercent
como o segundo parâmetro:
val tip = calculateTip(amount, tipPercent)
O código da função TipTimeLayout()
vai ficar parecido com este snippet de código:
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
var tipInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount, tipPercent)
Column(
modifier = Modifier.padding(40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp)
.align(alignment = Alignment.Start)
)
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChanged = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChanged = { tipInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
Text(
text = stringResource(R.string.tip_amount, tip),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
- 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?
5. 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 (concluído) para a caixa de texto Tip Percentage, que indica que o usuário terminou de digitar a gorjeta.
Confira 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 construtorKeyboardOptions
um argumento com o nomeimeAction
definido como um valorImeAction.Next
. Use a funçãoKeyboardOptions.Default.copy()
para garantir o uso das outras opções padrão.
import androidx.compose.ui.text.input.ImeAction
@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 mostra o botão de ação Next, como você pode conferir nesta imagem:
O teclado mostra o mesmo botão de ação Next quando o campo de texto Tip Percentage está selecionado. 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âmetrokeyboardOptions
na funçãoTextField()
está fixado no código. Para criar botões de ação diferentes para os campos de texto, transmita o objetoKeyboardOptions
como 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,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
//...
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
)
)
}
- Na definição da função
EditNumberField()
, adicione um parâmetrokeyboardOptions
do tipoKeyboardOptions
. No corpo da função, atribua-o ao parâmetrokeyboardOptions
da funçãoTextField()
:
@Composable
fun EditNumberField(
@StringRes label: Int,
keyboardOptions: KeyboardOptions,
// ...
){
TextField(
//...
keyboardOptions = keyboardOptions
)
}
- Na função
TipTimeLayout()
, atualize a primeira chamada de funçãoEditNumberField()
e transmita o parâmetrokeyboardOptions
para o campo de texto Bill Amount:
EditNumberField(
label = R.string.bill_amount,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
// ...
)
- Na segunda chamada de função
EditNumberField()
, mude aimeAction
do campo de texto Tip Percentage paraImeAction.Done
. A função será semelhante a este snippet de código:
EditNumberField(
label = R.string.how_was_the_service,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
// ...
)
- 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. Isso fecha o teclado.
6. Adicionar uma chave
Uma chave ativa ou desativa o estado de um único item.
Há dois estados em um botão de alternância que permitem ao usuário selecionar entre duas opções. Um botão de alternância consiste em uma faixa, um círculo e um ícone opcional, como 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 aparece 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. Este GIF mostra outro exemplo de um botão de ativação, em que a configuração "Visual options" (opções visuais) muda para o 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 conferir 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 umModifier
padrão, como argumentos semelhantes à funçãoEditNumberField()
:
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
- Implemente a função
RoundTheTipRow()
, adicione um elemento combinável de layoutRow
com omodifier
abaixo 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 o seguinte:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
- No bloco lambda do elemento combinável do layout
Row
, adicione um elementoText
que use o recurso de stringR.string.round_up_tip
para mostrar uma stringRound up tip?
:
Text(text = stringResource(R.string.round_up_tip))
- Depois do elemento combinável
Text
, adicione um elementoSwitch
e transmita um parâmetrochecked
definido comoroundUp
e um parâmetroonCheckedChange
definido 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.material3.Switch
- Na função
RoundTheTipRow()
, adicione um parâmetroroundUp
do tipoBoolean
e uma função lambdaonRoundUpChanged
que usa umBoolean
e 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 estemodifier
para alinhar o elementoSwitch
ao final da tela:
Switch(
modifier = modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
//...
)
- Importe o seguinte:
import androidx.compose.foundation.layout.wrapContentWidth
- Na função
TipTimeLayout()
, adicione uma variável var para o estado do elemento combinávelSwitch
. Crie uma variávelvar
com o nomeroundUp
e a defina comomutableStateOf()
, comfalse
como o valor inicial. Envolva a chamada comremember { }
.
fun TipTimeLayout() {
//...
var roundUp by remember { mutableStateOf(false) }
//...
Column(
...
) {
//...
}
}
Essa é a variável do estado do elemento combinável Switch
e "false" será o estado padrão.
- No bloco
Column
da funçãoTipTimeLayout()
, depois do campo de texto Tip Percentage, chame a funçãoRoundTheTipRow()
com estes argumentos: parâmetroroundUp
definido comoroundUp
e um parâmetro nomeadoonRoundUpChanged
definido como um callback lambda que atualiza o valorroundUp
:
@Composable
fun TipTimeLayout() {
//...
Column(
...
) {
Text(
...
)
Spacer(...)
EditNumberField(
...
)
EditNumberField(
...
)
RoundTheTipRow(
roundUp = roundUp,
onRoundUpChanged = { roundUp = it },
modifier = Modifier.padding(bottom = 32.dp)
)
Text(
...
)
}
}
A linha Round up tip? (arredondar gorjeta?) vai aparecer.
- Execute o app. O botão Round up tip? vai aparecer.
- Insira o valor da conta e a porcentagem da gorjeta e selecione a opção 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âmetroroundUp
do 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
. SeroundUp
fortrue
, defina uma variáveltip
como a funçãokotlin.math.
ceil
()
e, em seguida, transmita a funçãotip
como 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
TipTimeLayout()
, atualize a chamadacalculateTip()
e transmita um parâmetroroundUp
:
val tip = calculateTip(amount, tipPercent, roundUp)
- Execute o app. Agora, o valor da gorjeta vai ser arredondado, como você pode ver nestas imagens:
7. Adicionar suporte à orientação paisagem
Os dispositivos Android têm vários formatos, como smartphones, tablets, dispositivos dobráveis e com Chrome OS, com uma ampla variedade de tamanhos de tela. Seu app deve oferecer suporte às orientações retrato e paisagem.
- Teste seu app no modo paisagem e ative o giro automático.
- Gire o emulador ou dispositivo para a esquerda. O valor da gorjeta não aparece. Para resolver esse problema, você precisa de uma barra de rolagem vertical para rolar a tela do app.
- Adicione
.verticalScroll(rememberScrollState())
ao modificador para ativar a rolagem vertical da coluna. OrememberScrollState()
cria e lembra automaticamente do estado de rolagem.
@Composable
fun TipTimeLayout() {
// ...
Column(
modifier = Modifier
.padding(40.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
//...
}
}
- Importe o seguinte:
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
- Execute o app novamente. Role a tela no modo paisagem.
8. Adicionar ícone principal a campos de texto (opcional)
Os ícones podem tornar o campo de texto mais atraente visualmente e fornecer informações adicionais sobre o campo de texto. Os ícones podem ser usados para transmitir informações sobre a finalidade do campo de texto, por exemplo, o tipo de dados esperado ou o tipo de entrada necessária. Por exemplo, o ícone de um telefone ao lado de um campo de texto pode indicar que o usuário deve inserir um número de telefone.
Os ícones podem ser usados para orientar a entrada do usuário, fornecendo indicações visuais sobre o que é esperado. Por exemplo, o ícone de um calendário ao lado de um campo de texto pode indicar que o usuário deve inserir uma data.
O exemplo a seguir mostra um campo de texto com um ícone de pesquisa, indicando a inserção do termo de pesquisa.
Adicione outro parâmetro ao combinável EditNumberField()
com o nome leadingIcon
do tipo Int
. Anote-o com @DrawableRes
.
@Composable
fun EditNumberField(
@StringRes label: Int,
@DrawableRes leadingIcon: Int,
keyboardOptions: KeyboardOptions,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
)
- Importe o seguinte:
import androidx.annotation.DrawableRes
import androidx.compose.material3.Icon
- Adicione o ícone inicial ao campo de texto. O
leadingIcon
usa um combinável, que será transmitido no seguinte combinávelIcon
.
TextField(
value = value,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
//...
)
- Transmita o ícone principal para os campos de texto. Para sua conveniência, os ícones já estão presentes no código inicial.
EditNumberField(
label = R.string.bill_amount,
leadingIcon = R.drawable.money,
// Other arguments
)
EditNumberField(
label = R.string.how_was_the_service,
leadingIcon = R.drawable.percent,
// Other arguments
)
- Execute o app.
Parabéns! Agora seu app pode calcular gorjetas personalizadas.
9. 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).
10. 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 final. Compartilhe seu trabalho nas mídias sociais usando a hashtag #AndroidBasics.
Saiba mais
- Campos de texto: Material Design 3 (link em inglês)
- Chave: Material Design 3 (link em inglês)