1. Antes de começar
Neste codelab, você vai criar um app Dice Roller interativo que permite que os usuários toquem em um elemento combinável Button
para jogar um dado. O resultado da jogada aparece com um elemento combinável Image
na tela.
Você usa o Jetpack Compose com o Kotlin para criar o layout do app e, em seguida, programa a lógica de negócios para processar o que acontece quando o elemento combinável Button
é tocado.
Pré-requisitos
- Saber criar e executar um app básico do Compose no Android Studio.
- Saber como usar o elemento de composição
Text
em um app. - Saber como extrair texto em um recurso de string para facilitar a tradução do app e a reutilização de strings.
- Conhecimento básico de programação em Kotlin.
O que você vai aprender
- Como adicionar um elemento combinável
Button
a um app Android com o Compose. - Como adicionar um comportamento a um elemento de composição
Button
em um app Android com o Compose. - Como abrir e modificar o código da
Activity
de um app Android.
O que você vai criar
- Um app Android interativo chamado Dice Roller. Nele, os usuários jogam um dado e o resultado da jogada aparece na tela.
O que é necessário
- Um computador com o Android Studio instalado.
O app ficará assim quando você concluir este codelab:
2. Definir um valor de referência
Criar um projeto
- No Android Studio, clique em File > New > New Project.
- Na caixa de diálogo New Project, selecione Empty Activity e clique em Next.
- No campo Name, digite
Dice Roller
. - No campo Minimum SDK, selecione o nível de API mínimo 24 (Nougat) no menu e clique em Finish.
3. Criar a infraestrutura do layout
Visualizar o projeto
Para visualizar o projeto:
- Clique em Build & Refresh no painel Split ou Design.
Há uma visualização no painel Design. Se a visualização parecer pequena, não se preocupe, porque ela muda quando o layout é modificado.
Reestruturar o exemplo de código
É necessário mudar parte do código gerado para se assemelhar ao tema de um app para jogar dados.
Como você viu na captura de tela do app final, há a imagem de um dado e um botão para jogar. Você vai estruturar as funções combináveis para refletir essa arquitetura.
Para reestruturar o exemplo de código:
- Remova a função
GreetingPreview()
. - Crie uma função
DiceWithButtonAndImage()
com a anotação@Composable
.
Essa função de composição representa os componentes de IU do layout e também contém a lógica de clique no botão e de exibição de imagem.
- Remova a função
Greeting(name: String, modifier: Modifier = Modifier)
. - Crie uma função
DiceRollerApp()
com as anotações@Preview
e@Composable
.
Como esse app só tem um botão e uma imagem, ele pode ser visto como uma função de composição. É por isso que ele é chamado de função DiceRollerApp()
.
MainActivity.kt
@Preview
@Composable
fun DiceRollerApp() {
}
@Composable
fun DiceWithButtonAndImage() {
}
Como você removeu a função Greeting()
, a chamada para Greeting("Android")
no corpo da lambda DiceRollerTheme()
é destacada em vermelho. Isso ocorre porque o compilador não consegue mais encontrar uma referência a essa função.
- Exclua todo o código da lambda
setContent{}
encontrada no métodoonCreate()
. - No corpo da lambda
setContent{}
, chame a lambdaDiceRollerTheme{}
e, dentro da lambdaDiceRollerTheme{}
, chame a funçãoDiceRollerApp()
.
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DiceRollerTheme {
DiceRollerApp()
}
}
}
- Na função
DiceRollerApp()
, chame a funçãoDiceWithButtonAndImage()
.
MainActivity.kt
@Preview
@Composable
fun DiceRollerApp() {
DiceWithButtonAndImage()
}
Adicionar um modificador
O Compose usa um objeto Modifier
, que é uma coleção de elementos usados para decorar ou modificar o comportamento dos elementos da IU do Compose. Ele vai ser usado para definir o estilo dos componentes da interface do app Dice Roller.
Para adicionar um modificador:
- Modifique a função
DiceWithButtonAndImage()
para aceitar um argumentomodifier
do tipoModifier
e atribuir a ele um valor padrão deModifier
.
MainActivity.kt
@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
}
O snippet de código anterior pode ser confuso, então vamos explicar. A função permite que um parâmetro modifier
seja transmitido. O valor padrão do parâmetro modifier
é um objeto Modifier
, portanto usamos = Modifier
na assinatura do método. O valor padrão de um parâmetro permite que qualquer pessoa que chame esse método no futuro decida transmitir ou não um valor ao parâmetro. Se alguém transmitir o próprio objeto Modifier
, vai poder personalizar o comportamento e a decoração da IU. Se ela optar por não transmitir um objeto Modifier
, o valor padrão (que é o objeto Modifier
simples) vai ser usado. Você pode aplicar essa prática a qualquer parâmetro. Para mais informações sobre argumentos padrão, consulte Argumentos padrão (link em inglês).
- Agora que o elemento combinável
DiceWithButtonAndImage()
tem um parâmetro modificador, transmita um modificador quando o elemento for chamado. Como a assinatura do método para a funçãoDiceWithButtonAndImage()
mudou, um objetoModifier
com as decorações desejadas precisa ser transmitido quando for chamado. A classeModifier
é responsável pela decoração ou pela adição do comportamento a um elemento combinável na funçãoDiceRollerApp()
. Nesse caso, há algumas decorações importantes para adicionar ao objetoModifier
, que é transmitido à funçãoDiceWithButtonAndImage()
.
Você talvez queira saber por que se preocupar em transmitir um argumento Modifier
quando há um padrão. Isso acontece porque os elementos de composição podem passar por uma recomposição, o que significa que o bloco de código no método @Composable
é executado novamente. Se um objeto Modifier
for criado em um bloco de código, ele vai poder ser recriado, e isso não é eficiente. A recomposição será abordada mais adiante neste codelab.
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier)
- Encadeie um método
fillMaxSize()
no objetoModifier
para que o layout preencha toda a tela.
Esse método especifica que os componentes precisam preencher o espaço disponível. No início deste codelab, você conheceu uma captura de tela da interface final do app Dice Roller. Um recurso interessante é que os dados e o botão ficam centralizados na tela. O método wrapContentSize()
especifica que o espaço disponível precisa ser pelo menos tão grande quanto os componentes dentro dele. No entanto, como o método fillMaxSize()
é usado, se os componentes dentro do layout forem menores que o espaço disponível, um objeto Alignment
poderá ser transmitido ao método wrapContentSize()
que especifica como os componentes precisam se alinhar dentro do espaço disponível.
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier
.fillMaxSize()
)
- Encadeie o método
wrapContentSize()
no objetoModifier
e transmitaAlignment.Center
como um argumento para centralizar os componentes.Alignment.Center
especifica que um componente é centralizado na vertical e na horizontal.
MainActivity.kt
DiceWithButtonAndImage(modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
)
4. Criar um layout vertical
No Compose, os layouts verticais são criados com a função Column()
.
A função Column()
é um layout combinável que coloca os filhos em uma sequência vertical. No design esperado do app, é possível conferir que a imagem do dado é mostrada logo acima do botão de jogar:
Para criar um layout vertical:
- Na função
DiceWithButtonAndImage()
, adicione uma funçãoColumn()
.
- Transmita o argumento
modifier
da assinatura do métodoDiceWithButtonAndImage()
para o argumento do modificadorColumn()
.
O argumento modifier
garante que os elementos combináveis na função Column()
sigam as restrições chamadas na instância modifier
.
- Transmita um argumento
horizontalAlignment
para a funçãoColumn()
e defina-o com um valor deAlignment.CenterHorizontally
.
Isso garante que os filhos dentro da coluna fiquem centralizados na tela do dispositivo em relação à largura.
MainActivity.kt
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
Column (
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {}
}
5. Adicionar um botão
- No arquivo
strings.xml
, adicione uma string e defina-a como um valor deRoll
.
res/values/strings.xml
<string name="roll">Roll</string>
- No corpo da lambda da
Column()
, adicione uma funçãoButton()
.
- No arquivo
MainActivity.kt
, adicione uma funçãoText()
aoButton()
no corpo da lambda da função. - Transmita o ID do recurso da string
roll
à funçãostringResource()
e transmita o resultado ao elemento de composiçãoText
.
MainActivity.kt
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { /*TODO*/ }) {
Text(stringResource(R.string.roll))
}
}
6. Adicionar uma imagem
Outro componente essencial do app é a imagem do dado, que exibe o resultado quando o usuário toca no botão Roll. Você adiciona a imagem com um elemento de composição Image
, mas ela exige um recurso de imagem. Portanto, primeiro é necessário fazer o download de algumas imagens fornecidas para esse app.
Fazer o download das imagens de dados
- Abra este URL para fazer o download de um arquivo ZIP com imagens de dados e aguarde a conclusão.
Localize o arquivo no computador. É provável que esteja na pasta Downloads.
- Descompacte o arquivo ZIP para criar uma nova pasta
dice_images
que contém seis arquivos de imagem de dados com valores de 1 a 6.
Adicionar imagens de dados ao app
- No Android Studio, clique em View > Tool Windows > Resource Manager.
- Clique em + > Import Drawables para abrir um navegador de arquivos.
- Encontre e selecione a pasta com as seis imagens de dados e continue o upload.
As imagens enviadas aparecerão desta forma:
- Clique em Próxima.
A caixa de diálogo Import Drawables aparece e mostra onde os arquivos de recursos ficam na estrutura de arquivos.
- Clique em Import para confirmar que você quer importar as seis imagens.
As imagens vão aparecer no painel Resource Manager.
Bom trabalho! Na próxima tarefa, você vai usar essas imagens no app.
Adicionar um elemento combinável Image
A imagem do dado precisa aparecer acima do botão Roll. O Compose coloca naturalmente os componentes da IU de maneira sequencial. Em outras palavras, o elemento combinável é declarado e mostrado primeiro. Isso pode indicar que a primeira declaração é mostrada acima ou antes do elemento combinável declarado depois dela. Os elementos de composição dentro de um Column
vão ser exibidos acima / abaixo um do outro no dispositivo. Neste app, você usa uma Column
para empilhar os elementos combináveis na vertical. Portanto, o combinável que é declarado primeiro dentro da função Column()
é exibido antes dos combináveis que são declarados depois dele na mesma função Column()
.
Para adicionar um elemento combinável Image
:
- No corpo da função
Column()
, crie uma funçãoImage()
antes da funçãoButton()
.
MainActivity.kt
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image()
Button(onClick = { /*TODO*/ }) {
Text(stringResource(R.string.roll))
}
}
- Transmita um argumento
painter
à funçãoImage()
e atribua a ele um valorpainterResource
que aceite um argumento de ID de recurso drawable. Por enquanto, transmita este ID de recurso: argumentoR.drawable.dice_1
.
MainActivity.kt
Image(
painter = painterResource(R.drawable.dice_1)
)
- Sempre que você criar uma imagem no app, forneça o que é chamado de "descrição de conteúdo". As descrições de conteúdo são uma parte importante do desenvolvimento para Android. Elas anexam descrições aos respectivos componentes de IU para aumentar a acessibilidade. Para mais informações sobre as descrições de conteúdo, consulte Descrever cada elemento da IU. É possível transmitir uma descrição de conteúdo para a imagem como um parâmetro.
MainActivity.kt
Image(
painter = painterResource(R.drawable.dice_1),
contentDescription = "1"
)
Agora, todos os componentes de interface necessários estão presentes. No entanto, o Button
e a Image
estão perto demais.
- Para corrigir isso, adicione um elemento combinável
Spacer
entre os elementosImage
eButton
. UmSpacer
usa umModifier
como parâmetro. Nesse caso, aImage
está acima doButton
, de modo que precisa haver um espaço vertical entre eles. Portanto, a altura doModifier
pode ser definida e aplicada aoSpacer
. Tente definir a altura como16.dp
. Normalmente, as dimensões dp são mudadas em incrementos de4.dp
.
MainActivity.kt
Spacer(modifier = Modifier.height(16.dp))
- No painel Preview, clique em Build & Refresh.
Vai aparecer algo como esta imagem:
7. Criar a lógica para jogar dados
Agora que todos os elementos de composição necessários estão presentes, você vai modificar o app para que um toque no botão jogue o dado.
Tornar o botão interativo
- Na função
DiceWithButtonAndImage()
antes da funçãoColumn()
, crie uma variávelresult
e a defina como um valor1
. - Observe o elemento combinável
Button
. Você vai perceber que ele está transmitindo um parâmetroonClick
, que está definido como um par de chaves com o comentário/*TODO*/
dentro delas. Nesse caso, as chaves representam o que é conhecido como lambda e a área dentro delas é o corpo da lambda. Quando uma função é transmitida como um argumento, ela também pode ser chamada de "callback" (link em inglês).
MainActivity.kt
Button(onClick = { /*TODO*/ })
Um lambda é um literal de função, que é uma função como qualquer outra. No entanto, em vez de ser declarada separadamente com a palavra-chave fun
, ela é escrita in-line e transmitida como uma expressão. O elemento combinável Button
espera que uma função seja transmitida como o parâmetro onClick
. Esse é o lugar perfeito para usar um lambda. Você vai escrever o corpo do lambda nesta seção.
- Na função
Button()
, remova o comentário/*TODO*/
do valor do corpo da lambda do parâmetroonClick
. - A jogada do dado é aleatória. Para refletir isso no código, é preciso usar a sintaxe correta para gerar um número aleatório. Em Kotlin, você pode usar o método
random()
em um intervalo numérico. No corpo da lambdaonClick
, defina a variávelresult
como um intervalo entre 1 e 6 e, em seguida, chame o métodorandom()
nesse intervalo. Em Kotlin, os intervalos são designados por dois pontos finais entre o primeiro e o último número.
MainActivity.kt
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
var result = 1
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(R.drawable.dice_1),
contentDescription = "1"
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { result = (1..6).random() }) {
Text(stringResource(R.string.roll))
}
}
}
Agora, o botão pode ser tocado, mas um toque ainda não causa mudanças visuais porque você ainda precisa criar essa funcionalidade.
Adicionar uma condição ao app de jogar dados
Na seção anterior, você criou uma variável result
e a codificou como um valor de 1
. Por fim, o valor da variável result
é redefinido quando o usuário toca no botão Roll e a imagem que vai ser exibida é determinada.
Por padrão, os elementos combináveis são sem estado, o que significa que eles não contêm um valor e podem ser recompostos a qualquer momento pelo sistema, resultando na redefinição do valor. No entanto, o Compose oferece uma maneira prática de evitar isso. As funções combináveis podem armazenar um objeto na memória usando o elemento combinável remember
.
- Transforme a variável
result
em um elemento combinávelremember
.
O elemento combinável remember
exige que uma função seja transmitida.
- No corpo do elemento de composição
remember
, transmita uma funçãomutableStateOf()
e um argumento1
.
A função mutableStateOf()
retorna um elemento observável. Você vai aprender mais sobre os elementos observáveis no futuro, mas, por enquanto, isso significa que, quando o valor da variável result
muda, uma recomposição é acionada, o valor do resultado é refletido e a interface é atualizada.
MainActivity.kt
var result by remember { mutableStateOf(1) }
Agora, quando o usuário tocar no botão, a variável result
vai ser atualizada com um valor numérico aleatório.
A variável result
agora pode ser usada para determinar qual imagem vai aparecer.
- Abaixo da instanciação da variável
result
, crie uma variávelimageResource
imutável, definida como uma expressãowhen
, que aceite uma variávelresult
e defina cada resultado possível para o drawable.
MainActivity.kt
val imageResource = when (result) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
- Mude o ID transmitido ao parâmetro
painterResource
do elemento combinávelImage
do drawableR.drawable.dice_1
para a variávelimageResource
. - Mude o parâmetro
contentDescription
do elemento de composiçãoImage
para refletir o valor da variávelresult
. Para isso, converta a variávelresult
em uma string comtoString()
e transmita-a comocontentDescription
.
MainActivity.kt
Image(
painter = painterResource(imageResource),
contentDescription = result.toString()
)
- Execute o app.
O app Dice Roller vai funcionar totalmente agora.
8. Acessar o código da solução
Para baixar o código do codelab concluído, use este comando git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller.git
Se preferir, você pode baixar o 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).
- Navegue até a página do repositório do GitHub fornecida para o projeto.
- Verifique se o nome da ramificação corresponde ao especificado no codelab. Por exemplo, na captura de tela a seguir, o nome da ramificação é main.
- Na página do GitHub do projeto, clique no botão Code, que vai mostrar uma janela pop-up.
- Na janela pop-up, clique no botão Download ZIP para salvar o projeto no seu computador. Aguarde a conclusão do download.
- Localize o arquivo no computador. Geralmente ele é salvo na pasta Downloads.
- Clique duas vezes para descompactar o arquivo ZIP. Isso cria uma nova pasta com os arquivos do projeto.
Abrir o projeto no Android Studio
- Inicie o Android Studio.
- Na janela Welcome to Android Studio, clique em Open.
Observação: caso o Android Studio já esteja aberto, selecione a opção File > Open.
- No navegador de arquivos, vá até a pasta descompactada do projeto, que provavelmente está na pasta Downloads.
- Clique duas vezes nessa pasta do projeto.
- Aguarde o Android Studio abrir o projeto.
- Clique no botão Run para criar e executar o app. Confira se ele é criado da forma esperada.
9. Conclusão
Você criou um app Dice Roller interativo para Android com o Compose.
Resumo
- Defina funções de composição.
- Crie layouts com composições.
- Crie um botão com o elemento combinável
Button
. - Importe recursos
drawable
. - Mostre uma imagem com o elemento combinável
Image
. - Use elementos combináveis para fazer uma interface interativa.
- Use o elemento combinável
remember
para armazenar objetos em uma composição na memória. - Atualize a interface com a função
mutableStateOf()
para criar um elemento observável.