Migrar a IU para layouts responsivos

Os apps Android precisam oferecer suporte a um ecossistema de formatos de dispositivos em constante expansão. A interface de um app precisa ser responsiva para se adequar a uma grande variedade de tamanhos de tela, orientações diferentes e estados do dispositivo.

A IU responsiva se concentra nos princípios da flexibilidade e continuidade.

A flexibilidade se refere a layouts que fazem o uso ideal do espaço disponível e se ajustam quando esse espaço muda. Os ajustes podem fazer várias mudanças: aumentar o tamanho de uma única visualização, reposicionar as visualizações para que elas fiquem em locais mais acessíveis, mostrar ou ocultar outras visualizações ou uma combinação dessas opções.

Continuidade refere-se a uma experiência do usuário perfeita durante a transição de um tamanho de janela para outro. A experiência em que o usuário está envolvido precisa continuar sem interrupções. Como uma mudança de tamanho pode ser acompanhada por destruição e recriação de toda a hierarquia de visualização, é importante que o usuário não perca os dados dele nem a posição onde estava.

O que deve ser evitado

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 fixo, mas em muitas situações, esses valores não são úteis para determinar o espaço com que a interface pode trabalhar.

Em tablets, um app pode estar sendo executado no modo de várias janelas, o que significa que ele compartilha a tela com outro app. No ChromeOS, um app pode estar em uma janela redimensionável. Pode até mesmo haver mais de uma tela física, como em dispositivos dobráveis ou com várias telas. Em todos esses casos, o tamanho da tela física não é relevante para decidir como exibir conteúdo.

Vários dispositivos mostrando janelas de apps de tamanhos diferentes.
Figura 1. Os tamanhos das janelas podem ser diferentes do tamanho do dispositivo físico ou da tela.

Pelo mesmo motivo, evite fixar o app em uma orientação ou proporção específica. Embora o dispositivo possa estar em uma orientação específica, seu app pode estar em uma orientação diferente com base apenas no tamanho da própria janela. Por exemplo, em um tablet no modo paisagem durante o uso do modo de várias janelas, um app pode estar em modo retrato porque é mais alto do que largo.

Além disso, evite determinar se o dispositivo é um smartphone ou tablet. O que se qualifica especificamente como um tablet é subjetivo, porque pode se basear em um determinado tamanho, proporção ou uma combinação de tamanho e proporção. Com o surgimento de novos formatos, essas suposições podem mudar facilmente, e essa distinção perde a importância.

Em vez de tentar qualquer uma das estratégias anteriores, use pontos de interrupção e classes de tamanho de janela.

Pontos de interrupção e classes de tamanho de janela

A parte real da tela alocada para o app é a janela do app. Ela pode ocupar a tela inteira ou parte dela; então considere o tamanho da janela ao tomar decisões importantes sobre o layout do app.

Ao projetar para vários formatos, encontre valores limite em que essas decisões importantes se ramificam em direções diferentes. Para isso, a grade de layout responsivo (link em inglês) do Material Design oferece pontos de interrupção para largura e altura, o que permite mapear tamanhos brutos em grupos discretos e padronizados chamados de classes de tamanho de janelas. Devido ao uso frequente de rolagem vertical, a maioria dos apps se preocupa principalmente com as classes de tamanho de largura. É possível otimizar a maioria dos aplicativos para todos os tamanhos de tela, lidando com apenas alguns pontos de interrupção. Para conferir mais informações sobre classes de tamanho de janelas, consulte Suporte para tamanhos de tela diferentes.

Elementos persistentes da interface

As diretrizes de layout do Material Design definem regiões para barras de apps, navegação e conteúdo. Normalmente, os dois primeiros são elementos de IU persistentes na raiz da hierarquia de visualização (ou muito próximos dela). "Permanente" não significa necessariamente que a visualização está sempre visível, mas que permanece no lugar enquanto outras visualizações de conteúdo podem ser movidas ou alteradas. Por exemplo, um elemento de navegação pode estar em uma gaveta deslizante que não está aberta, mas o ícone dela continua no mesmo lugar.

Os elementos permanentes podem ser responsivos e geralmente ocupam a largura total ou a altura total da janela. Portanto, prefira usar classes de tamanho para decidir onde colocá-los. Isso delimita o espaço restante para o conteúdo. No snippet abaixo, a atividade usa uma barra na parte de baixo para telas compactas e uma barra de apps na parte de cima para telas maiores. Os layouts qualificados usam pontos de interrupção de largura, conforme descrito anteriormente.

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- content view(s) -->

    <com.google.android.material.bottomappbar.BottomAppBar
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w600dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

Conteúdo

Após posicionar os elementos permanentes da IU, use o espaço restante para o conteúdo, por exemplo, usando um NavHostFragment com o gráfico de navegação do seu app. Consulte Navegação para IUs responsivas para ver outras considerações.

Garantir que todos os dados estejam disponíveis para tamanhos diferentes

Atualmente, a maioria dos frameworks de apps usa um modelo de dados separado dos componentes do Android que contribuem para a IU (atividades, fragmentos e visualizações). Com o Jetpack, esse papel é normalmente realizado pelos ViewModels, que têm o benefício a mais de sobreviver às mudanças de configuração. Consulte Visão geral do ViewModel para mais informações.

Ao implementar um layout que se adapta a diferentes tamanhos, pode ser tentador usar um modelo de dados diferente com base no tamanho atual. No entanto, isso vai contra o princípio do fluxo de dados unidirecional. O fluxo de dados precisa ser reduzido para as visualizações, e eventos, como as interações do usuário, precisam ser distribuídos na parte de cima. A criação de uma dependência na outra direção, em que o modelo de dados depende da configuração da camada de IU, complica drasticamente isso. Quando o tamanho do app mudar, você vai precisar considerar converter um modelo de dados para outro.

