O suporte a diferentes tamanhos de tela permite que o app seja acessado pela maior variedade de dispositivos e pelo maior número de usuários.
Para oferecer suporte ao máximo possível de tamanhos de tela, sejam telas de dispositivos diferentes ou janelas de apps diferentes no modo de várias janelas, crie layouts de app responsivos e adaptáveis. Os layouts responsivos/adaptáveis proporcionam uma experiência do usuário otimizada, independente do tamanho de exibição. Isso permite que o app acomode smartphones, tablets, dispositivos dobráveis, dispositivos ChromeOS, orientações de retrato e paisagem e configurações de tela redimensionáveis, como o modo de tela dividida e o modo janela para computador.
Os layouts responsivos/adaptáveis mudam de acordo com o espaço de tela disponível. As mudanças variam de pequenos ajustes de layout que preenchem o espaço (design responsivo) até a substituição completa de um layout por outro para que o app possa acomodar melhor diferentes tamanhos de tela (design adaptável).
Como um kit de ferramentas de IU declarativo, o Jetpack Compose é ideal para projetar e implementar layouts que mudam dinamicamente para renderizar conteúdo de maneira diferente em diferentes tamanhos de tela.
Explicitar grandes mudanças de layout para elementos combináveis de conteúdo
Os elementos combináveis do app e do conteúdo ocupam todo o espaço de tela disponível para o app. Para esses tipos de elementos combináveis, pode ser útil mudar o layout geral do app em telas grandes.
Evite usar valores físicos de hardware para tomar decisões de layout. Pode ser tentador tomar decisões com base em um valor tangível fixo (o dispositivo é um tablet? A tela física tem determinada proporção?), mas as respostas a essas perguntas podem não ser úteis para determinar o espaço disponível para a interface.
Em tablets, um app pode ser executado no modo de várias janelas, o que significa que ele pode compartilhar a tela com outro app. No modo janela para computador ou no ChromeOS, um app pode estar em uma janela redimensionável. Pode haver até mais de uma tela física, como em um dispositivo dobrável. Em todos esses casos, o tamanho da tela física não é relevante para decidir como exibir conteúdo.
Em vez disso, tome decisões com base na parte real da tela alocada para o app, descrita pelas métricas de janela atuais fornecidas pela biblioteca WindowManager do Jetpack. Para um exemplo de como usar o WindowManager em um app do Compose, consulte o exemplo do JetNews.
Tornar seus layouts adaptáveis ao espaço de tela disponível também reduz a quantidade de tratamento especial necessário para oferecer suporte a plataformas como o ChromeOS e a formatos como tablets e dispositivos dobráveis.
Depois de determinar as métricas do espaço disponível para o app, converta o tamanho bruto em uma classe de tamanho de janela, conforme descrito em Usar classes de tamanho de janela. As classes de tamanho de janela são pontos de interrupção projetados para equilibrar a simplicidade da lógica do app com a flexibilidade de otimizar o app para a maioria dos tamanhos de tela.
As classes de tamanho de janela se referem à janela geral do app. Portanto, use-as para decisões de layout que afetam o layout geral do app. É possível transmitir classes de tamanho de janela como estado ou executar uma lógica adicional para criar um estado derivado a fim de transmitir para elementos combináveis aninhados.
@Composable fun MyApp( windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass ) { // Decide whether to show the top app bar based on window size class. val showTopAppBar = windowSizeClass.isHeightAtLeastBreakpoint(WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND) // MyScreen logic is based on the showTopAppBar boolean flag. MyScreen( showTopAppBar = showTopAppBar, /* ... */ ) }
Uma abordagem em camadas limita a lógica do tamanho de exibição a um único local, em vez de distribuí-la pelo app em muitos lugares que precisam ser sincronizados. Um único local produz um estado, que pode ser transmitido explicitamente para outros elementos combináveis, assim como qualquer outro estado do app. A transmissão explícita do estado simplifica os elementos combináveis individuais, já que eles usam a classe de tamanho de janela ou a configuração especificada com outros dados.
Composições aninhadas flexíveis são reutilizáveis
Os elementos combináveis são mais reutilizáveis quando podem ser colocados em uma grande variedade de lugares. Se um elemento combinável precisar ser colocado em um local específico com um tamanho específico, é improvável que ele seja reutilizável em outros contextos. Isso também significa que elementos combináveis individuais e reutilizáveis devem evitar depender implicitamente das informações de tamanho de exibição globais.
Imagine um elemento combinável aninhado que implemente um layout de detalhes e listas, que pode mostrar um ou dois painéis lado a lado:
A decisão de detalhes e listas precisa fazer parte do layout geral do app. Portanto, a decisão é transmitida de um elemento combinável de nível de conteúdo:
@Composable fun AdaptivePane( showOnePane: Boolean, /* ... */ ) { if (showOnePane) { OnePane(/* ... */) } else { TwoPane(/* ... */) } }
E se você quiser que uma função combinável mude o layout de forma independente, com base no espaço de tela disponível, por exemplo, um card que mostra detalhes adicionais, se houver espaço? Você quer executar alguma lógica com base em um tamanho de exibição disponível, mas qual tamanho especificamente?
Evite tentar usar o tamanho da tela real do dispositivo. Isso não será preciso para diferentes tipos de telas e também não será preciso se o app não estiver em tela cheia.
Como o elemento combinável não está no nível do conteúdo, não use as métricas da janela atual diretamente.
Se o componente for colocado com padding (como com encartes) ou se o app incluir componentes como colunas de navegação ou barras de apps, a quantidade de espaço de tela disponível para o elemento combinável poderá ser significativamente diferente do espaço geral disponível para o app.
Use a largura que o elemento combinável recebe para renderizar a si mesmo. Você tem duas opções para conseguir essa largura:
Caso queira mudar onde ou como o conteúdo é exibido, use um conjunto de modificadores ou um layout personalizado para tornar o layout responsivo. Isso pode ser tão simples quanto fazer com que um filho preencha todo o espaço disponível ou dispor filhos com várias colunas se houver espaço suficiente.
Se você quiser mudar o que mostrar, use
BoxWithConstraintscomo uma alternativa mais poderosa.BoxWithConstraintsoferece restrições de medição que podem ser usadas para chamar diferentes elementos combináveis com base no espaço de tela disponível. No entanto, isso tem algumas desvantagens, porque a funçãoBoxWithConstraintsadia a composição até a fase do layout, quando essas restrições são conhecidas, fazendo com que mais trabalho seja realizado durante o layout.
@Composable fun Card(/* ... */) { BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(/* ... */) Title(/* ... */) } } else { Row { Column { Title(/* ... */) Description(/* ... */) } Image(/* ... */) } } } }
Disponibilizar todos os dados para diferentes tamanhos de tela
Ao implementar um elemento combinável que aproveita o espaço de exibição extra, pode ser tentador fazer isso e carregar dados como um efeito colateral do tamanho de exibição atual.
No entanto, isso vai contra o princípio do fluxo de dados unidirecional, em que os dados podem ser elevados e fornecidos a elementos combináveis para renderização adequada. Dados suficientes precisam ser fornecidos ao elemento combinável para que ele sempre tenha conteúdo suficiente para qualquer tamanho de exibição, mesmo que parte do conteúdo nem sempre seja usada.
@Composable fun Card( imageUrl: String, title: String, description: String ) { BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(imageUrl) Title(title) } } else { Row { Column { Title(title) Description(description) } Image(imageUrl) } } } }
Com base no exemplo de Card, observe que a description é sempre transmitida para o Card. Embora a description seja usada apenas quando a largura permite a exibição, o Card sempre precisa dela, independentemente da largura disponível.description
A transmissão contínua de conteúdo suficiente deixa os layouts adaptáveis mais simples, tornando-os menos "com estado". Também evita o acionamento de efeitos colaterais ao alternar entre tamanhos de tela, o que pode ocorrer devido a redimensionamento de janela, a mudança de orientação ou à dobra e o desdobramento de um dispositivo.
Esse princípio também permite preservar o estado em todas as mudanças de layout. Ao elevar informações que não podem ser usadas em todos os tamanhos de tela, é possível preservar o estado do app conforme o tamanho do layout muda.
Por exemplo, é possível elevar uma flag booleana showMore para que o estado do app seja preservado quando o redimensionamento da tela fizer o layout alternar entre ocultar e mostrar conteúdo:
@Composable fun Card( imageUrl: String, title: String, description: String ) { var showMore by remember { mutableStateOf(false) } BoxWithConstraints { if (maxWidth < 400.dp) { Column { Image(imageUrl) Title(title) } } else { Row { Column { Title(title) Description( description = description, showMore = showMore, onShowMoreToggled = { newValue -> showMore = newValue } ) } Image(imageUrl) } } } }
Saiba mais
Para saber mais sobre layouts adaptáveis no Compose, consulte os seguintes recursos:
Apps de exemplo
- CanonicalLayouts é um repositório de padrões de design comprovados que oferecem uma experiência do usuário ideal em telas grandes.
- O JetNews mostra como projetar um app que adapta a interface para usar o espaço de tela disponível.
- O Reply é um exemplo adaptável para oferecer suporte a smartphones, tablets e dispositivos dobráveis.
- O Now in Android é um app que usa layouts adaptáveis para oferecer suporte a diferentes tamanhos de tela.
Vídeos
- Criar interfaces do Android para qualquer tamanho de tela
- Formatos | Conferência de Desenvolvedores Android 2022