WebCanape
Компания
44,07
рейтинг
17 января 2013 в 11:47

Разработка → Используем векторные изображения SVG в приложениях Android, или как убить фрагментацию экранов и не потерять в качестве (плюсы, минусы, особенности)

Достаточно долгое время мы занимаемся разработкой детских приложений под Android, постепенно постигая множество нюансов этой платформы. Есть одни грабли, которые подстерегают нас в каждом приложении, – это фрагментация экранов. Если делать одно изображение только под телефон маленького размера, то на планшете оно выглядит мягко говоря “не очень”. А если делать изображение высокого разрешения для планшетов и пытаться использовать его на телефонах, то с очень большой вероятность приложение вывалится с OutOfMemory.

Приходится готовить несколько экземляров одного и того же изображения под разные экраны. Еще сильнее облака сгущает новый монстр Galaxy Nexus 10 с безумным разрешением 2560х1600.



В общем, неплохо бы что-то изменить, решили мы. А что если использовать в приложениях не растровые изображения, а векторные? Такие изображения легко масштабируются под разные разрешения экранов, при этом не теряя в качестве. Можно использовать всего одно изображение под разные разрешения и размеры.

Сказано — сделано. Итак, под катом история внедрения векторных изображений в одно из наших приложений. В статье мы поделимся опытом и особенностями использования векторных изображений в формате SVG в приложениях Android.

Немного погуглив, выяснили, что векторные изображения для web и приложений обычно используются в формате SVG. С данным форматом работают векторные редакторы Adobe Illustrator и Inkscape. Вооружившись Inkscape, нарезали пробных картинок и принялись искать способы их загрузки и отображения в приложении Android.
Разбираться с устройством формата SVG и писать свой парсер не хотелось — наверняка же люди сталкивались с этим и до нас! Что ж, гуглим «android svg».
В итоге есть:

Берем самый популярный — SVG-Android (он, кстати, переехал на Github, но новых коммитов там нет). Подключаем библиотеку, векторное изображение помещаем в res/raw, загружаем её и устанавливаем ее во вьюшку:
SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.filename);
Drawable drawable = svg.createPictureDrawable();
imageView.setImageDrawable(drawable);

Загружаем тестовый проект с изображениями — всё отлично! Подключаем наши изображения — пусто. Как оказалось, данная библиотека поддерживает только формат SVG basic 1.1, который не поддерживается Inkspace, а рождается только в Adobe Illustrator.

Пробуем вторую библиотеку SVG-Android-2, которая является форком первого проекта и ушла чуть-чуть дальше.
Она уже понимает Inkscape, а также поддерживает другие фишки этого формата, о чем можно почитать тут. Здесь всё пошло проще, картинки загрузились и выглядели шикарно и на телефоне, и на планшете. На нем мы и остановились.

Пример SVG-изображения и неадаптированного по размеру под планшет PNG-изображения на планшете.
(просмотреть изображение в оригинальном размере 1280х800)

Первое — SVG (10 Кб), второе — PNG (22 Кб). Второе изображение имеет размытый контур и ступенчатый градиент

Масштабирование изображений


Изображения масштабируются только с сохранением пропорций. Поэтому использовать их в качестве фона, который может немного менять пропорции на разных разрешениях, не получится. В этом случае мы обычно делаем какое-то абстрактное изображение в PNG, которое довольно легко меняет свои пропорции для разных экранов.

Не забываем для SVG устанавливать свойство аdjustViewBounds в значение true, иначе изображение может рассчитывать свои границы не так, как вы задумали.

Размер изображений с тенями и подсветками


Некоторые элементы в нашем приложении изначально были отрисованы с небольшими тенями и подстветками — например, этот смайлик имеет серую подсветку сзади. Но это приводит к колоссальному увеличению размера файла SVG. 118 Кб против 1 Кб без этой подсветки. Чем больше размер файла — тем больше времени надо на его загрузку в программе, поэтому мы решили отказаться от этого эффекта.


