Layout de card clássico amplo com grade

ícone de avaliação de quatro estrelas

As diretrizes de design do app de TV definem uma variante de card chamada Card clássico amplo, que mostra uma imagem e o bloco de conteúdo descritivo lado a lado. Ao criar layouts bidimensionais complexos para telas de TV, o uso de posicionamento tradicional baseado em coordenadas ou contêineres Row e Column profundamente aninhados pode dificultar a leitura, a manutenção e a refatoração do código. O card clássico largo é um caso de uso ideal para a API Grid do Jetpack Compose, que ajuda a estruturar elementos lado a lado de forma limpa usando áreas de layout 2D semânticas definidas pelo usuário.

Especificação do componente de card clássico amplo mostrando cinco áreas de layout nomeadas:
      imagem, título, subtítulo, descrição e texto extra.
Figura 1. Card clássico largo com cinco áreas nomeadas: área de imagem e quatro áreas de texto no bloco de conteúdo.

Práticas recomendadas

Implemente o layout do card com Grid. O uso do contêiner Grid melhora a legibilidade do código, desacoplando as dimensões do layout físico do posicionamento do item filho. Em vez de depender de índices de faixa exatos, defina áreas semânticas nomeadas com a função area e coloque elementos combináveis filhos com o modificador gridItem. Combine o dimensionamento flexível de faixas, como GridTrackSize.Auto e minmax(0.dp, 1.fr), com lacunas exatas (gap(row = 8.dp, column = 16.dp)) para organizar componentes de cards complexos lado a lado de forma clara e robusta.

Ingredientes

  • Grid: um layout de contêiner 2D no Jetpack Compose que organiza elementos combináveis filhos em linhas e colunas com base em uma lambda de configuração.
  • gridItem: uma extensão Modifier que associa um elemento da interface filho a um areaId específico em um layout Grid.
  • area: uma função DSL de configuração em GridConfigurationScope que mapeia um areaId semântico para períodos ou intervalos específicos de linhas e colunas.
  • Card: um elemento combinável de contêiner do Material Design que agrupa elementos de interface e estilos relacionados.
  • GridTrackSize.MinMax: uma propriedade de dimensionamento de faixa que define limites flexíveis (min e max) para dimensões de linha ou coluna.
  • GridTrackSize.Auto: uma propriedade de dimensionamento de faixa que dimensiona a faixa de linha ou coluna automaticamente para se ajustar ao conteúdo (column(GridTrackSize.Auto)).
  • gap: uma função de configuração que especifica o espaçamento vertical (row) e horizontal (column) entre as faixas da grade.

Etapas

Defina identificadores de área, configure as faixas e áreas da grade e posicione os elementos de composição filhos.

1. Definir identificadores de área semântica

Primeiro, crie uma representação com segurança de tipos das regiões dentro do layout de card clássico amplo. Uma classe enum evita erros de digitação de strings e oferece segurança no tempo de compilação ao mapear faixas e posicionar itens filhos.

enum class CardArea {
    Image,
    Title,
    Subtitle,
    Description,
    ExtraText,
}

2. Configurar as faixas e as áreas nomeadas da grade 2D

No lambda config do elemento combinável Grid, defina as colunas e linhas que compõem a estrutura do card. Use GridTrackSize.Auto para a coluna de imagem, para que ela envolva a largura da miniatura de forma limpa. Use minmax(0.dp, 1.fr) para a coluna de conteúdo para que ela se expanda de forma flexível pela largura restante do card. Especifique o espaçamento entre faixas com gap(row = 8.dp, column = 16.dp). Mapeie os identificadores de CardArea para as coordenadas físicas da pista usando a função area.

Grid(
    config = {
        // Define columns: left column for image, right for content
        column(GridTrackSize.Auto)
        column(minmax(0.dp, 1.fr))

        // Define row tracks for the vertical content stack
        row(GridTrackSize.Auto)
        row(GridTrackSize.Auto)
        row(GridTrackSize.Auto)
        row(GridTrackSize.Auto)

        // Map semantic identifiers to grid coordinates and spans
        area(CardArea.Image, row = 1, column = 1, rowSpan = 4)
        area(CardArea.Title, row = 1, column = 2)
        area(CardArea.Subtitle, row = 2, column = 2)
        area(CardArea.Description, row = 3, column = 2)
        area(CardArea.ExtraText, row = 4, column = 2)

        gap(row = 8.dp, column = 16.dp)
    }
) {
    // Child elements placed in Step 3
}

3. Posicionar elementos combináveis filhos usando o modificador gridItem

Com as faixas e áreas configuradas, posicione cada elemento filho da interface puramente por intenção semântica usando o modificador gridItem. É possível declarar elementos combináveis filhos em qualquer ordem. O contêiner Grid posiciona cada elemento diretamente na região 2D atribuída de forma automática.

@Composable
fun WideClassicCard(
    imageContent: @Composable () -> Unit,
    title: String,
    subtitle: String,
    description: String,
    extraText: String,
    modifier: Modifier = Modifier,
) {
    Card(modifier = modifier) {
        Grid(
            config = {
                // Define columns: left column for image, right for content
                column(GridTrackSize.Auto)
                column(minmax(0.dp, 1.fr))

                // Define row tracks for the vertical content stack
                row(GridTrackSize.Auto)
                row(GridTrackSize.Auto)
                row(GridTrackSize.Auto)
                row(GridTrackSize.Auto)

                // Map semantic identifiers to grid coordinates and spans
                area(CardArea.Image, row = 1, column = 1, rowSpan = 4)
                area(CardArea.Title, row = 1, column = 2)
                area(CardArea.Subtitle, row = 2, column = 2)
                area(CardArea.Description, row = 3, column = 2)
                area(CardArea.ExtraText, row = 4, column = 2)

                gap(row = 8.dp, column = 16.dp)
            },
        ) {
            Box(modifier = Modifier.gridItem(CardArea.Image)) {
                imageContent()
            }
            Text(
                text = title,
                style = MaterialTheme.typography.titleLarge,
                modifier = Modifier
                    .gridItem(CardArea.Title)
                    .padding(top = 16.dp, end = 16.dp),
            )

            Text(
                text = subtitle,
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier
                    .gridItem(CardArea.Subtitle)
                    .padding(end = 16.dp),
            )

            Text(
                text = description,
                style = MaterialTheme.typography.bodyMedium,
                modifier = Modifier
                    .gridItem(CardArea.Description)
                    .padding(end = 16.dp),
            )

            Text(
                text = extraText,
                style = MaterialTheme.typography.labelSmall,
                modifier = Modifier
                    .gridItem(CardArea.ExtraText)
                    .padding(bottom = 16.dp, end = 16.dp),
            )
        }
    }
}

Resultados

Ao organizar o card clássico largo com Grid e áreas nomeadas, você consegue uma separação clara entre as faixas de layout físico, as coordenadas de área e o posicionamento do elemento filho que pode ser composto. Essa estrutura torna o layout legível e fácil de manter, além de seguir as diretrizes de design de apps de TV para componentes de miniatura e descrição lado a lado.

Outros recursos