1. Einführung
Material Design ist ein System zum Erstellen ausdrucksstarker und ansprechender digitaler Produkte. Indem sie Stil, Branding, Interaktion und Bewegung unter einem einheitlichen Satz von Prinzipien und Komponenten vereinen, können Produktteams ihr größtes Designpotenzial ausschöpfen.
Material Components (MDC) unterstützen Entwickler bei der Implementierung von Material Design. MDC wurde von einem Team von Entwicklern und UX-Designern bei Google entwickelt und bietet Dutzende ansprechender und funktionaler UI-Komponenten. Es ist für Android, iOS, Web und Flutter verfügbar.material.io/develop |
Was ist das Material Motion-System für Flutter?
Das Material Motion System für Flutter ist eine Reihe von Übergangsmustern im Animationspaket, die Nutzern helfen können, eine App zu verstehen und zu bedienen, wie in den Material Design-Richtlinien beschrieben.
Die vier wichtigsten Materialübergangsmuster sind folgende:
- Container Transform:Übergänge zwischen UI-Elementen, die einen Container enthalten; stellt eine sichtbare Verbindung zwischen zwei verschiedenen UI-Elementen her, indem ein Element nahtlos in ein anderes umgewandelt wird.
- Gemeinsame Achse: Übergänge zwischen UI-Elementen, die räumlich oder navigatorisch zueinander in einer Beziehung stehen; verwendet eine gemeinsame Transformation auf der X-, Y- oder Z-Achse, um die Beziehung zwischen den Elementen zu verstärken.
- Durchblenden: Übergänge zwischen UI-Elementen, die nicht stark miteinander verbunden sind. Es wird ein sequenzielles Aus- und Einblenden mit einer Skalierung des eingehenden Elements verwendet.
- Ausblenden:wird für UI-Elemente verwendet, die innerhalb des Bildschirms ein- oder ausgeblendet werden.
Das Animationspaket bietet Übergangs-Widgets für diese Muster, die auf der Flutter-Animationsbibliothek (flutter/animation.dart
) und der Flutter-Materialbibliothek (flutter/material.dart
) basieren:
In diesem Codelab verwenden Sie die Material-Übergänge, die auf dem Flutter-Framework und der Material-Bibliothek basieren. Sie werden also mit Widgets arbeiten. :)
Umfang
In diesem Codelab erfahren Sie, wie Sie mit Dart einige Übergänge in einer Beispiel-Flutter-E-Mail-App namens Reply erstellen. Dabei wird gezeigt, wie Sie mithilfe von Übergängen aus dem Animationspaket das Erscheinungsbild Ihrer App anpassen können.
Der Code für den Einstieg in die Reply App wird bereitgestellt. Sie fügen der App die folgenden Material-Übergänge hinzu, die im GIF unten im abgeschlossenen Codelab zu sehen sind:
- Umstellung von Container Transform von der E-Mail-Liste auf die Seite mit E-Mail-Details
- Umstellung von Container Transform von der UAS-Seite zur Seite zum Schreiben von E-Mails
- Gemeinsam genutzte Z-Achse: Übergang vom Suchsymbol zur Suchansicht
- Überblenden mit dem Übergang zwischen den Postfachseiten
- Übergang Überblenden zwischen Schreiben und Antworten FAB
- Wechsel per Überblendung zwischen verschwindendem Postfachtitel
- Übergang Überblenden zwischen Aktionen in der App-Leiste am unteren Rand
Voraussetzungen
- Grundkenntnisse in Flutter-Entwicklung und Dart
- Code-Editor
- Einen Android/iOS-Emulator oder ein Gerät
- Beispielcode (siehe nächster Schritt)
Wie würden Sie Ihre Erfahrung mit der Entwicklung von Flutter-Apps bewerten?
Was möchten Sie in diesem Codelab lernen?
2. Flutter-Entwicklungsumgebung einrichten
Für dieses Lab benötigen Sie zwei Softwareprogramme: das Flutter SDK und einen Editor.
Sie können das Codelab auf jedem dieser Geräte ausführen:
- Ein physisches Android- oder iOS-Gerät, das mit Ihrem Computer verbunden und auf den Entwicklermodus gesetzt ist.
- Der iOS-Simulator (erfordert die Installation von Xcode-Tools).
- Android-Emulator (Einrichtung in Android Studio erforderlich)
- Ein Browser (zur Fehlerbehebung wird Chrome benötigt)
- Als Windows-, Linux- oder macOS-Desktopanwendung Sie müssen die Entwicklung auf der Plattform durchführen, auf der Sie die Bereitstellung planen. Wenn Sie also eine Windows-Desktop-App entwickeln möchten, müssen Sie die Entwicklung unter Windows ausführen, damit Sie auf die entsprechende Build-Kette zugreifen können. Es gibt betriebssystemspezifische Anforderungen, die unter docs.flutter.dev/desktop ausführlich beschrieben werden.
3. Codelab-Starter-App herunterladen
Option 1: Start-Codelab-App von GitHub klonen
Führen Sie die folgenden Befehle aus, um dieses Codelab aus GitHub zu klonen:
git clone https://github.com/material-components/material-components-flutter-motion-codelab.git cd material-components-flutter-motion-codelab
Option 2: Lade die Start-Codelab-App als ZIP-Datei herunter.
Die Start-App befindet sich im Verzeichnis material-components-flutter-motion-codelab-starter
.
Projektabhängigkeiten prüfen
Das Projekt hängt vom animations-Paket ab. Beachten Sie in pubspec.yaml
, dass der Abschnitt dependencies
Folgendes enthält:
animations: ^2.0.0
Projekt öffnen und App ausführen
- Öffnen Sie das Projekt in einem beliebigen Editor.
- Folgen Sie der Anleitung zum Ausführen der App unter Erste Schritte: Testlauf für den ausgewählten Editor.
Fertig! Der Startcode für die Startseite von Antworten sollte auf deinem Gerät/deinem Emulator ausgeführt werden. Sie sollten den Posteingang mit einer Liste von E-Mails sehen.
Optional: Geräteanimationen verlangsamen
Da dieses Codelab schnelle, aber ausgefeilte Übergänge enthält, kann es hilfreich sein, die Animationen des Geräts zu verlangsamen, um bei der Implementierung einige Details der Übergänge zu beobachten. Das ist über eine In-App-Einstellung möglich, auf die Sie zugreifen können, indem Sie auf das Einstellungssymbol tippen, wenn die untere Leiste geöffnet ist. Keine Sorge, diese Methode zum Verlangsamen der Geräteanimationen hat keine Auswirkungen auf Animationen auf dem Gerät außerhalb der Reply App.
Optional: Dunkler Modus
Wenn das helle Design von Reply Ihre Augen anstrengt, haben wir genau das Richtige für Sie. Die App hat eine Einstellung, mit der du das App-Design in den dunklen Modus ändern kannst. Wenn die untere Schublade geöffnet ist, können Sie auf das Symbol „Einstellungen“ tippen, um diese Einstellung aufzurufen.
4. Mit dem Beispielcode der App vertraut machen
Sehen wir uns den Code an. Wir haben eine App bereitgestellt, die das Animationspaket für den Wechsel zwischen verschiedenen Bildschirmen innerhalb der App verwendet.
- Startseite: das ausgewählte Postfach
- InboxPage Eine Liste von E-Mails wird angezeigt.
- MailPreviewCard: zeigt die Vorschau einer E-Mail an.
- MailViewPage: Zeigt eine einzelne, vollständige E-Mail an.
- ComposePage:Hiermit können Sie eine neue E-Mail verfassen.
- SearchPage:Zeigt eine Suchansicht an.
router.dart
Wenn Sie wissen möchten, wie die Root-Navigation der App eingerichtet ist, öffnen Sie router.dart
im Verzeichnis lib
:
class ReplyRouterDelegate extends RouterDelegate<ReplyRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<ReplyRoutePath> {
ReplyRouterDelegate({required this.replyState})
: navigatorKey = GlobalObjectKey<NavigatorState>(replyState) {
replyState.addListener(() {
notifyListeners();
});
}
@override
final GlobalKey<NavigatorState> navigatorKey;
RouterProvider replyState;
@override
void dispose() {
replyState.removeListener(notifyListeners);
super.dispose();
}
@override
ReplyRoutePath get currentConfiguration => replyState.routePath!;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<RouterProvider>.value(value: replyState),
],
child: Selector<RouterProvider, ReplyRoutePath?>(
selector: (context, routerProvider) => routerProvider.routePath,
builder: (context, routePath, child) {
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
const CustomTransitionPage(
transitionKey: ValueKey('Home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const CustomTransitionPage(
transitionKey: ValueKey('Search'),
screen: SearchPage(),
),
],
);
},
),
);
}
bool _handlePopPage(Route<dynamic> route, dynamic result) {
// _handlePopPage should not be called on the home page because the
// PopNavigatorRouterDelegateMixin will bubble up the pop to the
// SystemNavigator if there is only one route in the navigator.
assert(route.willHandlePopInternally ||
replyState.routePath is ReplySearchPath);
final bool didPop = route.didPop(result);
if (didPop) replyState.routePath = const ReplyHomePath();
return didPop;
}
@override
Future<void> setNewRoutePath(ReplyRoutePath configuration) {
replyState.routePath = configuration;
return SynchronousFuture<void>(null);
}
}
Dies ist unser Stammnavigationselement, das die Bildschirme unserer App verwaltet, die den gesamten Canvas belegen, z. B. HomePage
und SearchPage
. Es überwacht den Status unserer App, um zu prüfen, ob wir die Route zum ReplySearchPath
festgelegt haben. Ist dies der Fall, wird der Navigator mit SearchPage
oben im Stack neu erstellt. Beachten Sie, dass unsere Bildschirme in einem CustomTransitionPage
-Element ohne definierte Übergänge verpackt sind. Hier sehen Sie eine Möglichkeit, zwischen Bildschirmen zu wechseln, ohne einen benutzerdefinierten Übergang zu verwenden.
home.dart
Wir legen unsere Route im Status unserer App auf ReplySearchPath
fest, indem wir in _BottomAppBarActionItems
in home.dart
Folgendes tun:
Align(
alignment: AlignmentDirectional.bottomEnd,
child: IconButton(
icon: const Icon(Icons.search),
color: ReplyColors.white50,
onPressed: () {
Provider.of<RouterProvider>(
context,
listen: false,
).routePath = const ReplySearchPath();
},
),
);
Im Parameter onPressed
greifen wir auf RouterProvider
zu und legen dessen routePath
auf ReplySearchPath
fest. RouterProvider
erfasst den Status des Root-Navigators.
mail_view_router.dart
Sehen wir uns nun an, wie das innere Navigationsmenü der App eingerichtet ist. Öffnen Sie mail_view_router.dart
im Verzeichnis lib
. Es wird ein Navigator angezeigt, der dem obigen ähnelt:
class MailViewRouterDelegate extends RouterDelegate<void>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
MailViewRouterDelegate({required this.drawerController});
final AnimationController drawerController;
@override
Widget build(BuildContext context) {
bool _handlePopPage(Route<dynamic> route, dynamic result) {
return false;
}
return Selector<EmailStore, String>(
selector: (context, emailStore) => emailStore.currentlySelectedInbox,
builder: (context, currentlySelectedInbox, child) {
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Fade through transition between mailbox pages (Motion)
CustomTransitionPage(
transitionKey: ValueKey(currentlySelectedInbox),
screen: InboxPage(
destination: currentlySelectedInbox,
),
)
],
);
},
);
}
...
}
Das ist unser innerer Navigator. Er verarbeitet die inneren Bildschirme unserer App, die nur den Canvas-Textkörper belegen, z. B. die InboxPage
. In der InboxPage
wird eine Liste mit E-Mail-Adressen angezeigt, je nachdem, welchen Status die App aktuell hat. Wenn sich die currentlySelectedInbox
-Eigenschaft des App-Status ändert, wird der Navigator neu erstellt, wobei die richtige InboxPage
oben im Stapel angezeigt wird.
home.dart
Wir setzen unser aktuelles Postfach auf den Status unserer App, indem wir innerhalb von _HomePageState
in home.dart
Folgendes tun:
void _onDestinationSelected(String destination) {
var emailStore = Provider.of<EmailStore>(
context,
listen: false,
);
if (emailStore.onMailView) {
emailStore.currentlySelectedEmailId = -1;
}
if (emailStore.currentlySelectedInbox != destination) {
emailStore.currentlySelectedInbox = destination;
}
setState(() {});
}
In der Funktion _onDestinationSelected
greifen wir auf unsere EmailStore
zu und legen ihre currentlySelectedInbox
auf das ausgewählte Ziel fest. Unser EmailStore
überwacht den Status des internen Navigationssystems.
home.dart
Wenn Sie sich ein Beispiel für ein verwendetes Navigations-Routing ansehen möchten, öffnen Sie home.dart
im Verzeichnis lib
. Suchen Sie in der Property onTap
des InkWell
-Widgets nach der Klasse _ReplyFabState
. Sie sollte so aussehen:
onTap: () {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = true;
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return const ComposePage();
},
),
);
},
Hier sehen Sie, wie Sie ohne benutzerdefinierte Übergänge zur Seite zum Verfassen von E-Mails gelangen. In diesem Codelab befassen wir uns mit dem Code von Reply, um Materialübergänge einzurichten, die zusammen mit den verschiedenen Navigationsaktionen in der App funktionieren.
Nachdem Sie sich mit dem Startcode vertraut gemacht haben, implementieren wir jetzt unsere erste Überleitung.
5. Container-Übergangsmuster von der E-Mail-Liste zur E-Mail-Detailseite hinzufügen
Als Erstes fügen Sie einen Übergang hinzu, wenn Sie auf eine E-Mail klicken. Für diese Navigationsänderung eignet sich das Containertransformationsmuster gut, da es für Übergänge zwischen UI-Elementen vorgesehen ist, die einen Container enthalten. Dieses Muster schafft eine sichtbare Verbindung zwischen zwei UI-Elementen.
Bevor Sie Code hinzufügen, versuchen Sie, die Antwort-App auszuführen und auf eine E-Mail zu klicken. Es sollte ein einfacher Jump-Cut verwendet werden, d. h., der Bildschirm wird ohne Übergang ersetzt:
Vorher
Fügen Sie zuerst oben in mail_card_preview.dart
einen Import für das Animationspaket hinzu, wie im folgenden Snippet gezeigt:
mail_card_preview.dart
import 'package:animations/animations.dart';
Nachdem Sie nun einen Import für das Animationspaket erstellt haben, können wir Ihrer App ansprechende Übergänge hinzufügen. Erstellen Sie als Erstes eine StatelessWidget
-Klasse, in der unser OpenContainer
-Widget gespeichert wird.
Fügen Sie in mail_card_preview.dart
nach der Klassendefinition der MailPreviewCard
das folgende Code-Snippet hinzu:
mail_card_preview.dart
// TODO: Add Container Transform transition from email list to email detail page (Motion)
class _OpenContainerWrapper extends StatelessWidget {
const _OpenContainerWrapper({
required this.id,
required this.email,
required this.closedChild,
});
final int id;
final Email email;
final Widget closedChild;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return OpenContainer(
openBuilder: (context, closedContainer) {
return MailViewPage(id: id, email: email);
},
openColor: theme.cardColor,
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(0)),
),
closedElevation: 0,
closedColor: theme.cardColor,
closedBuilder: (context, openContainer) {
return InkWell(
onTap: () {
Provider.of<EmailStore>(
context,
listen: false,
).currentlySelectedEmailId = id;
openContainer();
},
child: closedChild,
);
},
);
}
}
Jetzt wenden wir unseren neuen Wrapper an. Innerhalb der Klassendefinition MailPreviewCard
umschließen wir das Material
-Widget aus der build()
-Funktion mit der neuen _OpenContainerWrapper
:
mail_card_preview.dart
// TODO: Add Container Transform transition from email list to email detail page (Motion)
return _OpenContainerWrapper(
id: id,
email: email,
closedChild: Material(
...
Die _OpenContainerWrapper
hat ein InkWell
-Widget und die Farbeigenschaften von OpenContainer
definieren die Farbe des eingeschlossenen Containers. Daher können wir die Material- und Inkwell-Widgets entfernen. Der resultierende Code sieht so aus:
mail_card_preview.dart
// TODO: Add Container Transform transition from email list to email detail page (Motion)
return _OpenContainerWrapper(
id: id,
email: email,
closedChild: Dismissible(
key: ObjectKey(email),
dismissThresholds: const {
DismissDirection.startToEnd: 0.8,
DismissDirection.endToStart: 0.4,
},
onDismissed: (direction) {
switch (direction) {
case DismissDirection.endToStart:
if (onStarredInbox) {
onStar();
}
break;
case DismissDirection.startToEnd:
onDelete();
break;
default:
}
},
background: _DismissibleContainer(
icon: 'twotone_delete',
backgroundColor: colorScheme.primary,
iconColor: ReplyColors.blue50,
alignment: Alignment.centerLeft,
padding: const EdgeInsetsDirectional.only(start: 20),
),
confirmDismiss: (direction) async {
if (direction == DismissDirection.endToStart) {
if (onStarredInbox) {
return true;
}
onStar();
return false;
} else {
return true;
}
},
secondaryBackground: _DismissibleContainer(
icon: 'twotone_star',
backgroundColor: currentEmailStarred
? colorScheme.secondary
: theme.scaffoldBackgroundColor,
iconColor: currentEmailStarred
? colorScheme.onSecondary
: colorScheme.onBackground,
alignment: Alignment.centerRight,
padding: const EdgeInsetsDirectional.only(end: 20),
),
child: mailPreview,
),
);
In dieser Phase sollten Sie über eine voll funktionsfähige Containertransformation verfügen. Durch Klicken auf eine E-Mail-Adresse wird das Listenelement zu einem Detailbildschirm erweitert, während die Liste der E-Mail-Adressen zurückgeht. Wenn Sie auf „Zurück“ klicken, wird der Bildschirm mit den E-Mail-Details wieder zu einem Listenelement minimiert und die Liste der E-Mails wird maximiert.
Nachher
6. Container Transform-Umstellung von FAB zum Schreiben von E-Mails hinzufügen
Fahren wir mit der Containertransformation fort und fügen Sie einen Übergang von der unverankerten Aktionsschaltfläche zu ComposePage
hinzu, um die UAS auf eine neue E-Mail zu erweitern, die vom Nutzer geschrieben werden soll. Führen Sie die App zuerst noch einmal aus und klicken Sie auf die FAB. Sie sollten feststellen, dass beim Aufrufen des Bildschirms zum Verfassen von E-Mails kein Übergang erfolgt.
Vorher
Die Konfiguration dieses Übergangs ähnelt der im letzten Schritt, da wir dieselbe Widget-Klasse verwenden, den OpenContainer
.
Importieren Sie in home.dart
die package:animations/animations.dart
am Anfang der Datei und ändern Sie die _ReplyFabState
build()
-Methode. Verpacken wir das zurückgegebene Material
-Widget mit einem OpenContainer
-Widget:
home.dart
// TODO: Add Container Transform from FAB to compose email page (Motion)
return OpenContainer(
openBuilder: (context, closedContainer) {
return const ComposePage();
},
openColor: theme.cardColor,
onClosed: (success) {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = false;
},
closedShape: circleFabBorder,
closedColor: theme.colorScheme.secondary,
closedElevation: 6,
closedBuilder: (context, openContainer) {
return Material(
color: theme.colorScheme.secondary,
...
Zusätzlich zu den Parametern, die zur Konfiguration des vorherigen OpenContainer
-Widgets verwendet wurden, wird jetzt auch onClosed
festgelegt. onClosed
ist ein ClosedCallback
, der aufgerufen wird, wenn die OpenContainer
-Route entfernt wurde oder in den geschlossenen Status zurückgekehrt ist. Der Rückgabewert dieser Transaktion wird als Argument an diese Funktion übergeben. Mit dieser Callback
benachrichtigen wir den Anbieter unserer App, dass wir die ComposePage
-Route verlassen haben, damit er alle Listener benachrichtigen kann.
Ähnlich wie beim letzten Schritt entfernen wir das Material
-Widget aus unserem Widget, da das OpenContainer
-Widget die Farbe des Widgets verarbeitet, das vom closedBuilder
mit closedColor
zurückgegeben wird. Außerdem entfernen wir den Navigator.push()
-Aufruf in der onTap
des InkWell-Widgets und ersetzen ihn durch die openContainer() Callback
, die von der closedBuilder
des OpenContainer
-Widgets angegeben wird, da das OpenContainer
-Widget jetzt seine eigene Weiterleitung verarbeitet.
Der resultierende Code sieht so aus:
home.dart
// TODO: Add Container Transform from FAB to compose email page (Motion)
return OpenContainer(
openBuilder: (context, closedContainer) {
return const ComposePage();
},
openColor: theme.cardColor,
onClosed: (success) {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = false;
},
closedShape: circleFabBorder,
closedColor: theme.colorScheme.secondary,
closedElevation: 6,
closedBuilder: (context, openContainer) {
return Tooltip(
message: tooltip,
child: InkWell(
customBorder: circleFabBorder,
onTap: () {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = true;
openContainer();
},
child: SizedBox(
height: _mobileFabDimension,
width: _mobileFabDimension,
child: Center(
child: fabSwitcher,
),
),
),
);
},
);
Jetzt bereinigen wir etwas alten Code. Da unser OpenContainer
-Widget jetzt über die onClosed ClosedCallback
den Anbieter unserer App darüber informiert, dass wir nicht mehr auf der ComposePage
sind, können wir unsere vorherige Implementierung in mail_view_router.dart
entfernen:
mail_view_router.dart
// TODO: Add Container Transform from FAB to compose email page (Motion)
emailStore.onCompose = false; /// delete this line
return SynchronousFuture<bool>(true);
Das war's für diesen Schritt! Der Übergang vom Floating Action Button zum Bildschirm zum Verfassen einer Nachricht sollte so aussehen:
Nachher
7. Gemeinsamen Z‑Achsen-Übergang vom Suchsymbol zur Suchansicht hinzufügen
In diesem Schritt fügen wir einen Übergang vom Suchsymbol zur Suchansicht im Vollbildmodus hinzu. Da bei dieser Navigationsänderung kein persistenter Container verwendet wird, können wir einen gemeinsamen Z‑Achsenübergang verwenden, um die räumliche Beziehung zwischen den beiden Bildschirmen zu verstärken und anzuzeigen, dass man sich in der Hierarchie der App um eine Ebene nach oben bewegt.
Bevor Sie zusätzlichen Code hinzufügen, starten Sie die App und tippen Sie rechts unten auf dem Bildschirm auf das Suchsymbol. Daraufhin sollte der Bildschirm für die Suchansicht ohne Übergang angezeigt werden.
Vorher
Öffnen Sie zuerst die Datei router.dart
. Fügen Sie nach der ReplySearchPath
-Klassendefinition das folgende Snippet hinzu:
router.dart
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
class SharedAxisTransitionPageWrapper extends Page {
const SharedAxisTransitionPageWrapper(
{required this.screen, required this.transitionKey})
: super(key: transitionKey);
final Widget screen;
final ValueKey transitionKey;
@override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
settings: this,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SharedAxisTransition(
fillColor: Theme.of(context).cardColor,
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) {
return screen;
});
}
}
Nutzen wir jetzt unsere neue SharedAxisTransitionPageWrapper
, um die gewünschte Umstellung zu erreichen. In der ReplyRouterDelegate
-Klassendefinition unter der Eigenschaft pages
umschließen wir den Suchbildschirm mit einem SharedAxisTransitionPageWrapper
anstelle eines CustomTransitionPage
:
router.dart
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
const CustomTransitionPage(
transitionKey: ValueKey('Home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('Search'),
screen: SearchPage(),
),
],
);
Versuchen Sie nun, die App noch einmal auszuführen.
Es sieht jetzt alles super aus! Wenn Sie auf das Suchsymbol in der unteren App-Leiste klicken, wird die Suchseite durch einen gemeinsamen Achsenübergang eingeblendet. Beachten Sie jedoch, dass die Startseite nicht hochskaliert wird, sondern statisch bleibt, wenn die Suchseite darüber skaliert wird. Wenn Sie außerdem auf die Schaltfläche „Zurück“ klicken, wird die Startseite nicht maximiert, sondern bleibt statisch, während die Suchseite maximiert wird. Wir sind also noch nicht fertig.
Wir beheben beide Probleme, indem wir das HomePage
-Element auch in unseren SharedAxisTransitionWrapper
statt in CustomTransitionPage
setzen:
router.dart
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('search'),
screen: SearchPage(),
),
],
);
Fertig! Starten Sie die App jetzt noch einmal und tippen Sie auf das Suchsymbol. Die Startbildschirme und Suchansichtsbildschirme sollten gleichzeitig entlang der Z-Achse in der Tiefe verblassen und skaliert werden, um einen nahtlosen Übergang zwischen den beiden Bildschirmen zu schaffen.
Nachher
8. Zwischen den Seiten des Postfachs den Übergang „Weich ausblenden“ hinzufügen
In diesem Schritt fügen wir einen Übergang zwischen verschiedenen Postfächern hinzu. Da wir keine räumliche oder hierarchische Beziehung betonen möchten, verwenden wir einen Fade-Through-Effekt, um einen einfachen "Wechsel" zwischen E-Mail-Listen durchzuführen.
Bevor Sie zusätzlichen Code hinzufügen, versuchen Sie, die App auszuführen, auf das Antwort-Logo in der unteren App-Leiste zu tippen und das Postfach zu wechseln. Die Liste der E-Mails sollte sich ohne Übergang ändern.
Vorher
Öffnen Sie zuerst die Datei mail_view_router.dart
. Fügen Sie nach der MailViewRouterDelegate
-Klassendefinition das folgende Snippet hinzu:
mail_view_router.dart
// TODO: Add Fade through transition between mailbox pages (Motion)
class FadeThroughTransitionPageWrapper extends Page {
const FadeThroughTransitionPageWrapper({
required this.mailbox,
required this.transitionKey,
}) : super(key: transitionKey);
final Widget mailbox;
final ValueKey transitionKey;
@override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
settings: this,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
fillColor: Theme.of(context).scaffoldBackgroundColor,
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) {
return mailbox;
});
}
}
Ähnlich wie im letzten Schritt verwenden wir unsere neue FadeThroughTransitionPageWrapper
, um die gewünschte Umstellung zu erreichen. In der MailViewRouterDelegate
-Klassendefinition unter der pages
-Eigenschaft verwenden wir anstelle von CustomTransitionPage
das Element FadeThroughTransitionPageWrapper
, um den Posteingangsbildschirm einzufassen:
mail_view_router.dart
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Fade through transition between mailbox pages (Motion)
FadeThroughTransitionPageWrapper(
mailbox: InboxPage(destination: currentlySelectedInbox),
transitionKey: ValueKey(currentlySelectedInbox),
),
],
);
Starten Sie die App neu. Wenn Sie die untere Navigationsleiste öffnen und den Posteingang wechseln, sollte die aktuelle E-Mail-Liste verblassen und verkleinert werden, während die neue Liste verblasst und vergrößert wird. Sehr gut!
Nachher
9. Zwischen den FABs „Schreiben“ und „Antworten“ einen Übergang hinzufügen
In diesem Schritt fügen wir einen Übergang zwischen verschiedenen UAS-Symbolen hinzu. Da wir keine räumliche oder hierarchische Beziehung betonen möchten, verwenden wir eine Überblendung, um einen einfachen „Wechsel“ zwischen den Symbolen im FAB durchzuführen.
Bevor Sie zusätzlichen Code hinzufügen, versuchen Sie, die App auszuführen, auf eine E-Mail zu tippen und die E-Mail-Ansicht zu öffnen. Das Symbol für die Floating Action Button sollte sich ohne Übergang ändern.
Vorher
Für den Rest des Codelabs arbeiten wir in home.dart
. Sie müssen also keinen Import für das Animationspaket hinzufügen, da wir das bereits in Schritt 2 für home.dart
getan haben.
Die Konfiguration der nächsten Übergänge wird sehr ähnlich sein, da sie alle die wiederverwendbare Klasse _FadeThroughTransitionSwitcher
verwenden.
Fügen wir in home.dart
das folgende Snippet unter _ReplyFabState
hinzu:
home.dart
// TODO: Add Fade through transition between compose and reply FAB (Motion)
class _FadeThroughTransitionSwitcher extends StatelessWidget {
const _FadeThroughTransitionSwitcher({
required this.fillColor,
required this.child,
});
final Widget child;
final Color fillColor;
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
fillColor: fillColor,
child: child,
animation: animation,
secondaryAnimation: secondaryAnimation,
);
},
child: child,
);
}
}
Suchen Sie jetzt in der _ReplyFabState
nach dem fabSwitcher
-Widget. fabSwitcher
gibt ein anderes Symbol zurück, je nachdem, ob es sich in der E-Mail-Ansicht befindet oder nicht. Lassen Sie uns dies mit _FadeThroughTransitionSwitcher
abschließen:
home.dart
// TODO: Add Fade through transition between compose and reply FAB (Motion)
static final fabKey = UniqueKey();
static const double _mobileFabDimension = 56;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final circleFabBorder = const CircleBorder();
return Selector<EmailStore, bool>(
selector: (context, emailStore) => emailStore.onMailView,
builder: (context, onMailView, child) {
// TODO: Add Fade through transition between compose and reply FAB (Motion)
final fabSwitcher = _FadeThroughTransitionSwitcher(
fillColor: Colors.transparent,
child: onMailView
? Icon(
Icons.reply_all,
key: fabKey,
color: Colors.black,
)
: const Icon(
Icons.create,
color: Colors.black,
),
);
...
Wir geben unserem _FadeThroughTransitionSwitcher
ein transparentes fillColor
, damit beim Übergang kein Hintergrund zwischen den Elementen zu sehen ist. Außerdem erstellen wir eine UniqueKey
und weisen sie einem der Symbole zu.
Jetzt sollten Sie über eine vollständig animierte kontextbezogene UAS verfügen. Wenn Sie eine E-Mail-Ansicht öffnen, wird das alte FAB-Symbol ausgeblendet und skaliert, während das neue Symbol verblasst und herunterskaliert.
Nachher
10. Übergang zwischen ausgeblendetem Postfachtitel hinzufügen
In diesem Schritt fügen wir einen Übergang hinzu, mit dem der Postfachtitel in der E-Mail-Ansicht zwischen dem sichtbaren und dem unsichtbaren Status hindurchgeblendet wird. Da wir keine räumliche oder hierarchische Beziehung betonen möchten, verwenden wir einen einfachen Wechsel zwischen dem Text
-Widget, das den Postfachtitel enthält, und einem leeren SizedBox
.
Bevor Sie zusätzlichen Code hinzufügen, versuchen Sie, die App auszuführen, auf eine E-Mail zu tippen und die E-Mail-Ansicht zu öffnen. Der Titel des Postfachs sollte ohne Übergang verschwinden.
Vorher
Der Rest dieses Codelabs wird schnell erledigt, da wir die meisten Schritte im _FadeThroughTransitionSwitcher
bereits im letzten Schritt erledigt haben.
Rufen wir jetzt die _AnimatedBottomAppBar
-Klasse in home.dart
auf, um den Übergang hinzuzufügen. Wir verwenden _FadeThroughTransitionSwitcher
aus unserem letzten Schritt wieder und umschließen die onMailView
-Bedingung, die entweder ein leeres SizedBox
zurückgibt oder einen Postfachtitel, der mit der unteren Leiste synchronisiert wird:
home.dart
...
const _ReplyLogo(),
const SizedBox(width: 10),
// TODO: Add Fade through transition between disappearing mailbox title (Motion)
_FadeThroughTransitionSwitcher(
fillColor: Colors.transparent,
child: onMailView
? const SizedBox(width: 48)
: FadeTransition(
opacity: fadeOut,
child: Selector<EmailStore, String>(
selector: (context, emailStore) =>
emailStore.currentlySelectedInbox,
builder: (
context,
currentlySelectedInbox,
child,
) {
return Text(
currentlySelectedInbox,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ReplyColors.white50,
),
);
},
),
),
),
Das war's schon.
Starten Sie die App noch einmal. Wenn Sie eine E-Mail öffnen und zur E-Mail-Ansicht weitergeleitet werden, sollte der Titel des Postfachs in der unteren App-Leiste ausgeblendet und hochskaliert werden. Super!
Nachher
11. Zwischen den Aktionen in der unteren App-Leiste einen Übergang hinzufügen
In diesem Schritt fügen wir einen Übergang hinzu, mit dem die Aktionen der unteren App-Leiste je nach App-Kontext ausgeblendet werden. Da wir keine räumliche oder hierarchische Beziehung hervorheben möchten, verwenden wir ein Ausblenden, um eine einfache „Auswechslung“ zwischen den Aktionen in der unteren App-Leiste vorzunehmen, wenn sich die App auf der Startseite befindet, wenn die untere Leiste sichtbar ist und wenn wir uns in der E-Mail-Ansicht befinden.
Bevor Sie zusätzlichen Code hinzufügen, starten Sie die App, tippen Sie auf eine E-Mail und öffnen Sie die E-Mail-Ansicht. Du kannst auch auf das Antwortlogo tippen. Die Aktionen der unteren App-Leiste sollten sich ohne Übergang ändern.
Vorher
Ähnlich wie im letzten Schritt verwenden wir wieder _FadeThroughTransitionSwitcher
. Um den gewünschten Übergang zu erzielen, rufen Sie die _BottomAppBarActionItems
-Klassendefinition auf und umschließen Sie das Rückgabe-Widget der build()
-Funktion mit einem _FadeThroughTransitionSwitcher
:
home.dart
// TODO: Add Fade through transition between bottom app bar actions (Motion)
return _FadeThroughTransitionSwitcher(
fillColor: Colors.transparent,
child: drawerVisible
? Align(
key: UniqueKey(),
alignment: AlignmentDirectional.bottomEnd,
child: IconButton(
icon: const Icon(Icons.settings),
color: ReplyColors.white50,
onPressed: () async {
drawerController.reverse();
showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(
borderRadius: modalBorder,
),
builder: (context) => const SettingsBottomSheet(),
);
},
),
)
: onMailView
...
Probieren wir es jetzt aus! Wenn Sie eine E-Mail öffnen und zur E-Mail-Ansicht weitergeleitet werden, sollten die alten Aktionen in der unteren App-Leiste verblassen und herausgezoomt werden, während die neuen Aktionen verblassen und herangezoomt werden. Gut gemacht!
Nachher
12. Glückwunsch!
Mit weniger als 100 Zeilen Dart-Code hat das Animationspaket dabei geholfen, schöne Übergänge in einer vorhandenen App zu erstellen, die den Material Design-Richtlinien entspricht und auf allen Geräten einheitlich aussieht und funktioniert.
Weiteres Vorgehen
Weitere Informationen zum Material-Motion-System findest du in den Richtlinien und der vollständigen Entwicklerdokumentation. Versuche auch, deiner App einige Materialübergänge hinzuzufügen.
Vielen Dank, dass du Material Motion ausprobiert hast. Wir hoffen, dass Ihnen dieses Codelab gefallen hat.
Ich konnte dieses Codelab mit angemessenem Zeit- und Arbeitsaufwand abschließen
Ich möchte das Bewegungssystem Material auch in Zukunft verwenden.
Die Flutter-Galerie ansehen
Weitere Demos zur Verwendung von Widgets aus der Material Flutter-Bibliothek und dem Flutter-Framework finden Sie in der Flutter Gallery. |