1. Wprowadzenie
Material Design to system tworzenia atrakcyjnych i funkcjonalnych produktów cyfrowych. Łącząc styl, branding, interakcje i ruch w spójny zestaw zasad i komponentów, zespoły produktowe mogą w pełni wykorzystać swój potencjał projektowy.
| Komponenty Material (MDC) pomagają deweloperom wdrażać Material Design. MDC zostało stworzone przez zespół inżynierów i projektantów UX w Google. Zawiera dziesiątki atrakcyjnych i funkcjonalnych komponentów interfejsu, które są dostępne na platformach Android, iOS, internetowej i Flutter.material.io/develop |
Czym jest system ruchu Material dla Fluttera?
System ruchu Material dla Fluttera to zestaw wzorców przejść w pakiecie animacji, które mogą ułatwić użytkownikom zrozumienie aplikacji i poruszanie się po niej, zgodnie z opisem w wytycznych Material Design.
Oto 4 główne wzorce przejść w Material:
- Przekształcenie kontenera:przejścia między elementami interfejsu, które zawierają kontener; tworzy widoczne połączenie między dwoma różnymi elementami interfejsu, płynnie przekształcając jeden element w drugi.

- Wspólna oś:przejścia między elementami interfejsu, między którymi istnieje związek przestrzenny lub nawigacyjny; używa wspólnej transformacji na osi x, y lub z do wzmocnienia związku między elementami.

- Przenikanie:przejścia między elementami interfejsu, między którymi nie ma wyraźnego związku. Wykorzystuje sekwencyjne zanikanie i pojawianie się z skalowaniem elementu przychodzącego.

- Zanikanie:przeznaczone dla elementów interfejsu, które pojawiają się lub znikają na ekranie.

Pakiet animacji oferuje widżety przejść dla tych wzorców, które są oparte na bibliotece animacji Fluttera (flutter/animation.dart) i bibliotece materiałów Fluttera (flutter/material.dart):
W tym ćwiczeniu w Codelabs będziesz używać przejść Material opartych na platformie Flutter i bibliotece Material, co oznacza, że będziesz pracować z widżetami. :)
Co utworzysz
Ten Codelabs przeprowadzi Cię przez proces tworzenia przejść w przykładowej aplikacji do poczty e-mail na platformę Flutter o nazwie Reply. Użyjemy języka Dart, aby pokazać, jak za pomocą przejść z pakietu animacji dostosować wygląd i działanie aplikacji.
Otrzymasz kod startowy aplikacji Reply i wprowadzisz do niej te przejścia Material, które możesz zobaczyć na GIF-ie ukończonego ćwiczenia Codelabs poniżej:
- Przejście Przekształcenie kontenera z listy e-maili na stronę szczegółów e-maila
- Przejście przekształcenia kontenera z przycisku FAB na stronę tworzenia e-maila
- Przejście wspólnej osi Z od ikony wyszukiwania do strony widoku wyszukiwania
- przejście Zanikanie między stronami skrzynki pocztowej;
- Przejście Zanikanie między przyciskiem FAB tworzenia i odpowiadania
- Przejście Zanikanie między znikającym tytułem skrzynki pocztowej
- Przejście Fade Through między działaniami na dolnym pasku aplikacji

