Mit Material Motion für Flutter wunderschöne Übergänge erstellen

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.

logo_components_color_2x_web_96dp.png

Material Components (MDC) unterstützen Entwickler bei der Implementierung von Material Design. MDC wurde von einem Team aus Entwicklern und UX-Designern bei Google entwickelt. Es enthält Dutzende schöne und funktionale UI-Komponenten und ist für Android, iOS, das Web und Flutter.material.io/develop verfügbar.

Was ist das Bewegungssystem von Material für Flutter?

Das Material-Bewegungssystem für Flutter besteht aus einer Reihe von Übergangsmustern innerhalb des Animationspakets, die Nutzern helfen können, eine App zu verstehen und sich darin zurechtzufinden. Eine Beschreibung hierzu finden Sie in den Material Design-Richtlinien.

Die vier wichtigsten Materialübergangsmuster sind folgende:

  • Container Transform: Übergänge zwischen UI-Elementen, die einen Container enthalten. schafft eine sichtbare Verbindung zwischen zwei verschiedenen UI-Elementen, indem ein Element nahtlos in ein anderes übergeht.

11807bdf36c66657.gif

  • Gemeinsam genutzte Achse:Übergänge zwischen UI-Elementen, die eine räumliche oder Navigationsbeziehung haben. verwendet eine gemeinsame Transformation auf der x-, y- oder z-Achse, um die Beziehung zwischen Elementen zu verstärken.

71218f390abae07e.gif

  • Überblenden:Übergänge zwischen UI-Elementen, die keine starke Beziehung zueinander haben; ein sequentielles Aus- und Einblenden mit einer Skalierung des eingehenden Elements.

385ba37b8da68969.gif

  • Ausblenden:wird für UI-Elemente verwendet, die innerhalb der Bildschirmgrenzen ein- oder ausgeblendet werden.

cfc40fd6e27753b6.gif

Das Animationspaket bietet Übergangs-Widgets für diese Muster, die auf der Bibliothek für Flutter-Animationen (flutter/animation.dart) und der Bibliothek für Flutter-Material (flutter/material.dart) aufbauen:

In diesem Codelab verwenden Sie die Material-Übergänge, die auf dem Flutter-Framework und der Material-Bibliothek basieren. Das bedeutet, dass Sie es mit Widgets zu tun haben. :)

Inhalt

In diesem Codelab erfahren Sie, wie Sie mithilfe von Dart einige Übergänge in eine Flutter-E-Mail-App namens Reply erstellen und zeigen, wie Sie mit Übergängen aus dem Animationspaket das Erscheinungsbild Ihrer App anpassen können.

Der Startcode für die Antwort-App wird bereitgestellt und du integrierst die folgenden Materialübergänge in die App, die im fertigen Codelab im GIF unten 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
  • Übergang von gemeinsam genutzter Z-Achse vom Suchsymbol zur Seite für die Suchansicht
  • Überblenden mit dem Übergang zwischen den Postfachseiten
  • Übergang Überblenden zwischen Schreiben und Antworten FAB
  • Überblenden: Übergang zwischen ausgeblendetem Postfachtitel
  • Übergang Überblenden zwischen Aktionen in der App-Leiste am unteren Rand

b26fe84fed12d17d.gif

Voraussetzungen

  • Grundkenntnisse in Flutter-Entwicklung und Dart
  • Ein Code-Editor
  • Einen Android/iOS-Emulator oder ein Gerät
  • Beispielcode (siehe nächster Schritt)

Wie würdest du deine Erfahrung mit der Entwicklung von Flutter-Apps bewerten?

<ph type="x-smartling-placeholder"></ph> Neuling Mittel Kompetent
.

Was möchten Sie in diesem Codelab lernen?

<ph type="x-smartling-placeholder"></ph> Ich kenne dieses Thema noch nicht und möchte einen guten Überblick erhalten. Ich weiß etwas über dieses Thema, möchte aber meine Kenntnisse auffrischen. Ich suche nach Beispielcode für mein Projekt. Ich suche nach einer Erklärung zu etwas Bestimmtem.

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, das mit Ihrem Computer verbunden ist und sich im Entwicklermodus befindet.
  • Den 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 Die Entwicklung muss auf der Plattform erfolgen, 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, dass in pubspec.yaml der Abschnitt dependencies Folgendes enthält:

animations: ^2.0.0

