1. Antes de começar
Introdução
Neste ponto do curso, você já sabe criar apps com o Compose e tem conhecimento para fazer essa criação com XML, visualizações, vinculações de visualização e fragmentos. Depois de criar apps com visualizações, talvez você comece a gostar da conveniência de uma interface declarativa, como o Compose. No entanto, há alguns casos em que faz sentido usar visualizações em vez do Compose. Neste codelab, você vai aprender a usar as interoperabilidades de visualização para adicionar componentes de visualização a um app moderno do Compose.
Pré-requisitos:
- Concluir o curso Noções básicas do Android com o Compose no codelab Criar um app Android com visualizações.
O que é necessário
- Um computador com acesso à Internet e o Android Studio.
- Um dispositivo ou emulador.
- O código inicial do app Juice Tracker.
O que você vai criar
Neste codelab, você vai integrar três visualizações à interface do Compose para concluir a do app Juice Tracker. As visualizações vão ser chamadas de "Spinner", "RatingBar" e "AdView". Para criar esses componentes, use a interoperabilidade de visualização. Com ela, você pode adicionar visualizações ao seu app as envolvendo em um elemento combinável. No momento da elaboração deste codelab, os componentes da interface que você quer criar ainda não estão disponíveis no Compose. Esta é a oportunidade perfeita para usar a interoperabilidade de visualização.
Tutorial do código
Neste codelab, você vai trabalhar com o mesmo app Juice Tracker dos codelabs Criar um app Android com visualizações e Adicionar o Compose a um app baseado em visualização. A diferença desta versão é que o código inicial fornecido está todo no Compose. No momento, o app não tem as entradas de cor e nota na página da caixa de diálogo de registro e no banner de anúncio na parte de cima da tela da lista.
O diretório bottomsheet
contém todos os componentes da interface relacionados à caixa de diálogo de registro. Esse pacote precisa conter os componentes da interface para as entradas de cor e nota, quando criados.
A homescreen
contém os componentes da interface hospedados pela tela inicial, que inclui a lista JuiceTracker. Esse pacote precisa conter o banner de anúncio, quando criado.
Os principais componentes da interface, como a página inferior e a lista de sucos, estão hospedados no arquivo JuiceTrackerApp.kt
.
2. Acessar o código inicial
- No Android Studio, abra a pasta
basic-android-kotlin-compose-training-juice-tracker
. - Abra o código do app Juice Tracker no Android Studio.
3. Configuração do Gradle
Adicione a dependência de anúncios do Google Play Services ao arquivo build.gradle
do app.
app/build.gradle
android {
...
dependencies {
...
implementation "com.google.android.gms:play-services-ads:21.3.0"
}
}
4. Configurar
Adicione o seguinte valor ao manifesto do Android, acima da tag activity
, para ativar o banner de anúncio para testes:
AndroidManifest.xml
...
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
...
5. Preencher a caixa de diálogo de registro
Nesta seção, você vai preencher a caixa de diálogo de registro criando o ícone de carregamento de cores e a barra de nota. O ícone de carregamento de cores é o componente que permite escolher uma cor, e a barra de nota permite dar uma nota para o suco. Confira o design abaixo:
Criar o ícone de carregamento de cores
Para implementar um ícone de carregamento no Compose, temos que usar a classe Spinner
. A Spinner
é um componente de visualização, e não um elemento combinável. Por isso, a implementação precisa usar interoperabilidade.
- No diretório
bottomsheet
, crie um arquivo com o nomeColorSpinnerRow.kt
. - Crie uma classe dentro do arquivo chamado
SpinnerAdapter
. - No construtor do
SpinnerAdapter
, defina um parâmetro de callback com o nomeonColorChange
que usa um parâmetroInt
. OSpinnerAdapter
processa as funções de callback doSpinner
.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
- Implemente a interface
AdapterView.OnItemSelectedListener
.
A implementação dessa interface permite definir o comportamento de clique do ícone de carregamento. Você vai configurar esse adaptador em um elemento combinável mais tarde.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
}
- Implemente as funções de membro
AdapterView.OnItemSelectedListener
:onItemSelected()
eonNothingSelected()
.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
TODO("Not yet implemented")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
- Modifique a função
onItemSelected()
para chamar a função de callbackonColorChange()
. Assim, quando você selecionar uma cor, o app vai atualizar o valor selecionado na interface. - Modifique a função
onNothingSelected()
para definir a cor comoJuiceColor.Red.ordinal
. Assim, quando você não selecionar nada, a cor padrão será o vermelho.
bottomsheet/ColorSpinnerRow.kt
class SpinnerAdapter(val onColorChange: (Int) -> Unit): AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
onColorChange(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
onColorChange(JuiceColor.Red.ordinal)
}
}
O SpinnerAdapter
, que define o comportamento do ícone de carregamento com as funções de callback, já foi criado. Agora, você precisa criar o conteúdo do ícone de carregamento e preenchê-lo com dados.
- Dentro do arquivo
ColorSpinnerRow.kt
, mas fora da classeSpinnerAdapter
, crie um elemento combinável chamadoColorSpinnerRow
. - Na assinatura do método de
ColorSpinnerRow()
, adicione um parâmetroInt
para a posição do ícone de carregamento, uma função de callback que usa um parâmetroInt
e um modificador.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
}
- Dentro da função, crie uma matriz dos recursos de string de cores dos sucos usando o tipo enumerado
JuiceColor
. Essa matriz serve como o conteúdo que vai preencher o ícone de carregamento.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
}
- Adicione um elemento combinável
InputRow()
e transmita o recurso de string de cores para o rótulo de entrada e um modificador, que define a linha de entrada em que oSpinner
aparece.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
}
}
Chegou a hora de criar o Spinner
. Como o Spinner
é uma classe de visualização, a API de interoperabilidade de visualização do Compose precisa ser usada para o envolver em um elemento combinável. Isso pode ser feito com o combinável AndroidView
.
- Para usar um
Spinner
no Compose, crie um elemento combinávelAndroidView()
no corpo da lambdaInputRow
. OAndroidView()
cria um elemento ou uma hierarquia de visualização em um combinável.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val juiceColorArray =
JuiceColor.values().map { juiceColor -> stringResource(juiceColor.label) }
InputRow(inputLabel = stringResource(R.string.color), modifier = modifier) {
AndroidView()
}
}
O elemento combinável AndroidView
usa três parâmetros:
- A lambda
factory
, que é uma função que cria a visualização. - O callback
update
, que é chamado quando a visualização criada nafactory
é inflada. - Um elemento combinável
modifier
.
- Para implementar o
AndroidView
, comece transmitindo um modificador e preenchendo a largura máxima da tela. - Transmita uma lambda para o parâmetro
factory
. - A lambda
factory
usa umContext
como parâmetro. Crie uma classeSpinner
e transmita o contexto.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context)
}
)
}
}
Assim como um RecyclerView.Adapter
fornece dados a um RecyclerView
, um ArrayAdapter
fornece dados a um Spinner
. O Spinner
exige um adaptador para armazenar a matriz de cores.
- Defina o adaptador usando um
ArrayAdapter
. OArrayAdapter
exige um contexto, um layout XML e uma matriz. Transmitasimple_spinner_dropdown_item
para o layout, que é fornecido como padrão com o Android.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context).apply {
adapter =
ArrayAdapter(
context,
android.R.layout.simple_spinner_dropdown_item,
juiceColorArray
)
}
}
)
}
}
O callback factory
retorna uma instância da visualização criada dentro dele. O update
é um callback que usa um parâmetro do mesmo tipo retornado pelo callback factory
. Esse parâmetro é uma instância da visualização inflada por factory
. Nesse caso, como um Spinner
foi criado na fábrica, a instância desse Spinner
pode ser acessada no corpo da lambda update
.
- Adicione um callback
update
que transmita umspinner
. - Use o callback fornecido em
update
para chamar o métodosetSelection()
. - Use o
SpinnerAdapter
criado anteriormente para definir um callbackonItemSelectedListener()
noupdate
.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
Spinner(context).apply {
adapter =
ArrayAdapter(
context,
android.R.layout.simple_spinner_dropdown_item,
juiceColorArray
)
}
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
O código para o componente do ícone de carregamento de cores foi concluído.
- Implemente
ColorSpinnerRow
no elemento combinávelSheetForm
no arquivoEntryBottomSheet.kt
. Coloque o ícone de carregamento de cores após o texto "Description" e acima dos botões.
bottomsheet/EntryBottomSheet.kt
...
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
...
TextInputRow(
inputLabel = stringResource(R.string.juice_description),
fieldValue = juice.description,
onValueChange = { description -> onUpdateJuice(juice.copy(description = description)) }
)
ColorSpinnerRow(
colorSpinnerPosition = JuiceColor.valueOf(juice.color).ordinal,
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
ButtonRow(
modifier = Modifier.align(Alignment.CenterHorizontally),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
Criar a entrada de nota
- Crie um arquivo com o nome
RatingInputRow.kt
no diretóriobottomsheet
. - No arquivo
RatingInputRow.kt
, crie um elemento combinável com o nomeRatingInputRow()
. - Na assinatura do método, transmita um
Int
para a nota, um callback com um parâmetroInt
para processar uma mudança de seleção e um modificador.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChanged: (Int) -> Unit, modifier: Modifier = Modifier){
}
- Como na
ColorSpinnerRow
, adicione umaInputRow
ao elemento combinável que contém umAndroidView
, conforme mostrado no código de exemplo a seguir.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChanged: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
modifier = Modifier,
factory = {},
update = {}
)
}
}
- Defina o modificador
padding
como40.dp
. - No corpo da lambda
factory
, crie uma instância da classeRatingBar
, que fornece o tipo de barra de nota necessária para este design. Defina ostepSize
como1f
para que a nota sempre seja um número inteiro.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChanged: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
modifier = Modifier.padding(40.dp),
factory = { context ->
RatingBar(context).apply {
stepSize = 1f
}
},
update = {}
)
}
}
Quando a visualização é inflada, a nota é definida. Não se esqueça que factory
retorna a instância de RatingBar
ao callback de atualização.
- Use a nota transmitida ao elemento combinável para definir a nota da instância
RatingBar
no corpo da lambdaupdate
. - Quando uma nova nota for definida, use o callback
RatingBar
para chamar a função de callbackonRatingChange()
e atualizar a nota na interface.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChanged: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
modifier = Modifier.padding(40.dp),
factory = { context ->
RatingBar(context).apply {
stepSize = 1f
}
},
update = { ratingBar ->
ratingBar.rating = rating.toFloat()
ratingBar.setOnRatingBarChangeListener { _, _, _ ->
onRatingChange(ratingBar.rating.toInt())
}
}
)
}
}
O elemento combinável de entrada de nota foi concluído.
- Use o elemento combinável
RatingInputRow()
noEntryBottomSheet
. Posicione-o depois do ícone de carregamento de cores e acima dos botões.
bottomsheet/AdBanner.kt
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier.padding(horizontal = 16.dp)) {
...
ColorSpinnerRow(
colorSpinnerPosition = JuiceColor.valueOf(juice.color).ordinal,
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
RatingInputRow(
rating = juice.rating,
onRatingChange = { rating -> onUpdateJuice(juice.copy(rating = rating)) }
)
ButtonRow(
modifier = Modifier.align(Alignment.CenterHorizontally),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
}
Criar o banner de anúncio
- No pacote
homescreen
, crie um arquivo com o nomeAdBanner.kt
. - No arquivo
AdBanner.kt
, crie um elemento combinável chamadoAdBanner()
.
Ao contrário dos elementos combináveis anteriores, o AdBanner
não exige uma entrada. Não é necessário envolver essa função em um elemento combinável InputRow
. No entanto, ela exige uma AndroidView
.
- Tente criar o banner por conta própria usando a classe
AdView
. Defina o tamanho do anúncio comoAdSize.BANNER
e o ID do bloco de anúncios como"ca-app-pub-3940256099942544/6300978111"
. - Quando a
AdView
for inflada, carregue um anúncio usando aAdRequest Builder
.
homescreen/AdBanner.kt
@Composable
fun AdBanner(modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier.fillMaxWidth(),
factory = { context ->
AdView(context).apply {
setAdSize(AdSize.BANNER)
// Use test ad unit ID
adUnitId = "ca-app-pub-3940256099942544/6300978111"
}
},
update = { adView ->
adView.loadAd(AdRequest.Builder().build())
}
)
}
- Coloque o
AdBanner
antes daJuiceTrackerList
noJuiceTrackerApp
. AJuiceTrackerList
é declarada na linha 83.
ui/JuiceTrackerApp.kt
...
AdBanner(modifier.padding(top = 16.dp))
JuiceTrackerList(
juices = trackerState,
onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
onUpdate = { juice ->
juiceTrackerViewModel.updateCurrentJuice(juice)
scope.launch {
sheetState.show()
}
},
)
6. Acessar o código da solução
Para fazer o download do código do codelab concluído, use estes comandos git:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout compose-with-views
Se preferir, você pode fazer o download do repositório como um arquivo ZIP, descompactar e abrir no Android Studio.
Se você quiser ver o código da solução, acesse o GitHub (link em inglês).
7. Saiba mais
8. Conseguimos!
O curso pode terminar aqui, mas este é apenas o começo da sua jornada de desenvolvimento de apps Android.
Neste curso, você aprendeu a criar apps usando o Jetpack Compose, um kit de ferramentas de interface moderno para criar apps Android nativos. Além disso, você criou apps com listas, uma ou várias telas e navegou entre elas. Você aprendeu a criar apps interativos, fez o app responder à entrada do usuário e atualizou a interface. Você aplicou o Material Design e usou cores, formas e tipografia no tema do app. Você também usou o Jetpack e outras bibliotecas de terceiros para agendar tarefas, extrair dados de servidores remotos, manter dados localmente e muito mais.
Ao concluir este curso, você não só entende muito bem como criar apps lindos e responsivos usando o Jetpack Compose, como também tem o conhecimento e as habilidades necessárias para criar apps Android eficientes, fáceis de manter e visualmente atrativos. Essa base vai ajudar você a continuar aprendendo e desenvolvendo suas habilidades no Modern Android Development e no Compose.
Agradecemos a todos pela participação e conclusão do curso. Incentivamos que continuem aprendendo e ampliando suas habilidades com outros recursos, como os documentos da página Desenvolvedores Android, o curso Jetpack Compose para desenvolvedores Android, a documentação Arquitetura moderna de apps Android, o Blog de desenvolvedores Android (link em inglês), outros codelabs e projetos de exemplo (link em inglês).
Por fim, não se esqueça de compartilhar nas mídias sociais o que você criou e usar a hashtag #AndroidBasics para que nós e o restante da comunidade de desenvolvedores Android também possamos acompanhar sua jornada de aprendizado.
Divirta-se!