Так вышло, что в моем текущем проекте мне пришлось столкнуться с проблемой. Дизайнер согласовала с заказчиком набор иконок, раскрашенных простым линейным градиентом. И отправила мне эти иконки в svg формате, с чувством выполненного долга. Гугление выявило, что поддержка градиентов в vector drawable начинается только с SDK 24+.

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

В качестве рабочей картинки возьмем следующую:


Исходник:

<svg width="980px" height="980px" viewBox="10 10 980 980" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
    <desc>Created with Sketch.</desc>
    <defs>
        <linearGradient x1="0%" y1="0%" x2="101.999998%" y2="100.999999%" id="linearGradient-1">
            <stop stop-color="#2CF607" offset="0%"></stop>
            <stop stop-color="#038BF3" offset="100%"></stop>
        </linearGradient>
    </defs>
    <path d="M328.5,353 C369.1,353 402,320.1 402,279.5 C402,238.9 369.1,206 328.5,206 C287.9,206 255,238.9 255,279.5 C255,320.1 287.9,353 328.5,353 Z M500,10 C229.4,10 10,229.4 10,500 C10,770.6 229.4,990 500,990 C770.6,990 990,770.6 990,500 C990,229.4 770.6,10 500,10 Z M500,941 C256.5,941 59,743.6 59,500 C59,256.4 256.5,59 500,59 C743.5,59 941,256.4 941,500 C941,743.6 743.5,941 500,941 Z M671.5,353 C712.1,353 745,320.1 745,279.5 C745,238.9 712.1,206 671.5,206 C630.9,206 598,238.9 598,279.5 C598,320.1 630.9,353 671.5,353 Z M720.5,598 C720.5,598 637.8,745 500,745 C362.2,745 279.5,598 279.5,598 C233.4,572.2 189.2,624.9 230.5,671.5 C230.5,671.5 325.4,818.5 500,818.5 C674.6,818.5 769.5,671.5 769.5,671.5 C808.3,616.3 762.5,570.4 720.5,598 Z" id="Shape" stroke="none" fill="url(#linearGradient-1)" fill-rule="nonzero"></path>
</svg>

Пытаемся импортировать оригинальную картинку в Android Studio.