Projekt öffnen und App ausführen

  1. Öffnen Sie das Projekt in einem Editor Ihrer Wahl.
  2. 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. Der Posteingang sollte eine Liste mit E-Mails enthalten.

Startseite für Antworten

Optional: Geräteanimationen verlangsamen

Da dieses Codelab schnelle, aber ausgefeilte Übergänge beinhaltet, kann es hilfreich sein, die Animationen des Geräts zu verlangsamen, um während der Implementierung einige feinere Details der Übergänge zu beobachten. Das ist über eine In-App-Einstellung möglich. Tippe auf das Symbol „Einstellungen“, wenn die untere Leiste geöffnet ist. Keine Sorge, diese Methode zur Verlangsamung der Geräteanimationen hat keine Auswirkungen auf Animationen auf dem Gerät außerhalb der Antwort-App.

d23a7bfacffac509.gif

Optional: Dunkler Modus

Wenn dir das leuchtende Design der Antwort „Antworten“ schadet, musst du nichts weiter tun. Die App hat eine Einstellung, mit der du das App-Design in den dunklen Modus ändern kannst. Du kannst auf diese Einstellung zugreifen, indem du auf das Symbol „Einstellungen“ tippst, wenn die untere Leiste geöffnet ist.

87618d8418eee19e.gif

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: zeigt das ausgewählte Postfach an.
  • InboxPage Eine Liste von E-Mails wird angezeigt.
  • MailPreviewCard: zeigt die Vorschau einer E-Mail an.
  • MailViewPage:Eine einzelne, vollständige E-Mail wird angezeigt.
  • 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 Root-Navigator, mit dem die Bildschirme unserer App verwaltet werden, die den gesamten Canvas belegen, z. B. HomePage und SearchPage. Er überwacht den Status unserer App, um zu prüfen, ob die Route zu ReplySearchPath festgelegt ist. Ist dies der Fall, wird der Navigator mit SearchPage oben im Stack neu erstellt. Unsere Bildschirme sind von einem CustomTransitionPage umschlossen, für das keine Übergänge definiert sind. Dies zeigt Ihnen eine Möglichkeit, ohne benutzerdefinierten Übergang zwischen den Bildschirmen zu navigieren.

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();
   },
 ),
);

In unserem onPressed-Parameter greifen wir auf unsere RouterProvider zu und setzen ihre routePath auf ReplySearchPath. 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. Sie steuert die inneren Bildschirme der App, die nur den Textkörper des Canvas belegen, z. B. das 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. Mit EmailStore wird der Zustand des inneren Navigators erfasst.

home.dart

Um ein Beispiel für eine Navigationsroute zu sehen, öffne home.dart im Verzeichnis lib. Suchen Sie die Klasse _ReplyFabState in der Eigenschaft onTap des InkWell-Widgets, die so aussehen sollte:

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 Umstellung zur Seite zum Schreiben von E-Mails wechseln können. 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 nun mit dem Startcode vertraut sind, können wir unsere erste Umstellung implementieren.

5. Container Transform-Umstellung von der E-Mail-Liste auf die 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 sein, d. h., der Bildschirm wird ohne Übergang ersetzt:

Vorher

48b00600f73c7778.gif

Fügen Sie zuerst einen Import für das Animationspaket oben in mail_card_preview.dart hinzu, wie im folgenden Snippet gezeigt:

mail_card_preview.dart

import 'package:animations/animations.dart';

Nachdem Sie nun über einen Import für das Animationspaket verfügen, können wir damit beginnen, Ihrer App ansprechende Übergänge hinzuzufügen. Erstellen Sie zuerst eine StatelessWidget-Klasse, in der unser OpenContainer-Widget gespeichert wird.

Fügen Sie in mail_card_preview.dart das folgende Code-Snippet nach der Klassendefinition von MailPreviewCard ein:

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“ drücken, wird der Bildschirm mit den E-Mail-Details wieder zu einem Listenelement minimiert, während die Liste der E-Mails hochskaliert wird.

Nachher

663e8594319bdee3.gif

6. Container Transform-Umstellung von der UAS 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 zuerst die App noch einmal aus und klicken Sie auf die UAS. Sie sehen dann, dass es beim Starten des Bildschirms zum Verfassen von E-Mails keine Umstellung gibt.

Vorher

4aa2befdc5170c60.gif