Czego potrzebujesz
- Podstawowa znajomość tworzenia aplikacji we Flutterze i języka Dart
- edytor kodu,
- emulator lub urządzenie z Androidem lub iOS;
- Przykładowy kod (patrz następny krok)
Jak oceniasz swoje doświadczenie w tworzeniu aplikacji w Flutterze?
Czego chcesz się nauczyć podczas tego laboratorium?
2. Konfigurowanie środowiska programistycznego Fluttera
Aby ukończyć ten moduł, potrzebujesz 2 programów: pakietu SDK Flutter i edytora.
Codelab możesz uruchomić na dowolnym z tych urządzeń:
- fizyczne urządzenie z Android lub iOS podłączone do komputera i ustawione w trybie deweloperskim;
- Symulator iOS (wymaga zainstalowania narzędzi Xcode).
- Android Emulator (wymaga konfiguracji w Android Studio).
- przeglądarka (do debugowania wymagana jest Chrome);
- Jako aplikacja komputerowa na Windows, Linux lub macOS. Musisz tworzyć aplikację na platformie, na której zamierzasz ją wdrożyć. Jeśli chcesz opracować aplikację na komputery z systemem Windows, musisz to zrobić na komputerze z tym systemem, aby mieć dostęp do odpowiedniego łańcucha kompilacji. Istnieją wymagania dotyczące poszczególnych systemów operacyjnych, które są szczegółowo opisane na stronie docs.flutter.dev/desktop.
3. Pobierz aplikację startową do ćwiczeń z programowania
Opcja 1. Sklonuj aplikację z samouczka dla początkujących z GitHub
Aby skopiować ten codelab z GitHuba, uruchom te polecenia:
git clone https://github.com/material-components/material-components-flutter-motion-codelab.git cd material-components-flutter-motion-codelab
Opcja 2: pobierz plik ZIP z aplikacją codelab na początek.
Aplikacja startowa znajduje się w katalogu material-components-flutter-motion-codelab-starter.
Sprawdzanie zależności projektu
Projekt korzysta z pakietu animacji. W sekcji pubspec.yaml zwróć uwagę na sekcję dependencies, która zawiera te informacje:
animations: ^2.0.0
Otwórz projekt i uruchom aplikację
- Otwórz projekt w wybranym edytorze.
- Postępuj zgodnie z instrukcjami w sekcji „Uruchamianie aplikacji” w artykule Wprowadzenie: testowanie dotyczącym wybranego edytora.
Gotowe! Kod startowy strony głównej aplikacji Reply powinien działać na Twoim urządzeniu lub emulatorze. Powinna się wyświetlić skrzynka odbiorcza z listą e-maili.

Opcjonalnie: spowolnienie animacji na urządzeniu
Ten codelab obejmuje szybkie, ale dopracowane przejścia, więc podczas implementacji może być przydatne spowolnienie animacji na urządzeniu, aby obserwować niektóre szczegóły przejść. Możesz to zrobić w ustawieniach aplikacji, do których dostęp uzyskasz, klikając ikonę ustawień, gdy otwarte jest dolne menu. Nie martw się, ta metoda spowalniania animacji na urządzeniu nie wpłynie na animacje poza aplikacją Odpowiedz.

Opcjonalnie: tryb ciemny
Jeśli jasny motyw aplikacji Reply Cię oślepia, nie musisz już szukać dalej. W aplikacji jest dostępne ustawienie, które umożliwia zmianę motywu na tryb ciemny, aby był bardziej przyjazny dla oczu. To ustawienie jest dostępne po kliknięciu ikony ustawień, gdy otwarte jest dolne menu.

