
Sekitar setahun yang lalu, saya mulai menggunakan Kotlin di proyek Android saya. Saya ingin mencoba sesuatu yang baru yang menarik untuk dipelajari. Lalu aku menemukan Anko . Pada saat itu, menulis UI dalam xml cukup membosankan. Saya selalu suka menulis antarmuka dengan tangan saya, tanpa menggunakan WYSIWYG dan markup xml yang digunakan di Android Studio. Satu-satunya negatif adalah bahwa Anda harus me-restart aplikasi untuk memeriksa perubahan apa pun. Anda dapat menggunakan plugin yang menunjukkan seperti apa tampilan ui tanpa meluncurkan aplikasi, tetapi menurut saya agak aneh. Ia juga memiliki kemampuan keren untuk mengonversi xml ke Anko Layouts DSL.
Kelemahan terbesar dari perpustakaan adalah kurangnya dokumentasi yang lengkap. Untuk mengetahui cara menggunakannya dengan benar, saya sering harus melihat ke sumbernya. Pada artikel ini, pengembangan aplikasi menggunakan Anko Layouts dan Anko Coroutines akan dibahas secara rinci.
Perpustakaan Anko sendiri dibagi menjadi 4 bagian independen:
- Tata Letak Anko - membangun UI.
- Anko Commons - Alat dan fitur yang berguna.
- Anko SQLite - bekerja dengan basis data SQLite.
- Anko Coroutine adalah alat yang berguna untuk bekerja dengan coroutine.
Untuk menambahkan perpustakaan ke proyek, cukup tambahkan satu baris tergantung pada proyek:
implementation "org.jetbrains.anko:anko:$anko_version"
di mana anko_version
adalah versi perpustakaan saat ini, terdaftar dalam file build.gradle di tingkat proyek:
ext.anko_version='0.10.8'
Tata letak Anko
Layout Anko memungkinkan Anda untuk mengembangkan aplikasi Android UI lebih efisien daripada menggunakan Java.
Pemain utama di lapangan adalah AnkoComponent<T>
dengan metode createView tunggal yang menerima AnkoContext<T>
dan mengembalikan Tampilan. Dalam metode ini seluruh UI dibuat. AnkoContext<T>
adalah pembungkus di atas ViewManager . Lebih banyak tentang itu nanti.
Memiliki sedikit pemahaman tentang cara kerja AnkoComponent<T>
, mari mencoba membuat UI sederhana di kelas Aktivitas kami. Perlu diperjelas bahwa ejaan "langsung" dari UI hanya mungkin di Activity, karena fungsi ekstensi terpisah ankoView ditulis untuk itu, di mana metode addView dipanggil , dan AnkoContextImpl<T>
dibuat dalam metode itu sendiri dengan setContentView = true
parameter.
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 } } }
Jelas, untuk sesuatu yang lebih dari satu TextView, metode onCreate akan dengan cepat berubah menjadi dump. Mari kita coba memisahkan kelas Activity dari UI. Untuk melakukan ini, buat kelas lain yang akan dijelaskan.
class AppActivityUi: AnkoComponent<AppActivity> { override fun createView(ui: AnkoContext<AppActivity>): View = with(ui) { verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } } }
Sekarang, untuk meneruskan UI kami ke Aktivitas kami, Anda dapat menggunakan
AppActivityUi().setContentView(this)
Oke, tetapi bagaimana jika kita ingin membuat UI untuk fragmen? Untuk melakukan ini, Anda dapat menggunakan metode createView secara langsung dengan memanggilnya dari metode fragmen onCreateView. Ini terlihat seperti ini:
class AppFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return AppFragmentUi().createView(AnkoContext.create(requireContext(), this)) } }
Seperti yang telah disebutkan - AnkoContext<T>
adalah pembungkus di atas ViewManager. Objek pendampingnya memiliki tiga metode utama yang mengembalikan AnkoContext<T>
. Kami akan menganalisisnya secara lebih rinci.
buat
fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
dan saudara kembarnya
fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
kembalikan AnkoContextImpl<T>
.
Metode digunakan dalam semua kasus standar, seperti, misalnya, dalam contoh sebelumnya dengan Aktivitas dan Fragmen. Yang paling menarik di sini adalah opsi pemilik dan setContentView. Yang pertama memungkinkan Anda untuk mengirimkan Fragmen, Aktivitas, atau sesuatu yang lain ke metode instance createView.
MyComponent().createView(AnkoContext.create(context, myVew)) class MyComponent: AnkoComponent<View> { override fun createView(ui: AnkoContext<View>): View = with(ui) { val myView: View= ui.owner
Parameter kedua - setContentView - akan secara otomatis mencoba menambahkan tampilan yang dihasilkan jika Konteks adalah turunan dari Activity atau ContextWrapper . Jika dia tidak berhasil, dia akan melempar IllegalStateException.
buatDelegasikan
Metode ini bisa sangat berguna, tetapi dokumentasi resmi tentang gihab tidak mengatakan apa - apa tentang itu. Saya menemukannya di sumber ketika saya memecahkan masalah kembung kelas UI:
fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
Ini memungkinkan Anda untuk menambahkan hasil komponen createView ke pemilik.
Pertimbangkan penggunaannya sebagai contoh. Misalkan kita memiliki kelas besar yang menggambarkan salah satu layar aplikasi - AppFragmentUi.
verticalLayout { relativeLayout { id = R.id.toolbar
Secara logis, itu dapat dibagi menjadi dua bagian - toolbar dan konten, AppFragmentUiToolbar dan AppFragmentUiContent, masing-masing. Maka AppFragmentUi kelas utama kami akan menjadi lebih sederhana:
class AppFragmentUi: AnkoComponent<AppFragment> { override fun createView(ui: AnkoContext<AppFragment>) = with(ui) { verticalLayout { AppFragmentUiToolbar().createView(AnkoContext.createDelegate(this)) AppFragmentUiContent().createView(AnkoContext.createDelegate(this)) } } } class AppFragmentUiToolbar : AnkoComponent<_LinearLayout> { override fun createView(ui: AnkoContext<_LinearLayout>): View = with(ui.owner) { relativeLayout { id = R.id.toolbar
Perhatikan bahwa bukan ui, tetapi ui.owner diteruskan ke dengan with
fungsi sebagai objek.
Jadi, kami memiliki algoritma berikut:
- Contoh komponen dibuat.
- Metode createView menciptakan View yang akan ditambahkan.
- Tampilan yang dihasilkan ditambahkan ke pemilik.
Perkiraan yang lebih dekat: this.addView(AppFragmentUiToolbar().createView(...))
Seperti yang Anda lihat, opsi dengan createDelegate lebih mudah dibaca.
buat dapat digunakan kembali
Sepertinya standar AnkoContext.create, tetapi dengan sedikit tambahan - tampilan terbaru dianggap sebagai tampilan root:
class MyComponent: AnkoComponent<MyObject> { override fun createView(ui: AnkoContext<MyObject>): View = with(ui) { textView("Some text")
Dalam implementasi standar, jika tampilan root diatur, upaya untuk mengatur tampilan kedua secara paralel akan menghasilkan pengecualian .
Metode createReusable mengembalikan kelas ReusableAnkoContext , yang diwarisi dari AnkoContextImpl dan mengganti metode yang alreadyHasView()
.
Customview
Untungnya, Tata Letak Anko tidak terbatas pada fungsi ini. Jika kami perlu menunjukkan CustomView kami sendiri, kami tidak perlu menulis
verticalLayout { val view = CustomView(context)
Untuk melakukan ini, Anda dapat menambahkan pembungkus Anda sendiri, yang akan melakukan hal yang sama.
Komponen utama di sini adalah metode ekstensi <T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit)
dari ViewManager, Konteks atau Aktivitas.
- factory - sebuah fungsi, ke input yang konteksnya dilewati dan tampilan dikembalikan. Faktanya, itu adalah pabrik tempat pembuatan View berlangsung.
- tema adalah sumber daya gaya yang akan diterapkan pada tampilan saat ini.
- init adalah fungsi di mana parameter yang diperlukan akan ditetapkan untuk Tampilan yang dibuat.
Tambahkan implementasi Anda untuk CustomView kami
inline fun ViewManager.customView(theme: Int = 0, init: (CustomView).() -> Unit): CustomView { return ankoView({ CustomView(it) }, theme, init) }
Sekarang CustomView kami dibuat sangat sederhana:
customView { id = R.id.customview
Anda dapat menggunakan lparams untuk menerapkan LayoutParams ke View.
textView("text") { textSize = 12f }.lparams(width = matchParent, height = wrapContent) { centerInParent() }
Perlu dicatat bahwa ini tidak berlaku untuk semua View - semua metode lparams biasanya dinyatakan dalam pembungkus. Misalnya, _RelativeLayout adalah pembungkus di atas RelativeLayout . Dan untuk semua orang.
Untungnya, beberapa pembungkus telah ditulis untuk Pustaka Dukungan Android, jadi Anda hanya dapat menyertakan dependensi dalam file 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"
Antara lain, perpustakaan memungkinkan implementasi yang lebih nyaman dari berbagai pendengar. Contoh kecil dari repositori:
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 })
dan sekarang menggunakan Anko
seekBar { onSeekBarChangeListener { onProgressChanged { seekBar, progress, fromUser -> // do something } } }
Juga, beberapa pendengar mendukung coroutine:
verticalLayout{ val anyCoroutineContext = GlobalScope.coroutineContext onClick(anyCoroutineContext) {
Anko coroutine
Untuk mentransfer objek sensitif kebocoran memori dengan aman, metode asReference
. Ini didasarkan pada WeakReference dan mengembalikan objek Ref.
verticalLayout{ val activity = ui.owner val activityReference: Ref<AppActivity> = activity.asReference() onClick(anyCoroutineContext) { ref().doSomething() } }
Misalkan Anda ingin menambahkan dukungan untuk corutin di ViewPager.OnPageChangeListener standar. Mari kita buat sekeren contoh seekbar.
Pertama, buat kelas terpisah dan mewarisi dari ViewPager.OnPageChangeListener .
class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { }
Kami akan menyimpan lambdas dalam variabel yang akan dipanggil oleh 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
Kami menerapkan inisialisasi untuk salah satu variabel ini (sisanya dilakukan dengan cara yang sama)
fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action }
Dan pada akhirnya kami menerapkan fungsi dengan nama yang sama.
override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } }
Tetap menambahkan fungsi ekstensi untuk membuatnya berfungsi
fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
Dan rekatkan semua ini di bawah ViewPager
viewPager { onPageChangeListenerCoroutines { onPageScrolled { position, offset, pixels, coroutineContext ->
Kode lengkap di sini class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : 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 } fun onPageScrolled(action: ((Int, Float, Int, CoroutineContext) -> Unit)?) { onPageScrolled = action } fun onPageSelected(action: ((Int, CoroutineContext) -> Unit)?) { onPageSelected = action } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { GlobalScope.launch(coroutineContext) { onPageScrolled?.invoke(position, positionOffset, positionOffsetPixels, coroutineContext) } } override fun onPageSelected(position: Int) { GlobalScope.launch(coroutineContext) { onPageSelected?.invoke(position, coroutineContext) } } override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } } } fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
Juga di perpustakaan Tata Letak Anko ada banyak metode yang berguna, seperti terjemahan ke berbagai metrik .