Tutorial

Tutorial do Jetpack Compose

O Jetpack Compose é um toolkit moderno para criação de interface nativa do Android. Ele simplifica e acelera o desenvolvimento de IUs no Android com menos código, ferramentas com a mais alta tecnologia e APIs Kotlin intuitivas.

Neste tutorial, você vai criar um componente de interface simples com funções declarativas. Você não vai editar nenhum layout XML nem usar o Layout Editor. Em vez disso, você chamará funções de composição para definir quais elementos quer usar e o compilador do Compose fará o restante.

Visualização completa
Visualização completa

Lição 1: funções de composição

O Jetpack Compose foi criado com base em funções que podem ser compostas. Essas funções permitem que você defina a interface do app de maneira programática, descrevendo as dependências de dados e de formas dela, em vez de se concentrar no processo de construção da interface (inicializando um elemento, anexando esse elemento a um pai etc.). 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, faça o download da versão mais recente do Android Studio, crie um app selecionando New Project e, na categoria Phone and Tablet, selecione Empty Activity. Nomeie o app como ComposeTutorial e clique em Finish. O modelo padrão já contém alguns elementos do Compose, mas neste tutorial você vai criar um novo por etapas.

Primeiro, vamos mostrar a mensagem "Hello world!" adicionando um elemento de texto ao método onCreate. Para fazer isso, defina um bloco de conteúdo e chame a função de composição Text. O bloco setContent define o layout da atividade em que as funções de composição são chamadas. Elas só podem ser chamadas usando outras funções desse tipo.

O Jetpack Compose usa um plug-in do compilador Kotlin para transformar essas funções de composição nos elementos de IU do app. Por exemplo, a função Text que é definida pela biblioteca de IU do Compose mostra um identificador de texto na tela.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar prévia
ocultar prévia

Definir uma função que pode ser composta

Para tornar uma função composta, adicione a anotação @Composable. Para testar isso, defina uma função MessageCard que recebe um nome e o usa para configurar o elemento de texto.

// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

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

  
mostrar prévia
ocultar prévia

Visualizar a função no Android Studio

A anotação @Preview permite visualizar as funções de composição no Android Studio sem precisar criar e instalar o app em um emulador ou dispositivo Android. A anotação precisa ser usada em uma função de composição que não use parâmetros. Por esse motivo, não é possível visualizar a função MessageCard diretamente. Em vez disso, crie uma segunda função nomeada como PreviewMessageCard, que chama MessageCard com um parâmetro adequado. Adicione a anotação @Preview antes da @Composable.

// ...
import androidx.compose.ui.tooling.preview.Preview

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

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
mostrar prévia
ocultar prévia

Recrie seu projeto. O app em si não muda, porque a nova função PreviewMessageCard não é chamada em nenhum lugar, mas o Android Studio adiciona uma janela de prévia que pode ser aberta clicando na visualização (de design/código) dividida. Essa janela mostra uma prévia dos elementos da IU criados por funções de composição marcadas com a anotação @Preview. Para atualizar as prévias a qualquer momento, clique no botão "Atualizar" na parte de cima da janela delas.

Prévia de uma função de composição no Android Studio
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

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

  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.ui.tooling.preview.Preview

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

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
mostrar prévia
ocultar prévia
Prévia de uma função de composição no Android Studio

Lição 2: layouts

Os elementos da IU são hierárquicos, com elementos contidos em outros. No Compose, você cria uma hierarquia de interface chamando funções combináveis usando outras funções desse tipo.

Adicionar vários textos

Até agora, você criou sua primeira função de composição e uma prévia. Para conhecer mais recursos do Jetpack Compose, vamos criar uma tela de mensagens simples com uma lista de mensagens que pode ser aberta com algumas animações.

Comece complementando o elemento de composição da mensagem, mostrando o nome do autor e o conteúdo dela. Primeiro, você precisa mudar o parâmetro de composição para aceitar um objeto Message em vez de String e adicionar outra função de composição Text à MessageCard. Não se esqueça de também atualizar a prévia.

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
mostrar prévia
ocultar prévia

Esse código cria dois elementos de texto dentro da visualização do conteúdo. No entanto, como você não deu nenhuma informação sobre como organizar os elementos, eles são mostrados uns sobre os outros, o que deixa o texto ilegível.