In smile.svg:
ERROR@ line 5 <defs> is not supported
ERROR@ line 11 Unsupported URL value: url(#linearGradient-1)
ERROR@ line 11 Unsupported URL value: url(#linearGradient-1)

Что и следовало ожидать.

Удаляем секцию defs и заменяем атрибут fill каким-нибудь цветом. Параметры градиент-стопов запоминаем, они нам позднее понадобятся. Теперь студия не возражает.

Наш полученный vector drawable:


<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="980dp"
        android:height="980dp"
        android:viewportWidth="980.0"
        android:viewportHeight="980.0">
    <path
        android:pathData="M318.5,343C359.1,343 392,310.1 392,269.5C392,228.9 359.1,196 318.5,196C277.9,196 245,228.9 245,269.5C245,310.1 277.9,343 318.5,343ZM490,0C219.4,0 0,219.4 0,490C0,760.6 219.4,980 490,980C760.6,980 980,760.6 980,490C980,219.4 760.6,0 490,0ZM490,931C246.5,931 49,733.6 49,490C49,246.4 246.5,49 490,49C733.5,49 931,246.4 931,490C931,733.6 733.5,931 490,931ZM661.5,343C702.1,343 735,310.1 735,269.5C735,228.9 702.1,196 661.5,196C620.9,196 588,228.9 588,269.5C588,310.1 620.9,343 661.5,343ZM710.5,588C710.5,588 627.8,735 490,735C352.2,735 269.5,588 269.5,588C223.4,562.2 179.2,614.9 220.5,661.5C220.5,661.5 315.4,808.5 490,808.5C664.6,808.5 759.5,661.5 759.5,661.5C798.3,606.3 752.5,560.4 710.5,588Z"
        android:strokeColor="#00000000"
        android:fillColor="#00ff00"/>
</vector>

Следующий шаг состоит в том, чтобы сделать картинку «негативной», т.е. цветные участки преобразовать в прозрачные, а все остальное сделать цвета фона, в нашем простейшем примере белым. Для этого достаточно добавить к пути всего один обрамляющий контур (теоретически, может понадобиться изменить направление обхода на противоположное). Заодно, я полностью убрал android:strokeColor и заменил fillColor на белый:


<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="980dp"
        android:height="980dp"
        android:viewportWidth="980.0"
        android:viewportHeight="980.0">
    <path
        android:pathData="M0,0L980,0L980,980L0,980
        M318.5,343C359.1,343 392,310.1 392,269.5C392,228.9 359.1,196 318.5,196C277.9,196 245,228.9 245,269.5C245,310.1 277.9,343 318.5,343ZM490,0C219.4,0 0,219.4 0,490C0,760.6 219.4,980 490,980C760.6,980 980,760.6 980,490C980,219.4 760.6,0 490,0ZM490,931C246.5,931 49,733.6 49,490C49,246.4 246.5,49 490,49C733.5,49 931,246.4 931,490C931,733.6 733.5,931 490,931ZM661.5,343C702.1,343 735,310.1 735,269.5C735,228.9 702.1,196 661.5,196C620.9,196 588,228.9 588,269.5C588,310.1 620.9,343 661.5,343ZM710.5,588C710.5,588 627.8,735 490,735C352.2,735 269.5,588 269.5,588C223.4,562.2 179.2,614.9 220.5,661.5C220.5,661.5 315.4,808.5 490,808.5C664.6,808.5 759.5,661.5 759.5,661.5C798.3,606.3 752.5,560.4 710.5,588Z"
        android:fillColor="#ffffff"/>
</vector>

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

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:top="0.5dp"
        android:right="0.5dp"
        android:left="0.5dp"
        android:bottom="0.5dp">
        <shape android:shape="rectangle">
            <gradient
                android:startColor="#2CF607"
                android:endColor="#038BF3"
                android:angle="135"
                android:type="linear" />
            <size
                android:width="64dp"
                android:height="64dp">
            </size>
        </shape>
    </item>
    <item android:drawable="@drawable/ic_smile" />
</layer-list>

Отступы в полпикселя мне понадобились, чтобы избавиться от артефактов, которые были видны, когда я поместил картинку в layout.

Результат вполне достойный (только я случайно поменял местами startColor и endColor, а переделывать уже не хочется):


Приятного кодинга!
Поделиться с друзьями
-->

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


  1. DrPass
    02.04.2017 23:03

    И отправила мне эти иконки в svg формате, с чувством выполненного долга.

    Я правильно понял, что можно было и не в svg? Ну т.е. вместо всего этого достаточно было их конвертировать в растровый формат?


    1. br0x
      02.04.2017 23:05

      Разумеется. Раньше так и делали, в нескольких разрешениях в png, но на дворе ведь 21-й век


      1. DrPass
        02.04.2017 23:12

        А вам не кажется, что сохранить картинку растром в трех разрешениях — это наоборот, чуть более прогрессивный способ решения задачи, чем «в лоб» заставить тужиться несколько миллионов устройств (или какая там у вас будет аудитория :-), просчитывая несколько слоёв векторной картинки? Тем более что ваш эксперимент наверняка компании обошелся в несколько раз дороже, чем полчаса времени дизайнера на пересохранение всех пиктограмм в приложении.


        1. br0x
          03.04.2017 00:54
          +1

          Я вам скажу по секрету, что начал программировать с языка ассемблера и не признавал лет 10 ничего иного, как раз из соображений недопущения перегрева Вселенной :) Но правда оказалась в том, что всем пофиг. Никому не нужны оптимально работающие программы длиной 500 байт.
          В данной же ситуации наличие векторных изображений сильно сокращает время разработки, поскольку программисту можно отмасштабировать картинки согласно спецификациям, а кроме того, они используются на разных View в разных размерах


          1. DrPass
            03.04.2017 01:16

            В данной же ситуации наличие векторных изображений сильно сокращает время разработки,

            Я по поводу «сильно сокращает» возражаю. Экономия времени там близка к нулю на самом деле (Android с версии, если память не изменяет, 2.1 умеет автоматически подбирать изображение по разрешению экрана и делать ресемплинг), а если посчитать ещё и время на предложенную вами доводку svg до ума, то вместо экономии будет жуткий оверхед. Android 6+, если не ошибаюсь, сейчас это примерно треть действующих устройств, да? А у вас даже для пятой нужно руками допиливать каждый ресурс. Да ну к лешему такое «сильно сокращает».


            1. br0x
              03.04.2017 09:57

              Автоматический ресемплинг растровых изображений выглядит тем ужаснее, чем меньше иконка.
              Но суть статьи не в этом. Скорее я хотел предложить proof of concept


        1. NLO
          03.04.2017 01:19

          НЛО прилетело и опубликовало эту надпись здесь


          1. DrPass
            03.04.2017 01:48

            Чудес же не бывает. Оно только в ресурсах векторное и маленькое. А при отображении оно все равно будет растеризоваться, да ещё и с просчетами «на лету» всех тех эффектов, и памяти на все свои слои будет кушать. Наоборот, если весь этот процесс заранее сделают на компьютере дизайнера, ваш телефон только спасибо скажет.


            1. NLO
              03.04.2017 01:49

              НЛО прилетело и опубликовало эту надпись здесь


              1. jonic
                03.04.2017 02:49

                Оперативки в таком случае, значит, еще меньше?


                1. NLO
                  03.04.2017 02:57

                  НЛО прилетело и опубликовало эту надпись здесь


        1. madkite
          03.04.2017 12:17

          Тут палка о двух концах — с одной стороны рендеринг svg напряжен для cpu, с другой стороны пяток png по 10-50 киллобайт каждая весит заметно больше, чем svg на 600 байт, которая ещё и пакуется прикрасно. Юзерам это всё качать (часто используя мобильный интернет) и хранить в скромной внутренней памяти, которая нынче редко расширяется дополнительными sd-карточками. Так что не всё так однозначно.


          1. br0x
            03.04.2017 12:35

            С третьей стороны, в плохо написанной программе даже отображение мигающего курсора может напрячь 13% CPU


  1. sbnur
    03.04.2017 08:44

    Запомним — спасибо
    И спасибо дизайнеру за стимуляцию творчества программистов


  1. Buzik
    03.04.2017 09:51

    Круто. Интересный способ.
    Помню в прочтенный туториалах от Google они писали, что рекомендуют использовать вектора, если размеры не будут больше чем 300X300. С другой стороны приложение «похудеет».


    1. csero
      03.04.2017 12:36

      Разве при сборке APK студия не генерирует PNG по векторным изображениям?


      1. br0x
        03.04.2017 12:37

        Вероятно, так и произойдет для pre-Lollipop таргета


      1. Buzik
        03.04.2017 13:04

        Судя по APK, он не генерит растровые изображения из векторов при сборки.
        Для Pre-Lolipop мы используем Support либу для вывода векторов.