Compatibilidade com diferentes densidades de pixels

Os dispositivos Android não só têm diferentes tamanhos de tela, como celulares, tablets, TVs etc., mas também telas com diferentes tamanhos de pixel. Um dispositivo pode ter 160 pixels por polegada, enquanto outro cabe em 480 pixels no mesmo espaço. Se você não considerar essas variações em densidade de pixels, o sistema pode dimensionar as imagens, resultando em imagens desfocadas, ou elas podem aparecer no tamanho errado.

Esta página mostra como projetar seu app para oferecer suporte a diferentes densidades de pixel, usando unidades de medidas independentes de resolução e fornecendo recursos de bitmap alternativos para cada densidade de pixel.

Assista o vídeo a seguir para ter uma visão geral dessas técnicas.

Para saber mais sobre como criar recursos de ícone, consulte as diretrizes para ícones do Material Design (em inglês).

Usar pixels de densidade independente

Evite usar pixels para definir distâncias ou tamanhos. Definir dimensões com pixels é um problema, porque telas diferentes têm densidades de pixel distintas, de modo que o mesmo número de pixels corresponde a diferentes tamanhos físicos em dispositivos diferentes.

Uma imagem que mostra dois exemplos de telas de dispositivos com densidades diferentes
Figura 1: duas telas do mesmo tamanho podem ter um número de pixels diferente.

Para preservar o tamanho visível da interface em telas com diferentes densidades, crie sua interface usando pixels de densidade independente (dp, na sigla em inglês) como unidade de medida. Um dp é uma unidade de pixel virtual aproximadamente igual a um pixel em uma tela de média densidade (160 dpi, ou a densidade "valor de referência"). O Android converte esse valor para o número adequado de pixels reais para cada densidade.

Considere os dois dispositivos na Figura 1. Uma visualização com 100 pixels de largura aparece muito maior no dispositivo à esquerda. Uma visualização definida como 100 dp de largura aparece do mesmo tamanho nas duas telas.

Ao definir tamanhos de texto, você pode usar pixels escalonáveis (sp) como suas unidades. Por padrão, a unidade de sp é do mesmo tamanho que um dp, mas é redimensionada com base no tamanho de texto preferido do usuário. Nunca usar sp para tamanhos de layout.

Por exemplo, para especificar o espaçamento entre duas visualizações, use dp:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

Ao especificar o tamanho do texto, use sp:

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

Converter unidades de dp em unidades de pixel

Em alguns casos, você precisa expressar dimensões em dp e convertê-las em pixels. A conversão de unidades dp em pixels da tela é esta:

px = dp * (dpi / 160)

Observação:nunca fixe essa equação no código para calcular pixels. Em vez disso, use TypedValue.applyDimension(), que converte muitos tipos de dimensões (dp, sp etc.) em pixels.

Imagine um app em que um gesto de rolagem ou rolagem rápida seja reconhecido depois que o dedo do usuário tiver se movido pelo menos 16 pixels. Em uma tela de referência, o dedo do usuário precisa mover 16 pixels / 160 dpi, o que equivale a 2,5 mm (ou 1/10 de polegada), para que o gesto seja reconhecido.

Em um dispositivo com uma tela de alta densidade (240 dpi), o dedo do usuário precisa mover 16 pixels / 240 dpi, o que igual a 1,7 mm (ou 1/15 de polegada). A distância é muito mais curta e, portanto, o app parece ser mais sensível para o usuário.

Para corrigir esse problema, expresse o limite de gestos no código em dp e o converta em pixels reais. Por exemplo:

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

// Use gestureThreshold as a distance in pixels...

Java

// The gesture threshold expressed in dp
private final float GESTURE_THRESHOLD_DP = 16.0f;

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

// Use gestureThreshold as a distance in pixels...

O campo DisplayMetrics.density especifica o fator de escala usado para converter unidades de dp em pixels de acordo com a densidade de pixels atual. Em uma tela de densidade média, DisplayMetrics.density equivale a 1,0 e em uma tela de alta densidade, ele equivale a 1,5. Em uma tela de densidade extra-alta, ele equivale a 2,0 e em uma tela de densidade baixa, ele equivale a 0,75. Essa figura é usada pelo TypedValue.applyDimension() para conseguir a contagem real de pixels para a tela atual.

