Векторные картинки с градиентом в Андроид 5.0

Так вышло, что в моем текущем проекте мне пришлось столкнуться с проблемой. Дизайнер согласовала с заказчиком набор иконок, раскрашенных простым линейным градиентом. И отправила мне эти иконки в 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, а переделывать уже не хочется):


Приятного кодинга!
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 19

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

    Я правильно понял, что можно было и не в svg? Ну т.е. вместо всего этого достаточно было их конвертировать в растровый формат?
      0
      Разумеется. Раньше так и делали, в нескольких разрешениях в png, но на дворе ведь 21-й век
        0
        А вам не кажется, что сохранить картинку растром в трех разрешениях — это наоборот, чуть более прогрессивный способ решения задачи, чем «в лоб» заставить тужиться несколько миллионов устройств (или какая там у вас будет аудитория :-), просчитывая несколько слоёв векторной картинки? Тем более что ваш эксперимент наверняка компании обошелся в несколько раз дороже, чем полчаса времени дизайнера на пересохранение всех пиктограмм в приложении.
          +1
          Я вам скажу по секрету, что начал программировать с языка ассемблера и не признавал лет 10 ничего иного, как раз из соображений недопущения перегрева Вселенной :) Но правда оказалась в том, что всем пофиг. Никому не нужны оптимально работающие программы длиной 500 байт.
          В данной же ситуации наличие векторных изображений сильно сокращает время разработки, поскольку программисту можно отмасштабировать картинки согласно спецификациям, а кроме того, они используются на разных View в разных размерах
            0
            В данной же ситуации наличие векторных изображений сильно сокращает время разработки,

            Я по поводу «сильно сокращает» возражаю. Экономия времени там близка к нулю на самом деле (Android с версии, если память не изменяет, 2.1 умеет автоматически подбирать изображение по разрешению экрана и делать ресемплинг), а если посчитать ещё и время на предложенную вами доводку svg до ума, то вместо экономии будет жуткий оверхед. Android 6+, если не ошибаюсь, сейчас это примерно треть действующих устройств, да? А у вас даже для пятой нужно руками допиливать каждый ресурс. Да ну к лешему такое «сильно сокращает».
              0
              Автоматический ресемплинг растровых изображений выглядит тем ужаснее, чем меньше иконка.
              Но суть статьи не в этом. Скорее я хотел предложить proof of concept
          • UFO just landed and posted this here
              0
              Чудес же не бывает. Оно только в ресурсах векторное и маленькое. А при отображении оно все равно будет растеризоваться, да ещё и с просчетами «на лету» всех тех эффектов, и памяти на все свои слои будет кушать. Наоборот, если весь этот процесс заранее сделают на компьютере дизайнера, ваш телефон только спасибо скажет.
              • UFO just landed and posted this here
                  0
                  Оперативки в таком случае, значит, еще меньше?
                  • UFO just landed and posted this here
              0

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

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

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

            0
            Круто. Интересный способ.
            Помню в прочтенный туториалах от Google они писали, что рекомендуют использовать вектора, если размеры не будут больше чем 300X300. С другой стороны приложение «похудеет».
              0
              Разве при сборке APK студия не генерирует PNG по векторным изображениям?
                0
                Вероятно, так и произойдет для pre-Lollipop таргета
                  0
                  Судя по APK, он не генерит растровые изображения из векторов при сборки.
                  Для Pre-Lolipop мы используем Support либу для вывода векторов.
                    0
                    Нет, если включить поддержку векторов. В Саппорте есть такое

                Only users with full accounts can post comments. Log in, please.