Хабрахабр

Змеиная верстка и «квантовые» частицы в приложениях под Android (Часть 2)


Пришло время поговорить об обещанных «квантовых» частицах. Kuantum позволяет реактивно изменять состояния UI. На создание этой библиотеки меня вдохновил Vue. Сразу покажу библиотеку в бою.

Практика

Часть-1: «Квантовая» связка частиц
Продолжаем модифицировать приложение из предыдущего поста. Теперь добавим возможность добавлять/удалять элементы словаря и изменять их значения.

В build.gradle (app level) добавьте следующие строки

implementation 'com.github.Brotandos:koatl:v0.1.3'
implementation 'com.github.Brotandos:kuantum:v0.0.7'

Меняем Models.kt

import com.brotandos.kuantumlib.ListKuantum
import com.brotandos.kuantumlib.TextKuantum // Маркер 'q' означает "квантовую" переменную
data class Dictionary(var qTitle: TextKuantum, val qItems: ListKuantum<DictionaryItem>)
data class DictionaryItem(val qKey: TextKuantum, val qValue: TextKuantum)

Самое веселое, DictionaryFragment.kt:


import android.graphics.Color
import android.view.View
import android.widget.Button
import android.widget.ImageView
import com.brotandos.koatlib.*
import com.brotandos.kuantumlib.ListKuantum
import com.brotandos.kuantumlib.TextKuantum
import com.brotandos.kuantumlib.of
import org.jetbrains.anko.imageResource
import org.jetbrains.anko.matchParent
import org.jetbrains.anko.scrollView class DictionaryFragment: KoatlFragment(), View.OnClickListener override fun markup() = KUI { scrollView { vVertical { vFrame(bg(R.color.colorPrimary)) { // Слово "of" - infix функция. // Благодаря ней view-частица привязывается к тексту vLabel(10f.sp, text(Color.WHITE)).lp(submissive, g5) of qDictionary.qTitle }.lparams(matchParent, 50.dp) // Нижнюю частицу я вставил для демонстрации силы TextKuantum // Здесь мы привязываем титул словаря. Попробуйте туда что-нибудь написать vText(line, "Set dictionary name"()).lp(row) of qDictionary.qTitle // Функция String.invoke прописывает placeholder-текст для EditText // Привязываем recyclerView к списку и описываем верстку для каждого элемента vList(linear).lp(row, m(5.dp)) of qDictionary.qItems.vForEach { item, _ -> vVertical(bgLayerCard, mw) { vLinear(content456) { // Кнопка свернуть/развернуть vImage(icCollapsed, tag(item), this@DictionaryFragment()).lp(row, 5f()) // Привязываем само слово vText(line).lp(row, 1f()) of item.qKey // Кнопка удаления слова vImage(R.drawable.ic_remove, tag(item), this@DictionaryFragment()).lp(row, 5f()) }.lp(dominant, 1f()) // Привязываем значение слова vText(text(G.Color.PRIMARY), hidden).lp(dominant, 1f(), m(2.dp)) of item.qValue }.llp(row, m(2.dp)) } vBtn("Add item to qDictionary", this@DictionaryFragment()) }} } override fun onClick(v: View?) { // Нижняя функция самая сложная для понимания. // Мы внутри списка ищем подходящий элемент словаря... // ... и возвращаем view-частицу для описания слова // Оно нужно, чтобы развернуть элемент словаря... // ... и показать view, отвечающую за описание слова fun ListKuantum<DictionaryItem>.getItemValueView(itemAsViewTag: Any) = find { it == itemAsViewTag }!!.qValue.firstView if (v is Button) { // Добавляем в словарь новый элемент qDictionary.qItems.add(DictionaryItem(TextKuantum(), TextKuantum())) } else when ((v as ImageView).resourceId) { // Разворачиваем слово icCollapsed -> { v.imageResource = icExpanded qDictionary.qItems.getItemValueView(v.tag).visible() } // Сворачиваем слово icExpanded -> { v.imageResource = icCollapsed qDictionary.qItems.getItemValueView(v.tag).hidden() } // Удаляем элемент R.drawable.ic_remove -> qDictionary.qItems.removeFirstWhere { it == v.tag } } }
}

Результат

Код на github

Часть-2: Фильтр и BooleanKuantum
Теперь добавим возможность поиска по словарю.

Добавим иконку поиска

Изменим DictionaryFragment.kt (места изменений я пометил комментариями)


import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.view.View
import android.widget.Button
import android.widget.ImageView
import com.brotandos.koatlib.*
import com.brotandos.kuantumlib.*
import org.jetbrains.anko.imageResource
import org.jetbrains.anko.matchParent
import org.jetbrains.anko.scrollView class DictionaryFragment: KoatlFragment(), View.OnClickListener { private val icCollapsed = R.drawable.ic_collapsed private val icExpanded = R.drawable.ic_expanded private val qDictionary: Dictionary // Текст поиска private val qFilter = TextKuantum() // Флаг поиска по ключу private val qFilterByKey = BooleanKuantum(true) // Флаг поиска по значению private val qFilterByValue = BooleanKuantum(true) // Сохраним view-частицу, нам она пригодится private lateinit var vList: RecyclerView init { val list = mutableListOf<DictionaryItem>() for (i in 0 until 7) list += DictionaryItem(TextKuantum("key-$i"), TextKuantum("value-$i")) qDictionary = Dictionary(TextKuantum("First dictionary"), ListKuantum(list)) } override fun markup() = KUI { scrollView { vVertical { vFrame(bg(R.color.colorPrimary)) { vLabel(10f.sp, text(Color.WHITE)).lp(submissive, g5) of qDictionary.qTitle }.lparams(matchParent, 50.dp) // Строка поиска vText(line, "Search"(), icLeft(R.drawable.ic_search), pIcon(3.dp)).lp(row) of qFilter { // Здесь прописывается реакция на изменение текста поиска if (it.isEmpty()) { qDictionary.qItems.clearViewFilter() vList.scrollToPosition(0) } else filterDictionary(it) } vLinear(p(2.dp)) { // Флаг поиска по ключу vCheck("by key").lp(row, 1f()) of qFilterByKey { vList.requestLayout() if (it.not() && qFilterByValue.value.not()) { qFilterByKey becomes true qFilterByValue becomes true } filterDictionary(qFilter.value) } // Флаг поиска по значению vCheck("by value").lp(row, 1f()) of qFilterByValue { vList.requestLayout() if (it.not() && qFilterByKey.value.not()) { qFilterByKey becomes true qFilterByValue becomes true } filterDictionary(qFilter.value) } }.lp(submissive, g258) vList = vList(linear).lp(row, m(5.dp)) of qDictionary.qItems.vForEach { item, _ -> vVertical(bgLayerCard, mw) { vLinear(content456) { vImage(icCollapsed, tag(item), this@DictionaryFragment()).lp(row, 5f()) vText(line).lp(row, 1f()) of item.qKey vImage(R.drawable.ic_remove, tag(item), this@DictionaryFragment()).lp(row, 5f()) }.lp(dominant, 1f()) vText(text(G.Color.PRIMARY), hidden).lp(dominant, 1f(), m(2.dp)) of item.qValue }.llp(row, m(2.dp)) } vBtn("Add item to dictionary", this@DictionaryFragment()).lp(row) }} } override fun onClick(v: View?) { fun ListKuantum<DictionaryItem>.getItemValueView(itemAsViewTag: Any) = find { it == itemAsViewTag }!!.qValue.firstView if (v is Button) { qDictionary.qItems.add(DictionaryItem(TextKuantum(), TextKuantum())) vList.scrollToPosition(qDictionary.qItems.lastIndex) } else when ((v as ImageView).resourceId) { icCollapsed -> { v.imageResource = icExpanded qDictionary.qItems.getItemValueView(v.tag).visible() } icExpanded -> { v.imageResource = icCollapsed qDictionary.qItems.getItemValueView(v.tag).hidden() } R.drawable.ic_remove -> qDictionary.qItems.removeFirstWhere { it == v.tag } } } // Фильтруем словарь private fun filterDictionary(filterWord: String) { qDictionary.qItems.filterView { if (qFilterByKey.value && qFilterByValue.value.not()) it.qKey.value.contains(filterWord) else if (qFilterByValue.value && qFilterByKey.value.not()) it.qValue.value.contains(filterWord) else it.qKey.value.contains(filterWord) || it.qValue.value.contains(filterWord) } }
}

Результат

Код коммита на github

Все вместилось меньше чем 100 строк. Теперь у нас готов фрагмент словаря с фильтром. И это все в одном файле, не нужно прыгать между версткой и логикой. Без никаких xml, адаптеров, findViewById и танцев с textWatcher.

В следующий раз покажу прогрузку с интернета и подведу итоги, распишу известные мне плюсы и минусы, какие риски вас ждут.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»