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 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.
- 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.
- Überblenden:Übergänge zwischen UI-Elementen, die keine starke Beziehung zueinander haben; ein sequentielles Aus- und Einblenden mit einer Skalierung des eingehenden Elements.
- Ausblenden:wird für UI-Elemente verwendet, die innerhalb der Bildschirmgrenzen ein- oder ausgeblendet werden.
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
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">Was möchten Sie in diesem Codelab lernen?
<ph type="x-smartling-placeholder">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
- Öffnen Sie das Projekt in einem Editor Ihrer Wahl.
- 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.
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.
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.
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
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
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
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
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
Ö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.
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
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
Ö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
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
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
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
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
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
Ä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
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, dieses Codelab hat Ihnen gefallen.
Ich konnte dieses Codelab mit angemessenem Zeit- und Arbeitsaufwand abschließen
<ph type="x-smartling-placeholder">Ich möchte das Bewegungssystem Material auch in Zukunft verwenden.
<ph type="x-smartling-placeholder">Die Flutter-Galerie ansehen
Weitere Demos zur Verwendung von Widgets aus der Material Flutter-Bibliothek sowie zum Flutter-Framework finden Sie in der Flutter-Galerie. |