Tutorial

Tutorial do Jetpack Compose

O Jetpack Compose é um toolkit moderno para criação de IU nativa no Android. Ele 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 usará o Layout Editor. 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
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 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 IU (inicializando um elemento, anexando-o 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 Arctic Fox 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, exibiremos 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 Text(). O bloco setContent define o layout da atividade em que chamamos funções que podem ser compostas. As funções que podem ser compostas 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 que podem ser compostas nos elementos de IU do app. Por exemplo, a função Text() que é definida pela biblioteca de IU do Compose exibe um rótulo de texto na tela.

class MainActivity : ComponentActivity() {
    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

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.

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

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

  
mostrar visualização
ocultar visualização

Visualizar a função no Android Studio

O Android Studio permite visualizar as funções que podem ser compostas no ambiente de desenvolvimento integrado, em vez de instalar o app em um dispositivo ou emulador Android. A função que pode ser composta precisa fornecer valores padrão para todos os parâmetros. Por isso, não é possível visualizar a função MessageCard() diretamente. Em vez disso, criaremos uma segunda função com o nome PreviewMessageCard(), que chama o método MessageCard() com um parâmetro adequado. Adicione a anotação @Preview antes de @Composable.

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

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

Recrie o 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 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 : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
mostrar visualização
ocultar visualização
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

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

  
mostrar visualização
ocultar visualização
@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("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 usando outras funções desse tipo.

Adicionar vários textos

Até agora, criamos nossa primeira função que pode ser composta e uma visualização. Para descobrir mais recursos do Jetpack Compose, criaremos uma tela de mensagens simples com uma lista de mensagens que pode ser expandida com algumas animações.
Vamos começar enriquecendo nossa mensagem que pode ser composta, exibindo o nome do autor e o conteúdo da mensagem. Primeiro, é preciso mudar nosso parâmetro que pode ser composto para que ele aceite um objeto Message em vez de um String. Em seguida, adicione outro Text que pode ser composto ao MessageCard que pode ser composto. Atualize também a visualização:

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("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
mostrar visualização
ocultar visualização

Esse código cria dois 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.

Como usar uma coluna

A função Column permite empilhar elementos verticalmente. Adicione uma Column à função MessageCard().
Use "Row" para organizar os itens horizontalmente e "Box" para empilhar elementos.

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

mostrar visualização
ocultar visualização

Adicionar um elemento de imagem

Vamos enriquecer nosso card de mensagens adicionando uma foto do perfil ao remetente. Use o Resource Manager para importar uma imagem da sua biblioteca de fotos ou use esta. Adicione uma Row que pode ser composta para ter um design bem estruturado e uma Image que pode ser composta dentro dela:

@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 visualização
ocultar visualização

Configurar o layout

O layout da nossa mensagem tem a estrutura certa, mas os elementos dele não são bem espaçados, e a imagem é muito grande. Para decorar ou configurar um elemento que pode ser composto, 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. Você pode encadeá-los para criar elementos que podem ser compostos mais detalhados. Vamos usar alguns deles para melhorar o layout:

@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 visualização
ocultar visualização
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("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
mostrar visualização
ocultar visualização
@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

mostrar visualização
ocultar visualização
@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 visualização
ocultar visualização
@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 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.

Usar o Material Design

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

O Jetpack Compose oferece uma implementação do Material Design e elementos de IU prontos para uso. Melhoraremos a aparência do elemento que pode ser composto MessageCard usando o estilo do Material Design.

Para começar, unimos nossa função MessageCard com o tema do Material Design criado no projeto, ComposeTutorialTheme, neste caso. Faça isso nas funções @Preview e setContent.

O Material Design foi criado com base em três pilares: cor, tipografia e forma. Vamos adicioná-los um por um.

Observação: a atividade "Empty Compose" gera um tema padrão para o projeto, permitindo que você personalize o MaterialTheme. Se você nomeou seu projeto com algo diferente de ComposeTutorial, poderá encontrar o tema personalizado no pacote ui.theme.

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

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

  
mostrar visualização
ocultar visualização

Cor

Criar um estilo com cores do tema agrupado é fácil, e você pode usar valores do tema em qualquer lugar em que uma cor seja necessária.

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

@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.colors.secondary, CircleShape)
       )

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

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

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

  
mostrar visualização
ocultar visualização

Tipografia

Os estilos de tipografia do Material Design estão disponíveis no MaterialTheme. Basta adicioná-los aos elementos que podem ser compostos de texto.

@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

  
mostrar visualização
ocultar visualização

Forma

Com a opção "Shape", podemos adicionar os toques finais. Também adicionamos um padding à mensagem para um layout melhor.

@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

  
mostrar visualização
ocultar visualização

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 à compatibilidade com o Material Design, o Jetpack Compose pode processar o tema escuro por padrão. Depois de usar as cores do Material Design, o texto e os planos de fundo serão adaptados automaticamente ao planos de fundo escuro.

É possível criar diversas visualizações em seu arquivo como funções separadas ou adicionar várias anotações à mesma função.

Vamos adicionar uma nova anotação de visualização e ativar o modo noturno.

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

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, criamos um elemento de IU de mensagem que exibe uma imagem e dois textos com estilos diferentes. Ele fica bem em temas claros e escuros.

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
       )
   }
}
  