4. Zapoznaj się z kodem przykładowej aplikacji
Przyjrzyjmy się kodowi. Udostępniliśmy aplikację, która używa pakietu animacji do przechodzenia między różnymi ekranami.
- Strona główna: wyświetla wybraną skrzynkę pocztową.
- InboxPage: wyświetla listę e-maili.
- MailPreviewCard: wyświetla podgląd e-maila.
- MailViewPage: wyświetla pojedynczy, pełny e-mail.
- ComposePage:umożliwia utworzenie nowego e-maila.
- SearchPage: wyświetla widok wyszukiwania.
router.dart
Najpierw, aby dowiedzieć się, jak skonfigurowana jest nawigacja główna aplikacji, otwórz plik router.dart w katalogu 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);
}
}
Jest to nasz główny nawigator, który obsługuje ekrany aplikacji zajmujące całą przestrzeń, takie jak HomePage i SearchPage. Nasłuchuje stanu aplikacji, aby sprawdzić, czy trasa została ustawiona na ReplySearchPath. Jeśli tak, ponownie tworzy nawigatora, umieszczając SearchPage na górze stosu. Zwróć uwagę, że nasze ekrany są zawinięte w CustomTransitionPage bez zdefiniowanych przejść. Pokazuje to jeden ze sposobów przechodzenia między ekranami bez niestandardowego przejścia.
home.dart
Ustawiamy trasę na ReplySearchPath w stanie aplikacji, wykonując te czynności w funkcji _BottomAppBarActionItems w pliku home.dart:
Align(
alignment: AlignmentDirectional.bottomEnd,
child: IconButton(
icon: const Icon(Icons.search),
color: ReplyColors.white50,
onPressed: () {
Provider.of<RouterProvider>(
context,
listen: false,
).routePath = const ReplySearchPath();
},
),
);
W parametrze onPressed uzyskujemy dostęp do parametru RouterProvider i ustawiamy jego wartość routePath na ReplySearchPath. Nasz RouterProvider śledzi stan naszych głównych nawigatorów.
mail_view_router.dart
Teraz sprawdźmy, jak skonfigurowana jest wewnętrzna nawigacja w naszej aplikacji. Otwórz plik mail_view_router.dart w katalogu lib. Zobaczysz nawigator podobny do tego powyżej:
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,
),
)
],
);
},
);
}
...
}
To nasz wewnętrzny nawigator. Obsługuje wewnętrzne ekrany aplikacji, które zajmują tylko główną część obszaru, np. InboxPage. InboxPage wyświetla listę e-maili w zależności od tego, która skrzynka pocztowa jest obecnie aktywna w aplikacji. Nawigator jest przebudowywany z prawidłowym widokiem InboxPage na górze stosu za każdym razem, gdy zmienia się właściwość currentlySelectedInbox stanu naszej aplikacji.
home.dart
Bieżącą skrzynkę pocztową ustawiamy w stanie aplikacji, wykonując te czynności w _HomePageState w home.dart:
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(() {});
}
W naszej funkcji _onDestinationSelected uzyskujemy dostęp do EmailStore i ustawiamy jego currentlySelectedInbox na wybrane miejsce docelowe. Nasz EmailStore śledzi stan wewnętrznych nawigatorów.
home.dart
Na koniec, aby zobaczyć przykład użycia routingu nawigacji, otwórz plik home.dart w katalogu lib. Znajdź klasę _ReplyFabState we właściwości onTap widżetu InkWell, która powinna wyglądać tak:
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();
},
),
);
},
Ten przykład pokazuje, jak przejść do strony tworzenia e-maila bez żadnego niestandardowego przejścia. W tym ćwiczeniu przeanalizujesz kod aplikacji Reply, aby skonfigurować przejścia Material Design, które będą działać w połączeniu z różnymi działaniami nawigacyjnymi w aplikacji.
Znasz już kod startowy, więc teraz zaimplementujmy pierwsze przejście.
5. Dodawanie przejścia Przekształcenie kontenera z listy e-maili na stronę szczegółów e-maila
Na początek dodaj przejście po kliknięciu e-maila. W przypadku tej zmiany nawigacji wzorzec przekształcenia kontenera jest odpowiedni, ponieważ jest przeznaczony dla przejść między elementami interfejsu, które zawierają kontener. Tworzy on widoczne połączenie między dwoma elementami interfejsu.
Zanim dodasz jakikolwiek kod, spróbuj uruchomić aplikację Odpowiedz i kliknąć e-maila. Powinien wykonać proste cięcie, czyli ekran zostanie zastąpiony bez przejścia:
Przed

Zacznij od dodania importu pakietu animacji u góry pliku mail_card_preview.dart, jak pokazano w tym fragmencie kodu:
mail_card_preview.dart
import 'package:animations/animations.dart';
Po zaimportowaniu pakietu animacji możemy zacząć dodawać do aplikacji atrakcyjne przejścia. Zacznijmy od utworzenia klasy StatelessWidget, która będzie zawierać nasz widżet OpenContainer.
W pliku mail_card_preview.dart dodaj ten fragment kodu po definicji klasy MailPreviewCard:
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,
);
},
);
}
}
Teraz użyjmy naszego nowego modułu. W definicji klasy MailPreviewCard umieścimy widżet Material z funkcji build() w nowym widżecie _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(
...
Nasz _OpenContainerWrapper ma widżet InkWell, a właściwości koloru elementu OpenContainer określają kolor kontenera, który zawiera. Dlatego możemy usunąć widżety Material i Inkwell. Wynikowy kod wygląda tak:
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,
),
);
Na tym etapie powinna być już w pełni działająca transformacja kontenera. Kliknięcie e-maila powoduje rozwinięcie elementu listy do ekranu szczegółów i cofnięcie listy e-maili. Naciśnięcie przycisku Wstecz powoduje zwinięcie ekranu szczegółów e-maila z powrotem do elementu listy i powiększenie go na liście e-maili.
Po

