» » » Создание Android приложения с использованием Anko Layouts и Anko Coroutines

 

Создание Android приложения с использованием Anko Layouts и Anko Coroutines

Автор: admin от 3-03-2019, 20:00, посмотрело: 190

Создание Android приложения с использованием Anko Layouts и Anko Coroutines



Примерно год назад я начал использовать Kotlin в своих Android проектах. Мне хотелось попробовать что-то новое, что было бы интересно изучать. Тогда я и наткнулся на Anko. К тому времени писать UI на xml порядком осточертело. Мне всегда нравилось писать интерфейс руками, не прибегая к WYSIWYG и xml-разметке, используемой в Android Studio. Единственный минус заключается в том, что для проверки любого изменения придется перезапускать приложение. Можно использовать плагин, который показывает как будет выглядеть ui не запуская приложения, но мне он показался довольно странным. Так же у него есть крутая возможность конвертирования xml в Anko Layouts DSL.



Самый большой недостаток библиотеки — практически полное отсутствие документации. Чтобы разобраться, как ее правильно использовать, приходилось часто заглядывать в исходники. В этой статье будет подробно разобрано создание приложения используя Anko Layouts и Anko Coroutines.

AnkoComponent с единственным методом createView, принимающим AnkoContext и возвращающим View. Как раз в этом методе и происходит создание всего UI. Интерфейс AnkoContext является оберткой над ViewManager. Подробнее о нем будет позже.



Немного разобравшись с тем, как устроен AnkoComponent, попробуем создать простенький UI в классе нашей Activity. Стоит уточнить, что вот такое "прямое" написание UI возможно только в Activity, так как для нее написана отдельная extension функция ankoView, в которой вызывается метод addView, и уже в самом методе создается AnkoContextImpl с параметром setContentView = true.



class AppActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        verticalLayout {
            lparams(matchParent, matchParent)
            gravity = Gravity.CENTER
            textView("Cool Anko TextView") {
                gravity = Gravity.CENTER
            }
        }
}

Очевидно, что для чего-то большего, чем один TextView, метод onCreate быстро превратится в подобие свалки. Попробуем отделить класс Activity от UI. Для этого создадим еще один класс, в котором он будет описываться.


class AppActivityUi: AnkoComponent[leech=https://github.com/Kotlin/anko/blob/acb7e606dfca0ebd5becf04b93c5b932e9575246/anko/library/static/commons/src/main/java/AnkoContext.kt#L135]можно использовать[/leech]</p><br/>
[code]AppActivityUi().setContentView(this)

Хорошо, но как быть, если мы хотим создать UI для фрагмента? Для этого можно использовать метод createView напрямую, вызывая его из onCreateView метода фрагмента. Выглядит это следующим образом:


class AppFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        return AppFragmentUi().createView(AnkoContext.create(requireContext(), this))
    }
}

Как уже было сказано — AnkoContext является оберткой над ViewManager. У его вспомогательного объекта (companion object) есть три основных метода, возвращающих AnkoContext. Разберем их поподробнее.


create


 fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>

и его брат-близнец


fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>

возвращают AnkoContextImpl.


Методы используются во всех стандартных случаях, как, например, в предыдущих примерах с Activity и Fragment. Самым интересным здесь являются параметры owner и setContentView. Первый позволяет передать в метод createView instance конкретного Fragment'a, Activity или чего-либо еще.


MyComponent().createView(AnkoContext.create(context, myVew))

class MyComponent: AnkoComponent<View> {
    override fun createView(ui: AnkoContext<View>): View = with(ui) {
        val myView: View= ui.owner
        // И дальше по инструкции
    }
}

Второй параметр — setContentView — автоматически попробует добавить полученный view, если Context является экземпляром Activity или ContextWrapper. Если у него это не получится — он выкинет IllegalStateException.


createDelegate


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


fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)

Он позволяет добавить результат createView компонента в owner.


Рассмотрим его использование на примере. Допустим, у нас есть большой класс, описывающий один из экранов приложения — AppFragmentUi.


verticalLayout {
    relativeLayout {
        id = R.id.toolbar
        // тут много вложенных view
    }
    relativeLayout{
        id = R.id.content
        // тут тоже много вложенных view
    }
}

Логически его можно разделить на две части — на тулбар и контент, AppFragmentUiToolbar и AppFragmentUiContent соответственно. Тогда наш основной класс AppFragmentUi станет гораздо проще:


class AppFragmentUi: AnkoComponent[leech=https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/with.html]<code>with</code>[/leech] в качестве объекта передается не ui, a ui.owner.<br/>
Таким образом у нас выполняется следующий алгоритм:</p><br/>
<ol>
<li>Создается инстанс компонента.</li>
<li>Метод createView создает View который будет добавлен.</li>
<li>Полученный View добавляется в owner.</li>
</ol><br/>
<p>Более приближенный аналог: <code>this.addView(AppFragmentUiToolbar().createView(...))</code><br/>
Как можно заметить, вариант с createDelegate более приятен для чтения.</p><br/>
<h3 id="createreusable">createReusable</h3><br/>
<p>Похоже на стандартный AnkoContext.create, но с небольшим дополнением — корневым view считается самый последний:</p><br/>
[code]class MyComponent: AnkoComponent<MyObject> {
    override fun createView(ui: AnkoContext<MyObject>): View = with(ui) {
        textView("Some text")
        // в этом моменте не будет выкинут IllegalStateException: View is already set 
        // На экране будет показан "Another text"
        textView("Another text") 
    }
}

