Обычно дизайн приложения рисуется в векторном редакторе (например, 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, преимущественно перейдя на вектор.
Итак, чтобы перейти на векторный формат картинок в приложении надо:
- Подключить к приложению библиотеку BetterVectorDrawable
- Выгрузить из векторного редактора изображения в SVG-формате
- С помощью конвертера сконвертировать их все сразу в XML-формат vector drawable
- Положить полученные файлы в директорию приложения res/drawable
- Использовать векторные изображения в разметке и в коде как обычные ресурсы
- 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)
wholeman
01.09.2015 11:44+2«Легко… Часть 1 из 2.» Не так уж и легко, если пришлось на части делить.) Пожалуй, я лучше останусь со своими мэйкфайлами, которые автоматически генерируют «нарезку» из SVG:
Makefileinclude make.rules sport_icons = sport_running sport_biking sport_generic icons: $(call iconset,$(sport_icons) ) icons_clean: rm -fv $(call iconset,$(sport_icons) )
astudent
01.09.2015 14:02+2Каждый вправе выбирать подход по душе. Предложенный Вами вариант – это хорошее решение, но с минусом по сравнению с вектором: размер apk больше.
artemgapchenko
01.09.2015 15:28+1Все решают эти проблемы по-разному: кто-то пытается подключить SVG-библиотеку к проекту, кто-то генерирует нарезку с помощью утилиты.
А ещё есть Gradle плагин от Trello, генерирующий png drawables во время сборки проекта, и обещают в Android Gradle plugin версии 1.4 прикрутить официальную поддержку генерации png.
daspisch
А можно сравнение по производительности и памяти с обычными картинками? И что с 9path?
jMas
9path — это (если не изменяет память) просто тянущиеся растровые картинки, они не масштабируются как векторные, а «тянутся».
DevAndrew
Если быть совсем точным, то 9-patch.
astudent
Сравнение по производительности и памяти с обычными картинками – это тема для отдельной статьи, буду рад ее прочитать :) Рискну предположить, что с вектором все заметно лучше, чем с растром, во всяком случае ощущения, что приложение стало работать медленнее после перехода на вектор, не возникло, скорее наоборот.
9-patch поддерживается только для PNG.
ZimM
Вектор еще нужно в растр переводить, а растр уже готов для отрисовки. Потому вектор аж никак не может быть быстрее растра, вопрос только, насколько он медленнее.
astudent
Вы забыли только, что PNG тоже декодировать надо, и не известно, что быстрее.
KumoKairo
На самом деле чаще всего для отрисовки векторных изображений используется триангуляция и превращение векторных «путей» в 3д меши, которые рисуются напрямю видеокартой без текстур (крашеные вершины) или с простой лукап текстуркой с однопиксельными цветными квадратиками. Причём триангулировать и сохранять полученный результат можно заранее, например во время сборки приложения.
К сожалению, я не знаком с принципом работы VectorDrawable на Android, и не берусь утверждать что там это работает именно так. Но этот принцип триангуляции достаточно широко используется в сфере разработки игр, где такой подход позволяет сэкономить размер билда, не уменьшая при этом производительность отрисовки.
Я это собственно к тому, что по скорости отрисовки нельзя однозначно сказать, какой способ быстрее. Всё зависит от конкретного случая.
ZimM
В играх — да, потому что там очень часто масштаб может меняться. VectorDrawable же кеширует результаты рендера, грубо говоря, в текстуру, и рисует именно растр, перерисовывая вектор только при необходимости.
KamiSempai
Если брать VectorDravable из Android 5.0+ то там все векторные изображения кешируются. Так что существенного изменения производительности быть не должно.
На счет 9-patсh. Возможно вам поможет статья Программное создание NinePatchDrawable которую я написал пол года назад. К сожалению, в настоящее время у меня нет проектов, где можно было бы это использовать, так что библиотека из статьи пока не развивается.
yrouban
точно также отрисовываются векторные шрифты: один раз генерятся их растровые глифы, которые потом и накладываются на картинку на каждой отрисовке.