Interoperabilidade de visualização no Compose

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:

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.

8add99ed38670183.png

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

  1. No Android Studio, abra a pasta basic-android-kotlin-compose-training-juice-tracker.
  2. 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:

d5f824bc631bfa2a.png

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.

  1. No diretório bottomsheet, crie um arquivo com o nome ColorSpinnerRow.kt.
  2. Crie uma classe dentro do arquivo chamado SpinnerAdapter.
  3. No construtor do SpinnerAdapter, defina um parâmetro de callback com o nome onColorChange que usa um parâmetro Int. O SpinnerAdapter processa as funções de callback do Spinner.

bottomsheet/ColorSpinnerRow.kt

class SpinnerAdapter(val onColorChange: (Int) -> Unit){
}
  1. 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 {
}
  1. Implemente as funções de membro AdapterView.OnItemSelectedListener: onItemSelected() e onNothingSelected().

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")
    }
}
  1. Modifique a função onItemSelected() para chamar a função de callback onColorChange(). Assim, quando você selecionar uma cor, o app vai atualizar o valor selecionado na interface.
  2. Modifique a função onNothingSelected() para definir a cor como JuiceColor.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.

  1. Dentro do arquivo ColorSpinnerRow.kt, mas fora da classe SpinnerAdapter, crie um elemento combinável chamado ColorSpinnerRow.
  2. Na assinatura do método de ColorSpinnerRow(), adicione um parâmetro Int para a posição do ícone de carregamento, uma função de callback que usa um parâmetro Int e um modificador.

bottomsheet/ColorSpinnerRow.kt

...
@Composable
fun ColorSpinnerRow(
    colorSpinnerPosition: Int,
    onColorChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
}
  1. 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) }

}
  1. 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 o Spinner 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.

  1. Para usar um Spinner no Compose, crie um elemento combinável AndroidView() no corpo da lambda InputRow. O AndroidView() 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 na factory é inflada.
  • Um elemento combinável modifier.

3bb9f605719b173.png

  1. Para implementar o AndroidView, comece transmitindo um modificador e preenchendo a largura máxima da tela.
  2. Transmita uma lambda para o parâmetro factory.
  3. A lambda factory usa um Context como parâmetro. Crie uma classe Spinner 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.

  1. Defina o adaptador usando um ArrayAdapter. O ArrayAdapter exige um contexto, um layout XML e uma matriz. Transmita simple_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.

  1. Adicione um callback update que transmita um spinner.
  2. Use o callback fornecido em update para chamar o método setSelection().
  3. Use o SpinnerAdapter criado anteriormente para definir um callback onItemSelectedListener() no update.

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.

  1. Implemente ColorSpinnerRow no elemento combinável SheetForm no arquivo EntryBottomSheet.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

  1. Crie um arquivo com o nome RatingInputRow.kt no diretório bottomsheet.
  2. No arquivo RatingInputRow.kt, crie um elemento combinável com o nome RatingInputRow().
  3. Na assinatura do método, transmita um Int para a nota, um callback com um parâmetro Int 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){
}
  1. Como na ColorSpinnerRow, adicione uma InputRow ao elemento combinável que contém um AndroidView, 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 = {}
        )
    }
}
  1. Defina o modificador padding como 40.dp.
  2. No corpo da lambda factory, crie uma instância da classe RatingBar, que fornece o tipo de barra de nota necessária para este design. Defina o stepSize como 1f 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.

  1. Use a nota transmitida ao elemento combinável para definir a nota da instância RatingBar no corpo da lambda update.
  2. Quando uma nova nota for definida, use o callback RatingBar para chamar a função de callback onRatingChange() 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.

  1. Use o elemento combinável RatingInputRow() no EntryBottomSheet. 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

  1. No pacote homescreen, crie um arquivo com o nome AdBanner.kt.
  2. No arquivo AdBanner.kt, crie um elemento combinável chamado AdBanner().

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.

  1. Tente criar o banner por conta própria usando a classe AdView. Defina o tamanho do anúncio como AdSize.BANNER e o ID do bloco de anúncios como "ca-app-pub-3940256099942544/6300978111".
  2. Quando a AdView for inflada, carregue um anúncio usando a AdRequest 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())
        }
    )
}
  1. Coloque o AdBanner antes da JuiceTrackerList no JuiceTrackerApp. A JuiceTrackerList é 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!