Изорбражения с тенью и без: 118 Кб vs 1 Кб

Подсветку можно отключить или в графическом редакторе, или же прямым редактированием SVG-файла — удаляем тэг <image /> с огромным содержимым.

Отображение градиентов


На некоторых изображениях вдруг обнаружились черные пятна вместо фона. Оказалось, что градиент не поддерживается!
Проблема с градиентами решилась удалением лишних тэгов из svg (описано далее в статье). Но в принципе, и с этим можно было бы жить и в наших простых изображениях заменить градиент однородной заливкой, если бы не другой нюанс — значительное время загрузки изображений.

Вот как это выглядело на экране: слева — черное небо в виде градиента, справа — корректная картинка.


Время загрузки изображений


В приложении нужно было по 6 изображений на одной странице ViewPager, а поскольку они подгружаются в процессе прокрутки (если не кэшировать), интерфейс заметно дергался при скроллинге. Этого очень не хотелось, и было решено загружать все изображения при старте приложения. Получили время инициализации порядка 8 секунд, что было слишком долго.

В проблеме решили разобраться. Выкачали исходники проекта SVG-Android-2 и стали искать, что именно так тормозит. Оказалось, что в классе SVGParser XML-файл изображения парсится дважды: первый раз он собирает информацию о дополнительных атрибутах, которые используются при втором проходе. И, что самое интересное, — анализируется лишь атрибут xlink:href, который является некоторым подобием гиперссылок внутри самого документа. В наших проблемных изображениях как раз были такие ссылки, и вели они никуда. После того, как мы избавились от данных ссылок, отредактировав код SVG в некоторых изображениях, градиент стал корректно отображаться. Более того, убрав этот предварительный проход и немного оптимизирорав процесс загрузки, мы смогли уменьшить скорость загрузки с 8 секунд до 1,8-2. Следует заметить, что это соизмеримо с PNG среднего размера — загрузка этих же изображений в память заняла 1,7 секунд.

Ниже приведено сравнение загрузки 35 файлов в формате SVG и PNG.
SVG PNG(~500x500)
Размер, КБ 327 943
Время загрузки, с 1,9 1,7


Прозрачность и цветовые фильтры


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

Оказалось, что ни alpha, ни colorFilter мы применить не сможем, т.к. библиотека загружает не типичные bitmapDrawable, а pictureDrawable, и в исходниках Android мы видим пустые методы для этого класса:
@Override
public void setColorFilter(ColorFilter colorFilter) {}
@Override
public void setAlpha(int alpha) {}

До этого с классом pictureDrawable никогда не сталкивались, и это было большой неожиданностью.

Опять покопавшись в исходниках библиотеки, мы нашли в классе SVGHandler поле fillPaint типа Paint, которым рисуются все компоненты. Если до загрузки элемента ему установить colorFilter, то он будет работать как положено. Нас это вполне устраивало, поэтому мы чуть-чуть изменили метод загрузки SVG, добавив возможность передавать туда цвет фильтра, который при необходимости устанавливается перед загрузкой изображения. Теперь изображения загружались так:
SVG svg = SVGParser.getSVGFromResource(getResources(), rawSvgId, filterColor);

А в самом SVGHandler появился такой метод:
public void setFilterColor(int filterColor) {
        fillPaint.setColorFilter(new PorterDuffColorFilter(filterColor, Mode.MULTIPLY));
 }

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

Также можно установить и Alpha для fillPaint, но в играх это свойство требуется в динамической форме (нажали на элемент — сделался полупрозрачным), и подгружать каждый раз новое изображение неудобно. Поэтому этот эффект заменили масштабированием (нажали — элемент уменьшился).

Нюанс с принудительной обработкой GPU


После запуска приложения к нам стали такие поступать ошибки:
java.lang.UnsupportedOperationException
      at android.view.GLES20Canvas.drawPicture(GLES20Canvas.java:895)
      at android.graphics.drawable.PictureDrawable.draw(PictureDrawable.java:73)