Usar valores de configuração pré-dimensionados

Você pode usar a classe ViewConfiguration para acessar distâncias, velocidades e tempos comuns usados pelo sistema Android. Por exemplo, a distância em pixels usada pelo framework como limite de rolagem pode ser obtida com getScaledTouchSlop():

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

Os métodos em ViewConfiguration que começam com o prefixo getScaled retornam um valor em pixels exibido corretamente, independentemente da densidade de pixels atual.

Preferir gráficos vetoriais

Uma alternativa à criação de várias versões específicas de densidade de uma imagem é criar apenas um gráfico vetorial. Gráficos vetoriais criam uma imagem usando XML para definir caminhos e cores, em vez de usar bitmaps de pixel. Dessa forma, gráficos vetoriais podem ser dimensionados para qualquer tamanho sem artefatos de escalonamento, embora geralmente sejam melhores para ilustrações como ícones, não fotos.

Os gráficos vetoriais geralmente são fornecidos como arquivos SVG (Elementos gráficos vetoriais escaláveis), mas o Android não é compatível com esse formato. Por isso, é necessário converter os arquivos SVG no formato de drawable vetorial do Android.

É possível converter um SVG em um drawable vetorial usando o Vector Asset Studio do Android Studio:

  1. Na janela Project, clique com o botão direito do mouse no diretório res e selecione New > Vector Asset.
  2. Selecione Local file (SVG, PSD).
  3. Localize o arquivo que você quer importar e faça os ajustes necessários.

    Imagem mostrando como importar SVGs no Android Studio
    Figura 2: importação de um SVG com o Android Studio.

    Talvez você note alguns erros na janela Asset Studio, indicando que os drawables vetoriais não são compatíveis com algumas propriedades do arquivo. Isso não impede a importação do arquivo, as propriedades sem suporte são ignoradas.

  4. Clique em Next.

  5. Na próxima tela, confirme o conjunto de origem em que você quer o arquivo no projeto e clique em Finish.

    Como um drawable vetorial pode ser usado em todas as densidades de pixel, esse arquivo vai entrar no diretório de drawables padrão, conforme mostrado na hierarquia abaixo. Não é necessário usar diretórios específicos de densidade.

    res/
      drawable/
        ic_android_launcher.xml
    

Para mais informações sobre como criar gráficos vetoriais, leia a documentação sobre drawables vetoriais.

Fornecer bitmaps alternativos

Para oferecer uma boa qualidade gráfica em dispositivos com densidades de pixel diferentes, forneça várias versões de cada bitmap no app, uma para cada bucket de densidade, com uma resolução correspondente. Caso contrário, o Android vai precisar dimensionar o bitmap para que ele ocupe o mesmo espaço visível em cada tela, resultando em artefatos de dimensionamento, como desfoque.

Imagem mostrando tamanhos relativos para bitmaps em diferentes tamanhos de densidade
Figura 3: tamanhos relativos para bitmaps em diferentes buckets de densidade.

Há vários intervalos de densidade disponíveis para uso nos seus apps. A Tabela 1 descreve os diferentes qualificadores de configuração disponíveis e a quais tipos de tela eles se aplicam.

Tabela 1. Qualificadores de configuração para diferentes densidades de pixel.