mostrar visualização
ocultar visualização
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                MessageCard(Message("Android", "Jetpack Compose"))
            }
        }
    }
}

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

  
mostrar visualização
ocultar visualização
@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.colors.secondary, CircleShape)
       )

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

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

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

  
mostrar visualização
ocultar visualização
@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

  
mostrar visualização
ocultar visualização
@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

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

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

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

Criar uma lista de mensagens

Um bate-papo com uma mensagem parece um pouco solitário, então vamos mudar nossa conversa para que tenha mais de uma mensagem. Precisamos criar uma função Conversation para mostrar várias mensagens. Para esse caso de uso, podemos usar a LazyColumn e a LazyRow. do Compose. Esses elementos que podem ser compostos processam apenas os elementos visíveis na tela. Portanto, eles são muito eficientes para listas longas. Ao mesmo tempo, eles evitam a complexidade da RecyclerView com layouts XML.

Neste snippet de código, é possível ver que a LazyColumn tem um item filho. Ele usa uma List como parâmetro, e seu lambda recebe um parâmetro que chamamos de message (poderíamos ter dado qualquer outro nome), que é uma instância de Message. Em resumo, essa lambda é chamada para cada item da List fornecida. Importe este conjunto de dados de exemplo (link em inglês) para o projeto a fim de inicializar a conversa rapidamente:

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 visualização
ocultar visualização

Animar mensagens durante a expansão

Nossa conversa está ficando mais interessante. É hora de brincar com animações. Adicionaremos a capacidade de expandir uma mensagem para mostrar uma mais longa, 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 estendida ou não. Para acompanhar essa mudança de estado, é preciso usar as funções remember e mutableStateOf.

As funções que podem ser compostas podem armazenar o estado local na memória usando remember e rastrear as mudanças no valor transmitido para mutableStateOf. Os elementos que podem ser compostos (e os filhos dele) que usam esse estado serão redesenhados automaticamente quando o valor for atualizado. Chamamos isso de recomposição.

Usando as APIs de estado do Compose, como remember e mutableStateOf, qualquer mudança no estado atualiza automaticamente a IU:

Observação: adicione as seguintes importações para usar "by" corretamente. Use as teclas Alt+Enter para adicioná-las.

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.colors.secondaryVariant, 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.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
mostrar visualização
ocultar visualização

Agora poderemos alterar o plano de fundo do conteúdo da mensagem baseado em isExpanded quando clicarmos em uma mensagem. Usaremos o modificador clickable para gerenciar eventos de clique no elemento que pode ser composto. Em vez de apenas alternar a cor do plano de fundo da Surface, faremos uma animação com essa cor modificando gradativamente o valor de MaterialTheme.colors.surface para MaterialTheme.colors.primary e vice-versa. Para isso, usaremos a função animateColorAsState. Por fim, usaremos o modificador animateContentSize para animar o tamanho do contêiner da mensagem de modo suave:

@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.colors.secondaryVariant, 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: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

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

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
mostrar visualização
ocultar visualização
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 visualização
ocultar visualização
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.colors.secondaryVariant, 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.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
mostrar visualização
ocultar visualização
@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.colors.secondaryVariant, 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: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

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

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
mostrar visualização
ocultar visualização

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 expansíveis 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 100 linhas de código.

Veja o que você aprendeu até agora:

  • Como definir funções que podem ser compostas
  • 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 o estado e modificá-lo
  • Como adicionar interação do usuário a um elemento que pode ser composto
  • Como animar mensagens ao expandi-las

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

Continue seu aprendizado

Configurar
Agora que você concluiu o tutorial do Compose, está pronto para começar a criar com o Compose.
Programa de aprendizado
Confira nosso programa de codelabs e vídeos selecionados para ajudar você a aprender como usar e dominar o Jetpack Compose.