Ilustração de Virginia Poltrack

Existem dispositivos Android de todos os tamanhos, formas e densidades de tela. É por isso que sou um grande fã da usar recursos vetorizados que não dependem de resolução. Mas o que exatamente eles são? Quais são os benefícios? Quais são os custos? Quando devem ser usados? Como devem ser criados e usados? Nesta série de postagens, gostaria de explorar esses questionamentos e explicar por que eu acho que a grande maioria dos recursos dos aplicativos deve ser composta por vetores e como aproveitá-los ao máximo.

Rasterização vs. vetorização

A maioria dos formatos de imagem (png, jpeg, bmp, gif, webp, etc.) são rasterizados. Isso significa que eles descrevem a imagem como uma grade fixa de pixels. Sendo assim, são definidos em uma resolução específica e não conseguem entender informações sobre seu conteúdo, apenas a cor de cada pixel. Gráficos vetoriais, no entanto, descrevem a imagem como uma série de formas definidas em um tamanho de tela abstrato.

Por que vetorizar?

Os recursos vetorizados têm três benefícios principais. Eles são:

  • Nítidos
  • Pequenos
  • Dinâmicos

Nítidos

As imagens vetoriais podem ser redimensionadas com facilidade porque a descrição é feita em uma tela de tamanho abstrato. Assim, você pode aumentar ou diminuir a tela e redesenhar a imagem no tamanho desejado. Os recursos rasterizados, porém, podem se deteriorar ao serem redimensionados. A diminuição do tamanho dos recursos rasterizados costuma funcionar (já que você está descartando informações), mas aumentar o tamanho deles pode criar artefatos como distorções ou faixas porque os pixels que estiverem faltando precisarão ser interpolados.

Artefatos causados por aumento extremo do tamanho de uma imagem rasterizada (à esquerda) e vetorizada (à direita)

É por isso que no Android precisamos oferecer diversas versões de cada recurso rasterizado para telas de diferentes densidades:

  • res/drawable-mdpi/foo.png
  • res/drawable-hdpi/foo.png
  • res/drawable-xhdpi/foo.png

O Android seleciona a maior densidade aproximada e diminui o tamanho (se necessário). Devido à tendência de dispositivos com densidade de tela cada vez mais alta, os criadores de aplicativos precisam continuar gerando, incluindo e lançando versões cada vez maiores dos mesmos recursos. É importante lembrar que os dispositivos modernos não usam intervalos de densidade exatos. O Pixel 3 XL, por exemplo, é em 552 dpi, algo entre xxh dpi e xxxh dpi. Dessa forma, os recursos serão redimensionados com frequência.

Graças à excelente capacidade de redimensionamento dos recursos vetorizados, você pode adicionar um único recurso com a segurança de que ele funcionará em todas as densidades de tela.

Pequenos

Os recursos vetorizados são geralmente* mais compactos do que os rasterizados, pelo fato de você só precisar incluir uma única versão e pela compressão eficiente.

Por exemplo, aqui temos uma alteração feita no aplicativo Google I/O em que trocamos uma série de ícones PNG rasterizados por vetores, economizando 482 KB. Embora possa não parecer muito, esse resultado foi apenas com relação a pequenos ícones. Quando se trata de imagens maiores, como ilustrações, a economia é ainda mais relevante.

Esta ilustração, por exemplo, do fluxo de integração de um aplicativo I/O do ano passado:

Ilustrações podem ser boas opções para vetorização

Na época, não conseguimos substituir essa imagem por um VectorDrawable porque os gradientes ainda não eram amplamente compatíveis (spoiler: agora são!), então tivemos que lançar uma versão rasterizada. Usando um vetor, teríamos uma redução de 30% no tamanho e um resultado melhor :

  • Rasterização: Tamanho do download = 53,9 KB (Tamanho do arquivo bruto = 54,8 KB)
  • Vetorização: Tamanho do download = 3,7 KB (Tamanho do arquivo bruto = 15,8 KB)
Enquanto as divisões da configuração de densidade do Android App Bundle trazem benefícios semelhantes ao fornecer somente os recursos de densidade necessários ao dispositivo, um VectorDrawable geralmente será menor e além eliminará a necessidade de continuar criando recursos rasterizados cada vez maiores.

Dinâmicos

