Flutter Design Series: Glas Morphismus
Der Begriff Morphismus kommt aus der Mathematik und beschreibt (grob gesagt) die strukturerhaltende Überführung von mathematischen Konstrukten. Die Design-Welt hat sich diesen Begriff zu Eigen gemacht, um Designs zu beschreiben, die sich analog zu realen Konzepten verhalten.
Im Fall von Glas Morphismus (eng. Glassmorphism) sollen so die Eigenschaften von Glas, meist Milchglas, nachgestellt werden. Dafür kommen semitransparente Flächen zum Einsatz, die deren Hintergrund unscharf zeichnen und durch Schlagschatten Tiefe simulieren. Die Umsetzung in Flutter möchte ich in diesem Blogpost anhand eines einfachen Beispiels Schritt für Schritt aufzeigen.
Voraussetzung
Damit der Effekt wirkt, benötigt es einen kontrastreichen Hintergrund, am besten mit harten Kanten, die hinter dem Glas-Element verschwinden. Ohne diesen wirkt die Unschärfe nicht, welche für den Milchglaseffekt wichtig ist. Damit eigenen sich vor allem bunte und verspielte Hintergründe und man sollte vor der Umsetzung prüfen, ob ein evtl. vorhandener Styleguide dies zulässt.
Umsetzung
Im Folgenden breche ich die einzelnen Elemente des Effekts herunter und beschreibe deren Umsetzung in Flutter.
Schritt 1
In der einfachsten Form benötigen wir einen Container mit semitransparentem Hintergrund und Schlagschatten. Dies lässt sich einfach über die BoxDecoration am Flutter-Container umsetzen. Hier runden wir die Ecken ab und definieren die Hintergrundfarbe mit Transparenz sowie den Schlagschatten.
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.white.withOpacity(0.3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 20,
spreadRadius: 5,
offset: const Offset(5, 5),
)
],
),
child: …
),
Schritt 2
In der Regel verläuft das Licht nicht gleichmäßig über eine Glasplatte. Um den Effekt von Sonnenlicht, das von links oben auf die UI fällt, zu simulieren, können wir die statische Hintergrundfarbe durch einen linearen Gradienten ersetzen. Dafür definieren wir im Beispiel statt der color einen LinearGradient, der von links oben nach rechts unten verläuft und über die Fläche hinweg transparenter wird. Wer will kann natürlich weitere Farb- oder Deckkraftvariationen einfügen und mittels der stops deren Verteilung steuern (0.0 beschreibt den Anfang, hier links oben, und 1.0 das Ende).
Container(
decoration: BoxDecoration(
borderRadius: ...
boxShadow: ...
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white.withOpacity(0.5),
Colors.white.withOpacity(0.2),
],
stops: const [
0.0,
1.0,
],
),
),
child: …
),
Schritt 3
Der wichtigste Punkt ist, alles was hinter der simulierten Glasfläche liegt, weich zu zeichnen. Hierfür gibt es das BackdropFilter Widget, welches ImageFilter entgegen nimmt wie:
ImageFilter.blur(
sigmaX: 20.0,
sigmaY: 20.0,
),
Je größer die angegebenen Sigma-Werte sind, desto stärker wird der Hintergrund unscharf gezeichnet. Dies betriff zunächst alles, was nicht Kind-Element des BackdropFilters ist. Um es auf den Kasten zu beschränken, umschließen wir das ganze zusätzlich mit dem ClipRRect, was eine abgerundete, rechteckige Beschränkung um den Effekt legt.
Dies clippt aber auch den Schlagschatten, weswegen wir diesen über das ClipRRect -Widget in ein extra Container ziehen müssen:
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
boxShadow: …
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 20.0,
sigmaY: 20.0,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
gradient: …
),
child: …
),
),
),
),
),
Schritt 4
Eine reale Glasplatte hat eine gewisse Dicke und in ihren Rändern fängt sich das Licht. Dies können wir einfach mit einem dünnen weißen Rand simulieren, welchen wir an den inneren Container (an dem auch der Farbverlauf definiert ist) zeichnen.
Container(
decoration: BoxDecoration(
borderRadius: …
gradient: …
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1.0,
),
),
child:…
),
Schritt 5
Damit haben wir bereits einen recht überzeugenden Effekt. Um noch die unebene Oberfläche der Milchglasscheibe zu simulieren, überlagern wir ein Rausch-Bild mit geringer Deckkraft. Ein solches Bild kann einfach mittels Grafikprogrammen wie Photoshop oder über diverse Online-Generatoren (einfach nach noise image generator googeln) erstellt werden. Das Bild wird über DecorationImage eingebunden. Da es leider keine direkte Möglichkeit gibt, die Deckkraft zu steuern, nutzen wir einen colorFilter, welcher über den definierten BlendMode das selbe Ergebnis erzielt.
Container(
decoration: BoxDecoration(
borderRadius: …
gradient: …
border: …
image: DecorationImage(
image: Image.asset("images/noise.jpg").image,
fit: BoxFit.fill,
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.05),
BlendMode.dstATop,
),
),
),
child:…
),
Fazit
Da Flutter die volle Kontrolle über jeden gezeichneten Pixel bietet, ist die Umsetzung, wie beschrieben, einfach und schnell. Hat man einmal den grundlegenden Glas-Container definiert, kann man diesen beliebig wieder verwenden und überlagern.
Flutter kümmert sich dynamisch um die Zeichnung der Unschärfe, sodass auch animierte Hintergründe verwendet werden können, welche den Effekt noch einmal verstärken. An dieser Stelle aber nicht übertreiben, sonst lenkt der Hintergrund von der UI ab, auf der der Fokus liegen sollte.
Flutter Reihe:
- Teil 1: Mobile App-Entwicklung mit Google Flutter
- Teil 2: Flutter und Dart im Einsatz für Apps – ein Erfahrungsbericht
- Teil 3: Animationen mit Flutter - das exensio Logo als Ladeanimation
- Teil 4: Flutter for Web mittels Google Cloud Bucket veröffentlichen
- Teil 5: Wie erstellt man Packages mit Google Flutter
- Teil 6: Barcodes Scannen mit Google Flutter
- Teil 7: Wie eine Google Flutter App sprechen lernt
- Teil 8: Flutter meets Video
- Teil 9: Flutter und schimmernde Skelette
- Teil 10: Flutter Design Series: Glas Morphismus
- Teil 11: App State-Management mit Flutter BLoC
- Teil 12: Mit Quick Actions Schnellzugriffe in Flutter Apps umsetzen
- Teil 13: Listen in Listen: Flutter Performance Optimierung