Сегодня туманное и холодное воскресенье и у меня появилось желание написать какую-нибудь "полезную" статейку для Хабра.

В наше время Android программисты не так часто пишут UI чистым кодом за исключением таких библиотек, как Jetpack Compose

Само собой это может показаться извращением, вам придется использовать ViewGroup.LayoutParams для того, чтобы задать отступы между кнопками или текстовками, или вы будете юзать GradientDrawable для установки кастомного фона.

В целом, это утомительная работа, но на наше счастье в 2011 году появился Kotlin!

Благодаря глобальным extension функциям вы можете писать UI почти как в Jetpack Compose!

Ну что ж приступим!

Вспомогательные классы и Kotlin расширения

Я не собираюсь сейчас рассматривать вcе View компоненты и их свойства, а лишь сконцентрируюсь на главных моментах.

Давайте создадим обычный TextView с красным текстом в нашей MainActivity:

// выглядит неплохо, неправда ли?
val textView = textView(this) {
		text("Hello, World!")
    color(Color.RED)
}

// все благодаря глобальным функциям, ну и конечно Kotlin extensions!

// функция для создания нашей текстовки
fun textView(context: Context, init: AppCompatTextView.() -> Unit) : AppCompatTextView {
    val text = AppCompatTextView(context)
    text.init()
    return text
}

// теперь мы можем указать любое значение в качестве текста,
// он будет привидено к строке
fun <T> AppCompatTextView.text(value: T) {
    text = value.toString()
}

// цвет текста
fun AppCompatTextView.color(color: Int) {
    setTextColor(color)
}

Давайте создадим FrameLayout и разместим нашу текстовку по центру:

// наша текстовка
val textView = textView(this) {
    text("Hello, World!")
    color(Color.RED)
    // чтобы текстовка находилась в центре мы должны
    // указать для нее FrameLayout.LayoutParams
    layoutParams(frameLayoutParams().center().params())
}

// создаем FrameLayout и добавим в него текстовку
val parent = frameLayout(this) {
	addView(textView)
}

// указываем наш FrameLayout вместо activity_main макета
setContentView(parent)

// функция для создания FrameLayout
fun frameLayout(ctx: Context, builder: FrameLayout.() -> Unit) : FrameLayout {
    val frame = FrameLayout(ctx)
    builder(frame)
    return frame
}

// функция для создания FrameLayout.LayoutParams
fun frameLayoutParams() = FrameLayoutLP()

Вам наверно интересно, что за класс такой FrameLayoutLP?

Давайте взглянем на него:

// FrameLayoutLP является удобной оберткой для FrameLayout.LayoutParams
class FrameLayoutLP(private val params: FrameLayout.LayoutParams = 
    FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 
                             FrameLayout.LayoutParams.WRAP_CONTENT)) {
  
    fun matchWidth() = FrameLayoutLP(params.apply {
        width = FrameLayout.LayoutParams.MATCH_PARENT
    })

    fun wrapWidth() = FrameLayoutLP(params.apply {
        width = FrameLayout.LayoutParams.WRAP_CONTENT
    })

    fun matchHeight() = FrameLayoutLP(params.apply {
        height = FrameLayout.LayoutParams.MATCH_PARENT
    })

    fun wrapHeight() = FrameLayoutLP(params.apply {
        height = FrameLayout.LayoutParams.WRAP_CONTENT
    })

    fun center() = FrameLayoutLP(params.apply {
        gravity = Gravity.CENTER
    })

    fun params() = params
    
  	// ...
  	// вы можете изучить полный код на Github
    // https://github.com/KiberneticWorm/LearningApps/blob/master/FadingList/app/src/main/java/ru/freeit/fadinglist/core/FrameLayoutLP.kt
}

Мы рассмотрели самые главные функции и классы из которых состоит наша собственная мини UI либа.

Вы можете пойти дальше и создать больше вспомогательных функций и расширений для вьюшек, а также реализовать обертки для LinearLayout.LayoutParams и ConstraintLayout.LayoutParams