В стандартной реализации, если корневой view установлен — попытка установить второй view параллельно вызовет exception.


Метод createReusable возвращает класс ReusableAnkoContext, который наследуется от AnkoContextImpl и переопределяет метод alreadyHasView().


CustomView


К счастью, Anko Layouts не ограничивается лишь этим функционалом. Если нам нужно показать собственный CustomView, нам не придется писать


verticalLayout {
    val view = CustomView(context)
    //....
    addView(view) // или addView(view.apply { ... })
}

Для этого можно добавлять свою обертку, которая будет делать тоже самое.


Основным компонентов здесь выступает extension метод ankoView(factory: (Context) T, theme: Int, init: T.() Unit) от ViewManager, Context или Activity.



  • factory — функция, на вход которой передаётся Context и возвращается View. По сути является фабрикой, в которой происходит создание View.

  • theme — ресурс стиля, который будет применен для текущей view.

  • init — функция, в которой для созданной View будут устанавливаться необходимые параметры.


Добавим свою реализацию для нашего CustomView


inline fun ViewManager.customView(theme: Int = 0, init: (CustomView).()  Unit): CustomView {
    return ankoView({ CustomView(it) }, theme, init)
}

Теперь наш CustomView создается очень просто:


customView {
    id = R.id.customview
    // остальные параметры
}

Можно использовать lparams для применения LayoutParams к View.


textView("text") {
        textSize = 12f
    }.lparams(width = matchParent, height = wrapContent) {
        centerInParent()
}

Стоит заметить, что не ко всем View это применимо — все lparams методы как правило объявляются в обертках. Например _RelativeLayout — обертка над RelativeLayout. И так для каждого.


К счастью, для Android Support Library написано несколько оберток, поэтому можно только подключить зависимости в gradle файле.


    // Appcompat-v7 (Anko Layouts)
    implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
    implementation "org.jetbrains.anko:anko-coroutines:$anko_version"

    // CardView-v7
    implementation "org.jetbrains.anko:anko-cardview-v7:$anko_version"

    // Design
    implementation "org.jetbrains.anko:anko-design:$anko_version"
    implementation "org.jetbrains.anko:anko-design-coroutines:$anko_version"

    // GridLayout-v7
    implementation "org.jetbrains.anko:anko-gridlayout-v7:$anko_version"

    // Percent
    implementation "org.jetbrains.anko:anko-percent:$anko_version"

    // RecyclerView-v7
    implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version"
    implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version"

    // Support-v4 (Anko Layouts)
    implementation "org.jetbrains.anko:anko-support-v4:$anko_version"

    // ConstraintLayout
    implementation "org.jetbrains.anko:anko-constraint-layout:$anko_version"

Помимо всего прочего, библиотека позволяет более удобную имплементацию различных listener'ов. Небольшой пример из репозитория:


seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit
    override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
    override fun onStopTrackingTouch(seekBar: SeekBar) = Unit
})

и теперь используя Anko


seekBar {
    onSeekBarChangeListener {
        onProgressChanged { seekBar, progress, fromUser 
            // do something
        }
    }
}

Также некоторые listener'ы поддерживают корутины:


    verticalLayout{
    val anyCoroutineContext = GlobalScope.coroutineContext
    onclick(anyCoroutineContext) {
             //this: CoroutineScope
    }
}

Anko Coroutines


Для безопасной передачи чувствительных к утечкам памяти объектов используется метод asReference. Он базируется над WeakReference и возвращает объект Ref.

verticalLayout{
    val activity = ui.owner
    val activityReference: Ref[leech=https://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener]ViewPager.OnPageChangeListener[/leech] добавить поддержку корутин. Сделаем его таким же крутым, как и пример с seekbar'ом.<br/>
Во-первых, создаем отдельный класс и наследуемся от [leech=https://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener]ViewPager.OnPageChangeListener[/leech].</p><br/>
[code]class CoroutineOnPageChangeListener(
    private val coroutineContext: CoroutineContext = Dispatchers.Main
) : ViewPager.OnPageChangeListener {

}

В переменных будем хранить лямбды, которые будут вызываться ViewPager.OnPageChangeListener.


    private var onPageScrollStateChanged: ((Int, CoroutineContext)  Unit)? = null
    private var onPageScrolled: ((Int, Float, Int, CoroutineContext)  Unit)? = null
    private var onPageSelected: ((Int, CoroutineContext)  Unit)? = null

Реализуем инициализацию для одной из этих переменных (остальные делаются аналогично)


    fun onPageScrollStateChanged(action: ((Int, CoroutineContext)  Unit)?) {
        onPageScrollStateChanged = action
    }

И в конце имплементируем функцию с таким же названием.


    override fun onPageScrollStateChanged(state: Int) {
        GlobalScope.launch(coroutineContext) {
            onPageScrollStateChanged?.invoke(state, coroutineContext)
        }
    }

Осталось добавить extension функцию, чтобы это все работало


fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.()  Unit) {
    val listener = CoroutineOnPageChangerListener()
    listener.init()
    addOnPageChangeListener(listener)
}

И вставляем всё это дело под ViewPager


viewPager {
    onPageChangeListenerCoroutines {
        onPageScrolled { position, offset, pixels, coroutineContext 
            // делаем что-нибудь полезное в корутине.
        }
    }
}



Так же в библиотеке Anko Layouts есть множество полезных методов, таких как переводы в различные метрики.



Источник: Хабр / Интересные публикации

Теги: android kotlin

Категория: Android

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *