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 multi-janela, crie layouts de apps responsivos e adaptáveis. Os layouts responsivos/adaptáveis proporcionam uma experiência do usuário otimizada, independente do tamanho da tela. Isso permite que o app acomode smartphones, tablets, dispositivos dobráveis e ChromeOS, orientações de retrato e paisagem e configurações de tela redimensionáveis, como o modo de tela dividida e o uso de janelas no computador.
Os layouts responsivos/adaptáveis mudam de acordo com o espaço de exibição 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 telas de tamanhos diferentes.
Explicitar grandes mudanças de layout para elementos combináveis de conteúdo
Os elementos combináveis no nível do app e do conteúdo ocupam todo o espaço de exibição disponível para o app. Para esses tipos de elementos, pode ser interessante mudar o layout geral do app em telas grandes.
Evite usar valores físicos e 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 sua 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 de janelas 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 JetNews.
Tornar seus layouts adaptáveis ao espaço de exibição disponível também reduz a quantidade de processamento especial necessária para oferecer suporte a plataformas como o ChromeOS e 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().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 da tela 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 porque eles usam a classe de tamanho da janela ou a configuração especificada com outros dados.
Composições aninhadas flexíveis são reutilizáveis
Os elementos compostos 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 precisam evitar depender implicitamente das informações de tamanho de exibição globais.
Imagine um elemento combinável aninhado que implemente um layout de detalhes da lista, que pode mostrar um único painel ou dois painéis lado a lado:
A decisão de lista e detalhes deve fazer parte do layout geral do app. Por isso, ela é transmitida de um elemento combinável no nível do 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 disponível, por exemplo, um card que mostre detalhes adicionais se houver espaço? Você quer executar uma lógica com base em um tamanho de tela disponível, mas em qual tamanho especificamente?
Evite usar o tamanho da tela do dispositivo. Esse tamanho não é exato nem para vários tipos de telas e nem 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 pode ser significativamente diferente do espaço geral disponível para o app.
Use a largura em que o elemento combinável é realmente renderizado. Você tem duas opções para conseguir essa largura:
Se quiser 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 feito de forma simples, por exemplo, preenchendo todo o espaço disponível com um filho ou criando o layout de filhos com várias colunas, se houver espaço suficiente.
Se quiser mudar o que você mostra, use
BoxWithConstraintscomo uma alternativa mais eficiente. OBoxWithConstraintsfornece restrições de medição que podem ser usadas para chamar diferentes elementos combináveis com base no espaço de exibição 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 extra da tela, você pode ser tentado a ser eficiente e carregar dados como um efeito colateral do tamanho atual da tela.
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 tela, 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 da description, independentemente da
largura disponível.
A transmissão contínua de conteúdo suficiente deixa os layouts adaptáveis mais simples, tornando-os menos "com estado" e 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 disponível na tela.
- Reply é um exemplo adaptável para oferecer suporte a dispositivos móveis, tablets e dobráveis.
- O Now in Android é um app que usa layouts adaptáveis para compatibilidade com diferentes tamanhos de tela.
Vídeos
- Criar IUs do Android para qualquer tamanho de tela (vídeo em inglês)
- Formatos | Conferência de Desenvolvedores Android 2022