Compatibilidade com densidades de pixel diferentes

Dispositivos Android não só vêm com vários tamanhos de telas (celulares, tablets, TVs etc.) como as telas também têm diferentes tamanhos de pixel. Ou seja, enquanto um dispositivo tem 160 pixels por polegada quadrada, outro encaixa 480 pixels no mesmo espaço. Se você não considerar essas variações em densidade de pixels, o sistema pode dimensionar suas imagens (resultando em imagens borradas) ou as imagens podem aparecer em um tamanho completamente errado.

Esta página mostra como criar seu app para que seja compatível com diferentes densidades de pixel, usando unidades de medição independentes de resolução e oferecendo recursos de bitmap alternativos para cada densidade de pixel.

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

Para mais informações sobre a criação de ativos de ícones, consulte as orientações para ícones de Material Design (em inglês).

Usar pixels densidade independente

A primeira armadilha a ser evitada é usar pixels para definir distâncias ou tamanhos. Definir dimensões com pixels é um problema, porque telas diferentes têm densidades pixels diferentes, de maneira que o mesmo número de pixels pode corresponder a diferentes tamanhos físicos nos vários dispositivos.

Figura 1. Duas telas do mesmo tamanho podem ter um número diferente de pixels.

Para preservar o tamanho visível da sua interface do usuário em telas com diferentes densidades, você precisa projetar sua IU com pixels de densidade independente (dp) como unidade de medida. Um dp é uma unidade de pixel virtual aproximadamente do tamanho de um pixel em uma tela de densidade média (160 dpi, a densidade "básica"). O Android converte esse valor no número apropriado de pixels reais para cada densidade.

Por exemplo, veja os dois dispositivos da figura 1. Se você definisse uma exibição com "100 px" de largura, ela apareceria muito maior no dispositivo à esquerda. Portanto, você precisa usar "100 dp" para garantir que ela apareça do mesmo tamanho nas duas telas.

No entanto, você precisa usar pixels escalonáveis (sp) como suas unidades para definir tamanhos de texto, mas nunca para tamanhos de layout. Por padrão, a unidade sp é do mesmo tamanho que a dp, mas ela é redimensionada com base no tamanho de texto preferencial do usuário.

Por exemplo, ao 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, sempre 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ê precisará expressar dimensões em dp e convertê-las em pixels. A conversão das unidades de dp em pixels de tela é simples:

px = dp * (dpi / 160)

Imagine um app em que um gesto de rolagem ou navegação seja reconhecido depois que o dedo do usuário tenha sido movido em pelo menos 16 pixels. Em uma tela de configuração básica, o dedo do usuário precisa se mover 16 pixels / 160 dpi, o que equivale a 2,5 mm (ou 1/10 de uma polegada), para que o gesto seja reconhecido. Em um dispositivo com uma tela de alta densidade (240 dpi), o dedo do usuário precisa ser movido em 16 pixels / 240 dpi, o que equivale a 1,7 mm (ou 1/15 de polegada). A distância é muito mais curta e, dessa forma, o app parece ser mais sensível para o usuário.

Para corrigir esse problema, o limite do gesto precisa ser expresso no código em dp e convertido para pixels. Por exemplo:

Kotlin

    // The gesture threshold expressed in dp
    private const val GESTURE_THRESHOLD_DP = 16.0f
    ...
    private var mGestureThreshold: Int = 0
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Get the screen's density scale
        val scale: Float = resources.displayMetrics.density
        // Convert the dps to pixels, based on density scale
        mGestureThreshold = (GESTURE_THRESHOLD_DP * scale + 0.5f).toInt()

        // Use mGestureThreshold as a distance in pixels...
    }
    

Java

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

    // Get the screen's density scale
    final float scale = getResources().getDisplayMetrics().density;
    // Convert the dps to pixels, based on density scale
    mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);

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

O campo DisplayMetrics.density especifica o fator de escala a ser usado para converter unidades de dp em pixels, de acordo com a densidade da tela atual. Em uma tela de densidade média, DisplayMetrics.density equivale a 1,0; em uma tela de densidade alta, 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. Esse valor é o fator pelo qual você precisa multiplicar as unidades de dp 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, pode-se conseguir a distância em pixels usada pela estrutura como limite de rolagem com getScaledTouchSlop():

Kotlin

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

Java

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