Создание RecyclerView списка

Наверно почти в каждом приложении есть какие-либо списки и поэтому грех было не упомянуть о нашем любимом RecyclerView

Давайте создадим список моих друзей:

// Ну как вам?
val list = listView(this) {
    linearVertical()
    adapter(listOf("Вадим", "Света", "Кристина", "Рамиз"), object: ViewHolderWrapper<String>() {
        override fun view(ctx: Context): View {
            return textView(ctx) {
                padding(16.dp(context))
                color(white())
               
                listenItem { _, friendName ->  
                    text(friendName)
                }
            }
        }
    })
}

Возможно это выглядит немного странно и похоже на ад callbacks.

На самом деле все не так.

Вьюшку элемента списка мы можем вынести в отдельный класс и наш код снова станет почти линейным)

Давайте посмотрим на RecyclerView расширения:

// функция для установки вертикального LinearLayoutManager
fun RecyclerView.linearVertical(reverse: Boolean = false) {
    layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, reverse)
}

// создание нашего списка
fun listView(ctx: Context, init: RecyclerView.() -> Unit) : RecyclerView {
    val list = RecyclerView(ctx)
    list.init()
    return list
}

// и самое вкусненькое)
// здесь мы создаем CoreAdapter, который является наследником
// RecyclerView.Adapter и с помощью объекта ViewHolderWraper<T>
// получает элементы списка и связывает их с вьюшками
fun <T> RecyclerView.adapter(items: List<T>, viewHolder: ViewHolderWrapper<T>) {
    adapter = CoreAdapter(items, viewHolder)
}

// код CoreAdapter вы можете изучить самостоятельно
// https://github.com/KiberneticWorm/LearningApps/blob/master/FadingList/app/src/main/java/ru/freeit/fadinglist/core/CoreAdapter.kt

Я думаю, особых разьяснений не нужно.

Разве, что реализация CoreAdapter и ViewHolderWrapper<T> заслуживает отдельной статьи.

Подведем итоги

Создание UI чистым Kotlin кодом на самом деле довольно приятный и увлекательный challenge благодаря таким средствам как:

  • Глобальные функции в Kotlin

  • Extension функции

  • метод apply

  • возможности функционального программирования

  • Kotlin коллекции и data классы

