Обычно новым фреймворкам не хватает хороших примеров использования, туториалов и инструментов. Но не в случае с Jetpack Compose: параллельно с разработкой фреймворка Google развивал репозиторий, богатый на примеры использования. Вырисовывался набор полезных утилит, переиспользуемых виджетов и прочих удобств для разработки.
Так из репозитория с примерами вырос Accompanist — набор библиотек для Jetpack Compose, которые можно добавить в свой проект как зависимость. В статье рассмотрим подробнее, какой инструментарий он предоставляет.
AppCompat Theme Adapter
В легаси-проекте тема может быть достаточно большой, иметь большое поддерево тем и кастомных атрибутов. В легаси-проектах обычно это AppCompat тема, в то время как Compose использует MaterialTheme. Поэтому нужен «мост» между двумя темами.
В составе Accompanist есть AppCompatTheme — удобная обёртка, которая перенесёт тему из мира легаси и XML в мир Compose. Исходники простые: их можно дополнить или сделать по образу свою реализацию, если в теме много кастомных параметров.
Если проект уже использует Material Design Theme, то, согласно, документации, надо использовать MDC-Android Compose Theme Adapter из Material Design Components.
SystemUiController
SystemUiController — удобная обёртка над WindowInsetsControllerCompat. Помогает из кода управлять свойствами системных баров: видимостью и цветом. Незаменимая вещь, если вы поддерживаете в проекте API от версии 21 (Android 5.0).
Insets
Insets — обёртка над WindowInsetsCompat и набор расширений для совместимости с Compose. Поможет подписаться на изменения Inset’ов.
Кроме параметров из WindowInsetsCompat, можно использовать расширения для Modifier.
Для работы с padding’ами:
Для работы с размерами:
Permissions
Работа с опасными разрешениями требует некоторых усилий. Нужно:
Запросить разрешение у пользователя.
С помощью кастомного диалога (view, snackbar и других) объяснить пользователю, зачем нужно разрешение.
Правильно обработать ответ пользователя.
Предоставить возможность перейти в настройки приложения, чтобы пользователь мог принять решение, какие доступы дать.
Разработчики Accompanist проделали большую работу, чтобы работать с разрешениями было удобно. И что самое классное, они сделали запрос разрешений через ActivityResultLauncher: никакого больше переопределения onActivityResult в Activity, и теперь явно прописывается контракт для запроса разрешения.
API достаточно простое.
permissionState — состояние с параметрами разрешения. Кроме boolean флагов, нужен для запроса разрешения.
permissionNotGrantedContent — контент для состояния, когда нет разрешения.
permissionNotAvailableContent — контент для состояния, когда разрешение нельзя запросить. Тогда пользователя надо направить в настройки приложения.
content — контент для состояния, когда пользователь дал разрешение.
FlowLayout
FlexBoxLayout — контейнер для гибкой вёрстки. С ним удобно располагать элементы по горизонтали или вертикали: например, чтобы отобразить коллекцию элементов. Он сам рассчитает размеры и поймёт, как лучше перенести элемент на следующую строку или столбец.
FlexBox для Android — не родной контейнер для вёрстки. Первую версию FlexBoxLayout для CSS представили в 2009 году. До мира Android-разработки он добрался сильно позже: в 2016 году вышла первая alpha версия. С того времени вышло немало релизов, самый свежий — 3.0.0.
Также вышел FlexBoxLayoutManager для RecyclerView. Вещь удобная, и она не могла не появиться в Compose. Только назвали контейнер по-другому: FlowLayout. Название не менее лаконичное — и так же точно передаёт суть его работы.
Как и его предки, имеет такие же параметры для вёрстки. Поэтому если вы с ними уже встречались ранее, то и в Compose верстать с его помощью будет несложно.
SwipeRefreshLayout
SwipeRefreshLayout — один из самых известных и используемых контейнеров в Android разработке.
Использовать его просто: в XML необходимо контейнер с контентом обернуть в SwipeRefreshLayout и в коде реализовать интерфейс OnRefreshListener — например, чтобы начать перезагрузку контента на экране. Вроде всё хорошо, больше ничего и не надо.
Трудности начинаются, если разработчику нужно кастомизировать внешний вид лоадера. Возможности кастомизации ограничены — несмотря на громоздкую реализацию (~1200 строк).
«Из коробки» можно изменить цвет стрелки, размер и появление индикатора. Если вам необходимо изменить цвет индикатора или (о боже) «а давайте как на iOS», то придётся искать стороннюю библиотеку или писать самостоятельно. Но и то, и другое занимает время и несёт риски.
Accompanist помогает решить эти проблемы.
В Accompanist отсутствует XML, но принцип использования не изменился: контент всё так же оборачиваем в SwipeRefresh и используем лямбду для действия при обновлении.
Рассмотрим, что SwipeRefresh умеет «из коробки» и что с возможностью кастомизации.
state — хранит состояние загрузки и прогресс анимации индикатора.
onRefresh — лямбда-триггер для старта, например, перезагрузки контента.
modifier — модификатор контейнера: размеров, отступов и так далее.
swipeEnabled — включение и выключение реакции контейнера на свайп.
refreshTriggerDistance задаёт расстояние до триггера анимации. Чем расстояние больше, тем позже индикатор отреагирует на свайп.
indicatorAlignment — аналог Gravity. Интересная функциональность, которая пригодится, скорее, при реализации кастомного индикатора.
indicatorPadding — простая кастомизация отступов индикатора. Можно сделать отступ с определённой стороны, можно равный со всех.
indicator — реализация индикатора. «Из коробки» есть стандартная реализация индикатора из material design. Можно модифицировать её или написать полностью свою.
clipIndicatorToPadding — флаг, позволяющий обрезать индикатор по отступу контента.
content — непосредственно сам контент внутри SwipeRefresh.
Примеры работы SwipeRefresh из семпла библиотеки Accompanist.
SwipeRefreshTweakedIndicatorSample
SwipeRefreshCustomIndicatorSample
SwipeRefresh уже «из коробки» предоставляет неплохие возможности кастомизации привычного Android-разработчикам SwipeRefreshLayout. Реализация контейнера и индикатора занимает примерно 600 строк.
Pager
На UI языке Pager — горизонтальный или вертикальный список, у которого каждый элемент занимает всю или почти всю область экрана. Ключевое отличие от обычного списка — Snap-эффект, то есть «доскрол» элемента.
Pager, или ViewPager, имел несколько стадий развития как UI элемент в Android. Сначала был ViewPager, позже появился RecyclerView, и его приспособили под пейджинг. Сравнительно недавно появился ViewPager2, который под капотом использует тот же RecyclerView.
Давайте взглянем, как Pager может выглядеть на «языке» Compose. ViewPager, ViewPager2 и RecyclerView имеют общую черту: как UI-элементам для отображения коллекций, им нужен адаптер для заполнения данными. Pager на Compose этого лишён: взамен адаптера необходимо инициализировать PagerState. С него и начнём.
Параметров немного, а для инициализации обязательный всего один.
pageCount — количество элементов,
currentPage — текущая страница,
currentPageOffset — отступ страницы. Имеет значение от 0.0 до 1.0. Можно использовать для программного скролла страницы.
offscreenLimit — лимит страниц для подгрузки: количество подгружаемых элементов слева и справа от текущего элемента.
infiniteLoop — зацикленный скролл.
Сразу видно, какая функциональность «из коробки» самая крутая — конечно же, это зацикленный скролл. Сколько же было написано костылей для зацикливания ViewPager и RecyclerView :)
Accompanist предлагает две реализации для пейджера: HorizontalPager и VerticalPager. Если взглянуть в исходники, и то, и другое — просто обёртка на Compose функцией Pager. Разница только в инициализации параметра isVertical.
Многие параметры могут быть знакомы по RecyclerView и ViewPager. Поэтому рассмотрим те, которые не встречаются у них «из коробки».
verticalAlignment — аналог Gravity. Используется для центровки элемента: например, чтобы элемент доскроливался не до центра, а до края контейнера.
horizontalAlignment — аналогично vercticalAligment, только для горизонтали.
flingBehavior — конфиг поведения скрола: настраивает анимацию, когда пользователь перестаёт скролить или бросает скрол. Эти параметры примечательны тем, что у ViewPager и RecyclerView они не настраиваются легко «из коробки». Для ViewPager придётся «шаманить» с параметрами адаптера и с самим ViewPager. У RecyclerView для доскрола используются наследники SnapHelper’а.
В коде всё выглядит достаточно просто: инициализируем PagerState и заполняем пейджер элементами.
Часто пейджеры используются с индикаторами страниц, табами, а также трансформацией страниц при свайпе. И для этого у Accompanist’а тоже есть решение.
Как я писал выше, состояние Pager’a хранится в PagerState. Для анимации индикаторов, трансформации страниц работы с табами его можно переиспользовать. Очень удобно: одно состояние для всех компонент — нет необходимости проставлять слушателей. Как бонус — получаем минимальную связность.
Coil
Coil (Coroutine Image Loader) — сравнительно новая библиотека: первая версия вышла в 2019 году. Написана на Kotlin и Coroutines — и в этом её преимущество перед Glide и Picasso. У неё простое и гибкое API, она легко интегрируется с проектами на Kotlin, а ещё умеет работать с Svg, Gif и видео.
До недавнего времени Accompanist тоже имел в арсенале интеграцию с Coil и Glide. С версии 0.15.0 этот инструментарий помечен как Deprecated: разработчики Coil добавили поддержку Compose и сделали это на основе Accompanist с небольшими изменениями. Поэтому далее код будет из примера работы Coil, а не Accompanist.
Чтобы понять, как происходит интеграция Coil и Compose, рассмотрим параметры элемента ImageView в Compose. Почти все могут быть знакомы Android-разработчику.
painter — сущность для отрисовки графики. Это может быть цвет, Bitmap или Vector.
contentDescription — cпециальный параметр для работы с Accessibility. Вы же хороший разработчик и не ставите туда null? :)
modifier — модификатор для выставления параметров элемента: размера, отступов и так далее.
alignment — аналог Gravity.
contentScale — аналог ScaleType из ImageView.
alpha — прозрачность для отрисовки painter.
colorFilter — цветовой фильтр для отрисовки painter.
Вся магия происходит в painter — точнее, в наследниках абстрактного класса Painter. Собственно, Coil для интеграции предоставляет свои реализации Painter’a.
CrossFadePainter отрисовывает cross fade переход между двумя Painter’ами.
DrawablePainter — cамый простой представитель Painter’ов из библиотеки, но не менее полезный. Умеет отображать Drawable и AnimatableDrawable.
ImagePainter умеет больше всех. Поддерживает загрузку String, Uri ("android.resource", "content", "file", "http", and "https"), HttpUrl, File, DrawableRes, Drawable, Bitmap.
ImageRequest и ImageLoader — базовые классы из Coil.
С помощью ImageRequest настраиваются параметры загрузки картинки: ссылка, листенеры, политика кеширования и декодинга.
ImageLoader запускает ImageRequest, содержит настройки для работы с изображениями. Как правило он настраивается один раз и переиспользуется во всём приложении.
PlaceHolder
Skeleton-загрузчики уже давно стали стандартом для многих платформ. Во время загрузки пользователь видит псевдовёрстку экрана: по размеру и форме фигур он может предположить, где будет текст, где — фото, а где — кнопка. Это создаёт у пользователя позитивные впечатления.
Accompanist предлагает несколько реализаций placeholder’ов для создания skeleton загрузчиков:
Базовая — placeholder без анимаций.
Fade — placeholder c fade-анимацией.
Shimmer — placeholder c анимацией мерцания.
Accompanist — крутая поддержка для разработчиков на старте внедрения Compose
У Accompanist ещё не вышла версия 1.0, а инструментарий библиотеки уже достаточно богатый и поможет обойти кучу граблей.
Стоит заметить, что Accompanist — не единственная библиотека, которая поможет в поддержке и интеграции с Compose. Уже есть интеграция Compose c Hilt, Rx, Jetpack Navigation, Jetpack Paging. В Lottie 4.0.0 тоже добавили поддержку Compose.
quaer
Лучше бы усилия направили на качество и хорошую документацию, а не на количество.
Что толку от всех этих библиотек, если всё кривое и косое? Чуть не каждая попытка задейстовать что-то на Андроиде приводит к поиску обхода проблем и багов.
ozh-dev Автор
Да можно согласиться с вами, библиотека всё-таки версии 0.15 и до 1.0 далеко.
А вы тут документацию смотрели - https://google.github.io/accompanist/? Тут вроде по всем зависимостям библиотеки достаточно полно рассказывается.
Я считаю что баги не избежны сейчас, библиотека очень активно развивается. Вовремя написания статьи были задперекейчены зависимости для Coil и Glide, а через 4 часа после выхода статьи, вышла новая версия библиотеки.
quaer
Речь в принципе об экосистеме поверх которой всё это навёрнуто. Обёртка поверх обёртки. Почему бы не направить усилия на улучшение базы поверх которой всё это наворачивается? Вот буквально недавний пример: понадобился InputTextLayout. Казалось бы, куда стандартней? И тут же пришлось лезть искать почему бордюр рисуется поперёк текста метки. Пока пришлось убрать, потому что решения не нашлось.