Оказалось, что если на устройстве включена настройка “Принудительная обработка GPU” (Developer options — Force GPU Rendering), то наше приложение валится, т.к. метод drawPicture() у Canvas не поддерживает аппаратное ускорение. Об этом можно почитать на android developer.
Причем простое указание в манифесте android:hardwareAccelerated=«false» проблему не решает — пользовательская галочка в настройках имеет более высокий приоритет.

Было найдено довольно простое решение: для всех view, которые работают с нашими pictureDrawable, полученными из SVG, отключить аппаратное ускорение.
Так как функция аппаратного ускорения появилась в Аndroid 3.0 (api 11), то для работы с этим функционалом пришлось изменить target sdk нашего проекта с 8 на 11. И, конечно же, надо помнить про обратную совместимость — на более ранних платформах этих методов нет.
public static void setSoftwareLayerType(View view) {
       try {
         view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
       } catch (NoSuchMethodError e) {
           //Do nothing - this happens on API < 11
       }
   }


Выводы


Давайте подведем краткий итог работы с векторными изображениями в формате SVG в Android.

Плюсы:
  • Один огромный плюс, из которого следуют все остальные, — это одна векторная картинка.
  • Так как картинка векторная, она отлично отображается на всех размерах экранов.
  • Размер SVG-картинок мал.
  • Одна картинка используется несколько раз для разных разрешений.
  • Сокращается процесс подготовки изображений для приложения.

Минусы:
  • Картинки масштабируются только пропорционально.
  • Не поддерживается прозрачность.
  • Графику нужно упрощать — чем больше векторных элементов, тем больше весит файл.Нежелательно использовать тени и свечения, так как это в разы увеличивает размер SVG-файлов.

В результате экспериментов с SVG родилось приложение для детей “Учим формы и фигуры”. Ознакомится с приложением можно в Google Play:
play.google.com/store/apps/details?id=com.whisperarts.kids.forms
Количество получившихся изображений:
  • PNG — 3 (сплэшскрин и 2 фона для меню);
  • SVG-элементов — 97;
  • Размер приложения 3,5 Мб.

В сравнении с почти похожим по функционалу нашим приложением “Учим цвета” (размер которого 8 Мб) выигрыш более 50% налицо.

Для себя мы приняли решение использовать SVG-изображения в наших приложениях, так как это существенно ускоряет процесс разработки и адаптации картинок под разные разрешения экранов, а также существенно уменьшает вес приложения.

Надеемся, опыт, которым мы поделились в статье, поможет вам также пересмотреть процесс подготовки изображений для приложений и задуматься над использованием формата SVG.