Como usar uma coluna

A função Column permite organizar os elementos verticalmente. Adicione uma Column à função MessageCard.
Você pode usar a Row para organizar os itens horizontalmente e a Box para empilhar os elementos.

// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

mostrar prévia
ocultar prévia

Adicionar um elemento de imagem

Complemente seu card de mensagens adicionando uma foto do perfil do remetente. Use o Resource Manager para importar uma imagem da sua biblioteca de fotos ou use esta. Adicione uma função de composição Row para ter um design bem estruturado e uma Image dentro dela:

// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
mostrar prévia
ocultar prévia

Configurar o layout

O layout da mensagem tem a estrutura certa, mas os elementos não estão bem espaçados e a imagem é muito grande. Para decorar ou configurar uma função de composição, o Compose usa modificadores. Eles permitem alterar o tamanho, o layout e a aparência dos elementos que podem ser compostos ou adicionar interações de alto nível, como tornar um elemento clicável. Eles podem ser encadeados para criar funções combináveis mais detalhadas. Você vai usar alguns deles para melhorar o layout.

// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
mostrar prévia
ocultar prévia
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
mostrar prévia
ocultar prévia
Prévia de duas funções de composição Text sobrepostas
// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

mostrar prévia
ocultar prévia
// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
mostrar prévia
ocultar prévia

Lição 3: Material Design

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

Usar o Material Design

O design da mensagem agora tem um layout, mas ainda não está bom.

O Jetpack Compose oferece uma implementação do Material Design 3 e elementos de IU prontos para uso. Você vai melhorar a aparência da nossa função de composição MessageCard usando os estilos do Material Design.

Para começar, una a função MessageCard ao tema do Material Design criado no projeto, ComposeTutorialTheme, bem como a um Surface. Faça isso nas funções @Preview e setContent. Isso vai permitir que suas funções de composição herdem os estilos definidos no tema do app, garantindo a consistência dentro dele.

O Material Design foi criado com base em três pilares: Color (cor), Typography (tipografia) e Shape (forma). Eles vão ser adicionados um por um.

Observação: o modelo Empty Compose Activity gera um tema padrão para o projeto, que permite personalizar o MaterialTheme. Se você deu ao seu projeto um nome diferente de ComposeTutorial, pode encontrar o tema personalizado no arquivo Theme.kt do subpacote ui.theme.

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
mostrar prévia
ocultar prévia

Cor

Use MaterialTheme.colorScheme para definir o estilo com as cores do tema envolvido. Você pode usar esses valores do tema em qualquer lugar em que uma cor seja necessária. Este exemplo usa cores de temas dinâmicos (definidas pelas preferências do dispositivo). Você pode definir dynamicColor como false no arquivo MaterialTheme.kt para mudar isso.

Defina o estilo do título e adicione uma borda à imagem.

// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
mostrar prévia
ocultar prévia

Tipografia

Os estilos de tipografia do Material Design ficam disponíveis no MaterialTheme, eles só precisam ser adicionados à função de composição Text.

// ...

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
mostrar prévia
ocultar prévia

Forma

Com o recurso Shape, você pode adicionar os toques finais. Primeiro, envolva o texto do corpo da mensagem em uma Surface que pode ser composta. Isso permite personalizar a forma e a elevação do corpo da mensagem. Um padding também é adicionado para melhorar o layout.

// ...
import androidx.compose.material3.Surface

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
mostrar prévia
ocultar prévia

Ativar tema escuro

O tema escuro (ou modo noturno) pode ser ativado para evitar uma tela clara, especialmente à noite, ou apenas para economizar bateria do dispositivo. Graças ao suporte ao Material Design, o Jetpack Compose pode processar o tema escuro por padrão. Ao usar as cores do Material Design, o texto e os planos de fundo são adaptados automaticamente ao plano de fundo escuro.

É possível criar diversas prévias no seu arquivo como funções separadas ou adicionar diversas anotações à mesma função.

Adicione uma nova anotação de prévia e ative o modo noturno.

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
mostrar prévia
ocultar prévia

As opções de cores para temas claros e escuros são definidas no arquivo Theme.kt gerado pelo ambiente de desenvolvimento integrado.

