We write Android UI with clean code without additional libs
Today is a foggy and cold Sunday and I had a desire to write some “useful” article for habr.
Nowadays Android programmers do not write UI clean code very often, with the exception of such libraries as Jetpack Compose
Of course, this may seem like a perversion, you will have to use ViewGroup.LayoutParams
in order to set the indents between buttons or texts, or you will use GradientDrawable
to set a custom background.
In general, this is a tedious job, but luckily for us, Kotlin appeared in 2011!
Thanks to global extension functions, you can write UI almost like in Jetpack Compose!
Well, let’s get started!
Helper Classes and Kotlin Extensions
I’m not going to consider all View
components and their properties, but I will only concentrate on the main points.
Let’s create a regular TextView
with red text in our 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)
}
Let’s create FrameLayout
and place our text in the center:
// наша текстовка
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()
You might be wondering what kind of class this is. FrameLayoutLP
?
Let’s take a look at it:
// 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
}
We have considered the most important functions and classes that make up our own mini UI lib.
You can go ahead and create more helper functions and extensions for the views, and also implement wrappers for LinearLayout.LayoutParams
and ConstraintLayout.LayoutParams
Creating a RecyclerView list
Probably almost every application has some kind of lists and therefore it was a sin not to mention our beloved RecyclerView
Let’s create a list of my friends:
// Ну как вам?
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)
}
}
}
})
}
Maybe it looks a little strange and looks like hell callbacks…
In fact, it’s not like that.
We can move the view of the list item into a separate class and our code will again become almost linear)
Let’s take a look at RecyclerView
extensions:
// функция для установки вертикального 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
I think no special explanations are needed.
Is that the implementation CoreAdapter
and ViewHolderWrapper<T>
deserves a separate article.
Let’s summarize
Building a UI with pure Kotlin code is actually quite an enjoyable and challenging challenge, thanks to tools such as:
Global functions in Kotlin
Extension functions
method
apply
functional programming capabilities
Kotlin collections and
data
classes
I almost forgot, link to Github’chik (in this repository there is a mini-application from which you can take the helper classes that are in the core package and use them for your own purposes)
Friends, coders! More rest for you, pleasant moments in life, and of course love, which we all lack so much 🙂