Привет! Меня зовут Алексей, я работаю Android-разработчиком в Облаке Mail. Наша команда отвечает за возвращаемость пользователей в сервис. Чтобы сделать использование Облака приятным и удобным, мы проводим редизайн приложения, переписывая старый пользовательский интерфейс на Jetpack Compose по новым макетам. Для упрощения создания новых экранов мы разрабатываем UI Kit с готовыми Composable-компонентами.
Во время работы над новыми экранами мне часто приходилось использовать множество различных иконок, разбросанных по всему проекту. Это навело на мысль: было бы здорово собрать все иконки в UI Kit в единственном экземпляре и использовать их только оттуда — по аналогии с тем, как это делают дизайнеры в Figma. И тогда я вспомнил об одной особенности Jetpack Compose.
С момента появления в нём существует новый способ отрисовки иконок с помощью кода. На мой взгляд, этот метод отличается удобной семантикой и несколько более высокой производительностью. Чтобы наглядно продемонстрировать эти преимущества, давайте сначала рассмотрим, как работала отрисовка иконок раньше.
Как было раньше
С давних пор и до наших дней добавление иконок в проект Android осуществляется следующим образом: иконка в формате SVG преобразуется Android Studio в XML-ресурс, который затем используется в разметке пользовательского интерфейса.
Пример SVG иконки:
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M19.5 19.5V28.5C19.5 29.3284 18.8284 30 18 30C17.1716 30 16.5 29.3284 16.5 28.5V19.5H7.5C6.67157 19.5 6 18.8284 6 18C6 17.1716 6.67157 16.5 7.5 16.5H16.5V7.5C16.5 6.67157 17.1716 6 18 6C18.8284 6 19.5 6.67157 19.5 7.5V16.5H28.5C29.3284 16.5 30 17.1716 30 18C30 18.8284 29.3284 19.5 28.5 19.5H19.5Z" fill="#99A2AD"/> </svg>
Пример получаемого ресурса XML:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="36dp" android:height="36dp" android:viewportWidth="36" android:viewportHeight="36"> <path android:pathData="M19.5,19.5V28.5C19.5,29.328 18.828,30 18,30C17.172,30 16.5,29.328 16.5,28.5V19.5H7.5C6.672,19.5 6,18.828 6,18C6,17.172 6.672,16.5 7.5,16.5H16.5V7.5C16.5,6.672 17.172,6 18,6C18.828,6 19.5,6.672 19.5,7.5V16.5H28.5C29.328,16.5 30,17.172 30,18C30,18.828 29.328,19.5 28.5,19.5H19.5Z" android:fillColor="#99A2AD"/> </vector>
Наиболее универсальным и распространённым способом описания иконок является path. Это набор команд, задающий координаты опорных точек и линий для отрисовки геометрических фигур любой сложности.
Вот основные команды path:
-
M/m (moveTo) — устанавливает опорную точку. Указываются координаты точки.
-
L/l (lineTo) — проводит линию от текущей точки до заданной. Указываются координаты заданной точки, которая становится новой опорной.
-
H/h (horizontalLineTo) — проводит горизонтальную линию от текущей точки до заданной. Указывается только координата x заданной точки. Координата y остаётся равной ординате текущей точки. Новая точка становится опорной.
-
V/v (verticalLineTo) — проводит вертикальную линию от текущей точки до заданной. Указывается только координата y заданной точки. Координата x остаётся равной абсциссе текущей точки. Новая точка становится опорной.
-
A/a (elliptical arc) — проводит эллиптическую дугу.
-
C/c, S/s (cubic Bezier curve) — проводит кубическую кривую Безье.
-
Q/q, T/t (quadratic Bezier curve) — проводит квадратичную кривую Безье.
-
Z/z (closePath) — завершает построение объекта Path, соединяя текущую точку с начальной. Не требует параметров.
Команды path бывают абсолютными и относительными. При использовании абсолютных команд все координаты задаются в системе координат рисунка, где начало отсчёта находится в верхнем левом углу.
При работе с большим количеством точек удобнее использовать относительные команды. Относительные команды обозначаются строчными буквами, а абсолютные — заглавными.
Пример простого треугольника в абсолютных координатах:
<svg width="200" height="180"> <path stroke="orange" stroke-width="10" fill="gold" d="M 100,20 L 180,160 L 20,160 z"/> </svg>
Результат
Основная проблема работы с XML — отсутствие гибкости при создании UI. Это приводит к появлению в проекте множества вариантов одних и тех же иконок в разных размерах, цветах и с дополнительными контекстными элементами (например, тенью или подложкой, чтобы иконка выглядела как кнопка в тулбаре). В результате во многих проектах мы наблюдаем такую печальную картину в ресурсах:
Что изменилось в Compose
Jetpack Compose предлагает новый интересный способ создания иконок — с помощью кода. Это выглядит следующим образом:
_arrowLeft20 = ImageVector.Builder( name = "ArrowLeft20", defaultWidth = 20.dp, defaultHeight = 20.dp, viewportWidth = 20f, viewportHeight = 20f ).apply { path(fill = SolidColor(Color(0xFF99A2AD))) { moveTo(8.707f, 4.233f) curveTo(8.993f, 3.933f, 9.467f, 3.921f, 9.767f, 4.207f) curveTo(10.067f, 4.493f, 10.079f, 4.967f, 9.793f, 5.267f) lineTo(6f, 9.25f) horizontalLineTo(15.75f) curveTo(16.164f, 9.25f, 16.5f, 9.586f, 16.5f, 10f) curveTo(16.5f, 10.414f, 16.164f, 10.75f, 15.75f, 10.75f) horizontalLineTo(6f) lineTo(9.793f, 14.733f) curveTo(10.079f, 15.033f, 10.067f, 15.507f, 9.767f, 15.793f) curveTo(9.467f, 16.079f, 8.993f, 16.067f, 8.707f, 15.767f) lineTo(3.707f, 10.517f) curveTo(3.431f, 10.228f, 3.431f, 9.772f, 3.707f, 9.483f) lineTo(8.707f, 4.233f) close() } }.build()
Одно из преимуществ данного метода заключается в том, что при отрисовке иконки нам не нужно парсить XML и path — всё уже записано в виде функций, повторяющих команды из path. Это даёт небольшое ускорение — в статье приводится сравнение производительности двух подходов.
Но самое главное улучшение, на мой взгляд, — это существенное повышение семантики кода с иконками. С обычными ресурсами нам порой приходится писать длинные и не очень понятные идентификаторы.
(activity as AppCompatActivity) .supportActionBar ?.setHomeAsUpIndicator(ru.mail.cloud.uikitlibrary.R.drawable.ic_close_white)
В то же время с Compose иконками можно работать как с обычными классами. UI на Compose сам по себе очень гибкий, и в нём нет необходимости создавать копии одной и той же иконки. Достаточно просто добавить иконку в том виде, в котором она есть в дизайн-системе, а дальше её размеры и цвет можно легко изменять параметрами функции Icon. Благодаря гибкости и абстракциям в Compose-коде можно легко и наглядно создавать UI.
CloudToolbar( leftContent = { CloudNavbarButton( icon = VkUiIcons.Outlined.Cancel20, onClick = { onBackPress() }, ) }, )
Подход с классами также позволяет легко разделять различные наборы иконок, если вы используете несколько разных наборов (например, VKUI и Paradigm).
Как добавлять иконки
К сожалению, в настоящее время Android Studio не имеет встроенного конвертера для преобразования иконок в Compose-код. Для этой цели необходимо использовать сторонние инструменты. В моём случае наиболее удобными оказались следующие два:
-
Сайт https://www.composables.com/svgtocompose. Это ресурс с документацией по Compose Multiplatform, который также предоставляет конвертер иконок.
-
Плагин для Android Studio https://github.com/ComposeGears/Valkyrie. Он позволяет добавлять иконки непосредственно в IDE.
Эти два инструмента имеют некоторые отличия в работе:
-
Сайт включает в генерируемый код все аргументы функции path, даже те, что имеют значения по умолчанию. Плагин же добавляет только аргументы, отличающиеся от стандартных, что делает код более компактным.
-
Сайт не округляет координаты иконок, в то время как плагин округляет их до наименьшего значимого знака после запятой, используя внутренние оптимизации Android Studio (см. issue). Эта оптимизация применяется даже при добавлении иконок в формате XML (ссылка).
Пример работы конвертера сайта:
_Add36 = ImageVector.Builder( name = "Add36", defaultWidth = 36.dp, defaultHeight = 36.dp, viewportWidth = 36f, viewportHeight = 36f ).apply { path( fill = SolidColor(Color(0xFF99A2AD)), fillAlpha = 1.0f, stroke = null, strokeAlpha = 1.0f, strokeLineWidth = 1.0f, strokeLineCap = StrokeCap.Butt, strokeLineJoin = StrokeJoin.Miter, strokeLineMiter = 1.0f, pathFillType = PathFillType.NonZero ) { moveTo(19.5f, 19.5f) verticalLineTo(28.5f) curveTo(19.5f, 29.3284f, 18.8284f, 30f, 18f, 30f) curveTo(17.1716f, 30f, 16.5f, 29.3284f, 16.5f, 28.5f) verticalLineTo(19.5f) horizontalLineTo(7.5f) curveTo(6.6716f, 19.5f, 6f, 18.8284f, 6f, 18f) curveTo(6f, 17.1716f, 6.6716f, 16.5f, 7.5f, 16.5f) horizontalLineTo(16.5f) verticalLineTo(7.5f) curveTo(16.5f, 6.6716f, 17.1716f, 6f, 18f, 6f) curveTo(18.8284f, 6f, 19.5f, 6.6716f, 19.5f, 7.5f) verticalLineTo(16.5f) horizontalLineTo(28.5f) curveTo(29.3284f, 16.5f, 30f, 17.1716f, 30f, 18f) curveTo(30f, 18.8284f, 29.3284f, 19.5f, 28.5f, 19.5f) horizontalLineTo(19.5f) close() } }.build()
Пример работы конвертера плагина:
_Add36 = ImageVector.Builder( name = "Add36", defaultWidth = 36.dp, defaultHeight = 36.dp, viewportWidth = 36f, viewportHeight = 36f ).apply { path(fill = SolidColor(Color(0xFF99A2AD))) { moveTo(19.5f, 19.5f) verticalLineTo(28.5f) curveTo(19.5f, 29.328f, 18.828f, 30f, 18f, 30f) curveTo(17.172f, 30f, 16.5f, 29.328f, 16.5f, 28.5f) verticalLineTo(19.5f) horizontalLineTo(7.5f) curveTo(6.672f, 19.5f, 6f, 18.828f, 6f, 18f) curveTo(6f, 17.172f, 6.672f, 16.5f, 7.5f, 16.5f) horizontalLineTo(16.5f) verticalLineTo(7.5f) curveTo(16.5f, 6.672f, 17.172f, 6f, 18f, 6f) curveTo(18.828f, 6f, 19.5f, 6.672f, 19.5f, 7.5f) verticalLineTo(16.5f) horizontalLineTo(28.5f) curveTo(29.328f, 16.5f, 30f, 17.172f, 30f, 18f) curveTo(30f, 18.828f, 29.328f, 19.5f, 28.5f, 19.5f) horizontalLineTo(19.5f) close() } }.build()
При планировании работы с иконками стоит изначально выбрать один из этих двух инструментов и использовать только его. На мой взгляд, плагин лучше подходит для этой задачи — в своём проекте я добавлял иконки именно через него.
Надеюсь, этот материал оказался для вас полезным. Спасибо за внимание!
Источники
ссылка на оригинал статьи https://habr.com/ru/articles/840862/
Добавить комментарий