6. Dodawanie przejścia przekształcenia kontenera z przycisku typu FAB na stronę tworzenia e-maila
Kontynuujmy transformację kontenera i dodajmy przejście od pływającego przycisku polecenia do ComposePage rozwinięcia przycisku typu FAB do nowego e-maila, który ma napisać użytkownik. Najpierw uruchom ponownie aplikację i kliknij FAB, aby sprawdzić, czy podczas uruchamiania ekranu tworzenia e-maila nie ma przejścia.
Przed

Sposób skonfigurowania tego przejścia będzie bardzo podobny do tego, jak zrobiliśmy to w ostatnim kroku, ponieważ używamy tej samej klasy widżetu, czyli OpenContainer.
W home.dart zaimportujmy package:animations/animations.dart u góry pliku i zmodyfikujmy metodę _ReplyFabState build(). Owińmy zwrócony widżet Material widżetem OpenContainer:
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,
...
Oprócz parametrów używanych do konfigurowania poprzedniego widżetu OpenContainer ustawiany jest teraz też parametr onClosed. onClosed to ClosedCallback, która jest wywoływana, gdy trasa OpenContainer została usunięta lub wróciła do stanu zamkniętego. Wartość zwrotna tej transakcji jest przekazywana do tej funkcji jako argument. Używamy tego Callback, aby powiadomić dostawcę aplikacji, że opuściliśmy trasę ComposePage, dzięki czemu może on powiadomić wszystkich słuchaczy.
Podobnie jak w przypadku ostatniego kroku usuniemy widżet Material z naszego widżetu, ponieważ widżet OpenContainer obsługuje kolor widżetu zwracanego przez closedBuilder za pomocą closedColor. Usuniemy też wywołanie Navigator.push() w widżecie InkWell onTap i zastąpimy je wywołaniem openContainer() Callback podanym przez widżet OpenContainer w wywołaniu closedBuilder, ponieważ widżet OpenContainer obsługuje teraz własne przekierowywanie.
Wynikowy kod wygląda tak:
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,
),
),
),
);
},
);
Teraz czas na usunięcie starego kodu. Nasz widżet OpenContainer powiadamia teraz dostawcę aplikacji, że nie korzystamy już z ComposePage, za pomocą onClosed ClosedCallback, więc możemy usunąć poprzednią implementację w mail_view_router.dart:
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);
To wszystko w tym kroku. Przejście z przycisku FAB do ekranu tworzenia powinno wyglądać tak:
Po

7. Dodawanie przejścia Wspólna oś Z z ikony wyszukiwania na stronę widoku wyszukiwania
W tym kroku dodamy przejście od ikony wyszukiwania do widoku wyszukiwania na pełnym ekranie. Ponieważ w tej zmianie nawigacji nie ma trwałego kontenera, możemy użyć przejścia Wspólna oś Z, aby wzmocnić relację przestrzenną między dwoma ekranami i wskazać przejście o jeden poziom wyżej w hierarchii aplikacji.
Zanim dodasz kolejny kod, spróbuj uruchomić aplikację i kliknąć ikonę wyszukiwania w prawym dolnym rogu ekranu. Powinien pojawić się ekran widoku wyszukiwania bez przejścia.
Przed

Na początek otwórzmy plik router.dart. Po definicji klasy ReplySearchPath dodaj ten fragment kodu:
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;
});
}
}
Teraz użyjmy nowego SharedAxisTransitionPageWrapper, aby uzyskać pożądane przejście. W definicji klasy ReplyRouterDelegate, w sekcji właściwości pages, umieśćmy ekran wyszukiwania w elemencie SharedAxisTransitionPageWrapper zamiast 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(),
),
],
);
Spróbuj ponownie uruchomić aplikację.

