1. Android Views mit Jetpack Compose
1.1. Einleitung
Das wichtigste Element bei Android-Apps ist natürlich die GUI. Früher hat man die GUI mit Java-Code entwickelt und mit XML-Dateien konfiguriert (imperativ).
Seit ein paar Jahren ist man allerdings dazu übergegangen, diese mit Kotlin-Code unter Verwendung der deklarativen Library Jetpack Compose zu entwickeln (deklarativ).
1.2. Programm
Geht nach Kapitel 1 vor, um ein neues Projekt zu erstellen. Das Projekt sollte "Compose Basics" heißen und wir werden die Vorschau-Funktion verwenden, um die UI-Elemente zu betrachten.
-
In unserem Projekt, mach ein neues Package und nenne es
components
. Hier werden wir alle Komponenten hinzufügen, die wir erstellen. -
Erstelle ein Kotlin File und nenne es
UiComponents.kt
. Innerhalb von UIComponent, erstelle eine composable Funktion, nenne sieEditTextExample()
und rufe dieOutlinedTextField()
Funktion auf. Dabei wirst Du aufgefordert, den erforderlichen Import zu importieren, der androidx.Compose.material.OutlinedTextField ist:@Composable fun EditTextExample() { OutlinedTextField() }
-
Wenn man sich die Signatur von OutlineTextField() genauer ansieht, bemerkt man neben der @Composable-Annotation, dass sie eine Menge Parameter hat, die alle optional sind und mit einem Default-Wert versehen sind. Auf diese Weise können Sie die Felder parametrisieren und an Ihre Bedürfnisse anpassen.
-
Für dieses Beispiel werden wir nicht viel mit DU machen, die wir erstellen. Wir wollen nur zeigen wie man sie grundsätzlich erstellt.
-
Jetzt, damit wir unsere Methode für unsere Zwecke anpassen können, können wir die Parameter, die wir nicht benötigen, weglassen und nur die verwenden, die wir benötigen. Wir werden die folgenden Parameter verwenden:
Text
,Color
undModifier
, um es zu dekorieren. Dem Modifier können wir eine Liste von Modifier-Objekten übergeben, die wir verwenden möchten. So setzen wir zum BeispielfillMaxWidth()
, um die Breite des Textfelds auf die maximale Breite zu setzen. Wenn wirfill()
aufrufen, wird das Textfeld voll gefüllt. Wir setzenpadding(top)
auf16.dp
, was zusätzlichen Platz entlang jeder Kante des Inhalts in dp anwendet. Es hat auch einen Wert, der der Wert ist, der im OutlinedTextField eingegeben werden soll, und ein onValueChange-Lambda, das auf die Eingabeänderung hört.Hausübung: Installieren und auf die neueste Version updaten. -
Wir weisen unserem
OutlinedText
auch Randfarben zu, wenn er fokussiert und nicht fokussiert ist, um verschiedene Zustände darzustellen. Wenn Sie also mit der Eingabe beginnen, ändert sich die Boxfarbe zu Blau, wie im Code angegeben:@Composable fun EditTextExample() { OutlinedTextField( value = "", onValueChange = {}, label = { Text(stringResource(id = R.string.sample)) }, modifier = Modifier .fillMaxWidth() .padding(top = 16.dp), colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = Color.Blue, unfocusedBorderColor = Color.Black, ) ) }
Importiere alle notwendigen Libraries und definiere R.string.sample mit einem beliebigen Wert.
-
Wir haben auch einen anderen Typ von
TextField
, der nicht umrandet ist. Wenn Sie die Eingabeparameter vonOutlinedTextField
vergleichen, werden Sie feststellen, dass sie ziemlich ähnlich sind:@Composable fun NotOutlinedEditTextExample() { TextField( value = "", onValueChange = {}, label = { Text(stringResource(id = R.string.sample)) }, modifier = Modifier .fillMaxWidth() .padding(top = 16.dp), colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = Color.Blue, unfocusedBorderColor = Color.Black, ) ) }
-
Sie können die Anwendung ausführen, indem Sie die Compose-Funktionen innerhalb der
@Preview
-Compose-Funktion hinzufügen. In unserem Beispiel können wirUIElementPreview()
in der gleichen Klasse erstellen, was eine Vorschau-Funktion ist, um unsere Benutzeroberfläche anzuzeigen. In der nächsten Abbildung ist die obere Ansicht einOutlinedTextField
, während die zweite ein normalesTextField
ist.@Preview(showBackground = true) @Composable fun UiElementPreview() { MyApplicationTheme { Column(Modifier.padding(start = 5.dp, end = 5.dp)) { EditTextExample() NotOutlinedEditTextExample() ButtonWithIcon() } } }
Die
Column
sagt Compose, dass die Elemente in der Spalte angeordnet werden sollen. Wenn Sie die Anwendung ausführen, sollte das Ergebnis etwas Ähnliches wie in der nächsten Abbildung sein. -
Jetzt schauen wir uns Beispiele für Schaltflächen an. Wir werden verschiedene Möglichkeiten betrachten, Schaltflächen mit unterschiedlichen Formen zu erstellen. Wenn Sie mit der Maus über der Button()-Compose-Funktion schweben, sehen Sie, welche Eingabe sie akzeptiert, wie in der nächsten Abbildung dargestellt.
In unserem zweiten Beispiel werden wir versuchen, eine Schaltfläche mit einem Symbol darauf zu erstellen. Darüber hinaus werden wir Text hinzufügen, was entscheidend ist, wenn Schaltflächen erstellt werden, da wir den Benutzern angeben müssen, welche Aktion die Schaltfläche ausführt oder was passieren wird, wenn sie darauf geklickt wird.
-
Gehen Sie also voran und erstellen Sie eine Compose-Funktion in der gleichen Kotlin-Datei und nennen Sie sie
ButtonWithIcon()
. Importieren Sie dann dieButton()
-Compose-Funktion. -
Innerhalb dieser Funktion müssen Sie ein
Icon()
mitpainterResource
-Eingabe, einercontentDescription
, einemModifier
undtint
importieren. Wir benötigen auchText()
, der unserer Schaltfläche einen Namen gibt. In unserem Beispiel werden wirtint
nicht verwenden:@Composable fun ButtonWithIcon() { Button(onClick = {}) { Icon( painterResource(id = R.drawable.shopping_cart_24px), contentDescription = stringResource(id = R.string.shop), modifier = Modifier.size(20.dp) ) Text(text = stringResource(id = R.string.buy), Modifier.padding(start = 10.dp)) } }
Damit dieser Code einwandfrei funktioniert müssen sie das Icon zuerst herunterladen (https://fonts.google.com/icons?icon.platform=android, ein Icon suchen, auf den Tab 'Android' klicken und dann auf 'Download' klicken) und dann über den
Resource-Manager
imAndroid-Studio
alsdrawable
importieren (+
undImport drawables
). Dort bekommt es auch automatisch einen Namen und einen Eintrag in den Resourcen, über den wir das Bild ansprechen können. In meinem Beispiel ist diesshopping_cart_24px
, bei Ihnen kann der Name aber durchaus anders sein (kommt auf das gewählte Icon an). Alternativ können Sie sich so ein File hier herunterladen https://github.com/UnterrainerInformatik/lectures/blob/main/android/chapter1/shopping_cart-24px.xml. Danach noch das importierte XML-File anklicken und checken, ob alle Attribute richtig gesetzt sind (in meinem Fall wartint
auf einen Farbwert gesetzt, den ich nicht im Resource-File hatte). Weiters müssen Sie noch die StringsR.string.shop
undR.string.buy
in den Resourcen anlegen (für Quick-Action mit Maus über den Fehler fahren,Create string value resource
). -
Erstellen Sie eine neue Compose-Funktion und nennen Sie sie
CornerCutShapeButton()
. In diesem Beispiel werden wir versuchen, eine Schaltfläche mit abgeschnittenen Ecken zu erstellen:@Composable fun CornerCutShapeButton() { Button(onClick = {}, shape = CutCornerShape(10)) { Text(text = stringResource(id = R.string.cornerButton)) } }
-
Erstellen Sie eine neue Compose-Funktion und nennen Sie sie
RoundCornerShapeButton()
. In diesem Beispiel werden wir versuchen, eine Schaltfläche mit abgerundeten Ecken zu erstellen:@Composable fun RoundCornerShapeButton() { Button(onClick = {}, shape = RoundedCornerShape(10.dp)) { Text(text = stringResource(id = R.string.rounded)) } }
-
Erstellen Sie eine neue Compose-Funktion und nennen Sie sie
ElevatedButtonExample()
. In diesem Beispiel werden wir versuchen, eine Schaltfläche mit Erhebung zu erstellen:@Composable fun ElevatedButtonExample() { Button( onClick = {}, elevation = ButtonDefaults.buttonElevation( defaultElevation = 8.dp, pressedElevation = 10.dp, disabledElevation = 0.dp ) ) { Text(text = stringResource(id = R.string.elevated)) } }
-
Nachdem Sie die Anwendung gestartet haben, sollte ein Bild ähnlich wie in der nächsten Abbildung erscheinen. Die erste Schaltfläche nach dem TextField ist
ButtonWithIcon()
, die zweite istCornerCutShapeButton()
, die dritte istRoundCornerShapeButton()
, und schließlich haben wirElevatedButtonExample()
. -
Schauen wir uns nun ein letztes Beispiel an, da wir im Laufe des Buchs verschiedene Ansichten und Stile verwenden und dabei mehr lernen werden. Lassen Sie uns jetzt eine Bildansicht betrachten; die
Image()
-Compose-Funktion akzeptiert mehrere Eingaben, wie in der nächsten Abbildung dargestellt. -
In unserem Beispiel wird die
Image()
nur einenpainter
haben, der nicht null sein kann, was bedeutet, dass Sie ein Bild für diese Compose-Funktion bereitstellen müssen, eine Inhaltsbeschreibung für die Barrierefreiheit und einen Modifier:@Composable fun ImageViewExample() { Image( painterResource(id = R.drawable.android_logo), contentDescription = stringResource(id = R.string.image), modifier = Modifier.size(200.dp) ) }
Das Bild gehört wieder über den Resource-Manager importiert (Download hier, falls Sie kein eigenes finden: https://github.com/UnterrainerInformatik/lectures/blob/main/android/chapter1/android-logo.png) und die
contentDescription
wieder als String gesetzt. -
Sie können auch versuchen, mit anderen Dingen zu experimentieren, wie zum Beispiel das Hinzufügen von
RadioButton()
- undCheckBox()
-Elementen und deren Anpassung. Wenn Sie Ihre Anwendung ausführen, sollte das Ergebnis etwas Ähnliches wie in der nächsten Abbildung sein.Ihre
@Preview
-Funktion sollte jetzt so aussehen:@Preview(showBackground = true) @Composable fun UiElementPreview() { MyApplicationTheme { Column(Modifier.padding(start = 5.dp, end = 5.dp)) { EditTextExample() NotOutlinedEditTextExample() ButtonWithIcon() CornerCutShapeButton() RoundCornerShapeButton() ElevatedButtonExample() ImageViewExample() } } }
1.3. Funktionsweise
Jede Compose-Funktion ist mit der @Composable
-Annotation versehen. Diese Annotation teilt dem Compose-Compiler mit, dass der bereitgestellte Compiler dazu bestimmt ist, die bereitgestellten Daten in eine Benutzeroberfläche umzuwandeln. Es ist auch wichtig zu beachten, dass der Name jeder Compose-Funktion ein Nomen sein muss und kein Verb oder Adjektiv sein darf. Google stellt diese Richtlinien bereit.
Jede von Ihnen erstellte Compose-Funktion kann Parameter akzeptieren, die es der App-Logik ermöglichen, Ihre Benutzeroberfläche zu beschreiben oder zu ändern.
Wir erwähnen den Compose-Compiler, was bedeutet, dass dieser Compiler irgendein spezielles Programm ist, das den von uns geschriebenen Code analysiert und ihn in etwas übersetzt, das der Computer verstehen kann – oder Maschinensprache.
In Icon()
gibt painterResouce
das Symbol an, das wir der Schaltfläche hinzufügen werden. contentDescription
hilft bei der Barrierefreiheit, und der modifier
wird verwendet, um unser Symbol zu dekorieren.
Wir können die erstellten UI-Elemente vorab anzeigen, indem wir die @Preview-Annotation hinzufügen und showBackground = true
setzen:
@Preview(showBackground = true)
+
@Preview
ist sehr mächtig und wir werden uns die richtige Verwendung in späteren Kapiteln genauer ansehen.
2. Scrollable List mit Jetpack Compose
2.1. Einleitung
Beim Erstellen von Android-Anwendungen sind wir uns alle einig, dass Sie wissen müssen, wie Sie eine RecyclerView
erstellen, um Ihre Daten anzuzeigen. Mit unserer neuen, modernen Art, Android-Anwendungen zu erstellen, können wir LazyColumn
verwenden, was sich ähnlich verhält.
In diesem Rezept werden wir uns Zeilen, Spalten und LazyColumn
ansehen und eine scrollbare Liste mit unseren Dummy-Daten erstellen.
Zusätzlich werden wir dabei auch etwas Kotlin lernen.
Wir werden das Projekt Compose Basics weiterhin verwenden, um eine scrollbare Liste zu erstellen. Daher müssen Sie die vorherige Anleitung abgeschlossen haben, um zu beginnen.
2.2. Programm
-
Lassen Sie uns jetzt unsere erste scrollbare Liste erstellen. Zuerst brauchen wir jedoch Dummy-Daten, die in unserer Liste angezeigt werden sollen. Erstellen Sie daher ein Paket namens
favoritecity
, in dem unser scrollbares Beispiel leben wird. -
Innerhalb des Pakets
favoritecity
erstellen Sie eine neue Datenklasse und nennen Sie sieCity
; dies wird unsere Dummy-Datenquelle sein -data class City()
. -
Modellieren wir unsere City-Datenklasse. Stellen Sie sicher, dass Sie die erforderlichen Imports hinzufügen, sobald Sie die annotierten Werte hinzugefügt haben:
data class City( val id: Int, @StringRes val nameResourceId: Int, @DrawableRes val imageResourceId: Int )
-
Jetzt müssen wir in unseren Dummy-Daten eine Kotlin-Klasse erstellen und diese Klasse
CityDataSource
nennen. In dieser Klasse werden wir eine Funktion namensloadCities()
erstellen, die unsere Liste vonList<City>
zurückgibt, die wir in unserer scrollbaren Liste anzeigen werden.class CityDataSource { fun loadCities(): List<City> { return listOf( City(1, R.string.spain, R.drawable.spain), City(2, R.string.new_york, R.drawable.newyork), City(3, R.string.tokyo, R.drawable.tokyo), City(4, R.string.switzerland, R.drawable.switzerland), City(5, R.string.singapore, R.drawable.singapore), City(6, R.string.paris, R.drawable.paris), ) } }
-
Jetzt haben wir unsere Dummy-Daten, und es ist Zeit, diese in unserer scrollbaren Liste anzuzeigen. Erstellen Sie eine neue Kotlin-Datei in unserem
components
-Paket und nennen Sie sieCityComponents
. InCityComponents
erstellen wir unsere@Preview
-Funktion:@Preview(showBackground = true) @Composable private fun CityCardPreview() { CityApp() }
-
Innerhalb unserer
@Preview
-Funktion haben wir eine weitere Compose-Funktion,CityApp()
; innerhalb dieser Funktion rufen wir unsereCityList
-Compose-Funktion auf, die die Liste als Parameter hat. In dieser Compose-Funktion rufen wir außerdemLazyColumn
auf, und items wirdCityCard(cities)
sein. Weitere Erläuterungen zuLazyColumn
und items finden Sie im Abschnitt "Funktionsweise":@Composable fun CityList(cityList: List<City>) { LazyColumn { items(cityList) { cities -> CityCard(cities) } } }
-
Schließlich erstellen wir unsere
CityCard(city)
-Compose-Funktion:@Composable fun CityCard(city: City) { Card(modifier = Modifier.padding(10.dp), elevation = 4.dp) { Column { Image( painter = painterResource(city.imageResourceId), contentDescription = stringResource(city.nameResourceId), modifier = Modifier .fillMaxWidth() .height(154.dp), contentScale = ContentScale.Crop ) Text( text = LocalContext.current.getString(city.nameResourceId), modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.h5 ) } } }
Wenn Sie die
CityCardPreview
-Komponierfunktion ausführen, sollte eine scrollbare Liste erstellt werden, wie in der nächsten Abbildung zu sehen ist.
2.3. Funktionsweise
In Kotlin gibt es zwei Arten von Listen: unveränderliche (immutable
) und veränderliche (mutable
). Unveränderliche Listen sind Listen mit Elementen, die nicht geändert werden können (es werden Kopien zurückgegeben und nicht die eigentliche Liste), während veränderliche Listen Elemente enthalten, die in der Liste modifiziert werden können. Um eine Liste zu definieren, können wir sagen, dass eine Liste eine generische, geordnete Sammlung von Elementen ist, und diese Elemente können in Form von Ganzzahlen, Zeichenketten, Bildern usw. vorliegen, was größtenteils vom Typ der Daten abhängt, die unsere Listen enthalten sollen.
Zum Beispiel haben wir in unserem Beispiel eine Zeichenkette und ein Bild, die unsere Lieblingsstädte anhand ihres Namens und Bildes identifizieren.
In unserer City
-Datenklasse verwenden wir @StringRes
und @DrawableRes
, um dies direkt aus den res
-Ordnern für Drawable und String einfach abzurufen, und sie repräsentieren auch die ID
für die Bilder und Zeichenketten.
Wir haben CityList
erstellt und sie mit der composable
-Funktion annotiert und die Liste der Stadtobjekte als Parameter in der Funktion deklariert. Eine scrollbare Liste in Jetpack Compose wird mit LazyColumn
erstellt. Der Hauptunterschied zwischen LazyColumn
und Column
besteht darin, dass bei Verwendung von Column
nur kleine Elemente angezeigt werden können, da Compose alle Elemente auf einmal lädt.
Zusätzlich kann eine Spalte nur feste komponierbare Funktionen enthalten, während LazyColumn
, wie der Name schon sagt, den Inhalt bei Bedarf und auf Abruf lädt, was es gut für das Laden weiterer Elemente bei Bedarf macht. Außerdem verfügt LazyColumn
über eine eingebaute Scrollfähigkeit, was die Arbeit für Entwickler erleichtert.
Wir haben auch eine komposable Funktion, CityCard
, erstellt, in der wir das Card()
-Element aus Compose importieren. Eine Karte enthält Inhalte und Aktionen zu einem einzelnen Objekt. In unserem Beispiel hat unsere Karte zum Beispiel ein Bild und den Namen der Stadt. Das Card()
-Element in Compose hat die folgenden Eingaben in seinen Parametern:
@Composable
fun Card(
modifier: Modifier = Modifier,
shape: Shape = MaterialTheme.shapes.medium,
backgroundColor: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(backgroundColor),
border: BorderStroke? = null,
elevation: Dp = 1.dp,
content: @Composable () -> Unit
)
Das bedeutet, dass Sie Ihre Karte leicht nach Ihren Wünschen modellieren können. Unsere Karte hat zum Beispiel Padding
und Elevation
, und der Bereich enthält eine Spalte. In dieser Spalte haben wir ein Bild und Text, der dazu dient, das Bild näher zu beschreiben und mehr Kontext zu bieten.
3. Tab-Layout mit View Pager
3.1. Einleitung
In Android ist es Usus zwischen verschiedenen Inhalten zu 'sliden'. Dies trifft zum Beispiel bei Carousels oder bei Tabs zu.
Hier werden wir einen Pager
bauen, der zwischen einzelnen Seiten horizontal hin- und herwechseln kann. Er hat einen State
, der die Seite bestimmt, die dann angezeigt wird. Die Seite wird mittels einer Farbe symbolisiert.
Sie können im vorherigen Programm weiter machen.
3.2. Programm
-
Fügen sie folgende Zeilen zur
build.gradle(Module:app)
implementation("com.google.accompanist:accompanist-pager:0.32.0") implementation("com.google.accompanist:accompanist-pager-indicators:0.32.0")
Accompanist ist von JetPack und bietet einige nützliche Komponenten, die wir verwenden können.
-
Im gleichen Projekt