---
title: "Layouts adaptativos no Android com Kotlin e Compose em 2026"
url: "https://kotlin.dev.br/blog/layouts-adaptativos-android-compose-window-size-class-2026/"
markdown_url: "https://kotlin.dev.br/blog/layouts-adaptativos-android-compose-window-size-class-2026.MD"
description: "Crie layouts adaptativos no Android com Kotlin e Jetpack Compose usando WindowSizeClass, NavigationSuiteScaffold, painéis e testes para tablets e dobráveis."
date: "2026-08-14"
author: "Karina Melo"
---

# Layouts adaptativos no Android com Kotlin e Compose em 2026

Crie layouts adaptativos no Android com Kotlin e Jetpack Compose usando WindowSizeClass, NavigationSuiteScaffold, painéis e testes para tablets e dobráveis.


**Resposta rápida:** um layout adaptativo no Android não escolhe a interface pelo modelo do aparelho; ele reage ao **espaço disponível na janela**. Em Jetpack Compose, consulte a `WindowSizeClass`, troque a navegação conforme a largura e reorganize o conteúdo: uma coluna em janelas compactas, lista e detalhe lado a lado em janelas expandidas e uma solução intermediária nas médias. Preserve o mesmo estado e as mesmas ações entre as variações. Depois, teste redimensionamento, rotação, modo multiwindow, tablets e dobráveis sem assumir que “tablet” significa tela cheia ou que “celular” significa janela estreita.

Criar uma versão separada do app para tablets quase nunca é a melhor arquitetura. O Android moderno pode executar o mesmo aplicativo em uma janela livre, em tela dividida, em um desktop, em um tablet parcialmente ocupado ou em um dobrável aberto. A pergunta útil deixou de ser “este dispositivo é um tablet?” e passou a ser **“quanto espaço esta tela tem agora?”**.

Este guia mostra como tomar essa decisão com Kotlin e Compose, adaptar navegação, implementar o padrão lista-detalhe, preservar estado, lidar com dobráveis e montar uma estratégia de testes. Se você ainda está estruturando o projeto, revise o [guia de Jetpack Compose](/guias/guia-jetpack-compose/) e a [arquitetura MVVM com Kotlin](/guias/guia-arquitetura-mvvm-kotlin/). Para janelas realmente imersivas, combine as decisões daqui com [edge-to-edge no Android 16](/blog/android-16-edge-to-edge-compose-kotlin-2026/).

## Responsivo e adaptativo não são exatamente a mesma coisa

Os termos aparecem como sinônimos, mas ajudam a pensar em níveis diferentes:

- **responsivo:** componentes crescem, quebram linha e ajustam espaçamentos;
- **adaptativo:** a estrutura da interface muda para aproveitar melhor a janela;
- **orientado a postura:** o app também considera dobradiça, separação física e posição do dispositivo.

Um card que aumenta de largura é responsivo. Trocar uma barra inferior por um `NavigationRail` é adaptativo. Colocar lista de um lado da dobradiça e detalhe do outro considera a postura.

Na prática, uma tela madura costuma combinar os três níveis. Ela precisa permitir que textos e grids se ajustem, escolher uma estrutura coerente e evitar conteúdo importante em uma área ocluída.

## Por que detectar “tablet” é uma armadilha

Uma regra como `smallestScreenWidthDp >= 600` parece simples, mas não descreve todas as situações do app:

- um tablet pode estar em tela dividida e oferecer uma janela compacta;
- um celular dobrável aberto pode ter espaço de janela expandido;
- um Chromebook pode redimensionar o app continuamente;
- rotação altera proporções sem mudar o dispositivo;
- uma janela flutuante pode ficar menor que a tela física;
- barras do sistema, recortes e dobradiças reduzem a área realmente utilizável.

Por isso, prefira características atuais da **janela**, não rótulos permanentes do hardware. O layout deve poder mudar enquanto a Activity continua viva, sem perder seleção, formulário ou posição de navegação.

## As classes de tamanho de janela

O Material 3 organiza o espaço em classes de largura e altura. Os nomes mais comuns são:

| Classe | Interpretação prática | Estrutura inicial sugerida |
|---|---|---|
| Compacta | pouco espaço horizontal | uma coluna e navegação inferior |
| Média | espaço intermediário | rail, grid maior ou painel complementar |
| Expandida | bastante espaço horizontal | múltiplos painéis, lista-detalhe ou drawer |

