Tutorial

Noções básicas do Jetpack Compose

O Jetpack Compose é um kit de ferramentas moderno para criar uma IU nativa do Android. O Jetpack Compose simplifica e acelera o desenvolvimento de IUs no Android com menos código, ferramentas poderosas e APIs Kotlin intuitivas.

Neste tutorial, você criará um componente de IU simples com funções declarativas. Você não editará nenhum layout XML nem criará os widgets da IU diretamente. Em vez disso, você chamará funções do Jetpack Compose para dizer quais elementos quer, e o compilador do Compose fará o restante.

Visualização completa
Observação: o Jetpack Compose está atualmente na Visualização do desenvolvedor. A superfície da API ainda não foi finalizada e não pode ser usada em apps de produção.
Visualização completa

Lição 1: funções que podem ser compostas

O Jetpack Compose foi criado com base em funções que podem ser compostas. Essas funções permitem que você defina a IU do app programaticamente, descrevendo as dependências de dados e de formas dela em vez de se concentrar no processo de construção da IU. Para criar uma função que pode ser composta, basta adicionar a anotação @Composable ao nome da função.

Adicionar um elemento de texto

Para começar, siga as Instruções de configuração do Jetpack Compose e crie um app usando o modelo Atividade vazia do Compose. O modelo padrão já contém alguns elementos do Compose, mas vamos criá-lo passo a passo. Primeiro, exclua as funções "Saudação" e "Visualização padrão" e remova o bloco setContent de MainActivity, deixando a atividade em branco. Compile e execute seu app em branco.

Agora, adicione um elemento de texto à sua atividade em branco. Para fazer isso, defina um bloco de conteúdo e chame a função Text().

O bloco setContent define o layout da atividade. Em vez de definir o conteúdo do layout com um arquivo XML, chamamos funções que podem ser compostas. O Jetpack Compose usa um plug-in personalizado do compilador Kotlin para transformar essas funções que podem ser compostas nos elementos de IU do app. Por exemplo, a função Text() é definida pela biblioteca de IU do Compose. Chame essa função para declarar um elemento de texto no app.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar visualização
ocultar visualização

Definir uma função que pode ser composta

As funções que podem ser compostas só podem ser chamadas de dentro do escopo de outras funções desse tipo. Para tornar uma função composta, adicione a anotação @Composable. Para testar isso, defina uma função Greeting() que recebe um nome e usa esse nome para configurar o elemento de texto.

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
mostrar visualização
ocultar visualização

Visualizar a função no Android Studio

O build Canary atual do Android Studio permite visualizar suas funções compostas no ambiente de desenvolvimento integrado, em vez de fazer o download do app para um dispositivo ou emulador Android. A principal restrição é que a função composta não pode assumir nenhum parâmetro. Por isso, não é possível visualizar a função Greeting() diretamente. Em vez disso, crie uma segunda função chamada PreviewGreeting(), que chama Greeting() com um parâmetro adequado. Adicione a anotação @Preview antes de @Composable.

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
mostrar visualização
ocultar visualização

Recrie o projeto. O app em si não muda, porque a nova função previewGreeting() não é chamada em nenhum lugar, mas o Android Studio adiciona uma janela de visualização. Essa janela mostra uma visualização dos elementos da IU criados pela função que pode ser composta marcada com a anotação @Preview. Para atualizar as visualizações a qualquer momento, clique no botão Atualizar na parte superior da janela de visualização.

Figura 1. Como usar o Android Studio para visualizar funções que podem ser compostas.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar visualização
ocultar visualização
class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
mostrar visualização
ocultar visualização
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
mostrar visualização
ocultar visualização
Figura 1. Como usar o Android Studio para visualizar funções que podem ser compostas.

Lição 2: layouts

Os elementos da IU são hierárquicos, com elementos contidos em outros. No Compose, você cria uma hierarquia de IU chamando funções que podem ser compostas a partir de outras funções desse tipo.

Começar com texto

Volte para a atividade e substitua a função Greeting() por uma nova função NewsStory(). No restante do tutorial, você modificará a função NewsStory() e não mexerá mais no código Activity.

É uma prática recomendada criar funções de visualização separadas que não sejam chamadas pelo app. Ter funções de visualização dedicadas melhora o desempenho e também facilita a configuração de várias visualizações posteriormente. Por isso, crie uma função de visualização padrão que só chame a função NewsStory(). À medida que você modifica NewsStory() seguindo este tutorial, a visualização apresenta as mudanças.

Esse código cria três elementos de texto dentro da visualização do conteúdo. No entanto, como não fornecemos nenhuma informação sobre como organizá-los, os elementos de texto são desenhados uns sobre os outros, o que torna o texto ilegível.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
mostrar visualização
ocultar visualização

