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.
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.
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 interface do app Juice Tracker: uma Spinner, uma RatingBar e uma AdView. Para criar esses componentes, use a interoperabilidade de visualização. Com ela, você pode adicionar visualizações ao seu app envolvendo-as em um elemento combinável.

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
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-juice-tracker.git $ cd basic-android-kotlin-compose-training-juice-tracker $ git checkout compose-starter
- 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.kts do app.
app/build.gradle.kts
android {
...
dependencies {
...
implementation("com.google.android.gms:play-services-ads:22.2.0")
}
}
4. Configuração
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 nomeonColorChangeque usa um parâmetroInt. OSpinnerAdapterprocessa 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.
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<*>?) {
TODO("Not yet implemented")
}
}
- Modifique a função
onNothingSelected()para definir a cor como0. Assim, quando você não selecionar nada, a cor padrão será a primeira, vermelha.
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(0)
}
}
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âmetroIntpara a posição do ícone de carregamento, uma função de callback que usa um parâmetroInte 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 oSpinneraparece.
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) {
}
}
Em seguida, crie 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
Spinnerno 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
factoryusa umContextcomo parâmetro. Crie uma classeSpinnere 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. OArrayAdapterexige um contexto, um layout XML e uma matriz. Transmitasimple_spinner_dropdown_itempara 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
updateque transmita umspinner. Use o callback fornecido emupdatepara chamar o métodosetSelection().
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
//...
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
- Use o
SpinnerAdaptercriado anteriormente para definir um callbackonItemSelectedListener()noupdate.
bottomsheet/ColorSpinnerRow.kt
...
@Composable
fun ColorSpinnerRow(
colorSpinnerPosition: Int,
onColorChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
...
InputRow(...) {
AndroidView(
// ...
},
update = { spinner ->
spinner.setSelection(colorSpinnerPosition)
spinner.onItemSelectedListener = SpinnerAdapter(onColorChange)
}
)
}
}
O código para o componente do ícone de carregamento de cores foi concluído.
- Adicione a seguinte função utilitária para conferir o índice de tipo enumerado de
JuiceColor. Você usará isso na próxima etapa.
private fun findColorIndex(color: String): Int {
val juiceColor = JuiceColor.valueOf(color)
return JuiceColor.values().indexOf(juiceColor)
}
- Implemente
ColorSpinnerRowno elemento combinávelSheetFormno 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)) },
modifier = Modifier.fillMaxWidth()
)
ColorSpinnerRow(
colorSpinnerPosition = findColorIndex(juice.color),
onColorChange = { color ->
onUpdateJuice(juice.copy(color = JuiceColor.values()[color].name))
}
)
ButtonRow(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = dimensionResource(R.dimen.padding_medium)),
onCancel = onCancel,
onSubmit = onSubmit,
submitButtonEnabled = juice.name.isNotEmpty()
)
}
}
Criar a entrada de nota
- Crie um arquivo com o nome
RatingInputRow.ktno diretóriobottomsheet. - No arquivo
RatingInputRow.kt, crie um elemento combinável com o nomeRatingInputRow(). - Na assinatura do método, transmita um
Intpara a nota, um callback com um parâmetroIntpara processar uma mudança de seleção e um modificador.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
}
- Como na
ColorSpinnerRow, adicione umaInputRowao elemento combinável que contém umAndroidView, conforme mostrado no código de exemplo a seguir.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
factory = {},
update = {}
)
}
}
- 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 ostepSizecomo1fpara que a nota sempre seja um número inteiro.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
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
RatingBarno corpo da lambdaupdate. - Quando uma nova nota for definida, use o callback
RatingBarpara chamar a função de callbackonRatingChange()e atualizar a nota na interface.
bottomsheet/RatingInputRow.kt
@Composable
fun RatingInputRow(rating:Int, onRatingChange: (Int) -> Unit, modifier: Modifier = Modifier){
InputRow(inputLabel = stringResource(R.string.rating), modifier = modifier) {
AndroidView(
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/EntryBottomSheet.kt
@Composable
fun SheetForm(
juice: Juice,
onUpdateJuice: (Juice) -> Unit,
onCancel: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
...
ColorSpinnerRow(
colorSpinnerPosition = findColorIndex(juice.color),
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.BANNERe o ID do bloco de anúncios como"ca-app-pub-3940256099942544/6300978111". - Quando a
AdViewfor inflada, carregue um anúncio usando aAdRequest Builder.
homescreen/AdBanner.kt
@Composable
fun AdBanner(modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier,
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
AdBannerantes daJuiceTrackerListnoJuiceTrackerApp. AJuiceTrackerListé declarada na linha 83.
ui/JuiceTrackerApp.kt
...
AdBanner(
Modifier
.fillMaxWidth()
.padding(
top = dimensionResource(R.dimen.padding_medium),
bottom = dimensionResource(R.dimen.padding_small)
)
)
JuiceTrackerList(
juices = trackerState,
onDelete = { juice -> juiceTrackerViewModel.deleteJuice(juice) },
onUpdate = { juice ->
juiceTrackerViewModel.updateCurrentJuice(juice)
scope.launch {
bottomSheetScaffoldState.bottomSheetState.expand()
}
},
)
6. Acessar o código da solução
Para baixar o 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 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).
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!