Flutter için Material Motion ile Güzel Geçişler Oluşturma

1. Giriş

Materyal Tasarım, cesur ve güzel dijital ürünler oluşturmaya yönelik bir sistemdir. Ürün ekipleri, tarzı, markayı, etkileşimi ve hareketi tutarlı bir ilke ve bileşen grubu altında birleştirerek en yüksek tasarım potansiyellerini gerçekleştirebilir.

logo_components_color_2x_web_96dp.png

Material Bileşenleri (MDC), geliştiricilerin Material Design'u uygulamalarına yardımcı olur. Google'daki bir mühendis ve kullanıcı deneyimi tasarımcısı ekibi tarafından oluşturulan MDC, onlarca güzel ve işlevsel kullanıcı arayüzü bileşenine sahiptir ve Android, iOS, web ve Flutter'da kullanılabilir.material.io/develop

Material'ın Flutter için hareket sistemi nedir?

Flutter için Materyal hareket sistemi, Materyal Tasarım yönergelerinde açıklandığı gibi kullanıcıların bir uygulamayı anlamasına ve uygulamada gezinmesine yardımcı olabilen, animasyon paketinde yer alan bir dizi geçiş kalıbıdır.

Dört temel Materyal geçiş kalıbı şunlardır:

  • Kapsayıcı Dönüşümü: Kapsayıcı içeren kullanıcı arayüzü öğeleri arasında geçiş yapar. Bir öğeyi sorunsuz bir şekilde diğerine dönüştürerek iki farklı kullanıcı arayüzü öğesi arasında görünür bir bağlantı oluşturur.

11807bdf36c66657.gif

  • Paylaşılan Eksen: Üç boyutlu veya gezinme ilişkisi olan kullanıcı arayüzü öğeleri arasındaki geçişler; öğeler arasındaki ilişkiyi güçlendirmek için x, y veya z ekseninde ortak bir dönüşüm kullanır.

71218f390abae07e.gif

  • Şeffaflaşarak geçiş: Birbiriyle güçlü bir ilişkisi olmayan kullanıcı arayüzü öğeleri arasındaki geçişler için kullanılır. Gelen öğenin ölçeğiyle birlikte sıralı bir şekilde karartma ve açma işlemi gerçekleştirir.

385ba37b8da68969.gif

  • Şeffaflaşma: Ekran sınırları içine giren veya dışına çıkan kullanıcı arayüzü öğeleri için kullanılır.

cfc40fd6e27753b6.gif

Animasyon paketi, bu kalıplar için hem Flutter animasyon kitaplığının (flutter/animation.dart) hem de Flutter materyali kitaplığının (flutter/material.dart) üzerine kurulan geçiş widget'ları sunar:

Bu codelab'de Flutter çerçevesi ve Material kitaplığı temel alınarak oluşturulan Material geçişlerini kullanacaksınız. Bu sayede widget'larla ilgileneceksiniz. :)

Oluşturacaklarınız

Bu codelab, uygulamanızın görünümünü ve tarzını özelleştirmek için animasyonlar paketindeki geçişleri nasıl kullanabileceğinizi göstermek amacıyla Dart'ı kullanarak Yanıtla adlı örnek bir Flutter e-posta uygulamasına bazı geçişler eklemeniz konusunda size yol gösterecektir.

Yanıtla uygulaması için başlangıç kodu sağlanacak. Ardından, uygulamaya aşağıdaki Material geçişlerini dahil edeceksiniz. Bunları, tamamlanan codelab'in GIF'inde görebilirsiniz:

  • Kapsayıcı Dönüşümü'nde e-posta listesinden e-posta ayrıntıları sayfasına geçiş
  • Kapsayıcı Dönüşümü'nde FAB'den e-posta oluşturma sayfasına geçiş
  • Arama simgesinden arama görünümü sayfasına paylaşılan z ekseni geçişi
  • Posta kutusu sayfaları arasında Şeffaflaşma geçişi
  • Oluştur ve yanıtla FAB'leri arasında şeffaflaşma geçişi
  • Kaybolan posta kutusu başlığı arasında Şeffaflaşma geçişi
  • Alt uygulama çubuğu işlemleri arasında Kararma geçişi