Die Konfiguration dieser Umstellung ähnelt der im letzten Schritt, da wir dieselbe Widget-Klasse verwenden: 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-Wert, der aufgerufen wird, wenn die OpenContainer-Route mit einer Brücke beendet wurde oder wieder in den geschlossenen Zustand zurückkehrt. Der Rückgabewert dieser Transaktion wird als Argument an diese Funktion übergeben. Wir verwenden diese Callback, um den App-Anbieter darüber zu informieren, dass wir die ComposePage-Route verlassen haben, damit alle Hörer benachrichtigt werden können.

Ähnlich wie im letzten Schritt entfernen wir das Material-Widget aus unserem Widget, da das OpenContainer-Widget die Farbe des Widgets verarbeitet, das von closedBuilder mit closedColor zurückgegeben wird. Außerdem entfernen wir unseren Navigator.push()-Aufruf im onTap unseres InkWell-Widgets und ersetzen ihn durch den openContainer() Callback, der vom closedBuilder des OpenContainer-Widgets angegeben wurde, da das OpenContainer-Widget jetzt sein eigenes Routing 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,
         ),
       ),
     ),
   );
 },
);

Bereinigen Sie jetzt alten Code. Da unser OpenContainer-Widget jetzt den Anbieter unserer App darüber benachrichtigt, dass wir uns nicht mehr über die onClosed ClosedCallback auf dem ComposePage befinden, 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 UAS-Bildschirm zum Erstellen sollte wie folgt aussehen:

Nachher

5c7ad1b4b40f9f0c.gif

7. Übergang von Suchsymbol zur Suchansichtsseite hinzufügen

In diesem Schritt fügen wir einen Übergang vom Suchsymbol zur Suchansicht im Vollbildmodus hinzu. Da kein persistenter Container an dieser Navigationsänderung beteiligt ist, können wir einen Übergang mit gemeinsam genutzter Z-Achse verwenden, um die räumliche Beziehung zwischen den beiden Bildschirmen zu verstärken und anzuzeigen, dass Sie in der App-Hierarchie eine Ebene nach oben verschieben.

Bevor Sie zusätzlichen Code hinzufügen, versuchen Sie, die App auszuführen und unten rechts auf dem Bildschirm auf das Suchsymbol zu tippen. Daraufhin sollte der Bildschirm für die Suchansicht ohne Übergang angezeigt werden.

Vorher

df7683a8ad7b920e.gif

Öffnen Sie zuerst die Datei router.dart. Fügen Sie nach unserer 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;
       });
 }
}

Jetzt verwenden wir unser neues SharedAxisTransitionPageWrapper, um den gewünschten Übergang zu erreichen. Innerhalb der Klassendefinition ReplyRouterDelegate umschließen wir unseren Suchbildschirm unter der Eigenschaft pages mit SharedAxisTransitionPageWrapper statt 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.

81b3ea098926931.gif

Es sieht jetzt alles super aus! Wenn Sie in der unteren App-Leiste auf das Suchsymbol klicken, wird die Ansicht der Suchseite durch einen gemeinsamen Achsenübergang skaliert. Beachten Sie jedoch, dass die Startseite nicht hochskaliert wird, sondern statisch bleibt, wenn die Suchseite darüber skaliert wird. Außerdem wird die Startseite beim Drücken der Schaltfläche „Zurück“ nicht in die Ansicht skaliert, sondern statisch, wenn die Suchseite aus der Ansicht herausskaliert 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! Versuchen Sie nun, die App erneut auszuführen und auf das Suchsymbol zu tippen. Die Start- und Suchansicht sollten gleichzeitig entlang der Z-Achse tief ausgeblendet und skaliert werden, um einen nahtlosen Effekt zwischen den beiden Bildschirmen zu erzeugen.

Nachher

462d890086a3d18a.gif

8. Fade-Through-Übergang zwischen Postfachseiten 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 Austausch durchzuführen. zwischen E-Mail-Listen wechseln.

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-Mail-Adressen sollte sich ohne Übergang ändern.

Vorher

89033988ce26b92e.gif

Öffnen Sie zuerst die Datei mail_view_router.dart. Fügen Sie nach unserer 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 nun die neue FadeThroughTransitionPageWrapper, um den gewünschten Übergang zu erreichen. Innerhalb unserer Klassendefinition MailViewRouterDelegate verwenden Sie unter der Eigenschaft pages den Postfachbildschirm mit einem CustomTransitionPage statt FadeThroughTransitionPageWrapper:

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),
   ),
 ],
);

