Flutter – Cross-Plattform à la Google
Seite 4: Widgets in Bewegung!
Auch wenn exotischere Formate wie quadratische Bildschirme bei Smartphones größtenteils der Vergangenheit angehören, gilt es bei der App-Programmierung dennoch, eine Vielzahl an Bildschirmauflösungen und Ansichtsvarianten wie den Landscape Mode zu unterstützen. Um die erforderliche Größenanpassung und die Skalierung des Android-Benutzer-Interfaces umzusetzen, bieten Cross-Plattform-Frameworks wie Qt mittlerweile eine explizite Layout-Engine. Welche Möglichkeiten sich dadurch eröffnen, deutet das aus der Dokumentation von Google entnommene Beispiellayout an, das die Abbildungen 7 und 8 zeigen.
Dass die Steuerelemente in Abb. 7 in Rahmen eingepasst sind, ist auf das Attribut debugPaintSizeEnabled
zurückzuführen: Ist es auf true
gesetzt, liefert der GUI-Stack zusätzliche Debug-Zeicheninformationen.
Bei den in der Abbildung farblich hervorgehoben Diagrammelementen handelt es sich um Instanzen von Container
, einem Widget, das ein anderes Widget als Child
aufnehmen kann und diesem dann Styling-Attribute wie Margins (Ränder) und sonstige Abstände übergibt. Die eigentlichen Layout-Klassen finden sich in der erwähnten Widget-Galerie und sind exemplarisch in Abbildung 9 gezeigt.
Darüber hinaus gibt es noch die Placeholder
-Klasse, die Google in der Dokumentation als Stellvertreter für ein noch nicht angelegtes Widget beschreibt. In der Praxis dürfte sie sich aber auch als Ersatz für die aus Qt bekannten Spacer
-Steuerelemente eigenen.
Das Beispielprogramm arbeitet mit einer Textbox, die Eingaben entgegennehmen soll. Dazu sind folgende Anpassungen in der Datei scene2.dart
notwendig:
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
maxLines: null,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term'
),
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
]
)
),
Anstatt dem child
-Attribut wie bisher eine Widget-Instanz zu übergeben, übernimmt nun eine Instanz von Column
die Anordnung der Elemente am Bildschirm. Beim Anlegen einer mehrteiligen Textbox ist der Wert maxLines
auf Null zu setzen, damit die Textbox eine beliebige Größe annehmen kann. Fehlt das Attribut komplett, bleibt die Textbox auf eine Zeile beschränkt.
Beim Ausführen des Programms in der bisher vorliegenden Version und dem Öffnen der Textbox und mehrfachem Bestätigen der Dialoge bietet sich die in Abbildung 10 gezeigte Darstellung: Im Debug-Modus überprüft der Renderer, ob die gesamte Szene auf den Bildschirm passt. Ist das nicht der Fall, erhalten Entwickler die gezeigte Warnung.
Auf dem Startbildschirm präsentieren sich die Widgets des Formulars noch zentral angeordnet, wie Abbildung 11 zeigt. Ausschlaggebend dafür sind die Einstellungen für Row
und Column
, die beide das Element Main Axis
aufweisen. Diese Hauptachse gibt die Richtung vor, über die sich die child
-Steuerelemente anordnen lassen.
Row
gibt die horizontale, Column
die vertikale Anordnung vor. Von Besonderheit ist, dass die Ausrichtung der Widgets anhand der Textflussrichtung erfolgt: Auf einem beispielsweise auf Arabisch eingestellten BlackBerry würden die Formulare daher anders aussehen. Die bei der mehrsprachigen Lokalisierung von Apps offensichtliche Wichtigkeit dieses Konzepts spiegelt sich auch in Abbildung 12 wider, die die Anordnung grafisch beschreibt. MainAxisSize
bestimmt dabei die Ausbreitung, der standardmäßig eingestellte Wert max
lässt die Achse auf den maximal verfügbaren Raum anwachsen.
Die Anordnung der einzelnen Widgets erfolgt über das MainAxisAlignment
-Attribut, das bisher die sechs in der Tabelle gezeigten Attribute kennt. Im Beispielprogramm soll MainAxisAligment.start
die Steuerelemente am oberen Rand des Bildschirms versammeln:
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
Attribut | Verhalten |
MainAxisAligment.start | Platziert alle Steuerelemente am Beginn der Achse |
MainAxisAligment.end | Platziert alle Steuerelemente am Ende der Achse |
MainAxisAligment.center | Platziert alle Steuerelemente in der Mitte der Achse |
MainAxisAligment.spaceBetween | Nutzt den gesamten Platz und verteilt die Widgets äquidistant |
MainAxisAligment.spaceEvenly | Nutzt den gesamten Platz und verteilt die Widgets äquidistant. Vor dem ersten und nach dem letzten Widget finden sich in diesem Betriebsmodus ebenfalls zwei Spaces |
MainAxisAligment.spaceAround | Entspricht SpaceEvenly, wobei der oberste und der unterste Space nur halb so hoch sind |
Der nächste Schritt stellt sicher, dass das Textfeld den maximal möglichen Platz auf dem Bildschirm einnimmt. Hierzu dient das Expanding
-Steuerelement, das das als child
übergebene Widget maximal ausbreitet, wie in Abbildung 13 zu sehen ist:
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term'
),
),
),
RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
]
)
Entwickler finden detaillierte Informationen zu den Anordnungswerkzeugen in der Flutter-Dokumention zum Layout.
Daten aus der Textbox einlesen
Die Entscheidung für TextField
erfolgte nicht zufällig. Das Steuerelement ist insofern interessant, als die Erfassung und Weiterverarbeitung der Daten Aufwand verursacht. Im ersten Schritt ist dazu das bisher zustandsfreie Zweitformular in ein Widget umzuwandeln, das wie oben besprochen einen Status hält.
Ein StatefulWidget
lässt sich mit createState
erzeugen. Darauf folgt eine zweite Klassendeklaration, die über die schon vorhandenen Methoden des vorhergehenden StatelessWidgets
zu platzieren ist:
class SecondRoute extends StatefulWidget {
@override
_SecondRouteState createState() => _SecondRouteState();
}
class _SecondRouteState extends State<SecondRoute> {
@override
Widget build(BuildContext context) {
Im nächsten Schritt entsteht eine Instanz von TextEditingController
. Als eine Container
-Klasse enthält er die für die Interaktion mit Textboxen verantwortliche Logik:
class _SecondRouteState extends State<SecondRoute> {
final myController = TextEditingController();
Anschließend muss die Textbox Kontakt zum Controller aufnehmen. Dazu ist dem controller
-Attribut des Steuerelements myController
hinzuzufügen:
Expanded(
child: TextField(
controller: myController,
maxLines: null,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term'
),
),
),
Eine einfach zu bedienende Hilfsfunktion erlaubt daraufhin den Zugriff auf die Textinformationen:
Text(myController.text),