Обычно дизайн приложения рисуется в векторном редакторе (например, Sketch), но типичным форматом картинок в приложении под Android является растровый (как правило, PNG). При разработке приложения необходимо для каждого векторного изображения заниматься утомительной работой по изготовлению набора растровых картинок для разных плотностей экранов. Количество таких комплектов может доходить до шести по числу возможных плотностей: ldpi, mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi (плотность xxxhdpi необходима только для иконки приложения). При верстке иногда приходится задать в разметке явные размеры для изображения, что может потребовать перемасштабирования растровой картинки, а это, в свою очередь, наверняка приведет к появлению артефактов. К тому же наличие нескольких комплектов картинок отрицательно сказывается на размере выходного apk.

Все решают эти проблемы по-разному: кто-то пытается подключить SVG-библиотеку к проекту, кто-то генерирует нарезку с помощью утилиты.

Как мне кажется, наиболее правильным решением является отказ от использования растровой графики в приложении в пользу векторной. При этом хотелось бы по максимуму задействовать системные возможности. В Android 5.0 появился VectorDrawable – поддержка векторного формата для картинок, которые размещаются в виде ресурсов с расширением xml в папке drawable. На такие картинки можно ссылаться обычным образом из XML-разметки.

Использование VectorDrawable было бы отличным решением, если бы не необходимость поддержки устройств с Android 4.0+, коих большинство. VectorDrawable нет в support library и неизвестно, когда он там появится (хотя начало положено). Но не стоит печалиться: есть замечательная библиотека BetterVectorDrawable с открытым исходным кодом и лицензией Apache 2.0, которая фактически переносит VectorDrawable на Android 4.0+, предоставляя тот же интерфейс, и позволяет при необходимости использовать системный VectorDrawable на Android 5.0+. Нужно отметить, что есть еще пара аналогичных библиотек, но они не стоят Вашего внимания, поскольку не дают полноценно ссылаться на vector drawable ресурсы из разметки.

К сожалению, разработчики Android не предусмотрели поддержку в VectorDrawable градиентов, текстур, масок. Это небольшая проблема, но об этом следует помнить при составлении дизайна приложения. Если от этих элементов невозможно отказаться, то можно как прежде использовать в отдельных местах растровую графику / shape drawable, преимущественно перейдя на вектор.

Итак, чтобы перейти на векторный формат картинок в приложении надо:
  1. Подключить к приложению библиотеку BetterVectorDrawable
  2. Выгрузить из векторного редактора изображения в SVG-формате
  3. С помощью конвертера сконвертировать их все сразу в XML-формат vector drawable
  4. Положить полученные файлы в директорию приложения res/drawable
  5. Использовать векторные изображения в разметке и в коде как обычные ресурсы
  6. Profit

Библиотека BetterVectorDrawable


Подключаем библиотеку


Чтобы подключить библиотеку к приложению достаточно добавить одну строчку в секции dependencies файла build.gradle, расположенном в директории модуля приложения:

dependencies {
    …
    compile 'com.bettervectordrawable:lib:0.4+'
}

Библиотека распространяется через репозиторий JCenter, который используется по умолчанию в новых проектах Android Studio.

Если Вы создавали проект давно, то, возможно, у Вас используется репозиторий Maven Central. Чтобы это проверить, надо в файлах build.gradle поискать вхождения строки

mavenCentral()

и добавь рядом с ней

jcenter()

Включаем перехват ресурсов


Библиотеке необходимо передать список идентификаторов векторных ресурсов, чтобы она понимала, какие из них являются vector drawable. BetterVectorDrawable будет перехватывать обращения к ним и создавать экземпляры VectorDrawable.
Поскольку передать список нужно один раз, лучше всего это сделать в методе onCreate() класса Application, для чего придется создать его наследника:

package com.bettervectordrawable.demo;

import android.app.Application;
import com.bettervectordrawable.VectorDrawableCompat;

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // вызов VectorDrawableCompat.enableResourceInterceptionFor()
    }
}

И указать этого наследника в манифесте приложения:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.bettervectordrawable.demo">

    <application
        android:name=".App"
…

Существует три способа передать список: удобный, быстрый и ручной.

Удобный способ


int[] ids = VectorDrawableCompat.findAllVectorResourceIdsSlow(getResources(), R.drawable.class);
VectorDrawableCompat.enableResourceInterceptionFor(getResources(), ids);

Метод findAllVectorResourceIdsSlow сканирует все drawable XML-ресурсы и убеждается, что каждый возвращаемый ресурс является vector drawable. Разработчики советуют использовать этот метод по умолчанию, тем не менее, это наименее производительный способ, т.е. на старых устройствах время запуска приложения может существенно возрасти.

На Google Nexus 5 в приложении с 400 векторными ресурсами findAllVectorResourceIdsSlow отрабатывает менее чем за 150 мс.

Быстрый способ


int[] ids = VectorDrawableCompat.findVectorResourceIdsByConvention(getResources(), R.drawable.class, Convention.ResourceNameHasVectorSuffix);
VectorDrawableCompat.enableResourceInterceptionFor(getResources(), ids);