Чуть не забыл, ссылка на Github'чик (в этом репозитории мини-приложение из которого вы можете взять вспомогательные классы, находящиеся в пакете core, и юзать их для своих целей)

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

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


  1. superkeka
    06.12.2021 16:58
    +1

    Замечательно. Посмотрел бы я, кто такой подход будет в проде юзать.


    1. KiberneticWorm Автор
      06.12.2021 17:00
      +1

      У меня есть проект в проде, там соотношение xml / code - 70 % / 30 %


    1. snuk182
      06.12.2021 17:05

      Бизнес-требования всякие бывают, может кому и надо генерировать морду динамически. Другое дело, что тут легко пролететь со стилизацией и масштабированием. Как компромисс можно создавать компонент динамически, надувать его каким-то дефолтным содержимым из ресурса, а потом уже кромсать руками.


    1. Elsajalee
      06.12.2021 18:18
      +2

      Flutter проекты примерно такой подход и используют.


  1. GreenNick
    06.12.2021 17:03
    +5

    Anko, ты ли это?


  1. Felan
    06.12.2021 19:41
    +4

    Эх... а я вот динозавр который еще помнит, как весь мир выл от лапши которая получается когда UI делают из кода, и все радовались как дети, когда UI шагнул в декларативную сторону... А теперь обратно все? :) Даже вон целый композ выпустили...

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


    1. Elsajalee
      07.12.2021 00:29

      Здесь значительная часть колхоза получалась за счет адаптивности (планшеты, мобильники, разные dpi). Во времена динозавров UI получился ориентирован на "традиционные" мониторы, что прекрасно сохранилось и по сей момент.


      1. Felan
        07.12.2021 07:27
        +2

        Да причем тут мониторы?

        Раньше выли, что UI часть кода и неудобно. Народ разделить, что бы разметка была отдельно, а код отдельно. А теперь наоборот, неудобно им и "код учить" и "разметку", а давайте сделаем, так, что все в коде было...

        И это уже несколько раз на моей памяти было :(

        Приходят новые поколения и думают, что вот сейчас то они сделают серебряную пулю :)


    1. w201
      07.12.2021 05:26

      Люто плюсую. На моей памяти это уже надцатая итерация... Ещё со времён VisualAge было интересно наблюдать как данные и их представление пытаются то разбежаться, то соединиться...

      С compose ещё более непонятно. Вроде только все срослось с разделением экранов по фрагментам, только более менее навигацию прикрутили, рррраз и все отменили.

      Котлин вроде решил придерживаться разделения классов как в Яве, но потом рррраз и все compose функции топ уровня в 99%


      1. Felan
        07.12.2021 07:35
        +2

        Ну да.

        А Kotlin вообще немного странно в мир пришел. Чувствую сейчас нахватаю минусов от... разных людей :)

        Но Kotlin по сути ничего нового не представляет из себя. Пользуется полностью инфраструктурой Java. И только по одной причине, Google и Oracle когда-то поссорились и начали Java делить. Вот что бы от Oracle не огрести, забабахали "новый" язык с очень сомнительным синтаксисом, кстати.

        Вообще в современном мире некоторые тенденции мне кажутся странными. По моему общедоступность исходников (GitHub etc) открыла ящик Пандоры :)


        1. w201
          07.12.2021 09:40
          +1

          Ну тут пара моментов

          1. Не обязательно сделать что-то революционное, что бы оно стало сверх популярным. Достаточно предоставить это в нужный момент, либо в новом свете (iphone, tesla и пр.)

          2. Kotlin начали разрабатывать задолго до ссоры google и oracle.

          3. Kotlin дал довольно сильного пинка java в развитии, что тоже плюс

          4. Kotlin вывел из стогнации андроид разработку и реально сократил время разработки на 10-15% примерно.

          В общем плюсы есть, но и если бы ссоры не было, и новый циклы java начались на несколько лет раньше,то Котлин мог бы и не взлететь.

          А вот с Compose пока вопрос. Я не вижу как красиво и логично организовать хранение например 50 экранов, чтобы оно не было кашей. Как максимально развести данные, логику и их представление.

          Databinding наглядно показал в какую кашу можно превратить проект если смешивать это вместе. Хотя идея сама по себе была интересная и вполне себе в стиле compose.


          1. Felan
            07.12.2021 18:41

            Ну тогда пара контр-моментов :)

            1. Не обязательно делать велосипед, когда уже есть 150, а надо только 10. Пусть и другого цвета.

            2. Ну где он был до этой ссоры? Понятно, что его не с нуля запустили. Где-то она там варился на задворках, ну просто потому, что никакой нужды в нем не было, кроме как "политической".

            3. Ну это популизм. На конференциях такое хорошо задвигать. А в реальности, пока его насильно из пыльного угла не достали, че-то никого он не пинал. Да и пинает ли, вообще вопрос открытый. Java вполне себе зарекомендовавшая себя вещь, и развивается сама самостоятельно. Это как когда ребенок и папа делят деньги. У вас почему-то получается что папа будет меньше пить, хотя в реальности ребенок будет меньше есть.

            4. Чушь собачья :) С чего это андроид разработка была в стагнации? Кто это и как замерял? Я вот еще эклер помню. И все эти годы все было хорошо с разработкой.

              Вы поменьше смотрите телевизор и конференции всякие (не, есть хорошие). :)

              1. Мне вот очень интересно каким образом котлин ускорил разработку? Да еще прям так точно посчитали. Почему на 15%, а может на 20% или почему не на 30% или может вообще на 5%?

                Вы с чем сравниваете? С десять лет назад? Тогда все было медленнее. И фрейморков таких не было. Только вот фреймворк это не язык.

                Вот я понимаю почему С++ ускорил разработку по сравнению с ассемблером. Или почему автоматический менеджмент ресурсов ее ускорил. Или почему разработка на Java быстрее чем на C++, хотя тут уже однозначно говорить не приходится.
                Чем котлин ускорил? Я слышал только один аргумент - писать меньше. Ну так это для машинистки аргумент. У программиста основное время уходит на обдумывание. А IDE и так за него большую часть напечатает.


            1. w201
              07.12.2021 19:04

              1. Ну я не готов одобрить идею "надо только 10". 640KB должно быть достаточно всем. Поэтому пусть делают столько велосипедов, сколько можно.

              2. Я думаю, что не сильно ошибусь, если скажу, что основной разлад был в 2015-2017. Kotlin начали разрабатывать в 2010 (думаю это те самые задворки), а RC1 поспел в 2016. Так что все удачно сложилось. Скорее всего не без прицела на эти проблемы.

              3. Конечно популизм. А Oracle давно задумал изменить цикл релизов... просто так совпало... Конкуренция это всегда хорошо. Причем тут конференции не понял. И те кто пишут на java и те кто на kotlin должны быть благодарны, что рилизы фичи в языке пошли гораздо быстрее.

              4. Да, было замечательно. java 1.7, через 100 лет 1.8 и то урезанная...

                1. Замерял инвойсами, которые выставляю клиентам. А стаж фриланса довольно большой, отсюда и достаточно точная, в моем конкретном случае, цифра.
                  Сравниваю исключительно с java vs kotlin. Фреймворк остался тот же. Перешел на kotlin спустя почти год после поддержки Google.
                  За счет чего ускорил четко сказать не могу. Думаю и за счет меньшей писанины (шаблонного кода до сих пор очень и очень много) и более гибкой организации кода. Плюс, также, как и в случае с visualage for smalltalk, основной плюс kotlin, это обширная "stdlib"..... Если ее хорошо изучить, то кода станет гораздо меньше.

              Я не топлю за котлин. Начинал на нем писать с большим скепсисом и гораздо позже, чем стартанул тренд.
              Но и отрицать его вклад в Android разработку нельзя.

              Но вообще мы начинали говорить о другом. Если у вас есть что сказать за compose - буду рад выслушать мнение (совмещение логики и данных).


              1. Felan
                08.12.2021 02:11

                1. Да я вообще не против если че. Хотят, пусть делают. Просто у меня это вызывает снисходительную улыбку.

                2. Ну конечно совпало. Разлад пошел. Стали думать что делать. Смотреть что уже есть. Да, удачно совпало.

                3. Ну я просто популизм очень не люблю. А конференции это как раз его квинтэссенция как правило. Релизы пошли быстрее не ради котлина. Время такое настало. Благодарным быть за что? Я не очень понимаю за что надо быть благодарным.

                4. Ну я далек от разработки Javа. Но я бы сказал, что чрезмерный сахар только портит язык. А фундаментально вроде ничего особо не меняют никогда.

                  1. Так это не язык. Это у вас опыт растет :)

                Да я тоже ни за что не топлю. Так, поболтать хотел. Лично мне не приходится котлин использовать, все кругом на Java. Но вот вклад в разработку я пожалуй буду отрицать. А так, ради бога. Если мне попадется проект на котлине, да ради бога. Работа есть работа.

                Да, по compose сказать больше нечего, кроме того, что уже сказал. Разделение логики данных это фундаментальная вещь, она даже на ассемблере существует. И это фундаментальная архитектурная особенность. Ну не нравится мне когда смешивают UI и код. Я считаю, что отделение разметки от кода это хорошо. Расстраиваюсь периодически, когда тренды в другую сторону веют.


  1. Felan
    07.12.2021 18:40

    Веткой промахнулся, сорри.


  1. javavirys
    09.12.2021 02:07

    Хорошая статья, спасибо