b26fe84fed12d17d.gif

Gerekenler

  • Flutter geliştirme ve Dart ile ilgili temel bilgiler
  • Kod düzenleyici
  • Bir Android/iOS emülatörü veya cihazı
  • Örnek kod (sonraki adıma bakın)

Flutter uygulamaları geliştirme deneyiminizi nasıl değerlendirirsiniz?

Acemi Orta Uzman

Bu codelab'den ne öğrenmek istersiniz?

Konuya yeni başladım ve konuya genel bir bakış istiyorum. Bu konuyla ilgili bilgim var ancak bilgilerimi tazelemek istiyorum. Projemde kullanabileceğim örnek kod arıyorum. Belirli bir konuyla ilgili açıklama arıyorum.

2. Flutter geliştirme ortamınızı kurma

Bu laboratuvarı tamamlamak için Flutter SDK ve bir düzenleyici yazılımına ihtiyacınız vardır.

Aşağıdaki cihazlardan herhangi birini kullanarak kod laboratuvarını çalıştırabilirsiniz:

  • Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android veya iOS cihaz.
  • iOS simülasyon aracı (Xcode araçlarının yüklenmesi gerekir).
  • Android Emülatör (Android Studio'da kurulum gerektirir).
  • Tarayıcı (Hata ayıklama için Chrome gerekir).
  • Windows, Linux veya macOS masaüstü uygulaması olarak Uygulamayı dağıtmayı planladığınız platformda gerçekleştirmeniz gerekir. Bu nedenle, bir Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'da geliştirme yapmanız gerekir. docs.flutter.dev/desktop adresinde işletim sistemine özgü gereksinimler ayrıntılı olarak açıklanmıştır.

3. codelab başlangıç uygulamasını indirme

1. Seçenek: Başlangıç kod laboratuvarının uygulamasını GitHub'dan kopyalama

Bu codelab'i GitHub'dan kopyalamak için aşağıdaki komutları çalıştırın:

git clone https://github.com/material-components/material-components-flutter-motion-codelab.git
cd material-components-flutter-motion-codelab

2. Seçenek: Başlangıç codelab uygulamasının posta dosyasını indirin

Başlangıç uygulaması material-components-flutter-motion-codelab-starter dizinindedir.

Proje bağımlılıklarını doğrulama

Proje, animasyon paketine bağlıdır. pubspec.yaml dokümanındaki dependencies bölümünde aşağıdakilerin bulunduğuna dikkat edin:

animations: ^2.0.0

Projeyi açın ve uygulamayı çalıştırın

  1. Projeyi istediğiniz düzenleyicide açın.
  2. Seçtiğiniz düzenleyici için Başlarken: Deneme sürümü bölümündeki "Uygulamayı çalıştırma" talimatlarını uygulayın.

Başarıyla gerçekleştirildi. Reply'in ana sayfası için başlatıcı kod, cihazınızda/emülatörünüzde çalışmalıdır. E-posta listesinin yer aldığı Gelen Kutusu gösterilir.

Yanıtla ana sayfası

İsteğe bağlı: Cihaz animasyonlarını yavaşlatma

Bu kod laboratuvarı hızlı ancak şık geçişler içerdiğinden, geçişleri uygularken geçişlerin bazı ayrıntılarını gözlemlemek için cihazın animasyonlarını yavaşlatmanız yararlı olabilir. Bunu, alt çekmece açıkken ayarlar simgesine dokunarak erişebileceğiniz bir uygulama içi ayar kullanarak yapabilirsiniz. Endişelenmeyin, cihaz animasyonlarını yavaşlatmaya yönelik bu yöntem, Yanıtla uygulamasının dışında cihazdaki animasyonları etkilemez.

d23a7bfacffac509.gif

İsteğe bağlı: Koyu Mod

Yanıt özelliğinin parlak teması gözlerinizi acıyorsa başka yere yardım etmeyin. Uygulama içinde bulunan bir ayar sayesinde uygulama temasını koyu moda çevirerek gözlerinize daha uygun hale getirebilirsiniz. Bu ayara, alt çekmece açıkken ayarlar simgesine dokunarak erişebilirsiniz.

87618d8418eee19e.gif

4. Örnek uygulama kodu hakkında bilgi edinin

Koda bakalım. Uygulamadaki farklı ekranlar arasında geçiş yapmak için animasyon paketini kullanan bir uygulama sağladık.

  • HomePage: Seçilen posta kutusunu görüntüler
  • InboxPage: E-postaların listesini gösterir.
  • MailPreviewCard: E-postanın önizlemesini gösterir.
  • MailViewPage: Tek bir e-postayı tam olarak gösterir.
  • ComposePage: Yeni bir e-posta oluşturmanıza olanak tanır.
  • SearchPage: Bir arama görünümünü görüntüler

router.dart

İlk olarak, uygulamanın kök gezinme işlevinin nasıl ayarlandığını anlamak için lib dizininde router.dart dosyasını açın:

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

Bu kök gezinme aracımızdır ve uygulamamızın tuvalin tamamını kaplayan HomePage ve SearchPage gibi ekranlarını yönetir. Araç, uygulamamızın durumunu dinleyerek ReplySearchPath rotasını belirleyip belirlemediğimizi kontrol eder. Varsa gezginimizi yığının en üstündeki SearchPage ile yeniden oluşturur. Ekranlarımızın, geçiş tanımlanmamış bir CustomTransitionPage içine sarıldığını görebilirsiniz. Bu görselde, özel geçiş olmadan ekranlar arasında gezinmenin bir yolu gösterilmektedir.

home.dart

home.dart içindeki _BottomAppBarActionItems içinde aşağıdakileri yaparak rotamızı uygulamamızın durumunda ReplySearchPath olarak belirledik:

Align(
 alignment: AlignmentDirectional.bottomEnd,
 child: IconButton(
   icon: const Icon(Icons.search),
   color: ReplyColors.white50,
   onPressed: () {
     Provider.of<RouterProvider>(
       context,
       listen: false,
     ).routePath = const ReplySearchPath();
   },
 ),
);

onPressed parametremizde RouterProvider'ımıza erişip routePath değerini ReplySearchPath olarak ayarlıyoruz. RouterProvider uygulamamız, kök gezginlerin durumunu izler.

mail_view_router.dart

Şimdi, uygulamamızın iç gezinme menüsünün nasıl ayarlandığına bakalım. lib dizininde mail_view_router.dart uygulamasını açın. Yukarıdakine benzer bir gezgin görürsünüz:

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

Bu, iç navigasyon cihazımızdır. Uygulamamızın, InboxPage gibi yalnızca kanvasın gövdesini kaplayan iç ekranlarını işler. InboxPage, uygulamamızın durumundaki mevcut posta kutusuna bağlı olarak e-postaların listesini gösterir. Uygulamamızın durumunun currentlySelectedInbox özelliğinde bir değişiklik olduğunda, gezinme grubu, yığının üst kısmında doğru InboxPage olacak şekilde yeniden oluşturulur.

home.dart

home.dart içinde _HomePageState içinde aşağıdakileri yaparak mevcut posta kutumuzu uygulamamızın durumuna göre ayarladık:

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

_onDestinationSelected işlevimizde, EmailStore işlevimize erişiyor ve currentlySelectedInbox değerini seçilen hedefe ayarlıyoruz. EmailStore, iç gezginimizin durumunu izler.

home.dart

Son olarak, kullanılan gezinme yönlendirmesinin bir örneğini görmek için lib dizininde home.dart dosyasını açın. InkWell widget'ının onTap özelliğinde _ReplyFabState sınıfını bulun. Bu özellik aşağıdaki gibi görünür:

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

Bu görsel, herhangi bir özel geçiş olmadan e-posta oluşturma sayfasına nasıl gidebileceğinizi gösterir. Bu codelab'de, uygulamadaki çeşitli gezinme işlemleriyle birlikte çalışan Material geçişleri ayarlamak için Reply'in koduna göz atacaksınız.

Artık başlangıç kodunu öğrendiğinize göre ilk geçişimizi uygulayalım.

5. E-posta listesinden e-posta ayrıntıları sayfasına kapsayıcı dönüşümü geçişi ekleyin

Başlamak için bir e-postayı tıklayarak geçiş ekleyeceksiniz. Kapsayıcı dönüşüm deseni, kapsayıcı içeren kullanıcı arayüzü öğeleri arasındaki geçişler için tasarlandığından bu gezinme değişikliği için çok uygundur. Bu desen, iki kullanıcı arayüzü öğesi arasında görünür bir bağlantı oluşturur.

Herhangi bir kod eklemeden önce, Yanıtla uygulamasını çalıştırmayı ve bir e-postayı tıklamayı deneyin. Sıçramalı kesme sayesinde ekran, geçiş olmadan değiştirilir:

Önce

48b00600f73c7778.gif

Aşağıdaki snippet'te gösterildiği gibi, mail_card_preview.dart dosyasının en üstüne animasyon paketi için bir içe aktarma işlemi ekleyerek başlayın:

mail_card_preview.dart

import 'package:animations/animations.dart';

Animasyon paketini içe aktardığınıza göre uygulamanıza güzel geçişler eklemeye başlayabiliriz. OpenContainer widget'ımızı barındıracak bir StatelessWidget sınıfı oluşturarak başlayalım.

mail_card_preview.dart dosyasında, MailPreviewCard sınıf tanımından sonra aşağıdaki kod snippet'ini ekleyin:

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

Şimdi yeni sarmalayıcımızı kullanalım. MailPreviewCard sınıf tanımının içinde, build() işlevimizdeki Material widget'ını yeni _OpenContainerWrapper ile sarmalayacağız:

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(
...

_OpenContainerWrapper öğesimizin bir InkWell widget'ı vardır ve OpenContainer öğesinin renk özellikleri, içinde bulunduğu kapsayıcının rengini tanımlar. Bu nedenle, Material ve Mürekkep Damlası widget'larını kaldırabiliriz. Elde edilen kod aşağıdaki gibi görünür:

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

Bu aşamada, tamamen çalışan bir kapsayıcı dönüştürme işleminiz olmalıdır. Bir e-postayı tıkladığınızda, e-posta listesi çıkartılırken liste öğesi bir ayrıntılar ekranına genişletilir. Geri tuşuna bastığınızda e-posta ayrıntıları ekranı tekrar bir liste öğesine daraltılırken e-posta listesinde yukarı ölçeklenir.

Sonra

663e8594319bdee3.gif

6. FAB'den e-posta oluşturma sayfasına Kapsayıcı Dönüşümü geçişi ekleme

Şimdi, container dönüşümüne devam edelim ve Kayan İşlem Düğmesi'nden ComposePage öğesine bir geçiş ekleyerek FAB'yi kullanıcı tarafından yazılacak yeni bir e-posta adresine genişletelim. Öncelikle uygulamayı yeniden çalıştırın ve e-posta oluşturma ekranı açıldığında geçiş olmadığını görmek için FAB'ı tıklayın.

Önce

4aa2befdc5170c60.gif

Aynı widget sınıfını (OpenContainer) kullandığımız için bu geçişi yapılandırma şeklimiz, önceki adımda yaptığımıza çok benzer olacaktır.

home.dart ürününde, dosyanın en üstündeki package:animations/animations.dart öğesini içe aktaralım ve _ReplyFabState build() yöntemini değiştirelim. Döndürülen Material widget'ını bir OpenContainer widget'ı ile saralım:

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,
     ...

Önceki OpenContainer widget'ımızı yapılandırmak için kullanılan parametrelere ek olarak, onClosed de ayarlanıyor. onClosed, OpenContainer rotası açıldığında veya kapalı duruma döndüğünde çağrılan bir ClosedCallback. Bu işlemin döndürdüğü değer, bu işleve bağımsız değişken olarak iletilir. Bu Callback numarasını, uygulama sağlayıcımızı ComposePage rotasından ayrıldığımızı bildirmek için kullanıyoruz. Bu şekilde tüm dinleyicilere bilgi verebiliyoruz.

Son adımımızda yaptığımıza benzer şekilde, OpenContainer widget'ı closedColor ile closedBuilder tarafından döndürülen widget'ın rengini işlediği için Material widget'ını widget'ımızdan kaldıracağız. Ayrıca, InkWell widget'ımızın onTap içindeki Navigator.push() çağrımızı kaldırıp OpenContainer widget'ının closedBuilder tarafından verilen openContainer() Callback ile değiştireceğiz. Artık OpenContainer widget'ı kendi yönlendirmesini yapıyor.

Elde edilen kod aşağıdaki gibidir:

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

Şimdi bazı eski kodları temizleyeceğiz. OpenContainer widget'ımız artık uygulamamızın sağlayıcısını onClosed ClosedCallback üzerinden ComposePage'da olmadığımızı bildirmekle ilgilendiği için mail_view_router.dart'daki önceki uygulamamızı kaldırabiliriz:

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

Bu adımda bu kadar. FAB'den oluşturma ekranına geçişiniz aşağıdaki gibi görünmelidir:

Sonra

5c7ad1b4b40f9f0c.gif

7. Arama simgesinden arama görünümü sayfasına paylaşılan z ekseni geçişi ekleme

Bu adımda, arama simgesinden tam ekran arama görünümüne geçiş animasyonu ekleyeceğiz. Bu gezinme değişikliğine dahil olan kalıcı bir kapsayıcı olmadığından, iki ekran arasındaki uzamsal ilişkiyi güçlendirmek ve uygulamanın hiyerarşisinde bir seviye yukarı hareket ettiğini göstermek için Paylaşılan Z ekseni geçişini kullanabiliriz.

Ek kod eklemeden önce uygulamayı çalıştırıp ekranın sağ alt köşesindeki arama simgesine dokunmayı deneyin. Bu işlem, geçiş olmadan arama görünümü ekranını getirir.

Önce

df7683a8ad7b920e.gif

Başlamak için router.dart dosyamıza gidelim. ReplySearchPath sınıf tanımımızdan sonra aşağıdaki snippet'i ekleyin:

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

Şimdi, istediğimiz geçişi gerçekleştirmek için yeni SharedAxisTransitionPageWrapper aracımızı kullanalım. ReplyRouterDelegate sınıf tanımı içinde, pages mülkünün altında arama ekranımızı CustomTransitionPage yerine SharedAxisTransitionPageWrapper ile saralım:

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

Şimdi uygulamayı yeniden çalıştırmayı deneyin.

81b3ea098926931.gif

Her şey yolunda gitmeye başlıyor. Alt uygulama çubuğundaki arama simgesini tıkladığınızda, ortak eksen geçişi arama sayfasını görüntüye göre ölçeklendirir. Ancak arama sayfası genişledikçe ana sayfanın nasıl ölçeklenmediğini ve sabit kaldığını gösterir. Ayrıca, geri düğmesine basıldığında ana sayfa görünüm olarak ölçeklendirilmez. Arama sayfası görünümden çıktığında ana sayfa statik olarak kalır. Henüz işimiz bitmedi.

HomePage öğesini CustomTransitionPage yerine SharedAxisTransitionWrapper ile sarmalayarak her iki sorunu da düzeltelim:

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

İşte bu kadar. Şimdi uygulamayı yeniden çalıştırmayı ve arama simgesine dokunmayı deneyin. Ana sayfa ve arama görünümü ekranları aynı anda Z ekseninde ayrıntılı olarak kararma ve ölçeklenmeli, böylece iki ekran arasında kesintisiz bir efekt sağlanmalıdır.

Sonra

462d890086a3d18a.gif

8. Posta kutusu sayfaları arasında Şeffaflaştırma geçişi ekleme

Bu adımda, farklı posta kutuları arasında geçiş ekleyeceğiz. Uzamsal veya hiyerarşik bir ilişkiyi vurgulamak istemediğimizden, e-posta listeleri arasında basit bir "değişim" yapmak için geçiş efekti kullanacağız.

Başka kod eklemeden önce uygulamayı çalıştırmayı, alt uygulama çubuğundaki Yanıtla logosuna dokunmayı ve posta kutularını değiştirmeyi deneyin. E-posta listesi, geçiş olmadan değişecektir.

Önce

89033988ce26b92e.gif

Başlamak için mail_view_router.dart dosyamıza gidelim. MailViewRouterDelegate sınıf tanımımımızın ardından aşağıdaki snippet'i ekleyin:

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

Son adımımıza benzer şekilde, istediğimiz geçişi gerçekleştirmek için yeni FadeThroughTransitionPageWrapper özelliğimizi kullanalım. MailViewRouterDelegate sınıf tanımımızda, pages özelliği altında, posta kutusu ekranımızı CustomTransitionPage ile sarmalamak yerine FadeThroughTransitionPageWrapper kodunu kullanın:

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

Uygulamayı yeniden çalıştırın. Alt gezinme çekmecesini açıp posta kutularını değiştirdiğinizde, mevcut e-posta listesi kaybolarak genişler ve yeni liste genişler ve genişler. Güzel!

Sonra

8186940082b630d.gif

9. Oluşturma ve yanıtlama FAB'si arasında Şeffaflaştırma geçişi ekle

Bu adımda farklı FAB simgeleri arasında bir geçiş ekleyeceğiz. Uzamsal veya hiyerarşik bir ilişkiyi vurgulamak istemediğimizden, FAB'daki simgeler arasında basit bir "değişim" yapmak için karartma yöntemini kullanacağız.

Başka kod eklemeden önce bir e-postaya dokunup e-posta görünümünü açarak uygulamayı çalıştırmayı deneyin. FAB simgesi geçiş olmadan değişmelidir.

Önce

d8e3afa0447cfc20.gif

Codelab'in geri kalanında home.dart üzerinde çalışacağız. Bu nedenle, 2. adımda home.dart için zaten yaptığımızdan animasyon paketi için içe aktarmayı ekleme konusunda endişelenmeyin.

Sonraki birkaç geçişin tümü yeniden kullanılabilir bir sınıftan (_FadeThroughTransitionSwitcher) yararlanacağı için yapılandırma biçimimiz çok benzer olacaktır.

home.dart için _ReplyFabState altına şu snippet'i ekleyelim:

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

Ardından _ReplyFabState'te fabSwitcher widget'ını bulun. fabSwitcher, e-posta görünümünde olup olmadığına bağlı olarak farklı bir simge döndürür. _FadeThroughTransitionSwitcher ile toparlayalım:

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

_FadeThroughTransitionSwitcher için şeffaf bir fillColor sunuyoruz. Böylece geçiş sırasında öğeler arasında arka plan olmayacak. Ayrıca bir UniqueKey oluşturup simgelerden birine atarız.

Bu adımda, bağlama dayalı ve tamamen animasyonlu bir FAB'niz olmalıdır. E-posta görünümüne geçtiğinizde eski FAB simgesi şeffaflaşır ve küçültür, yenisi ise şeffaflaşır ve büyür.

Sonra

c55bacd9a144ec69.gif

10. Kaybolan posta kutusu başlığı arasındaki Şeffaflaştırma geçişi ekleme

Bu adımda, e-posta görünümündeyken posta kutusu başlığını görünür ve görünmez durum arasında soldan sağa doğru kaydırarak göstermek için bir soldan sağa kaydırma geçişi ekleyeceğiz. Uzamsal veya hiyerarşik bir ilişkiyi vurgulamak istemediğimizden, posta kutusu başlığını kapsayan Text widget'ı ile boş bir SizedBox arasında basit bir "değişim" yapmak için geçiş efekti kullanacağız.

Ek kod eklemeden önce uygulamayı çalıştırmayı, bir e-postaya dokunup e-posta görünümünü açmayı deneyin. Posta kutusu başlığı, geçiş olmadan kaybolacaktır.

Önce

59eb57a6c71725c0.gif

Son adımımızda _FadeThroughTransitionSwitcher özelliğindeki işin çoğunu zaten gerçekleştirdiğimiz için bu codelab'in geri kalanı hızlı olacak.

Şimdi, geçişimizi eklemek için home.dart uygulamasındaki _AnimatedBottomAppBar dersimize gidelim. Son adımımızdan itibaren _FadeThroughTransitionSwitcher yeniden kullanılacak ve boş bir SizedBox döndüren onMailView koşulumuzu sarmalayacağız veya alt çekmeceyle senkronize olarak kaybolan bir posta kutusu başlığı döndüreceğiz:

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

Hepsi bu kadar, bu adımı da tamamladık.

Uygulamayı yeniden çalıştırın. Bir e-postayı açıp e-posta görünümüne yönlendirildiğinizde, alt uygulama çubuğundaki posta kutusu başlığı şeffaf bir şekilde belirir ve genişler. Mükemmel!

Sonra

3f1a3db01a481124.gif

11. Alt uygulama çubuğu işlemleri arasında şeffaflaşma geçişi ekleme

Bu adımda, uygulamaların bağlamına göre alt uygulama çubuğu işlemlerini yavaş yavaş göstermek için bir karartma geçişi ekleyeceğiz. Üç boyutlu veya hiyerarşik bir ilişkiyi vurgulamak istemediğimizden, uygulama Ana Sayfa'da, alt çekmece görünür olduğunda ve e-posta görünümündeyken alt uygulama çubuğu işlemleri arasında basit bir "değişim" yapmak için geçiş efekti kullanacağız.

Ek kod eklemeden önce uygulamayı çalıştırmayı, bir e-postaya dokunup e-posta görünümünü açmayı deneyin. Yanıtla logosuna dokunmayı da deneyebilirsiniz. Alt uygulama çubuğundaki işlemler geçiş olmadan değişmelidir.

Önce

5f662eac19fce3ed.gif

Son adıma benzer şekilde, _FadeThroughTransitionSwitcher aracımızı tekrar kullanacağız. İstenen geçişi elde etmek için _BottomAppBarActionItems sınıf tanımımıza gidin ve build() işlevimizin dönüş widget'ını bir _FadeThroughTransitionSwitcher ile sarmalayın:

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
...

Şimdi deneyelim. Bir e-postayı açıp e-posta görünümüne yönlendirildiğinizde eski alt uygulama çubuğu işlemleri, yeni işlemler kademeli olarak artarken ve genişlerken kaybolmalıdır. Tebrikler!

Sonra

cff0fa2afa1c5a7f.gif

12. Tebrikler!

100 satırdan kısa bir Dart kodu kullanarak animasyon paketi, mevcut bir uygulamada Material Design yönergelerine uygun ve tüm cihazlarda tutarlı bir görünüm ve davranışa sahip güzel geçişler oluşturmanıza yardımcı oldu.

d5637de49eb64d8a.gif

Sonraki adımlar

Materyal hareket sistemi hakkında daha fazla bilgi edinmek için yönergeleri ve geliştirici dokümanlarının tamamını incelediğinizden emin olun ve uygulamanıza bazı Materyal geçişler eklemeyi deneyin!

Material Motion'u denediğiniz için teşekkür ederiz. Bu codelab'den keyif aldığınızı umuyoruz.

Bu codelab'i makul bir zaman ve çabayla tamamlayabildim

Kesinlikle katılıyorum Katılıyorum Ne katılıyorum ne katılmıyorum Katılmıyorum Kesinlikle katılmıyorum

Gelecekte Materyal hareket sistemini kullanmaya devam etmek istiyorum

Kesinlikle katılıyorum Katılıyorum Nötr Katılmıyorum Kesinlikle katılmıyorum

Material Flutter kitaplığı ve Flutter çerçevesi tarafından sağlanan widget'ları nasıl kullanacağınızla ilgili daha fazla demo için Flutter Gallery'yi ziyaret etmeyi unutmayın.

46ba920f17198998.png

6ae8ae284bf4f9fa.png