Wszystko zaczyna wyglądać świetnie. Gdy klikniesz ikonę wyszukiwania na dolnym pasku aplikacji, przejście po wspólnej osi powiększy stronę wyszukiwania. Zwróć jednak uwagę, że strona główna nie skaluje się w poziomie, tylko pozostaje statyczna, a strona wyszukiwania odskaluje się na jej tle. Dodatkowo po naciśnięciu przycisku Wstecz strona główna nie powiększa się, tylko pozostaje statyczna, a strona wyszukiwania się pomniejsza. To jeszcze nie koniec.
Rozwiążmy oba problemy, umieszczając element HomePage w naszym elemencie SharedAxisTransitionWrapper zamiast w elemencie 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 SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('search'),
screen: SearchPage(),
),
],
);
To wszystko. Teraz spróbuj ponownie uruchomić aplikację i kliknąć ikonę wyszukiwania. Ekrany widoku głównego i wyszukiwania powinny jednocześnie zanikać i skalować się wzdłuż osi Z, tworząc płynne przejście między nimi.
Po

8. Dodawanie przejścia z efektem zanikania między stronami skrzynki pocztowej
W tym kroku dodamy przejście między różnymi skrzynkami pocztowymi. Nie chcemy podkreślać przestrzennej ani hierarchicznej relacji, dlatego użyjemy przejścia z jednej listy e-maili do drugiej.
Zanim dodasz dodatkowy kod, spróbuj uruchomić aplikację, kliknąć logo Odpowiedz na dolnym pasku aplikacji i przełączyć skrzynki pocztowe. Lista e-maili powinna się zmieniać bez przejścia.
Przed

Na początek otwórzmy plik mail_view_router.dart. Po definicji klasy MailViewRouterDelegate dodaj ten fragment kodu:
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;
});
}
}
Podobnie jak w ostatnim kroku użyjemy nowej funkcji FadeThroughTransitionPageWrapper, aby uzyskać pożądane przejście. W definicji klasy MailViewRouterDelegate we właściwości pages zamiast opakowywać ekran skrzynki pocztowej elementem CustomTransitionPage użyj elementu 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),
),
],
);
Uruchom ponownie aplikację. Gdy otworzysz dolny panel nawigacji i zmienisz skrzynki pocztowe, bieżąca lista e-maili powinna zniknąć i skalować w poziomie, a nowa lista powinna się pojawić i odskalować. Super!
Po

9. Dodawanie przejścia Rozmycie między przyciskiem FAB tworzenia i odpowiadania
W tym kroku dodamy przejście między różnymi ikonami FAB. Nie chcemy podkreślać relacji przestrzennej ani hierarchicznej, dlatego użyjemy przejścia z zanikaniem, aby wykonać proste „zamienianie” ikon w przycisku FAB.
Zanim dodasz dodatkowy kod, spróbuj uruchomić aplikację, kliknąć e-maila i otworzyć widok e-maila. Ikona FAB powinna się zmieniać bez przejścia.
Przed

W pozostałej części tego laboratorium będziemy pracować w home.dart, więc nie musisz dodawać importu pakietu animacji, ponieważ zrobiliśmy to już w home.dart w kroku 2.
Sposób konfiguracji kolejnych kilku przejść będzie bardzo podobny, ponieważ wszystkie będą korzystać z klasy wielokrotnego użytku _FadeThroughTransitionSwitcher.
W home.dart dodaj ten fragment kodu pod _ReplyFabState:
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,
);
}
}
Teraz w _ReplyFabState znajdź widżet fabSwitcher. Znak fabSwitcher zwraca inną ikonę w zależności od tego, czy jest wyświetlany w widoku e-maila. Owińmy go znakiem _FadeThroughTransitionSwitcher:
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,
),
);
...
Nadajemy naszemu elementowi _FadeThroughTransitionSwitcher przezroczysty atrybut fillColor, dzięki czemu podczas przejścia między elementami nie ma tła. Tworzymy też UniqueKey i przypisujemy go do jednej z ikon.
Na tym etapie powinna być już w pełni animowana kontekstowa ikona FAB. Po przejściu do widoku e-maila stary pływający przycisk działania zanika i zmniejsza się, a nowy pojawia się i powiększa.
Po