Como usar uma coluna

A função Column permite empilhar elementos verticalmente. Adicione Column à função NewsStory().

As configurações padrão empilham todos os filhos diretamente, um após o outro, sem espaçamento. A coluna é posicionada no canto superior esquerdo da visualização do conteúdo.

@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização

Adicionar configurações de estilo à coluna

Ao transmitir parâmetros para a chamada de Column, é possível configurar o tamanho e a posição da coluna e de que maneira os filhos dela são organizados.

A configuração tem o seguinte significado:

  • modifier: permite que você configure o layout. Nesse caso, aplique um modificador Modifier.padding, que insere a coluna da visualização circundante.
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização

Adicionar uma imagem

Queremos adicionar um gráfico acima do texto. Use o Gerenciador de recursos para adicionar essa imagem aos recursos desenháveis do app, com o nome header.

Agora, modifique a função NewsStory(). Você adicionará uma chamada a Image() para colocar o gráfico no Column. A proporção da imagem ficará incorreta, mas isso não é um problema. Você corrigirá isso na próxima etapa.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização

O gráfico é adicionado ao layout, mas ainda não foi dimensionado adequadamente. Para definir o estilo do gráfico, transmita um Modifier de tamanho para a chamada de Image().

  • preferredHeightIn(maxHeight = 180.dp): especifica a altura máxima da imagem.
  • fillMaxWidth(): especifica que a imagem precisa ser larga o suficiente para preencher o layout ao qual pertence.

Você também precisa transmitir um parâmetro contentScale para Image():

  • contentScale = ContentScale.Crop: especifica que o gráfico precisa preencher a largura da coluna e ser recortado, se necessário, na altura apropriada.
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        HeightSpacer(16.dp)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização

Adicione um Spacer para separar o elemento gráfico dos títulos.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        HeightSpacer(16.dp)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização

Lição 3: Material Design

O Compose foi criado para oferecer compatibilidade com os princípios do Material Design. Muitos dos elementos de IU implementam o Material Design por padrão. Nesta lição, você estilizará seu app com widgets do Material Design.

Aplicar uma forma

Um dos princípios do sistema do Material Design é Shape. Use a função clip() para arredondar os cantos da imagem.

Shape é invisível, mas como o elemento gráfico é cortado para caber em Shape, agora ele tem cantos levemente arredondados.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização

Estilizar o texto

O Compose facilita o uso dos princípios do Material Design. Aplique MaterialTheme aos componentes que você criou.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
mostrar visualização
ocultar visualização

As mudanças são sutis, mas o texto agora usa o estilo de texto padrão de MaterialTheme. Em seguida, aplique estilos de parágrafo específicos a cada elemento do texto.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar visualização
ocultar visualização

Nesse caso, o título do artigo era curto. No entanto, pode ser que um artigo tenha um título longo, e não queremos que isso estrague a aparência do app. Tente mudar o primeiro elemento do texto.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar visualização
ocultar visualização

Configure o elemento de texto para definir um comprimento máximo de duas linhas. A configuração não terá efeito se o texto for curto o suficiente para caber nesse limite, mas o texto exibido será truncado automaticamente se for muito longo.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
        ) {
        val imageModifier = Modifier
            .preferredHeightIn(maxHeight = 180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar visualização
ocultar visualização
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
            ) {
            val imageModifier = Modifier
                .preferredHeightIn(maxHeight = 180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
mostrar visualização
ocultar visualização

Tudo pronto

Bom trabalho! Você aprendeu as noções básicas do Compose.

Conteúdo abordado:

  • Como definir funções que podem ser compostas
  • Como usar e estilizar colunas para melhorar o layout
  • Como estilizar seu app com os princípios do Material Design

Se quiser ir além em alguma dessas etapas, dê uma olhada no codelab de Noções básicas do Jetpack Compose (link em inglês).

Agora que você aprendeu o básico do Compose, configure seu ambiente para que você possa testá-lo por conta própria.

Recursos

Amostra

Com demonstrações de vários componentes, o app de amostra Jetnews traz exemplos de código ativo para desenvolvedores interessados em entender como usar o Compose em um app.

Referência

Analise os documentos para saber mais sobre as APIs Jetpack Compose mencionadas neste tutorial:

Guia

O Jetpack Compose foi projetado com o Material Design por padrão. As diretrizes do Material Design abordam tudo o que você precisa saber sobre como criar seu app: desde o fluxo da experiência do usuário até o design visual, o movimento, as fontes e muito mais.