
Vor ungefähr einem Jahr habe ich angefangen, Kotlin in meinen Android-Projekten zu verwenden. Ich wollte etwas Neues ausprobieren, das interessant zu lernen wäre. Dann bin ich auf Anko gestoßen . Zu diesem Zeitpunkt war das Schreiben der Benutzeroberfläche in XML ziemlich langweilig. Ich habe es immer gemocht, die Benutzeroberfläche mit meinen Händen zu schreiben, ohne auf WYSIWYG und das in Android Studio verwendete XML-Markup zurückzugreifen. Der einzige Nachteil ist, dass Sie die Anwendung neu starten müssen, um nach Änderungen zu suchen. Sie können ein Plugin verwenden , das zeigt, wie die Benutzeroberfläche aussehen wird, ohne die Anwendung zu starten, aber es kam mir ziemlich seltsam vor. Er hat auch die coole Fähigkeit, XML in Anko Layouts DSL zu konvertieren.
Der größte Nachteil der Bibliothek ist der fast vollständige Mangel an Dokumentation. Um herauszufinden, wie man es richtig benutzt, musste ich oft in die Quelle schauen. In diesem Artikel wird die Anwendungsentwicklung mit Anko Layouts und Anko Coroutines ausführlich erläutert.
Die Anko-Bibliothek selbst ist in 4 unabhängige Teile unterteilt:
- Anko Layouts - Erstellen einer Benutzeroberfläche.
- Anko Commons - Nützliche Tools und Funktionen.
- Anko SQLite - Arbeit mit der SQLite-Datenbank.
- Anko Coroutines sind nützliche Werkzeuge für die Arbeit mit Coroutines.
Um dem Projekt eine Bibliothek hinzuzufügen, fügen Sie je nach Projekt nur eine Zeile hinzu:
implementation "org.jetbrains.anko:anko:$anko_version"
Dabei ist anko_version
die aktuelle Version der Bibliothek, die auf Projektebene in der Datei build.gradle registriert ist:
ext.anko_version='0.10.8'
Anko Layouts
Mit Anko Layouts können Sie UI-Android-Anwendungen effizienter entwickeln als mit Java.
Der Hauptakteur auf dem Feld ist die AnkoComponent<T>
-Schnittstelle mit einer einzelnen createView-Methode, die AnkoContext<T>
akzeptiert und eine Ansicht zurückgibt. Bei dieser Methode wird die gesamte Benutzeroberfläche erstellt. Die AnkoContext<T>
-Schnittstelle ist ein Wrapper über den ViewManager . Mehr dazu später.
Nachdem Sie ein wenig über die Funktionsweise von AnkoComponent<T>
, versuchen wir, eine einfache Benutzeroberfläche in der Klasse unserer Aktivität zu erstellen. Es sollte klargestellt werden, dass eine solche „direkte“ Schreibweise der Benutzeroberfläche nur in Activity möglich ist, da für sie eine separate Erweiterungsfunktion ankoView geschrieben wird, in der die addView- Methode aufgerufen wird , und AnkoContextImpl<T>
in der Methode selbst mit dem Parameter setContentView = true
wird.
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 } } }
Bei mehr als einer Textansicht wird die onCreate-Methode offensichtlich schnell zu einem Speicherauszug. Versuchen wir, die Aktivitätsklasse von der Benutzeroberfläche zu trennen. Erstellen Sie dazu eine weitere Klasse, in der sie beschrieben wird.
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 } } } }
Um unsere Benutzeroberfläche an unsere Aktivität zu übergeben, können Sie sie jetzt verwenden
AppActivityUi().setContentView(this)
Ok, aber was ist, wenn wir eine Benutzeroberfläche für das Fragment erstellen möchten? Zu diesem Zweck können Sie die createView-Methode direkt verwenden, indem Sie sie über die onCreateView-Fragmentmethode aufrufen. Es sieht so aus:
class AppFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return AppFragmentUi().createView(AnkoContext.create(requireContext(), this)) } }
Wie bereits erwähnt - AnkoContext<T>
ist ein Wrapper über den ViewManager. Das Begleitobjekt verfügt über drei Hauptmethoden, die AnkoContext<T>
. Wir werden sie genauer analysieren.
erstellen
fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T>
und sein Zwillingsbruder
fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context>
return AnkoContextImpl<T>
.
Methoden werden in allen Standardfällen verwendet, wie beispielsweise in den vorherigen Beispielen mit Aktivität und Fragment. Am interessantesten sind hier die Optionen owner und setContentView. Mit der ersten Option können Sie ein bestimmtes Fragment, eine bestimmte Aktivität oder etwas anderes an die createView-Instanzmethode übergeben.
MyComponent().createView(AnkoContext.create(context, myVew)) class MyComponent: AnkoComponent<View> { override fun createView(ui: AnkoContext<View>): View = with(ui) { val myView: View= ui.owner
Der zweite Parameter - setContentView - versucht automatisch, die resultierende Ansicht hinzuzufügen, wenn Context eine Instanz einer Aktivität oder eines ContextWrappers ist . Wenn er keinen Erfolg hat, löst er eine IllegalStateException aus.
createDelegate
Diese Methode kann sehr nützlich sein, aber die offizielle Dokumentation zum Gihab sagt nichts darüber aus. Ich bin in der Quelle darauf gestoßen, als ich das Problem des Aufblähens von UI-Klassen gelöst habe:
fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner)
Hiermit können Sie dem Eigentümer das Ergebnis der createView-Komponente hinzufügen.
Betrachten Sie seine Verwendung als Beispiel. Angenommen, wir haben eine große Klasse, die einen der Anwendungsbildschirme beschreibt - AppFragmentUi.
verticalLayout { relativeLayout { id = R.id.toolbar
Logischerweise kann es in zwei Teile unterteilt werden - die Symbolleiste und den Inhalt, AppFragmentUiToolbar bzw. AppFragmentUiContent. Dann wird unsere Hauptklasse AppFragmentUi viel einfacher:
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
Beachten Sie, dass nicht ui, sondern ui.owner als Objekt an die with
Funktion übergeben wird.
Wir haben also den folgenden Algorithmus:
- Die Komponenteninstanz wird erstellt.
- Die Methode createView erstellt die hinzuzufügende Ansicht.
- Die resultierende Ansicht wird dem Eigentümer hinzugefügt.
Eine nähere Annäherung: this.addView(AppFragmentUiToolbar().createView(...))
Wie Sie sehen können, ist die Option mit createDelegate besser lesbar.
createReusable
Es sieht aus wie die Standardansicht AnkoContext.create, aber mit einer kleinen Ergänzung wird die neueste Ansicht als Stammansicht betrachtet:
class MyComponent: AnkoComponent<MyObject> { override fun createView(ui: AnkoContext<MyObject>): View = with(ui) { textView("Some text")
Wenn in der Standardimplementierung die Stammansicht festgelegt ist, löst ein Versuch, die zweite Ansicht parallel festzulegen, eine Ausnahme aus .
Die Methode createReusable gibt die ReusableAnkoContext- Klasse zurück, die von AnkoContextImpl erbt und die Methode alreadyHasView()
überschreibt.
Benutzerdefinierte Ansicht
Glücklicherweise ist Anko Layouts nicht auf diese Funktionalität beschränkt. Wenn wir unsere eigene CustomView zeigen müssen, müssen wir nicht schreiben
verticalLayout { val view = CustomView(context)
Dazu können Sie Ihren eigenen Wrapper hinzufügen, der dasselbe tut.
Die Hauptkomponenten hier sind die Erweiterungsmethode <T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit)
von ViewManager, Context oder Activity.
- factory - eine Funktion, an deren Eingabe der Kontext übergeben und die Ansicht zurückgegeben wird. In der Tat ist es die Fabrik, in der die Erstellung der Ansicht stattfindet.
- Thema ist eine Stilressource, die auf die aktuelle Ansicht angewendet wird.
- init ist eine Funktion, in der die erforderlichen Parameter für die erstellte Ansicht festgelegt werden.
Fügen Sie Ihre Implementierung für unser CustomView hinzu
inline fun ViewManager.customView(theme: Int = 0, init: (CustomView).() -> Unit): CustomView { return ankoView({ CustomView(it) }, theme, init) }
Jetzt wird unsere CustomView ganz einfach erstellt:
customView { id = R.id.customview
Sie können lparams verwenden, um LayoutParams auf die Ansicht anzuwenden.
textView("text") { textSize = 12f }.lparams(width = matchParent, height = wrapContent) { centerInParent() }
Es ist anzumerken, dass dies nicht für alle View gilt - alle lparams-Methoden werden normalerweise in Wrappern deklariert. Beispielsweise ist _RelativeLayout ein Wrapper über RelativeLayout . Und so für alle.
Glücklicherweise wurden mehrere Wrapper für die Android Support Library geschrieben, sodass Sie nur die Abhängigkeiten in die Gradle-Datei aufnehmen können.
// 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"
Die Bibliothek ermöglicht unter anderem eine bequemere Implementierung verschiedener Listener. Ein kleines Beispiel aus dem Repository:
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 })
und jetzt mit Anko
seekBar { onSeekBarChangeListener { onProgressChanged { seekBar, progress, fromUser -> // do something } } }
Einige Hörer unterstützen auch Coroutinen:
verticalLayout{ val anyCoroutineContext = GlobalScope.coroutineContext onClick(anyCoroutineContext) {
Anko Coroutinen
Um speicherleckempfindliche Objekte sicher zu übertragen, wird die asReference
Methode asReference
. Es basiert auf WeakReference und gibt ein Ref- Objekt zurück .
verticalLayout{ val activity = ui.owner val activityReference: Ref<AppActivity> = activity.asReference() onClick(anyCoroutineContext) { ref().doSomething() } }
Angenommen, Sie möchten Corutin im Standard-ViewPager.OnPageChangeListener unterstützen. Machen wir es so cool wie das Beispiel für die Suchleiste.
Erstellen Sie zunächst eine separate Klasse und erben Sie sie von ViewPager.OnPageChangeListener .
class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { }
Wir speichern Lambdas in Variablen, die von ViewPager.OnPageChangeListener aufgerufen werden.
private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null
Wir implementieren die Initialisierung für eine dieser Variablen (der Rest erfolgt auf die gleiche Weise).
fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action }
Und am Ende implementieren wir eine gleichnamige Funktion.
override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } }
Es bleibt eine Erweiterungsfunktion hinzuzufügen, damit es funktioniert
fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) }
Und fügen Sie diese ganze Sache unter ViewPager ein
viewPager { onPageChangeListenerCoroutines { onPageScrolled { position, offset, pixels, coroutineContext ->
Vollständiger Code hier 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) }
Auch in der Anko Layouts-Bibliothek gibt es viele nützliche Methoden, z. B. Übersetzungen in verschiedene Metriken .