Führen Sie die App noch einmal aus. Wenn Sie die untere Navigationsleiste öffnen und die Posteingänge wechseln, sollte die aktuelle Liste der E-Mails ausgeblendet und hochskaliert werden, während die neue Liste ausgeblendet und herunterskaliert wird. Sehr gut!

Nachher

8186940082b630d.gif

9. FAB-Übergang „Einblenden“ zwischen „Schreiben“ und „Antworten“ 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 einen Fade-Through-Effekt, um einen einfachen Austausch durchzuführen. zwischen den Symbolen in der UAS.

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 UAS-Symbol sollte sich ohne Übergang ändern.

Vorher

d8e3afa0447cfc20.gif

Für den Rest des Codelabs arbeiten wir in home.dart. Machen Sie sich also keine Gedanken über das Hinzufügen des Imports für das Animationspaket, da wir das bereits in Schritt 2 für home.dart getan haben.

Die nächsten Übergänge werden sehr ähnlich konfiguriert, da sie alle die wiederverwendbare Klasse _FadeThroughTransitionSwitcher verwenden.

Fügen wir in home.dart das folgende Snippet unter _ReplyFabState ein:

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 es beim Übergang zwischen den Elementen keinen Hintergrund gibt. Außerdem wird ein UniqueKey erstellt und einem der Symbole zugewiesen.

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

c55bacd9a144ec69.gif

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 sichtbar und unsichtbar wird. Da wir keine räumliche oder hierarchische Beziehung betonen möchten, verwenden wir einen Fade-Through-Effekt, um einen einfachen Austausch durchzuführen. 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

59eb57a6c71725c0.gif

Der Rest dieses Codelabs wird schnell erledigt, da wir die meisten Schritte im _FadeThroughTransitionSwitcher bereits im letzten Schritt erledigt haben.

Gehen wir jetzt zu unserem _AnimatedBottomAppBar-Kurs in home.dart und fügen Sie die Umstellung hinzu. 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, wir sind mit diesem Schritt fertig!

Führen Sie die App noch einmal aus. Wenn Sie eine E-Mail öffnen und zur E-Mail-Ansicht weitergeleitet werden, sollte der Titel des Posteingangs in der unteren App-Leiste ausgeblendet und skaliert werden. Super!

Nachher

3f1a3db01a481124.gif

11. Fade-Through-Übergang zwischen Aktionen in der App-Leiste 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 betonen möchten, verwenden wir einen Fade-Through-Effekt, um einen einfachen Austausch durchzuführen. zwischen den Aktionen der unteren App-Leiste, 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, versuchen Sie, die App auszuführen, auf eine E-Mail zu tippen und die E-Mail-Ansicht zu öffnen. Du kannst auch auf das Antwortlogo tippen. Die Aktionen der unteren App-Leiste sollten sich ohne Übergang ändern.

Vorher

5f662eac19fce3ed.gif

Ähnlich wie im letzten Schritt verwenden wir wieder _FadeThroughTransitionSwitcher. Um den gewünschten Übergang zu erreichen, rufen Sie die Klassendefinition _BottomAppBarActionItems 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 Aktionen der alten unteren App-Leiste ausgeblendet und hochskaliert werden, während die neuen Aktionen ein- und ausgeblendet werden. Gut gemacht!

Nachher

cff0fa2afa1c5a7f.gif

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.

d5637de49eb64d8a.gif

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, dieses Codelab hat Ihnen gefallen.

Ich konnte dieses Codelab mit angemessenem Zeit- und Arbeitsaufwand abschließen

<ph type="x-smartling-placeholder"></ph> Stimme vollkommen zu Stimme zu Weder zufrieden noch unzufrieden Stimme nicht zu Stimme überhaupt nicht zu

Ich möchte das Bewegungssystem Material auch in Zukunft verwenden.

<ph type="x-smartling-placeholder"></ph> Stimme vollkommen zu Stimme zu Weder zufrieden noch unzufrieden Stimme nicht zu Stimme überhaupt nicht zu

Weitere Demos zur Verwendung von Widgets aus der Material Flutter-Bibliothek sowie zum Flutter-Framework finden Sie in der Flutter-Galerie.

46ba920f17198998.png

6ae8ae284bf4f9fa.png