Por descrever seu conteúdo, ao invés de “achatar” as informações em pixels, as imagens vetoriais trazem novas e interessantes possibilidades como animação, interatividade ou temas dinâmicos. Falaremos mais sobre isso nas próximas postagens.

Os vetores mantêm a estrutura da imagem. Dessa forma, elementos individuais podem ser temáticos ou animados

Prós e contras

Os vetores têm algumas desvantagens que precisam ser consideradas:

Decodificação

Como mencionado anteriormente, os recursos vetorizados descrevem o conteúdo e, portanto, precisam ser inflados e desenhados antes do uso.

As etapas para decodificar um vetor antes da renderização

Há duas etapas para isso:

  1. Inflação. O arquivo vetorizado precisa ser lido e analisado em um modelo VectorDrawable de caminhos, grupos e outras informações declaradas por você.
  2. Desenho. Esses objetos-modelo precisam então ser desenhados por meio da execução de comandos de desenho do Canvas.

Essas duas etapas são proporcionais à complexidade do vetor e ao tipo de operação realizada. Se você usar formas muito intricadas, a análise para transformá-las em um caminho será mais demorada. De forma semelhante, mais operações de desenho levarão mais tempo para serem realizadas (e algumas são mais caras, como as operações de recorte). Abordaremos novamente esse assunto em uma postagem futura sobre a determinação dos custos.

Para vetores estáticos, o desenho precisa ser realizado somente uma vez e pode então ser armazenado em cache em um Bitmap. Os vetores animados não podem fazer essa otimização porque as propriedades são necessariamente alteradas, exigindo redesenho.

Compare isso com recursos rasterizados como o PNG, que só precisa decodificar o conteúdo do arquivo, algo que foi amplamente otimizado com o passar do tempo.

Esse é o principal ponto a ser considerado ao comparar os prós e os contras da rasterização e da vetorização. Os vetores proporcionam os benefícios mencionados acima, mas ao custo de uma renderização mais cara. No início do Android, os dispositivos eram menos poderosos e as densidades de tela variavam pouco. Atualmente, os dispositivos Android são mais poderosos e têm uma enorme variedade de densidades de tela. É por isso que eu acredito ser a hora de todos os aplicativos mudarem para recursos vetorizados.

Adequabilidade

Devido à natureza do formato, os vetores são ótimos para descrever alguns recursos, como ícones simples. Eles são péssimos para codificar imagens fotográficas, em que é mais difícil descrever o conteúdo com uma série de formas. Nesse caso, seria muito mais eficiente usar um formato rasterizado (como o webp). É claro que há muitas variações, então tudo depende da complexidade do recurso.

Conversão

Nenhuma ferramenta de design (que eu conheça) pode criar VectorDrawables diretamente. Isso significa que há uma etapa de conversão a partir de outros formatos. Isso pode complicar o fluxo de trabalho entre designers e desenvolvedores. Trataremos desse tópico em detalhes em uma postagem futura.

Por que não o SVG?

Se você já trabalhou com formatos de imagem vetorial, já deve ter visto o formato SVG (gráficos vetoriais escalonáveis, na sigla em inglês), o padrão do setor na Web. Esse formato é eficiente, estabelecido e tem ferramentas consagradas, mas é também um padrão amplo . Ele inclui muitos recursos complexos como a execução de javascript arbitrário, efeitos de desfoque e filtro ou a incorporação de outras imagens, até mesmo gifs animados. O Android é executado em dispositivos móveis restritos, portanto, oferecer suporte a toda a especificação do SVG não era uma meta realista.

No entanto, o SVG inclui uma especificação de caminho que define como descrever e desenhar formas. Com essa API, você pode expressar a maioria das formas vetoriais. De modo geral, o Android é compatível com a especificação de caminho do SVG (e alguns adicionais).

Além disso, ao definir seu próprio formato, o VectorDrawable pode ser integrado a recursos da plataforma Android. Ele trabalha, por exemplo, com os recursos de sistema do Android para referenciar @colors, @dimens ou @strings, funcionando com atributos de tema ou AnimatedVectorDrawable usando animadores padrão.

Recursos do VectorDrawable

Como mencionado, o VectorDrawable é compatível com a especificação de caminho do SVG, o que permite especificar uma ou várias formas a serem desenhadas. Ele é escrito como um documento XML semelhante a este:

