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. 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.
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â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) {
}
}
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
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 emupdate
para 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
SpinnerAdapter
criado 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
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)) },
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.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, onRatingChange: (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, 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 ostepSize
como1f
para 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
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, 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.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,
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
.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 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!