Até agora, você criou um elemento de IU de mensagem que mostra uma imagem e dois textos com estilos diferentes. Ele fica bem tanto em temas claros quanto escuros.

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
mostrar prévia
ocultar prévia
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
mostrar prévia
ocultar prévia
// ...

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.material3.Surface

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
mostrar prévia
ocultar prévia
// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
mostrar prévia
ocultar prévia
Prévia mostrando funções combináveis em temas claros e escuros.

Lição 4: listas e animações

Listas e animações estão por toda parte nos apps. Nesta lição, você vai aprender como o Compose facilita a criação de listas e torna divertido o acréscimo de animações.

Criar uma lista de mensagens

Um chat com apenas uma mensagem parece um pouco solitário, então vamos mudar a conversa para que tenha mais de uma mensagem. Você vai precisar criar uma função Conversation que mostrará várias mensagens. Para este caso de uso, use a LazyColumn e a LazyRow do Compose. Essas funções de composição processam apenas os elementos visíveis na tela. Portanto, elas são muito eficientes para listas longas.

Neste snippet de código, é possível ver que a LazyColumn tem um elemento items filho. Uma List é usada como parâmetro, e o lambda recebe um parâmetro que chamamos de message, mas poderíamos ter dado qualquer outro nome, que é uma instância da Message. Em resumo, essa lambda é chamada para cada item da List fornecida. Copie o conjunto de dados de exemplo no seu projeto para inicializar a conversa mais rápido.

// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
mostrar prévia
ocultar prévia

Animar mensagens ao abrir

A conversa está ficando mais interessante. Chegou a hora de brincar com animações. Você vai adicionar a capacidade de abrir uma mensagem para mostrar uma parte maior dela, animando o tamanho do conteúdo e a cor do plano de fundo. Para armazenar esse estado da IU local, precisamos conferir se uma mensagem foi aberta ou não. Para monitorar essa mudança de estado, é preciso usar as funções remember e mutableStateOf.

As funções de composição podem armazenar o estado local na memória usando remember e monitorar as mudanças no valor transmitido para mutableStateOf. As funções de composição (e os filhos delas) que usam esse estado serão redesenhadas automaticamente quando o valor for atualizado. Isso é chamado de recomposição.

Com o uso das APIs de estado do Compose, como remember e mutableStateOf, qualquer mudança no estado atualiza automaticamente a IU.

Observação:é necessário adicionar as importações abaixo para usar corretamente a sintaxe da propriedade delegada do Kotlin (a palavra-chave by). Alt + Enter ou Option + Enter adiciona para você.
import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue

// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostrar prévia
ocultar prévia

Agora, você pode mudar o plano de fundo do conteúdo da mensagem com base em isExpanded ao clicar em uma mensagem. Você vai usar o modificador clickable para processar eventos de clique na função de composição. Em vez de apenas alternar a cor do plano de fundo da Surface, você vai criar uma animação com essa cor modificando gradativamente o valor dela de MaterialTheme.colorScheme.surface para MaterialTheme.colorScheme.primary e vice-versa. Para isso, você vai usar a função animateColorAsState. Por fim, você vai usar o modificador animateContentSize para animar o tamanho do contêiner da mensagem de modo suave:

// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostrar prévia
ocultar prévia
// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
mostrar prévia
ocultar prévia

Próximas etapas

Parabéns! Você concluiu o tutorial do Compose! Você criou uma tela de chat simples que mostra com eficiência uma lista de mensagens que podem ser abertas e animadas com uma imagem e textos. A tela foi projetada usando os princípios do Material Design com um tema escuro e visualizações, tudo em menos de cem linhas de código.

Veja o que você aprendeu até agora:

  • Como definir funções combináveis
  • Como adicionar elementos diferentes à função que pode ser composta
  • Como estruturar o componente de IU usando elementos de layout que podem ser compostos
  • Como estender elementos que podem ser compostos usando modificadores
  • Como criar uma lista eficiente
  • Como acompanhar e modificar o estado
  • Como adicionar interação do usuário a um elemento combinável
  • Como animar mensagens ao expandi-las

Se você quiser se aprofundar em algumas dessas etapas, conheça os recursos abaixo.

Próximas etapas

Configuração
Agora que você concluiu o tutorial, pode começar a criar com o Compose.
Programa de aprendizado
Confira nosso caminho de codelabs e vídeos selecionados que ajudarão você a aprender como usar e dominar o Jetpack Compose.