10. Dodaj przejście z efektem zanikania między znikającym tytułem skrzynki pocztowej
W tym kroku dodamy przejście z efektem zanikania, aby tytuł skrzynki pocztowej zanikał między stanem widocznym a niewidocznym w widoku e-maila. Nie chcemy podkreślać relacji przestrzennej ani hierarchicznej, dlatego użyjemy przejścia z efektem zanikania, aby wykonać proste „zamienienie” Text widżetu, który obejmuje tytuł skrzynki pocztowej, na pusty SizedBox.
Zanim dodasz dodatkowy kod, spróbuj uruchomić aplikację, kliknąć e-maila i otworzyć widok e-maila. Tytuł skrzynki pocztowej powinien zniknąć bez przejścia.
Przed

Pozostała część tego samouczka będzie szybka, ponieważ większość pracy wykonaliśmy już w _FadeThroughTransitionSwitcher w ostatnim kroku.
Teraz przejdźmy do klasy _AnimatedBottomAppBar w home.dart, aby dodać przejście. Wykorzystamy _FadeThroughTransitionSwitcher z ostatniego kroku i umieścimy w warunku onMailView, który zwraca pusty element SizedBox lub tytuł skrzynki pocztowej, który pojawia się stopniowo w synchronizacji z dolnym panelem:
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,
),
);
},
),
),
),
To wszystko. Ten krok został ukończony.
Ponownie uruchom aplikację. Gdy otworzysz e-maila i przejdziesz do widoku e-maila, tytuł skrzynki pocztowej na dolnym pasku aplikacji powinien zniknąć i zmniejszyć się. Świetnie!
Po

11. Dodawanie przejścia zanikania między działaniami na dolnym pasku aplikacji
W tym kroku dodamy przejście z efektem zanikania, aby zanikały działania na dolnym pasku aplikacji w zależności od kontekstu aplikacji. Nie chcemy podkreślać relacji przestrzennej ani hierarchicznej, dlatego użyjemy przejścia z zanikaniem, aby wykonać proste „zamienianie” działań na dolnym pasku aplikacji, gdy aplikacja jest na stronie głównej, gdy widoczna jest dolna szuflada i gdy jesteśmy w widoku e-maila.
Zanim dodasz dodatkowy kod, spróbuj uruchomić aplikację, kliknąć e-maila i otworzyć widok e-maila. Możesz też kliknąć logo Odpowiedz. Działania na dolnym pasku aplikacji powinny się zmieniać bez przejścia.
Przed

Podobnie jak w ostatnim kroku użyjemy ponownie _FadeThroughTransitionSwitcher. Aby uzyskać pożądane przejście, otwórz definicję klasy _BottomAppBarActionItems i otocz zwracany widżet funkcji build() elementem _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
...
Spróbujmy! Gdy otworzysz e-maila i przejdziesz do widoku e-maila, stare działania na dolnym pasku aplikacji powinny zniknąć i zmniejszyć się, a nowe działania powinny pojawić się i powiększyć. Brawo!
Po

12. Gratulacje!
Za pomocą pakietu animacji, który zawiera mniej niż 100 wierszy kodu w języku Dart, możesz tworzyć piękne przejścia w istniejącej aplikacji zgodnej z wytycznymi Material Design. Wygląd i działanie aplikacji są spójne na wszystkich urządzeniach.

Dalsze kroki
Więcej informacji o systemie ruchu Material znajdziesz w wytycznych i pełnej dokumentacji dla programistów. Możesz też dodać do aplikacji przejścia Material.
Dziękujemy za wypróbowanie animacji Material. Mamy nadzieję, że to ćwiczenie było dla Ciebie przydatne.
Udało mi się ukończyć to ćwiczenie w rozsądnym czasie i przy rozsądnym nakładzie pracy.
Chcę w przyszłości nadal korzystać z systemu ruchu Material
Odwiedź Flutter Gallery
| Więcej demonstracji korzystania z widżetów udostępnianych przez bibliotekę Material Flutter i platformę Flutter znajdziesz w Flutter Gallery. |



