Про Jetpack Compose на сегодняшний день слышал, пожалуй, каждый android-разработчик. Некоторым уже удалось «затащить его в прод», кто-то пробовал его в своих пет-проектах, а кто-то до сих пор сомневается в его целесообразности. Ведь на первый взгляд мы имеем все тоже самое: <TextView> заменили на Text(), Box() очень похож на <FrameLayout> и др. Единственное, что сразу произвело впечатление — новые Lazy-списки, которые являются заменой привычного RecyclerView и позволяют писать меньше кода. Но, опять же, скопировать и подправить адаптер под очередной экран это уже привычное действие и оно не занимает много времени, а само действие отработано до автоматизма. К числу сомневающихся и, возможно совсем немного lazy, можно было отнести и меня. Но несколько задач, отличных от рутинного перекрашивания кнопок, заставили меня пересмотреть свое отношение к Compose.
Случай первый. Дано: макет в фигме следующего вида
Если у вас, как и у меня в свое время, появились сомнения в увиденном, то скорее вы все поняли правильно. Необходимо реализовать таблицу с горизонтальным скроллом, при этом первая ячейка должна быть жестко фиксирована. Зачем? «Увидели, понравилось, хотим себе».
Как такое делать я не знал. Stackoverflow выдал несколько решений, от которых я начал нервничать. В одном из них была реализация через <Table>. Когда про него вспоминали последний раз? Другие решения были на базе RecyclerView (уже лучше) — кастомные LayoutManager или декораторы. Но главная причина, по которой я отверг эти решения, — ячейки имели статические размеры, а в моем случае высота зависит от текста в первой колонке. Желания прокидывать размеры и пересчитывать высоты ячеек в списке у меня не было, и я снова вернулся к гуглу.
На этом этапе я решил не игнорировать ссылки, которые вели на статьи с Jetpack Compose. Оказывается в LazyColumn/LazyRow помимо item-элементов были добавлены (пока еще Experimental) stickyHeaders (https://developer.android.com/jetpack/compose/lists#sticky-headers).
Собственно это та штука, которая «прилипает» к верху экрана при скролле. Данная находка открыла во мне второе дыхание. Я посчитал, что для реализации задуманного будет удобно ответ сервера представить ввиде нескольких списков, по списку на каждый столбец.
Тогда начало нашей Compose-функции (назовем ее по-простому DataTable) будет выглядеть так:
Теперь опишем элементы таблицы, их всего три: текстовый хедер, картинка с отображением статуса и ячейка с галочкой/ без галочки.
Время строить таблицу:
Используется LazyRow, так как нужен именно горизонтальный скролл. В stickyHeader задаем фиксированный столбец. В itemsIndexed будем отрисовывать остальные столбцы с «галочками». Если запустить наш код на данном этапе, то экран будет выглядеть вот так:
Некрасиво.
Как видно, изначальная проблема с высотами ячеек при таком подходе все равно остается, поэтому поищем способ ее решения. У Modifier в доступных функциях есть колбэк onGloballyPositioned. Он возвращает координаты элемента на экране после его отрисовки и через них мы будем находить высоту. Для «прокидывания» высот в другие ячейки я использовал mutableState:
Ключ - индекс элемента(можно позволить, поскольку кол-во ячеек во всех столбцах одинаково), значение - высота.
Конечный код LazyRow элемента будет иметь вид:
Первым создается stickyHeader. После заполнения ячеек контентом, высчитываются высоты и сохраняются в mutableState.
После отрисовываются столбцы с «галочками». Из mutableState берутся высоты. В верху столбца (для index == 0) ставится картинка.
Запускаем, получаем такой экран, радуемся результату:
Вот так ленивый список сэкономил кучу времени ленивому разработчику и дал мощный пинокзаряд мотивации для изучения Jetpack Compose.
Посмотреть как работает можно тут: https://play.google.com/store/apps/details?id=su.art.spbrealty&hl=ru
Случай второй. Буквально через пару недель мне поручили «небольшой» редизайн экрана. Из макета видно, что планируется блок со списком услуг по категроиям. При скролле он фиксируется вверху, в нем перелистываются категории, пор нажатию на категорию мы скроллим до нужной позиции в списке услуг:
В stickyHeader можно поместить любую @Composable - функцию, а значит подход из первого случая будет работать и здесь. Список будет иметь вид:
Сначала размещаем элементы, которые будут скрываться под тулбаром, затем stickyHeader, после - все остальное.
По итогу имеем такой экран:
Посмотреть можно здесь : https://play.google.com/store/search?q=мой%20теле2&c=apps&hl=ru
Случай третий. Кнопка. В данном случае было необходимо разработать экран объявления. При создании макета дизайнеры вдохновлялись Авито, в частности поведением кнопки «написать автору объявления»:
Длинный экран со скроллом. У кнопки «Написать» есть свое место на этом экране. Но если пользователь не дошел до этого места, то кнопка должна быть видна поверх контента.
Первой мыслью было поискать в документации stickyFooter, поскольку это как stickyHeader, но внизу. Но, к сожалению, разработчики Google не посчитали нужным такой элемент. И тут у меня появилась безумная идея.
Что будет, если у LazyColumn задать параметр reverseLayout = true? Будет ли stickyHeader «липнуть» к низу экрана? Как оказалось — будет. Дело за малым: переворачиваем порядок в списке задом наперед и получаем желаемый вид экрана:
Если вы еще не используете Compose в своих проектах, то определенно стоит подумать о том, что бы начать, поскольку он позволяет делать сложные вещи простыми и, что самое главное, экономит время (и нервы).
Хочу надеяться, что мой опыт окажется полезным и поможет другим разработчикам в создании нестандартных экранов. И не бойтесь изучать что-то новое. Возможно когда-нибудь это новое сделает вашу жизнь чуточку легче.
Статью подготовил Илья Кубышкин, Android- разработчик в e-legion