Métodos em ViewConfiguration iniciados pelo prefixo getScaled garantem o retorno de um valor em pixels exibido corretamente independentemente da densidade de pixels atual.

Fornecer bitmaps alternativos

Para fornecer boa qualidade gráfica em dispositivos com densidades de pixels diferentes, você precisa oferecer várias versões de cada bitmap no seu app: uma para cada intervalo de densidade em uma resolução correspondente. Se não fizer isso, o Android precisará dimensionar seu bitmap para que ele ocupe o mesmo espaço visível em cada tela, resultando em artefatos de dimensionamento, como desfoque.

Figura 2. Tamanhos relativos para bitmaps em diferentes densidades.

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) (cerca de 120 dpi).
mdpi Recursos para telas de média densidade (mdpi) (cerca de 160 dpi). Essa é a densidade básica.
hdpi Recursos para telas de alta densidade (hdpi) (cerca de 240 dpi).
xhdpi Recursos para telas de densidade extra-alta (xhdpi) (cerca de 320 dpi).
xxhdpi Recursos para telas de densidade extra-extra-alta (xxhdpi) (cerca de 480 dpi).
xxxhdpi Recursos para telas de densidade extra-extra-extra-alta (xxxhdpi) (cerca de 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, independentemente da densidade da tela atual.
tvdpi Recursos para telas entre mdpi e hdpi; aproximadamente 213 dpi. Esse não é considerado um grupo de densidade "principal". Ele é destinado principalmente a televisões, e a maioria dos apps provavelmente não precisa dele. Fornecer recursos mdpi e hdpi é o suficiente para a maioria dos apps, e o sistema os dimensionará conforme for apropriado. Se julgar necessário fornecer recursos tvdpi, dimensione-os para um fator de 1,33*mdpi. Por exemplo, uma imagem de 100 x 100 px para telas mdpi precisa ter 133 x 133 px para tvdpi.

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

  • 36 x 36 (0,75 x) para densidade baixa (ldpi)
  • 48 x 48 (configuração básica de 1,0 x) para densidade média (mdpi)
  • 72 x 72 (1,5 x) para densidade alta (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)

Em seguida, coloque os arquivos de imagem gerados no subdiretório adequado em res/, e o sistema escolherá automaticamente o correto com base na densidade de pixels do dispositivo em que seu app está sendo executado:

    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 selecionará o bitmap apropriado com base no dpi da tela. Se você não fornecer um recurso específico de densidade, o sistema selecionará a melhor correspondência e dimensionará o app para se ajustar à tela.

Dica: se você tiver alguns recursos drawable que queira que o sistema nunca dimensione (talvez você pretenda fazer ajustes na imagem por conta própria no momento da execução), coloque-os em um diretório com o qualificador de configuração nodpi. Recursos com esse qualificador são considerados agnósticos em relação à densidade, e o sistema não os dimensionará.

Para mais informações sobre outros qualificadores de configuração e como o Android seleciona os recursos apropriados para a configuração de tela atual, consulte Fornecimento de recursos.

Colocar ícones de app em diretórios mipmap

Como todos os outros ativos de bitmap, você precisa fornecer versões específicas de densidade do ícone do seu app. No entanto, algumas telas de início exibem o ícone do seu app 25% maior do que o necessário para o intervalo de densidade do dispositivo.

Por exemplo, se o intervalo 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 dimensionará o ícone, fazendo com que pareça menos nítido. Portanto, forneça um ícone na tela de início com densidade ainda maior no diretório mipmap-xxxhdpi. Assim, a tela de início poderá usar o recurso xxxhdpi.

Como o ícone do seu app pode ser dimensionado dessa forma, você precisa colocar todos os ícones nos diretórios mipmap, em vez de drawable. Ao contrário do diretório drawable, todos os diretórios mipmap são mantidos no APK, mesmo que você criar APKs específicos para densidades. Isso permite que os apps da tela de início selecionem a melhor resolução de ícone 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
    

Para orientações sobre criação de ícones, consulte o guia de Material Design para ícones (em inglês).

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

Usar gráficos vetoriais

Uma alternativa à criação de múltiplas versões específicas de densidade de uma imagem é criar um único gráfico vetorial. Gráficos vetoriais criam uma imagem usando XML para definir caminhos e cores em vez de usar bitmaps de pixels. Dessa forma, gráficos vetoriais podem ser dimensionados para qualquer tamanho sem artefatos de dimensionamento, apesar de serem melhores para ilustrações como ícones, e não para fotografias.

Gráficos vetoriais geralmente são fornecidos como um arquivo SVG (Scalable Vector Graphics), mas o Android não é compatível com esse formato, então é necessário converter arquivos SVG no formato de drawable vetorial do Android.

Você pode converter facilmente um SVG em um drawable vetorial do Android Studio usando o Vector Asset 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.

    Figura 3. Como importar um SVG com o Android Studio.

    Alguns erros podem aparecer na janela Asset Studio, indicando algumas propriedades do arquivo que não são compatíveis com drawables vetoriais. Mas isso não impede a importação, porque as propriedades incompatíveis são simplesmente ignoradas.

  4. Clique em Next.

  5. Na tela seguinte, confirme o conjunto de origem em que você quer colocar o arquivo e clique em Finish.

    Como um drawable vetorial pode ser usado com qualquer densidade de pixel, esse arquivo entra no seu diretório de drawables padrão, sem necessidade de usar diretórios específicos de densidade:

        res/
          drawable/
            ic_android_launcher.xml
        

Para mais informações sobre criação de gráficos vetoriais, leia a documentação de Drawables vetoriais.

Orientações para problemas de densidade incomuns

Esta seção contém mais informações sobre 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 tido problemas na execução em diferentes densidades de pixel, você pode ignorar esta seção.

Para saber como oferecer compatibilidade com várias densidades ao manipular gráficos no tempo de execução, você precisa entender que o sistema ajuda a garantir o dimensionamento correto para bitmaps 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 carrega os recursos padrão e os amplia ou reduz conforme a necessidade. O sistema presume que os recursos padrão (aqueles que se encontram em um diretório sem qualificadores de configuração) são projetados para a densidade de pixel básica (mdpi) e redimensiona esses bitmaps para o tamanho apropriado para a densidade de pixels atual.

    Se você solicitar as dimensões de um recurso pré-dimensionado, o sistema retornará valores que representem 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 em uma tela hdpi (caso não haja um recurso alternativo para hdpi) e o sistema declara o tamanho dessa maneira.

    Existem 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 de acordo com a densidade atual do dispositivo.

  2. Autodimensionamento de dimensões e coordenadas em pixels

    Você pode desativar o pré-dimensionamento de dimensões e imagens definindo android:anyDensity como "false" no manifesto ou de forma programática para um Bitmap definindo inScaled como "false". Nesse caso, o sistema autodimensiona qualquer coordenada absoluta em pixels e valores de dimensões em pixels no tempo de desenho. Isso é feito para garantir que os elementos da tela definidos como pixels ainda sejam exibidos no mesmo tamanho físico aproximado que eles teriam na tela de densidade básica (mdpi). O sistema realiza esse dimensionamento de forma transparente para o app 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, com 480 x 800 e aproximadamente o mesmo tamanho de uma tela HVGA tradicional, mas que ela esteja executando um app que tenha desativado o pré-dimensionamento. Nesse caso, o sistema "mentirá" para o app quando ele consultar as dimensões da tela e informará um tamanho de 320 x 533, ou seja, a conversão em mdpi aproximada para a densidade da tela. Em seguida, quando o app executar operações de desenho, como invalidar o retângulo de (10,10) para (100,100), o sistema transformará as coordenadas dimensionando-as para os valores corretos e invalidará a região (15,15) para (150,150). Essa discrepância pode causar comportamentos inesperados se o app manipular o bitmap dimensionado diretamente, mas essa é considerada uma troca razoável para manter o desempenho dos apps no mais alto nível possível. Se você encontrar essa situação, leia Converter unidades de dp em unidades de pixel.

    Geralmente, não se recomenda desativar o pré-dimensionamento. A melhor maneira de oferecer compatibilidade com várias telas é seguir as técnicas básicas descritas neste documento.

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

Testar em todas as densidades de pixel

É importante testar seu app em vários dispositivos com diferentes densidades de pixel para garantir que sua interface do usuário seja dimensionada corretamente. Testar em um dispositivo físico é fácil, mas você também pode usar o Android Emulator se não tiver acesso a dispositivos físicos para todas as densidades de pixel diferentes.

Caso você prefira testar em um dispositivo físico, mas não queira comprar dispositivos, pode usar o Firebase Test Lab para acessar dispositivos em um data center do Google.