Qualificador de densidade Descrição
ldpi Recursos para telas de baixa densidade (ldpi) (aproximadamente 120 dpi).
mdpi Recursos para telas de média densidade (mdpi) (aproximadamente 160 dpi). Essa é a densidade de referência.
hdpi Recursos para telas de alta densidade (hdpi) (aproximadamente 240 dpi).
xhdpi Recursos para telas de densidade extra-alta (xhdpi) (aproximadamente 320 dpi).
xxhdpi Recursos para telas de densidade extra-extra-alta (xxhdpi) (aproximadamente 480 dpi).
xxxhdpi Recursos para usos de densidade extra-extra-extra-alta (xxxhdpi) (aproximadamente 640 dpi).
nodpi Recursos para todas as densidades. Esses são recursos independentes de densidade. O sistema não dimensiona recursos marcados com esse qualificador, independente da densidade da tela atual.
tvdpi Recursos para telas entre mdpi e hdpi, com aproximadamente 213 dpi. Não é considerado um grupo de densidade "principal".' É destinado principalmente para televisões e a maioria dos apps não precisa dele. Fornecer recursos mdpi e hdpi é suficiente para a maioria dos apps, e o sistema os dimensiona conforme adequado. Se for necessário fornecer recursos tvdpi, dimensione-os a um fator de 1,33 * mdpi. Por exemplo, uma imagem de 100 x 100 pixels para telas mdpi é de 133 x 133 pixels para tvdpi.

Para criar drawables de bitmap alternativos para diferentes densidades, siga a proporção de dimensionamento 3:4:6:8:12:16 entre as seis densidades principais. Por exemplo, se você tiver um drawable de bitmap de 48 x 48 pixels para telas de densidade média, os tamanhos vão ser:

  • 36 x 36 (0,75 x) para baixa densidade (ldpi)
  • 48 x 48 (1,0x linha de base) para densidade média (mdpi)
  • 72 x 72 (1,5 x) para alta densidade (hdpi)
  • 96 x 96 (2,0 x) para densidade extra-alta (xhdpi)
  • 144 x 144 (3,0 x) para densidade extra-extra-alta (xxhdpi)
  • 192 x 192 (4,0 x) para densidade extra-extra-extra-alta (xxxhdpi)

Coloque os arquivos de imagem gerados no subdiretório adequado em res/:

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

Assim, sempre que você referenciar @drawable/awesomeimage, o sistema vai selecionar o bitmap apropriado com base no DPI da tela. Se você não fornecer um recurso específico de densidade para essa densidade, o sistema vai localizar a melhor correspondência e dimensioná-la para caber na tela.

Dica:se você tem recursos drawable que não quer que o sistema dimensione, como ao executar alguns ajustes na imagem durante a execução, coloque-os em um diretório com o qualificador de configuração nodpi. Recursos com esse qualificador são considerados independentes de densidade e o sistema não os dimensiona.

Para saber mais sobre outros qualificadores de configuração e como o Android seleciona os recursos adequados para a configuração de tela atual, consulte Visão geral dos recursos de app.

Colocar ícones de app em diretórios mipmap

Assim como acontece com outros recursos de bitmap, você precisa fornecer versões específicas de densidade do ícone do app. No entanto, algumas telas de início exibem o ícone do app em até 25% maior do que o exigido pelo bucket de densidade do dispositivo.

Por exemplo, se o bucket de densidade de um dispositivo for xxhdpi e o maior ícone de app que você fornecer estiver em drawable-xxhdpi, a tela de início vai dimensionar esse ícone, fazendo com que ele pareça menos nítido.

Para evitar isso, coloque todos os ícones do app nos diretórios mipmap em vez de drawable. Ao contrário dos diretórios drawable, todos os diretórios mipmap são mantidos no APK, mesmo que você crie APKs específicos para densidades. Isso permite que os apps da tela de início escolham o melhor ícone de resolução para exibir na tela inicial.

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

No exemplo anterior de um dispositivo xxhdpi, é possível fornecer um ícone na tela de início de densidade mais alta no diretório mipmap-xxxhdpi.

Para orientações sobre criação de ícones, consulte Ícones do sistema.

Para receber ajuda com a criação de ícones, consulte Criar ícones com o Image Asset Studio.

Orientações para problemas de densidade incomuns

Esta seção descreve como o Android executa o dimensionamento para bitmaps em diferentes densidades de pixel e como você pode ter mais controle sobre como os bitmaps são renderizados em diferentes densidades. A menos que seu app manipule gráficos ou que você tenha encontrado problemas ao executar em densidades de pixel diferentes, ignore esta seção.