Метод findVectorResourceIdsByConvention подразумевает, что названия всех векторных ресурсов начинаются на vector_ или заканчиваются на _vector. Соглашение по именованию нужно указать с помощью параметра resourceNamingConvention.

На Google Nexus 5 в приложении с 400 векторными ресурсами findVectorResourceIdsByConvention отрабатывает менее чем за 20 мс.

Ручной способ


VectorDrawableCompat.enableResourceInterceptionFor(getResources(),
    R.drawable.your1_vector,
    R.drawable.your2_vector,
    R.drawable.your3_vector);

Просто передается список всех идентификаторов векторных картинок. 0 мс.

Используем vector drawable


В коде:

Drawable drawable = getResources().getDrawable(R.drawable.your_vector);

Или из разметки:

<View
    android:layout_width="210dp"
    android:layout_height="210dp"
    android:background="@drawable/your_vector" />

Как видите, все просто.

Если у Вас возникли вопросы, то можно задать их мне либо посмотреть демо-приложение. О проблемах с библиотекой лучше сообщать разработчикам в GitHub.

В следующей части мы обсудим конвертацию изображений из SVG в vector drawable XML.

Комментарии (13)


  1. daspisch
    01.09.2015 11:14
    +1

    А можно сравнение по производительности и памяти с обычными картинками? И что с 9path?


    1. jMas
      01.09.2015 11:40
      +1

      9path — это (если не изменяет память) просто тянущиеся растровые картинки, они не масштабируются как векторные, а «тянутся».


      1. DevAndrew
        01.09.2015 15:22
        +1

        Если быть совсем точным, то 9-patch.


    1. astudent
      01.09.2015 13:53

      Сравнение по производительности и памяти с обычными картинками – это тема для отдельной статьи, буду рад ее прочитать :) Рискну предположить, что с вектором все заметно лучше, чем с растром, во всяком случае ощущения, что приложение стало работать медленнее после перехода на вектор, не возникло, скорее наоборот.
      9-patch поддерживается только для PNG.


      1. ZimM
        01.09.2015 16:37
        -1

        Вектор еще нужно в растр переводить, а растр уже готов для отрисовки. Потому вектор аж никак не может быть быстрее растра, вопрос только, насколько он медленнее.


        1. astudent
          01.09.2015 16:48
          +2

          Вы забыли только, что PNG тоже декодировать надо, и не известно, что быстрее.


        1. KumoKairo
          01.09.2015 16:57
          +2

          На самом деле чаще всего для отрисовки векторных изображений используется триангуляция и превращение векторных «путей» в 3д меши, которые рисуются напрямю видеокартой без текстур (крашеные вершины) или с простой лукап текстуркой с однопиксельными цветными квадратиками. Причём триангулировать и сохранять полученный результат можно заранее, например во время сборки приложения.

          К сожалению, я не знаком с принципом работы VectorDrawable на Android, и не берусь утверждать что там это работает именно так. Но этот принцип триангуляции достаточно широко используется в сфере разработки игр, где такой подход позволяет сэкономить размер билда, не уменьшая при этом производительность отрисовки.

          Я это собственно к тому, что по скорости отрисовки нельзя однозначно сказать, какой способ быстрее. Всё зависит от конкретного случая.


          1. ZimM
            01.09.2015 18:16
            +1

            В играх — да, потому что там очень часто масштаб может меняться. VectorDrawable же кеширует результаты рендера, грубо говоря, в текстуру, и рисует именно растр, перерисовывая вектор только при необходимости.


    1. KamiSempai
      01.09.2015 15:45
      +1

      Если брать VectorDravable из Android 5.0+ то там все векторные изображения кешируются. Так что существенного изменения производительности быть не должно.
      На счет 9-patсh. Возможно вам поможет статья Программное создание NinePatchDrawable которую я написал пол года назад. К сожалению, в настоящее время у меня нет проектов, где можно было бы это использовать, так что библиотека из статьи пока не развивается.


      1. yrouban
        02.09.2015 14:29

        точно также отрисовываются векторные шрифты: один раз генерятся их растровые глифы, которые потом и накладываются на картинку на каждой отрисовке.


  1. wholeman
    01.09.2015 11:44
    +2

    «Легко… Часть 1 из 2.» Не так уж и легко, если пришлось на части делить.) Пожалуй, я лучше останусь со своими мэйкфайлами, которые автоматически генерируют «нарезку» из SVG:

    Makefile
    include make.rules
    
    sport_icons = sport_running sport_biking sport_generic
    
    icons: $(call iconset,$(sport_icons) )
    
    icons_clean:
            rm -fv $(call iconset,$(sport_icons) )
    
    


    1. astudent
      01.09.2015 14:02
      +2

      Каждый вправе выбирать подход по душе. Предложенный Вами вариант – это хорошее решение, но с минусом по сравнению с вектором: размер apk больше.


  1. artemgapchenko
    01.09.2015 15:28
    +1

    Все решают эти проблемы по-разному: кто-то пытается подключить SVG-библиотеку к проекту, кто-то генерирует нарезку с помощью утилиты.

    А ещё есть Gradle плагин от Trello, генерирующий png drawables во время сборки проекта, и обещают в Android Gradle plugin версии 1.4 прикрутить официальную поддержку генерации png.