Esses limites são **pontos de decisão**, não uma obrigação de design. Uma tela de leitura pode continuar em uma coluna centralizada mesmo numa janela expandida. Já um cliente de e-mail desperdiça espaço se mostrar somente uma mensagem e esconder a lista inteira.

A pergunta deve ser feita por tela:

1. Qual conteúdo precisa ficar visível ao mesmo tempo?
2. Existe uma relação natural entre lista e detalhe?
3. A navegação ocupa espaço demais ou de menos?
4. Qual largura máxima mantém a leitura confortável?
5. O que acontece quando a janela muda durante uma tarefa?

## Dependências e versões

As APIs adaptativas evoluem junto com o Material 3 e as bibliotecas AndroidX. Centralize versões no Version Catalog e confira a documentação oficial da versão adotada pelo projeto:

```toml
# gradle/libs.versions.toml
[versions]
material3Adaptive = "<versao-estavel-atual>"

[libraries]
androidx-material3-adaptive = {
  module = "androidx.compose.material3.adaptive:adaptive",
  version.ref = "material3Adaptive"
}
androidx-material3-adaptive-layout = {
  module = "androidx.compose.material3.adaptive:adaptive-layout",
  version.ref = "material3Adaptive"
}
androidx-material3-navigation-suite = {
  module = "androidx.compose.material3:material3-adaptive-navigation-suite",
  version.ref = "material3Adaptive"
}
```

```kotlin
// build.gradle.kts do módulo app
dependencies {
    implementation(libs.androidx.material3.adaptive)
    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.navigation.suite)
}
```

A composição exata dos artefatos pode mudar entre versões. Não copie números antigos sem validar. O [Compose BOM](/blog/compose-bom-android-kotlin-guia-2026/) ajuda a alinhar bibliotecas Compose incluídas em seu catálogo, mas artefatos experimentais ou com ciclo próprio ainda devem ser conferidos individualmente.

## Lendo a WindowSizeClass no Compose

Concentre a leitura da janela perto da raiz da feature e transforme o resultado em um modelo pequeno do seu domínio de UI:

```kotlin
enum class AppLayoutMode {
    SinglePane,
    SupportingPane,
    ListDetail,
}

@Composable
fun rememberAppLayoutMode(): AppLayoutMode {
    val adaptiveInfo = currentWindowAdaptiveInfo()
    val widthClass = adaptiveInfo.windowSizeClass.windowWidthSizeClass

    return when (widthClass) {
        WindowWidthSizeClass.COMPACT -> AppLayoutMode.SinglePane
        WindowWidthSizeClass.MEDIUM -> AppLayoutMode.SupportingPane
        WindowWidthSizeClass.EXPANDED -> AppLayoutMode.ListDetail
        else -> AppLayoutMode.SinglePane
    }
}
```

Encapsular a decisão evita espalhar comparações de largura por dezenas de composables. Também permite mudar a regra depois: uma feature pode exigir duas colunas apenas em largura expandida, enquanto outra funciona bem com duas colunas já na média.

Use a altura quando ela realmente alterar a experiência. Uma janela larga e muito baixa pode precisar de uma navegação diferente ou esconder um painel secundário. Não transforme, porém, cada combinação em um layout independente; o custo de manutenção cresce rapidamente.

## Adaptando a navegação com NavigationSuiteScaffold

Um app compacto normalmente usa barra inferior. Com mais largura, um rail ou drawer aproveita melhor a lateral. `NavigationSuiteScaffold` existe para representar essa intenção sem duplicar toda a tela:

```kotlin
@Composable
fun AdaptiveAppShell(
    selected: AppDestination,
    onSelect: (AppDestination) -> Unit,
    content: @Composable () -> Unit,
) {
    NavigationSuiteScaffold(
        navigationSuiteItems = {
            AppDestination.entries.forEach { destination ->
                item(
                    selected = selected == destination,
                    onClick = { onSelect(destination) },
                    icon = {
                        Icon(
                            imageVector = destination.icon,
                            contentDescription = destination.label,
                        )
                    },
                    label = { Text(destination.label) },
                )
            }
        },
    ) {
        content()
    }
}
```

O componente escolhe uma apresentação adequada a partir da informação adaptativa disponível. Quando o produto exige uma regra própria, forneça a política correspondente em vez de duplicar o grafo de navegação.

A navegação deve manter os mesmos destinos, permissões e semântica. Trocar bottom bar por rail não pode criar um “segundo app” com funções diferentes. Isso também vale para acessibilidade: ordem de foco, labels e estados selecionados precisam continuar claros.