Para entender melhor como oferecer suporte a várias densidades ao manipular gráficos no tempo de execução, você precisa saber como o sistema ajuda a garantir o dimensionamento adequado para bitmaps. Isso é feito das seguintes maneiras:

  1. Pré-dimensionamento de recursos, como drawables de bitmap

    Com base na densidade da tela atual, o sistema usa qualquer recurso específico de densidade do seu app. Se os recursos não estiverem disponíveis na densidade correta, o sistema carregará os recursos padrão e os dimensionará conforme necessário. O sistema presume que os recursos padrão (os de um diretório sem qualificadores de configuração) foram projetados para a densidade de pixel de referência (mdpi) e redimensiona esses bitmaps para o tamanho apropriado da densidade de pixel atual.

    Se você solicitar as dimensões de um recurso pré-dimensionado, o sistema retornará valores que representam as dimensões após o dimensionamento. Por exemplo, um bitmap projetado a 50 x 50 pixels para uma tela mdpi é dimensionado para 75 x 75 pixels em uma tela hdpi (se não houver um recurso alternativo para hdpi), e o sistema vai informar o tamanho dessa forma.

    Há algumas situações em que você pode não querer que o Android pré-dimensione um recurso. A maneira mais fácil de evitar o pré-dimensionamento é colocar o recurso em um diretório de recursos com o qualificador de configuração nodpi. Por exemplo:

    res/drawable-nodpi/icon.png

    Quando o sistema usa o bitmap icon.png dessa pasta, ele não o dimensiona com base na densidade atual do dispositivo.

  2. Escalonamento automático de dimensões e coordenadas de pixel

    É possível desativar dimensões e imagens de pré-dimensionamento definindo android:anyDensity como "false" no manifesto ou programaticamente para um Bitmap definindo inScaled como "false". Nesse caso, o sistema faz o escalonamento automático de todas as coordenadas absolutas e dos valores de dimensão de pixel no tempo de desenho. Isso garante que os elementos da tela definidos em pixels ainda sejam mostrados no mesmo tamanho físico aproximado que eles aparecem na densidade de pixel básica (mdpi). O sistema processa esse dimensionamento de forma transparente e informa as dimensões ajustadas em pixels ao app em vez das dimensões físicas em pixels.

    Por exemplo, suponha que um dispositivo tenha uma tela WVGA de alta densidade, que tenha 480 x 800 e aproximadamente o mesmo tamanho de uma tela HVGA tradicional, mas que esteja executando um app que tenha desativado o pré-dimensionamento. Nesse caso, o sistema "está" no app quando ele consulta as dimensões da tela e informa 320 x 533, que é a tradução em mdpi aproximada para a densidade de pixels.

    Depois, quando o app realiza operações de desenho, como invalidar um retângulo de (10,10) a (100, 100), o sistema transforma as coordenadas dimensionando-as para o valor adequado e, na verdade, invalida a região (15,15) para (150, 150). Essa discrepância pode causar um comportamento inesperado se o app manipular diretamente o bitmap dimensionado, mas isso é considerado uma troca razoável para garantir o melhor desempenho possível. Se você se deparar com essa situação, leia Converter unidades de dp em unidades de pixel.

    Normalmente, você não desativa o pré-escalonamento. A melhor maneira de oferecer suporte a várias telas é seguir as técnicas básicas descritas nesta página.

Caso seu app manipule bitmaps ou interaja diretamente com pixels na tela de outra forma, pode ser necessário seguir outras etapas para oferecer suporte a diferentes densidades de pixel. Por exemplo, se você responder a gestos de toque contando o número de pixels que um dedo cruza, será necessário usar os valores de pixels de densidade independente adequados, em vez de pixels reais, mas você pode converter entre valores dp e px.

Testar em todas as densidades de pixel

Teste seu app em vários dispositivos com diferentes densidades de pixel para garantir que a interface seja dimensionada corretamente. Faça testes em um dispositivo físico quando possível. Use o Android Emulator se você não tiver acesso a dispositivos físicos para todas as densidades de pixel diferentes.

Se você quiser testar em dispositivos físicos, mas não quiser comprar dispositivos, use o Firebase Test Lab para acessar dispositivos em um data center do Google.