Пишем Android UI чистым кодом без дополнительных либ

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

В наше время 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, и юзать их для своих целей)

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

Источник: https://habr.com/ru/post/593655/


Интересные статьи

Интересные статьи

Напишем вместе HTTP-сервис на golang с нуля? Я уверен, что это довольно несложно. Для тех, кто каждую неделю этим занимается, моя статья не будет особенно интересна, но я все равно рекомендую взглян...
Прошло Google I/O и мы узнали абсолютно всё про новую версию Android. Основной упор в релизе был сделан на усиление безопасности ОС и приватности данных, а также провели ...
Идея появилась из проблемы. Проблема появилась из негодования. Мне было жутко неприятно от мысли, что при повороте экрана приходится проделывать серьезную работу над тем,...
В данной статье разобран пример создания делегата для SharedPreferences, который уменьшает boilerplate и делает использование SharedPrefernces более удобным. Те кто хочет посмотреть результат, мо...