Para organizar destinos e rotas, veja [Navigation Compose com NavHost e NavController](/blog/navigation-compose-navhost-navcontroller-kotlin-2026/) e o guia de [Navigation 3 no Android](/blog/navigation-3-compose-android-2026/).

## Padrão lista-detalhe sem duplicar estado

E-mail, catálogo, mensagens, configurações e painéis administrativos combinam naturalmente com lista-detalhe:

- em janela compacta, o usuário vê a lista **ou** o detalhe;
- em janela expandida, vê lista **e** detalhe;
- em janela média, a regra depende da importância do painel e do espaço mínimo de cada parte.

O erro comum é criar um ViewModel para celular e outro para tablet. O estado de negócio deveria ser o mesmo:

```kotlin
data class InboxUiState(
    val messages: List<MessageSummary> = emptyList(),
    val selectedMessageId: String? = null,
    val loading: Boolean = false,
    val error: String? = null,
)

class InboxViewModel(
    private val repository: MessageRepository,
) : ViewModel() {
    private val selectedId = MutableStateFlow<String?>(null)

    val state: StateFlow<InboxUiState> = combine(
        repository.observeMessages(),
        selectedId,
    ) { messages, id ->
        InboxUiState(
            messages = messages,
            selectedMessageId = id,
        )
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = InboxUiState(loading = true),
    )

    fun selectMessage(id: String?) {
        selectedId.value = id
    }
}
```

A UI escolhe como apresentar esse estado:

```kotlin
@Composable
fun InboxRoute(
    state: InboxUiState,
    layoutMode: AppLayoutMode,
    onMessageSelected: (String) -> Unit,
    onBackToList: () -> Unit,
) {
    when (layoutMode) {
        AppLayoutMode.SinglePane -> {
            if (state.selectedMessageId == null) {
                MessageList(
                    messages = state.messages,
                    onSelect = onMessageSelected,
                )
            } else {
                MessageDetail(
                    messageId = state.selectedMessageId,
                    onBack = onBackToList,
                )
            }
        }

        AppLayoutMode.SupportingPane,
        AppLayoutMode.ListDetail -> {
            Row(Modifier.fillMaxSize()) {
                MessageList(
                    messages = state.messages,
                    onSelect = onMessageSelected,
                    modifier = Modifier.weight(0.38f),
                )
                VerticalDivider()
                MessageDetailOrPlaceholder(
                    messageId = state.selectedMessageId,
                    modifier = Modifier.weight(0.62f),
                )
            }
        }
    }
}
```

Esse exemplo manual deixa a regra clara. Para produção, os scaffolds adaptativos de lista-detalhe e seus navegadores reduzem trabalho com histórico, painéis e comportamento de voltar. Como essas APIs podem mudar de assinatura, siga a documentação da versão AndroidX usada pelo projeto em vez de congelar um snippet antigo.

## Estado, back e mudança de tamanho

Imagine que o usuário abre uma mensagem num tablet e coloca o app em tela dividida. A janela passa de expandida para compacta. O item selecionado não deve desaparecer; o app deve mostrar o detalhe em um painel único e permitir voltar para a lista.

Algumas regras úteis:

- mantenha o item selecionado no ViewModel ou estado salvo;
- não use `isTablet` como chave de `remember`;
- preserve rascunhos com `rememberSaveable` ou `SavedStateHandle`;
- trate o botão voltar segundo o painel visível, não segundo o aparelho;
- não limpe seleção apenas porque a janela mudou;
- restaure scroll de lista com estado estável e chaves consistentes.

O [Predictive Back com Compose](/blog/predictive-back-android-compose-kotlin-2026/) merece atenção especial. Num fluxo compacto, voltar pode fechar o detalhe e revelar a lista; numa janela expandida, a lista já está presente e o back talvez deva sair da feature ou desfazer uma navegação anterior. Modele essa semântica explicitamente.

## Conteúdo expandido não deve ficar esticado

Nem toda tela precisa preencher 100% da largura. Um formulário de login com campos de 1.400 dp fica difícil de ler e parece inacabado. Use largura máxima e centralização:

```kotlin
@Composable
fun ReadableForm(
    content: @Composable ColumnScope.() -> Unit,
) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.TopCenter,
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .widthIn(max = 720.dp)
                .padding(horizontal = 24.dp, vertical = 32.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp),
            content = content,
        )
    }
}
```

Para conteúdo visual, uma janela maior pode aumentar o número de colunas:

```kotlin
LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 220.dp),
    contentPadding = PaddingValues(24.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp),
    verticalArrangement = Arrangement.spacedBy(16.dp),
) {
    items(items, key = { it.id }) { item ->
        CatalogCard(item)
    }
}
```

`GridCells.Adaptive` resolve bem grids, mas não substitui uma decisão estrutural. Um catálogo pode usar grid adaptativo; a área de filtros talvez precise virar painel lateral em janelas expandidas.

## Dobráveis, dobradiças e posturas

A largura sozinha não informa se existe uma dobradiça separando o conteúdo. Em aparelhos dobráveis, consulte informações de postura e recursos de display fornecidos pelo ecossistema WindowManager/Material Adaptive.

Trate uma separação física como restrição de layout:

- não coloque um botão principal sobre a dobradiça;
- evite texto contínuo atravessando uma área ocluída;
- use cada região para painéis relacionados quando isso fizer sentido;
- não presuma que o aparelho aberto está sempre em landscape;
- mantenha uma alternativa de painel único.

O padrão lista-detalhe se adapta bem: lista em uma região e detalhe na outra. Vídeo e controles também podem ocupar áreas diferentes em postura de mesa. Ainda assim, só use uma postura especial quando ela melhora a tarefa; não force uma divisão visual apenas porque o hardware permite.

## Edge-to-edge, insets e área segura

Layouts adaptativos precisam respeitar barras do sistema, recortes e teclado. O espaço da janela não é automaticamente uma área segura para conteúdo interativo.

Ao usar `Scaffold`, confirme quem consome os insets. Evite aplicar padding de sistema em pai e filho ao mesmo tempo. Em painéis lado a lado, cada região pode precisar de tratamento próprio, principalmente quando uma barra lateral chega à borda.

Para imagens e fundos, desenhar atrás das barras pode ser desejável. Para botões, campos e listas, aplique os insets necessários. O artigo de [edge-to-edge com Compose](/blog/android-16-edge-to-edge-compose-kotlin-2026/) detalha essa separação.

## Acessibilidade em telas grandes

Mais espaço não autoriza reduzir a legibilidade. Verifique:

- largura máxima de parágrafos;
- foco previsível entre navegação, lista e detalhe;
- touch targets adequados;
- suporte a fonte ampliada sem cortar labels;
- ordem semântica coerente mesmo com painéis visuais lado a lado;
- contraste e indicação de item selecionado;
- navegação por teclado e D-pad em dispositivos compatíveis.

Uma interface com dois painéis pode confundir leitores de tela se o detalhe mudar silenciosamente. Anuncie mudanças importantes e mantenha headings semânticos. Consulte o guia de [acessibilidade Android com Compose](/blog/acessibilidade-android-compose-kotlin-2026/) para testes e APIs específicas.

## Como testar layouts adaptativos

Não limite o QA a um celular e um tablet. Monte uma matriz baseada em janelas:

| Cenário | O que verificar |
|---|---|
| Compacta em portrait | painel único, bottom navigation, back |
| Compacta em landscape | altura reduzida, teclado, scroll |
| Média | rail ou estrutura intermediária |
| Expandida | lista-detalhe, largura máxima, foco |
| Redimensionamento em execução | estado, seleção e scroll preservados |
| Multiwindow | mudança sem reiniciar tarefa |
| Fonte grande | labels, botões e grids sem corte |
| Dobrável | dobradiça, postura e áreas ocluídas |

Separe testes em três camadas:

1. **teste da política:** determinada classe de janela produz o modo esperado;
2. **teste de UI:** cada modo exibe ações e conteúdo essenciais;
3. **teste instrumentado:** redimensionamento, back, postura e integrações reais.

A política pura é fácil de testar:

```kotlin
fun layoutModeFor(widthClass: WindowWidthSizeClass): AppLayoutMode =
    when (widthClass) {
        WindowWidthSizeClass.COMPACT -> AppLayoutMode.SinglePane
        WindowWidthSizeClass.MEDIUM -> AppLayoutMode.SupportingPane
        WindowWidthSizeClass.EXPANDED -> AppLayoutMode.ListDetail
        else -> AppLayoutMode.SinglePane
    }

class LayoutModePolicyTest {
    @Test
    fun expanded_uses_list_detail() {
        assertEquals(
            AppLayoutMode.ListDetail,
            layoutModeFor(WindowWidthSizeClass.EXPANDED),
        )
    }
}
```