Observe que você precisa especificar o tamanho intrínseco do recurso, que seria o tamanho que ele teria ao ser definido em um ImageView wrap_content. Os tamanhos da segunda janela de visualização definem a tela virtual ou coordenam o espaço no qual todos os comandos de desenho subsequentes são definidos. As dimensões intrínsecas e da janela de visualização podem variar (mas devem estar na mesma proporção): é possível definir os vetores em uma tela 1x1 se você desejar.

O elemento <vector> contém um ou muitos elementos <path>. Eles podem ser nomeados para referência posterior (como no caso de animações), mas é essencial que especifiquem um elemento pathData que descreva a forma. Essa string de aparência complexa pode ser compreendida como uma série de comandos controlando uma caneta em uma tela virtual:

Visualização de operações de caminho

Os comandos acima movem a caneta virtual, desenham uma linha até outro ponto, elevam e movem a caneta, para então desenhar uma nova linha. Com apenas os quatro comandos mais comuns, podemos descrever praticamente qualquer forma (há outros comandos, veja a especificação):

  • M mover para
  • L linha para
  • C curva (de bézier quadrática) para
  • Z fechar (linha para o ponto inicial)

(Comandos em letra maiúscula usam coordenadas absolutas e aqueles em letra minúscula usam coordenadas relativas)

Você pode se perguntar se esse nível de detalhes é necessário. Não é possível conseguir isso com arquivos SVG? Embora você não precise ler um caminho e entender o que ele desenhará, ter um entendimento básico do que um VectorDrawable está fazendo é extremamente útil e necessário para compreender alguns dos recursos avançados que abordaremos depois.

Os caminhos por si só não desenham nada. Eles precisam ser traçados e/ou preenchidos.

A segunda parte desta série entrará em mais detalhes sobre as diferentes maneiras de preencher/traçar caminhos.

Você também pode definir grupos de caminhos. Isso permite que você defina transformações que serão aplicadas a todos os caminhos dentro do grupo.

Observe que você não pode girar, dimensionar ou traduzir caminhos individuais. Se desejar fazer isso, será necessário colocá-los em um grupo. Essas transformações fazem pouco sentido para imagens estáticas que poderiam “incorporá-las” em seus caminhos diretamente, mas são extremamente úteis no caso de animações.

Você também pode definir clip-paths, ou seja, mascarar a área na qual outros caminhos do mesmo grupo podem desenhar. Eles são definidos da mesma maneira que os caminhos.

Uma limitação importante é que os clip-paths não podem ser suavizados.

Demonstração de clip-path sem suavização

Esse exemplo (que eu tive que aumentar muito de tamanho para poder mostrar o efeito) demonstra duas abordagens de desenho do ícone de um obturador de câmera. A primeira desenha os caminhos, enquanto a segunda desenha um quadrado sólido, mascarado com a forma do obturador. O ato de mascarar pode criar efeitos interessantes (especialmente no caso de animações), mas é relativamente caro. Assim, procure evitá-lo, desenhando a forma de um modo diferente.

Os caminhos podem ser ajustados: é só desenhar um subconjunto do caminho inteiro. É possível ajustar caminhos preenchidos, mas os resultados podem ser surpreendentes! É mais comum ajustar caminhos traçados.

Ajuste de caminhos

Você pode ajustar do início ou do fim de um caminho. Também é possível aplicar um deslocamento a qualquer ajuste. Eles são definidos como uma fração do caminho [0,1]. Veja como a definição de diferentes valores de ajuste altera a parte da linha que é desenhada. Perceba também que os deslocamentos podem fazer com que os valores de ajuste “se agrupem”. Mais uma vez, essa propriedade não faz muito sentido para imagens estáticas, mas é útil para animações.

O elemento vetor raiz é compatível com uma propriedade alfa [0, 1]. Os grupos não têm propriedade alfa, mas caminhos individuais são compatíveis com fillAlpha/strokeAlpha.

Declaração de independência

Espero que esta postagem ajude a compreender o que são os recursos vetorizados e os prós e contras deles. O formato vetorial do Android é eficiente e tem amplo suporte. Levando em consideração a variedade de dispositivos no mercado, o uso de recursos vetorizados deveria ser a escolha padrão, deixando a rasterização apenas para casos especiais. Junte-se a nós nas próximas postagens para saber mais:

Em breve: Desenho de um caminho
Em breve: Criação de recursos vetorizados para Android
Em breve: Uso de recursos vetorizados em aplicativos Android
Em breve: Criação de perfil do Android
VectorDrawables