Em vez disso, autorize que o modelo de dados acomode a maior classe de tamanho. Assim, você pode mostrar, ocultar ou reposicionar o conteúdo na IU para se adaptar à classe de tamanho atual. Veja abaixo algumas estratégias que você pode usar para decidir como o layout vai se comportar ao fazer a transição entre classes de tamanho.

Abrir conteúdo

Layouts canônicos (link em inglês): feed

O espaço expandido pode ser uma oportunidade de simplesmente aumentar o tamanho e reformatar o conteúdo para torná-lo mais acessível.

Aumente o tamanho das coleções. Muitos apps mostram uma coleção de itens em um contêiner de rolagem, como RecyclerView ou ScrollView. Permitir que um contêiner se torne automaticamente maior significa que é possível mostrar mais conteúdo. No entanto, tenha cuidado para que o conteúdo dentro do contêiner não fique muito esticado ou distorcido. Por exemplo, com uma RecyclerView, considere usar um gerenciador de layout diferente como GridLayoutManager, StaggeredGridLayoutManager ou FlexboxLayout (link em inglês), quando a largura não for compacta.

Um dispositivo dobrado e desdobrado mostrando como diferentes gerenciadores de layout mostram o app de forma diferente com base na classe de tamanho da largura.
Figura 2. Diferentes gerenciadores de layout para diferentes classes de tamanho de janela.

Itens individuais também podem usar um tamanho ou uma forma diferente para mostrar mais conteúdo e distinguir os limites do item com mais facilidade.

Enfatize um elemento principal. Se o layout tiver um ponto focal específico, como uma imagem ou um vídeo, expanda o elemento quando a janela do app aumentar para manter a atenção do usuário nele. Outros elementos de apoio podem ser reorganizados em volta ou abaixo da visualização principal.

Há muitas maneiras de criar um layout assim, mas o uso de ConstraintLayout é especialmente adequado para essa finalidade, porque fornece muitas maneiras de restringir o tamanho de uma visualização filha (como por porcentagem ou aplicação de uma proporção) e de posicionar as filhas em relação a si próprias ou a outras. Saiba mais sobre todos esses recursos em Criar uma IU responsiva com o ConstraintLayout.

Mostrar conteúdo recolhível por padrão. Quando houver espaço disponível, exponha conteúdo que só seria acessado com interações a mais do usuário, como toques, rolagem ou gestos. Por exemplo, o conteúdo que aparece em uma interface com guias quando compacto pode ser reorganizado em colunas ou uma lista quando há mais espaço disponível.

Expandir margens. Se o espaço for muito grande e não for possível definir uma organização interessante mesmo após usar todos os itens, expanda as margens do layout para que o conteúdo permaneça centralizado e as visualizações individuais tenham tamanhos naturais e espaçamento entre elas.

Como alternativa, um componente de tela cheia pode se transformar em uma IU de caixa de diálogo flutuante. Isso se adequa especialmente bem quando esse componente exige foco exclusivo para atender a uma tarefa imediata do usuário, como escrever um e-mail ou criar um evento da agenda.

Smartphone padrão mostrando uma caixa de diálogo em tela cheia e um smartphone dobrável aberto mostrando a mesma caixa de diálogo como uma janela flutuante.
Figura 3. Caixa de diálogo em tela cheia transformada em uma caixa de diálogo de tamanho padrão com largura média e expandida.

Adicionar conteúdo

Layouts canônicos (link em inglês): painel de suporte, visualização em detalhes da lista

Usar um painel de suporte. Um painel de suporte apresenta conteúdo extra ou ações contextuais relacionadas ao conteúdo principal, como comentários em um documento ou itens em uma playlist. Normalmente, eles usam um terço da parte de baixo da tela para a altura expandida ou um terço ao longo da borda à direita para a largura expandida.

É importante considerar onde colocar esse conteúdo quando não há espaço suficiente para mostrar o painel. Confira algumas alternativas a serem exploradas:

  • Gaveta lateral ao longo da borda usando o DrawerLayout.
  • Gaveta de baixo usando o BottomSheetBehavior.
  • Menu ou janela pop-up acessível ao tocar em um ícone de menu.
Figura 4. Outras maneiras de apresentar mais conteúdo em um painel de suporte.

Criar um layout de dois painéis. Telas grandes podem mostrar uma combinação de recursos que normalmente aparecem separadamente em telas menores. Um padrão de interação comum em muitos apps é mostrar uma lista de itens, como contatos ou resultados da pesquisa, e alternar para os detalhes de um item selecionado. Em vez de ampliar a lista para telas maiores, use a visualização em detalhes da lista para mostrar os dois recursos lado a lado em um layout de dois painéis. Diferente de um painel de suporte, o painel de visualização em detalhes da lista é um elemento que pode ser mostrado de forma independente em telas menores.

Use o widget dedicado SlidingPaneLayout para implementar uma visualização em detalhes da lista. Esse widget calcula automaticamente se há espaço suficiente para mostrar ambos os painéis com base no valor layout_width especificado neles, e qualquer espaço restante pode ser distribuído usando layout_weight. Quando não há espaço suficiente, cada painel usa a largura total do layout, e o painel de detalhes desliza para fora da tela ou para a parte de cima do painel da lista.

SlidingPaneLayout mostrando os dois painéis de um layout de detalhes da lista em um dispositivo com uma tela ampla.
Figura 5. SlidingPaneLayout mostrando dois painéis com largura expandida e um painel com largura compacta.

O guia Criar um layout de dois painéis contém mais detalhes sobre o uso do SlidingPaneLayout. Esse padrão também pode afetar a forma como você estrutura o gráfico de navegação. Consulte Navegação para interfaces responsivas.

Outros recursos