Em previews, forneça artificialmente o modo de layout ao composable de conteúdo. Isso deixa a UI revisável sem depender de um emulador específico. Para regressões visuais, combine previews parametrizados com a estratégia de [testes de screenshot no Compose](/blog/testes-screenshot-compose-kotlin-2026/).

## Erros comuns

### Usar orientação como decisão principal

Landscape não significa espaço expandido. Um celular em landscape pode continuar estreito depois dos insets. Use a janela e trate orientação apenas quando a tarefa realmente depender dela.

### Duplicar telas para celular e tablet

Duas árvores inteiras de UI divergem rapidamente. Extraia componentes e estado compartilhados; varie scaffolds, posição e visibilidade de painéis.

### Recarregar dados quando o tamanho muda

Uma mudança de janela é configuração de apresentação, não uma nova sessão. Dados no ViewModel devem sobreviver e continuar disponíveis.

### Mostrar informação diferente em cada tamanho

É aceitável priorizar ou mover conteúdo secundário, mas não esconda ações essenciais de um grupo de usuários. Teste paridade funcional.

### Esticar tudo

Use `widthIn`, grids, painéis e espaços de respiro. Aproveitar a tela não significa preencher cada pixel com texto.

### Testar apenas em emuladores fixos

O problema mais difícil aparece na transição. Redimensione a janela durante edição, seleção, reprodução e carregamento.

## Checklist de implementação

Antes de considerar a feature adaptativa pronta:

- [ ] a decisão usa espaço da janela, não nome do dispositivo;
- [ ] existe uma política clara para compacta, média e expandida;
- [ ] navegação muda sem trocar destinos ou regras de permissão;
- [ ] estado e seleção sobrevivem ao redimensionamento;
- [ ] back funciona em painel único e múltiplos painéis;
- [ ] conteúdo de leitura tem largura máxima confortável;
- [ ] grids usam tamanho mínimo coerente, não número mágico de colunas;
- [ ] insets e edge-to-edge foram verificados;
- [ ] fonte ampliada, teclado e leitor de tela foram testados;
- [ ] dobradiça não cobre conteúdo essencial;
- [ ] previews e testes cobrem mais de uma classe de janela;
- [ ] dependências adaptativas foram validadas na versão AndroidX do projeto.

## Perguntas frequentes

### Preciso criar resources diferentes para tablet?

Não obrigatoriamente. Compose permite tomar decisões pela janela no mesmo código. Recursos alternativos ainda podem ser úteis para dimensões, imagens ou casos específicos, mas não devem ser a única estratégia de adaptação.

### WindowSizeClass substitui completamente WindowManager?

Não. A classe de tamanho resolve a maior parte das decisões por espaço. Informações de postura, dobradiça e recursos físicos da tela exigem APIs adicionais do ecossistema de janelas.

### Devo usar duas colunas em toda tela expandida?

Não. Use múltiplos painéis quando conteúdos relacionados se beneficiam de simultaneidade. Formulários e textos longos frequentemente ficam melhores centralizados com largura máxima.

### NavigationSuiteScaffold elimina o NavController?

Não. Ele adapta a apresentação da navegação. O estado de destinos e o back stack continuam sendo responsabilidades da arquitetura de navegação adotada pelo app.

### Como começar sem reescrever o projeto?

Escolha uma feature de alto impacto, como lista-detalhe. Primeiro extraia estado e componentes. Depois introduza um modo de layout, adapte a navegação e adicione testes de redimensionamento. Repita a estratégia nas próximas telas.

## Próximos passos

Comece pela tela que mais desperdiça espaço em tablets ou janelas grandes. Meça seus mínimos visuais, defina três modos simples e preserve um único estado de domínio. Depois adapte navegação e back antes de adicionar detalhes específicos de dobráveis.

Uma boa sequência de estudo é:

1. [Jetpack Compose completo](/guias/guia-jetpack-compose/);
2. [Navigation Compose](/blog/navigation-compose-navhost-navcontroller-kotlin-2026/);
3. [edge-to-edge no Android 16](/blog/android-16-edge-to-edge-compose-kotlin-2026/);
4. [Material 3 Expressive](/blog/material-3-expressive-compose-kotlin-2026/);
5. [testes de interface Android](/guias/testes-android-compose-maestro/).

O objetivo não é manter uma versão “para celular” e outra “para tablet”. É construir uma interface que continue útil quando o espaço muda — inclusive no meio da tarefa. Essa é a base de um app Android moderno com Kotlin e Compose.
