Статья о библиотеках, позволившие мне обходиться без паттерна MVP и XML-разметки в android-приложениях.
buildscript {
ext.kotlin_version = '1.2.40'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.brotandos.dictionary"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'com.github.Brotandos:koatl:v0.1.1'
}
import android.annotation.SuppressLint
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import org.jetbrains.anko.frameLayout
class MainActivity : AppCompatActivity() {
private val fragManager = supportFragmentManager
private val container = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
frameLayout { id = container }
// If there are any instances saved, return
if (savedInstanceState != null) return
// else run default fragment
changeFragment(DictionaryFragment())
}
@SuppressLint("PrivateResource")
private fun changeFragment(f: Fragment, needToCleanStack: Boolean = false) {
if (needToCleanStack) clearBackStack()
fragManager.beginTransaction()
.setCustomAnimations(
R.anim.abc_fade_in,
R.anim.abc_fade_out,
R.anim.abc_popup_enter,
R.anim.abc_popup_exit)
.replace(container, f)
.addToBackStack(f::class.simpleName)
.commit()
}
private fun clearBackStack() {
if (fragManager.backStackEntryCount == 0) return
val first = fragManager.getBackStackEntryAt(0)
fragManager.popBackStack(first.id, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
override fun onBackPressed() {
if (fragManager.backStackEntryCount > 1) fragManager.popBackStack()
else finish()
}
}
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
data class Dictionary(var title: String, val items: MutableList<DictionaryItem>)
data class DictionaryItem(val key: String, val value: String)
object G {
object Color {
const val CARD_SHADOW_1: Int = 0xFFEEEEEE.toInt()
const val CARD_SHADOW_2: Int = 0xFFDDDDDD.toInt()
const val CARD: Int = 0xFFFEFEFE.toInt()
const val PRIMARY: Int = 0xFF3F51B5.toInt()
}
}
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.view.View
import com.brotandos.koatlib.times
import org.jetbrains.anko.dip
fun Context.cardBg(color: Int, radius: Int = dip(2)) = GradientDrawable() * {
shape = GradientDrawable.RECTANGLE
cornerRadius = radius.toFloat()
setColor(color)
}
fun View.layerCard(color: Int = G.Color.CARD) = LayerDrawable(arrayOf(
context.cardBg(G.Color.CARD_SHADOW_1, dip(4)),
context.cardBg(G.Color.CARD_SHADOW_2, dip(3)),
context.cardBg(color)
)) * {
setLayerInset(0, 0, 0, 0, 0)
setLayerInset(1, 0, 0, 0, dip(1))
setLayerInset(2, dip(1), dip(1), dip(1), dip(2))
}
val bgLayerCard: View.() -> Unit = {
background = layerCard()
}
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.widget.TextView
import com.brotandos.koatlib.*
import org.jetbrains.anko.imageResource
import org.jetbrains.anko.matchParent
import org.jetbrains.anko.sdk25.coroutines.onClick
class DictionaryFragment: KoatlFragment() {
private val dictionary: Dictionary
private val icCollapsed = R.drawable.ic_collapsed
private val icExpanded = R.drawable.ic_expanded
private lateinit var vList: RecyclerView
init {
val list = mutableListOf<DictionaryItem>()
for (i in 0 until 20) list += DictionaryItem("key-$i", "value-$i")
dictionary = Dictionary("First dictionary", list)
}
// Все веселье начинается здесь
override fun markup() = KUI {
// Многие view частицы начинаются с маркера 'v'. Ниже LinearLayout с вертикальной ориентацией
vVertical {
// FrameLayout, bg(colorRes: Int) - лямбда-функция, которая изменяет background частицы
vFrame(bg(R.color.colorPrimary)) {
// TextView, здесь функция-расширение Float.sp изменяет размер текста
// функция lp - сокращенное от layoutParams
// submissive означает width = wrapContent и height = wrapContent
// g5 - gravity = Gravity.CENTER.
// Аттрибут гравитации в библиотеке описан по принципу кнопок телефона
// 1 - слева-наверху, 2 - центр-вверх, 5 - середина, 456 - середина вертикали и т.д.
vLabel(dictionary.title, 10f.sp, text(Color.WHITE)).lp(submissive, g5)
}.lparams(matchParent, 50.dp) // Надеюсь здесь Int.dp интуитивно понятно
// Ниже верстается RecyclerView. Моя самая любимая часть библиотеки
// Позволяет отказаться от создания адаптеров
vList = vList(linear).forEachOf(dictionary.items) {
item, i -> // item - текущий объект, i - позиция
// bgLayerCard - описана внутри Styles.kt.
// Это моя попытка внедрить CSS концепцию стилизации view-частиц
// mw - сокращенное от width = matchParent и height = wrapContent
vVertical(bgLayerCard, mw) {
lateinit var vValue: TextView
// content456 то же, что и gravity = Gravity.CENTER_VERTICAL
// Концепция телефонных кнопок удобна тем,
// что благодаря ей легче представлять расположение дочерних view-частиц
vLinear(content456) {
vImage(icCollapsed) {
onClick {
if (this@vImage.resourceId == icCollapsed) {
imageResource = icExpanded
vValue.visible()
} else {
imageResource = icCollapsed
vValue.hidden()
}
}
}.lp(row, 5f()) // row - то же, что и width = matchParent и height = wrapContent
// функция Float.invoke() у дочерних частиц LinearLayout'а означает weight
//
vLabel(item.key).lp(row, 1f())
}.lp(dominant) // dominant - width = matchParent, height = matchParent
// hidden - visibility = View.GONE
vValue = vLabel(item.value, text(G.Color.PRIMARY), hidden).lp(dominant, 1f(), m(2.dp))
}.llp(row, m(2.dp)) // функция m(number: Int) то же, что и margin
}.lp(row, m(5.dp))
}
}
}
К сожалению, не доступен сервер mySQL