PS: Если вы уже использовали SVG в своих проектах или по другому обходили проблемы, с которыми столкнулись мы при использовании SVG — пишите в комментариях. Будем рады услышать Ваш опыт.
Автор: @Rivers
WebCanape
рейтинг 44,07
Компания прекратила активность на сайте

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

  • +3
    >>Оказалось, что градиент не поддерживается!
    При этом выше у вас нарисован скат с градиентом.
    И какие-то минусы у вас неоднозначные.
    >>Картинки масштабируются только пропорционально.
    Почему?
    >>Не поддерживается прозрачность.
    Это еще почему? Сам по себе svg поддерживает прозрачность. Проблема конкретного выбранного отрисовщика? Или прозрачность в загруженных PNG файлах?
    >>Нежелательно использовать тени и сечения, так как это в разы увеличивает размер SVG-файлов.
    Погодите, что-то не сходится. Во первых для свечения можно было применять градиент, во-вторых — фильтры.

    Как дела с производительностью, не просела?
    • +2
      Проблема с градиентом была решена, об этом в статье рассказывается. Скат показывает уже результат

      Проблема масштабирования и альфа — это проблема данной конкретной реализации библиотеки, но так как альтернатив нет — мы использовали её.

      Спасибо за подсказку по поводу свечений. Мы описали то, с чем столкнулись. Художник сделал нам изображение с тенью в таком виде, и мы с ним работали

      На производительности использование svg практически не сказалась. Проблема долгой загрузки и её решение описаны в статье
      • 0
        Дефолтное свечение в иллюстраторе — растровая картинка.
  • +1
    >Некоторые элементы в нашем приложении изначально были отрисованы с небольшими тенями и подстветками — например, этот смайлик имеет серую подсветку сзади. Но это приводит к колоссальному увеличению размера файла SVG.

    Предполагаю, что это от того, что круглая тень превращается в сотню кругов разного цвета.

    www.w3schools.com/svg/svg_feoffset.asp
    В SVG есть тени.
    • +1
      Спасибо за подсказки, будем пробовать. О результатах напишем. Как я написал в комментрии выше, у нас изображения пришли готовые от художника, и использовали то, что имели.
  • +5
    Какое устройство? Если у вас 4 гигалошади под капотом, то загрузка и ресайзы кучи PNG размерами в 3-4 мегапикселя будут идти те же несколько секунд.

    P.S. Правки SVG парсера неплохо бы выложить на github, все-таки ведь GPL лицензия.
    • 0
      А разве GPL совместима с гугловским маркетом?
  • +1
    Спасибо за статью, может когда пригодится.

    А вы изменения после оптимизации не коммитнули? Не перенесли проект на github?
    • +1
      Это был наш первый опыт с этой библиотекой, и изменения носят очень локальный характер для сугубо наших целей. Но мы и дальше будем использовать эту библиотеку, и тогда уже будем рады поделиться нашими наработками.
      • +4
        Локальные изменения можно не выкладывать только если вы не распространяете приложение или компонент. Похоже в вашем случае факт распространения имеет место быть. А соответственно нужно или влить изменения в исходный проект, или выложить изменённую версию.
        P.S. Заранее извиняюсь, если что-то напутал.
  • +1
    Здорово, надо будет ваш метод попробовать.
    Сам я уже пробовал использовать AndEngineSVGTextureRegionExtension, вроде бы как получалось, но так много фишек не реализовано.
    Я рисовал сам графику в Inkscape, но потом еще немного оптимизировал код svg (там есть много мусора, который ни на что не влияет). Более простые элементы типа кнопок, бордюров так вообще мне было проще написать ручками :)
  • +1
    Я начал подходить к этой же задаче несколько с другой стороны — генерировать ресурсы из SVG при сборке. Это, конечно, ограничивает область применения (во время выполнения программы нельзя изменять размеры/цвета/...), но зато и проблем с отображением меньше, так как для растеризации используется достаточно мощная библиотека Batik.

    Проект доступен на https://github.com/kriomant/buketan
    Пока возможностей не слишком много, но если кого заинтересует, то продолжу развитие.
  • 0
    Интересовался подобной темой, но отрисовка SVG занимает больше времени и сжирает больше ресурсорв (батарея садится быстрее). Имхо, проще брать изображения высокого разрешения, а при первом запуске ресайзить и сохранять на SD под активное разрешение.
    • –1
      Может взять SVG-изображение, отрендерить в png (чтобы в качестве не терять) под текущее устройство и сохранить на sd? Или это сложно? Я сам в этой теме не селен.
  • +1
    Очень у вас странная работа с SVG.
    Подключаем наши изображения — пусто. Как оказалось, данная библиотека поддерживает только формат SVG basic 1.1, который не поддерживается Inkspace, а рождается только в Adobe Illustrator.
    Inkscape прекрасно понимает SVG 1.1. Проблемы могут быть с проприетарными расширениями, которые не имеют отношения к стандарту.

    Подсветка в 118 Кб это жесть. Она делается обычным фильтром с гауссовым размытием. Здесь, похоже, просто внедрена картинка — Адоби Иллюстратор «удружил», отсюда и проблемы. Используемая вами библиотека поддерживает фильтры? Если да, то всё просто, хотя они тоже могут тормозить. Если нет, то тут уже сложнее, однако, для кругов можно попробовать сымитировать её радиальным градиентом, переходящим в прозрачный цвет ;-).
    На некоторых изображениях вдруг обнаружились черные пятна вместо фона. Оказалось, что градиент не поддерживается!
    Тоже похоже на «руку помощи» Иллюстратора.
    Картинки масштабируются только пропорционально.
    Формат SVG поддерживает непропорциональное масштабирование, но по умолчания оно пропорциональное. Включить поддержку можно атрибутом preserveAspectRatio:
    <svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
    

    Я немного не в теме: а в последних андроидах (3 и 4 версий) нет родной поддержки SVG? В браузеры поддержка добавлена.
    • +1
      Речь шла именно о формате svg basic 1.1, именно его понимает первая версия библиотеки и кантинки из inkscape, увы, не отображались. Во второй это исправили и всё пошло хорошо.

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

      Попоробовали добавить этот тэг — к сожалению, не помогло. Видимо это пока не поддерживается на уровне библиотеки. Возможно попробуем сами в будущем расширить этот функционал. Спасибо за подсказки!
  • +2
    Как вы думаете, а почему Google ввел именно механизм разделения ресурсов для разных DPI? Почему они (в Google) не написали библиотеку изначально в фреймворке которая берет все из SVG? Скорее всего от того что это меньшее зло (производительность выше). И наверное возможности у PNG как формата передачи изображения, все же выше =), потом еще найнпач, очень удобно.

    А если вам приходится масштабировать элементы интерфейса, так это скорее проблема дизайна приложения.

    В любом случае, спасибо за «наводку».
  • 0
    есть еще вариант использовать иконочные шрифты, это вроде как проще и производительнее
    • +2
      Как вы собрались рисовать ската из статьи иконочным шрифтом?
      скат из статьи
      image
  • +1
    делали что то подобное, у svg формата есть проблема:
    — это слабый набор инструментария/библиотек, код буквально приходится «допиливать», это усложняет внесение каких-либо правок. Например:
    желаете трансформации по применять — писать скрипт,
    мусор после экспорта — чистить, одними скриптами не обойтись,
    а качество и количество библиотек оставляет желать лучшего.
    Процесс оптимизации можно слегка автоматизировать — мы используем это: svgo.
  • +1
    Когда только изучал разработку под Android, сильно удивился, что нет штатного средства для работы с векторной графикой (при наличии неудобного спецэфичного редактора интерфейса). И, видимо, ничего не изменилось.

    Автору и всем остальным, кто выкладывал ссылки на инструменты, с которыми приходилась работать, огромное спасибо! Сейчас все больше поглядываю на Android в плане разработки.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +2
      Спасибо за подсказку с Build.VERSION — что-то совсем забыл про это.

      По поводу target sdk — у нас детское приложение, и нативных элементов там практически нет. Так что это не критично, а вот охватить как можно больше устройств — это предпочтительно, поэтому min у нас даже на 7 стоит.

      Исходников нам нисколько не жалко, что вы! Просто как я писал выше — изменения довольно специфичны для нас. Ну кому еще может понадобится загружать изображение с заранее наложенным цветовым фильтром? (в статье кстати описано, как мы это сделали). Вот если бы делать такое с уже загруженными… Вот как сделаем — зальем!

      Ну а по поводу ускорения — класс SVGParser, метод parse. Убираем первый проход парсера:
      //			IDHandler idHandler = new IDHandler();
      //			xr.setContentHandler(idHandler);
      //			xr.parse(new InputSource(cin.getCopy()));
      //			svgHandler.idXml = idHandler.idXml;
      

      Но тут надо быть осторожным. Это сейчас он работает только с xlink(я писал в статье), я боюсь, что дальше библиотека будет расширена для поддержки других фич.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка