Flutter के लिए मटीरियल मोशन इस्तेमाल करके खूबसूरत ट्रांज़िशन बनाना

1. परिचय

मटीरियल डिज़ाइन एक ऐसा सिस्टम है जो अच्छे और आकर्षक डिजिटल प्रॉडक्ट बनाता है. सिद्धांतों और कॉम्पोनेंट के एक जैसे सेट के तहत स्टाइल, ब्रैंडिंग, इंटरैक्शन, और मोशन की सुविधा इस्तेमाल करके, प्रॉडक्ट टीम अपने डिज़ाइन की क्षमता को पहचान सकती हैं.

logo_components_color_2x_web_96dp.png

मटीरियल कॉम्पोनेंट (एमडीसी), मटीरियल डिज़ाइन लागू करने में डेवलपर की मदद करते हैं. Google में इंजीनियरों और UX डिज़ाइनर की टीम ने बनाया है. एमडीसी में दर्जनों खूबसूरत और काम करने वाले यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट हैं. यह Android, iOS, वेब और Flutter.material.io/प्रॉडक्ट के लिए उपलब्ध है

Flutter के लिए Material का मोशन सिस्टम क्या है?

Flutter के लिए मटीरियल मोशन सिस्टम, ऐनिमेशन पैकेज में ट्रांज़िशन पैटर्न का एक सेट है. यह मटीरियल डिज़ाइन से जुड़े दिशा-निर्देश के मुताबिक, ऐप्लिकेशन को समझने और इस्तेमाल करने में उपयोगकर्ताओं की मदद कर सकता है.

चार मुख्य मटीरियल ट्रांज़िशन पैटर्न इस तरह हैं:

  • कंटेनर ट्रांसफ़ॉर्म: यूज़र इंटरफ़ेस (यूआई) एलिमेंट के बीच ट्रांज़िशन करता है जिनमें कंटेनर शामिल होता है; एक एलिमेंट को दूसरे एलिमेंट में आसानी से बदलकर, दो अलग-अलग यूज़र इंटरफ़ेस (यूआई) एलिमेंट के बीच दिखने वाला कनेक्शन बनाता है.

11807bdf36c66657.gif

  • शेयर किया गया ऐक्सिस: यूज़र इंटरफ़ेस (यूआई) एलिमेंट के बीच ऐसे ट्रांज़िशन जो जगह या नेविगेशन से जुड़े होते हैं; एलिमेंट के बीच संबंध को समझाने के लिए, x, y या z ऐक्सिस पर शेयर किए गए बदलाव का इस्तेमाल किया जाता है.

71218f390ABe07e.gif

  • फ़ेड थ्रू: यूज़र इंटरफ़ेस (यूआई) एलिमेंट के बीच ऐसे ट्रांज़िशन जिनका एक-दूसरे से बेहतर संबंध नहीं है; आने वाले एलिमेंट के स्केल के साथ, क्रम में फ़ेड आउट और फ़ेड इन का इस्तेमाल करता है.

385ba37b8da68969.gif

  • फ़ेड: इसका इस्तेमाल यूज़र इंटरफ़ेस (यूआई) एलिमेंट के लिए किया जाता है, जो स्क्रीन की सीमाओं के अंदर से जुड़ते हैं या बाहर निकलते हैं.

cfc40fd6e27753b6.gif

ऐनिमेशन पैकेज, इन पैटर्न के लिए ट्रांज़िशन विजेट उपलब्ध कराता है, जो Flutter Animation Library (flutter/animation.dart) और Flutterकॉन्टेंट लाइब्रेरी (flutter/material.dart), दोनों के ऊपर बने होते हैं:

इस कोडलैब में, आपको Flutter फ़्रेमवर्क और Material लाइब्रेरी के ऊपर बने मटीरियल ट्रांज़िशन का इस्तेमाल करना होगा. इसका मतलब है कि आपको विजेट इस्तेमाल करने होंगे. :)

आपको क्या बनाना होगा

यह कोडलैब आपको Dart का इस्तेमाल करके, जवाब नाम के Flutter ईमेल ऐप्लिकेशन में कुछ ट्रांज़िशन बनाने में मदद करेगा, ताकि आप यह दिखा सकें कि अपने ऐप्लिकेशन के लुक और स्टाइल को पसंद के मुताबिक बनाने के लिए, ऐनिमेशन पैकेज से ट्रांज़िशन का इस्तेमाल कैसे किया जा सकता है.

जवाब ऐप्लिकेशन के लिए स्टार्टर कोड दिया जाएगा. साथ ही, आपको ऐप्लिकेशन में नीचे दिए गए मटीरियल ट्रांज़िशन शामिल करने होंगे. इन्हें नीचे दिए गए कोडलैब के GIF में देखा जा सकता है:

  • कंटेनर ट्रांसफ़ॉर्म का इस्तेमाल, ईमेल सूची से ईमेल की ज़्यादा जानकारी वाले पेज पर किया जाता है
  • ईमेल पेज लिखने के लिए, एफ़एबी से कंटेनर ट्रांसफ़ॉर्म का ट्रांज़िशन
  • शेयर किया गया Z-ऐक्सिस का खोज आइकॉन से खोज व्यू पेज पर ट्रांज़िशन
  • मेलबॉक्स पेजों के बीच फ़ेड थ्रू ट्रांज़िशन
  • लिखने और जवाब देने के एफ़एबी के बीच ट्रांज़िशन के लिए फ़ेड थ्रू विकल्प
  • गायब होने वाले मेलबॉक्स के शीर्षक के बीच फ़ेड थ्रू ट्रांज़िशन
  • ऐप्लिकेशन बार पर नीचे दी गई कार्रवाइयों के बीच ट्रांज़िशन के लिए फ़ेड थ्रू

b26fe84fed12d17d.gif

आपको इन चीज़ों की ज़रूरत होगी

  • Flutter के डेवलपमेंट और Dart के बारे में बुनियादी जानकारी
  • कोड एडिटर
  • Android/iOS एम्युलेटर या डिवाइस
  • सैंपल कोड (अगला चरण देखें)

Flutter ऐप्लिकेशन बनाने के अपने अनुभव को आप कितनी रेटिंग देंगे/देंगी?

शुरुआती इंटरमीडिएट कुशल

आपको इस कोडलैब से क्या सीखना है?

मुझे इस विषय के बारे में पता नहीं है और मुझे इसके बारे में खास जानकारी चाहिए. मुझे इस विषय के बारे में कुछ पता है, लेकिन मुझे इस बारे में फिर से जानना है. मुझे अपने प्रोजेक्ट में इस्तेमाल करने के लिए, उदाहरण के तौर पर एक कोड चाहिए. मुझे किसी खास चीज़ के बारे में जानकारी चाहिए.

2. Flutter डेवलपमेंट एनवायरमेंट को सेट अप करें

इस लैब को पूरा करने के लिए, आपको सॉफ़्टवेयर के दो हिस्सों की ज़रूरत होगी—Flutter SDK टूल और एडिटर.

इनमें से किसी भी डिवाइस का इस्तेमाल करके, कोडलैब चलाया जा सकता है:

  • आपके कंप्यूटर से कनेक्ट किया गया Android या iOS डिवाइस होना चाहिए और वह डेवलपर मोड पर सेट होना चाहिए.
  • iOS सिम्युलेटर (Xcode टूल इंस्टॉल करना ज़रूरी है).
  • Android Emulator (Android Studio में सेटअप करना ज़रूरी है).
  • ब्राउज़र (डीबग करने के लिए Chrome ज़रूरी है).
  • Windows, Linux या macOS डेस्कटॉप ऐप्लिकेशन के तौर पर. आपको उस प्लैटफ़ॉर्म पर गेम बनाना होगा जहां आपको इसे डिप्लॉय करना है. इसलिए, अगर आपको Windows डेस्कटॉप ऐप्लिकेशन डेवलप करना है, तो आपको सही बिल्ड चेन ऐक्सेस करने के लिए Windows पर डेवलप करना होगा. ऑपरेटिंग सिस्टम से जुड़ी कुछ खास शर्तें हैं, जिनके बारे में docs.flutter.dev/desktop पर जानकारी दी गई है.

3. कोडलैब स्टार्टर ऐप्लिकेशन डाउनलोड करें

पहला विकल्प: GitHub से स्टार्टर कोडलैब ऐप्लिकेशन का क्लोन बनाना

GitHub से इस कोडलैब का क्लोन बनाने के लिए, नीचे दिए गए निर्देश चलाएं:

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

विकल्प 2: स्टार्टर कोडलैब ऐप्लिकेशन की ज़िप फ़ाइल डाउनलोड करें

स्टार्टर ऐप्लिकेशन, material-components-flutter-motion-codelab-starter डायरेक्ट्री में है.

प्रोजेक्ट डिपेंडेंसी की पुष्टि करें

यह प्रोजेक्ट ऐनिमेशन पैकेज पर निर्भर करता है. pubspec.yaml के dependencies सेक्शन में ये चीज़ें शामिल हैं:

animations: ^2.0.0

प्रोजेक्ट खोलें और ऐप्लिकेशन चलाएं

  1. अपनी पसंद के एडिटर में प्रोजेक्ट खोलें.
  2. "ऐप्लिकेशन चलाएं" के निर्देशों का पालन करें शुरू करें: टेस्ट ड्राइव में.

हो गया! जवाब दें सुविधा के होम पेज के लिए स्टार्टर कोड, आपके डिवाइस/एम्युलेटर पर चलना चाहिए. आपको एक इनबॉक्स दिखेगा, जिसमें ईमेल की सूची होगी.

होम पेज पर जवाब दें

ज़रूरी नहीं: डिवाइस पर चलने वाले ऐनिमेशन की रफ़्तार कम करना

इस कोडलैब में, तेज़ और बेहतर ट्रांज़िशन शामिल होते हैं. इसलिए, डिवाइस के ऐनिमेशन को धीमा करके, ट्रांज़िशन की बारीकियों पर नज़र रखें. ऐसा इन-ऐप्लिकेशन सेटिंग से किया जा सकता है. इसे सबसे नीचे वाला पैनल खुले होने पर, सेटिंग आइकॉन पर टैप करके ऐक्सेस किया जा सकता है. चिंता न करें, डिवाइस में ऐनिमेशन की रफ़्तार धीमी करने के इस तरीके से, जवाब देने वाले ऐप्लिकेशन के बाहर डिवाइस पर मौजूद ऐनिमेशन पर कोई असर नहीं पड़ेगा.

d23a7bfacffac509.gif

ज़रूरी नहीं: गहरे रंग वाला मोड

अगर जवाब दें की चमकदार थीम आपकी आंखों को दुख दे रही है, तो और आगे न देखें. इसमें एक इन-ऐप्लिकेशन सेटिंग भी शामिल है, जिससे आपको अपनी आंखों के हिसाब से, ऐप्लिकेशन की थीम को गहरे रंग वाले मोड में बदलने की सुविधा मिलती है. नीचे वाला पैनल खुला होने पर, सेटिंग आइकॉन पर टैप करके इस सेटिंग को ऐक्सेस किया जा सकता है.

87618d8418eee19e.gif

4. सैंपल ऐप्लिकेशन कोड के बारे में जानें

आइए कोड को देखें. हमने एक ऐसा ऐप्लिकेशन उपलब्ध कराया है जो ऐप्लिकेशन में अलग-अलग स्क्रीन के बीच ट्रांज़िशन करने के लिए, ऐनिमेशन पैकेज का इस्तेमाल करता है.

  • HomePage: चुने गए मेलबॉक्स को दिखाता है
  • InboxPage: ईमेल की सूची दिखाता है
  • MailPreviewCard: यह किसी ईमेल की झलक दिखाता है
  • MailViewPage: एक पूरा ईमेल दिखाता है
  • ComposePage: इससे नया ईमेल लिखा जा सकता है
  • SearchPage: इससे खोज का व्यू दिखता है

router.dart

सबसे पहले, ऐप्लिकेशन का रूट नेविगेशन सेटअप करने का तरीका समझने के लिए, router.dart को 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);
 }
}

यह हमारा रूट नेविगेटर है और यह हमारे ऐप्लिकेशन की उन स्क्रीन को हैंडल करता है जो पूरे कैनवस का इस्तेमाल करती हैं, जैसे कि HomePage और SearchPage. यह हमारे ऐप्लिकेशन की स्थिति को सुनकर यह देखता है कि हमने ReplySearchPath पर रूट सेट किया है या नहीं. अगर हमारे पास है, तो यह स्टैक के शीर्ष पर SearchPage के साथ हमारे नेविगेटर को फिर से बनाता है. ध्यान दें कि हमारी स्क्रीन बिना किसी ट्रांज़िशन के CustomTransitionPage में रैप की गई हैं. इस विकल्प की मदद से, अपनी पसंद के मुताबिक ट्रांज़िशन किए बिना एक से दूसरे स्क्रीन पर नेविगेट किया जा सकता है.

home.dart

हमने home.dart में _BottomAppBarActionItems के अंदर यह करके, अपने ऐप्लिकेशन की स्थिति में ReplySearchPath पर अपना रास्ता सेट किया:

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 पैरामीटर में, हम RouterProvider को ऐक्सेस करते हैं और उसके routePath को ReplySearchPath पर सेट करते हैं. हमारा RouterProvider हमारे रूट नेविगेटर की स्थिति पर नज़र रखता है.

mail_view_router.dart

अब देखते हैं कि हमारे ऐप्लिकेशन का इनर नेविगेशन कैसे सेट अप किया गया है. mail_view_router.dart को lib डायरेक्ट्री में खोलें. आपको ऊपर दिए गए नेविगेटर जैसा एक नेविगेटर दिखेगा:

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

यह हमारा आंतरिक नेविगेटर है. यह हमारे ऐप्लिकेशन की उन इनर स्क्रीन को हैंडल करता है जो सिर्फ़ कैनवस का मुख्य हिस्सा इस्तेमाल करती हैं, जैसे कि InboxPage. हमारे ऐप्लिकेशन की स्थिति के हिसाब से InboxPage, ईमेल की सूची दिखाता है. जब भी हमारे ऐप्लिकेशन की स्थिति की currentlySelectedInbox प्रॉपर्टी में कोई बदलाव होता है, तब स्टैक के सबसे ऊपर सही InboxPage के साथ नेविगेटर को फिर से बनाया जाता है.

home.dart

हमने home.dart में _HomePageState में ये काम करके, अपने मौजूदा मेलबॉक्स को अपने ऐप्लिकेशन की स्थिति में सेट किया है:

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 फ़ंक्शन में, हम अपने EmailStore को ऐक्सेस करते हैं और उसके currentlySelectedInbox को चुने गए डेस्टिनेशन पर सेट करते हैं. हमारा EmailStore हमारे नेविगेटर की स्थिति को ट्रैक करता है.

home.dart

आखिर में, इस्तेमाल किए जा रहे नेविगेशन रूटिंग का उदाहरण देखने के लिए, home.dart को lib डायरेक्ट्री में खोलें. InkWell विजेट की onTap प्रॉपर्टी में, _ReplyFabState क्लास ढूंढें. यह कुछ ऐसा दिखना चाहिए:

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

इसमें दिखाया गया है कि कस्टम ट्रांज़िशन के बिना, ईमेल लिखने वाले पेज पर कैसे जाया जा सकता है. इस कोडलैब के दौरान, जवाब का कोड इस्तेमाल करके मटीरियल ट्रांज़िशन को सेट अप किया जा सकता है, जो ऐप्लिकेशन में नेविगेशन की अलग-अलग कार्रवाइयों के साथ मिलकर काम करते हैं.

स्टार्टर कोड के बारे में जानने के बाद, अब हमारा पहला ट्रांज़िशन लागू करते हैं.

5. ईमेल सूची से ईमेल की ज़्यादा जानकारी वाले पेज पर, कंटेनर ट्रांसफ़ॉर्मेशन का ट्रांज़िशन जोड़ें

शुरू करने के लिए, ईमेल पर क्लिक करते ही आपको एक ट्रांज़िशन जोड़ना होगा. नेविगेशन के इस बदलाव के लिए, कंटेनर ट्रांसफ़ॉर्म पैटर्न सबसे सही है, क्योंकि इसे कंटेनर वाले यूज़र इंटरफ़ेस (यूआई) एलिमेंट के बीच ट्रांज़िशन के लिए डिज़ाइन किया गया है. इस पैटर्न से, दो यूज़र इंटरफ़ेस (यूआई) एलिमेंट के बीच कनेक्शन दिखता है.

किसी भी कोड को जोड़ने से पहले, जवाब दें ऐप्लिकेशन को चलाकर और ईमेल पर क्लिक करके देखें. इसे बस जंप-कट किया जाना चाहिए, जिसका मतलब है कि स्क्रीन को बिना किसी ट्रांज़िशन के बदल दिया गया है:

पहले

48b00600f73c7778.gif

नीचे दिए गए स्निपेट में दिखाए गए तरीके से mail_card_preview.dart के सबसे ऊपर ऐनिमेशन पैकेज के लिए एक इंपोर्ट जोड़कर शुरुआत करें:

mail_card_preview.dart

import 'package:animations/animations.dart';

अब जब आपके पास ऐनिमेशन पैकेज को इंपोर्ट करने की सुविधा है, तो हम आपके ऐप्लिकेशन में सुंदर ट्रांज़िशन जोड़ना शुरू कर सकते हैं. आइए, एक StatelessWidget क्लास बनाकर शुरुआत करें. इसमें हमारा OpenContainer विजेट सेव होगा.

mail_card_preview.dart में, 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,
       );
     },
   );
 }
}

आइए, अब अपने नए रैपर का इस्तेमाल करते हैं. MailPreviewCard क्लास की परिभाषा के अंदर, हम अपने build() फ़ंक्शन से Material विजेट को अपने नए _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(
...

हमारे _OpenContainerWrapper में एक InkWell विजेट है और OpenContainer की रंग प्रॉपर्टी, इसके अंदर मौजूद कंटेनर का रंग तय करती हैं. इसलिए, हम Material और Inkwell विजेट हटा सकते हैं. इससे मिलने वाला कोड इस तरह दिखेगा:

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

इस चरण में, आपके कंटेनर का ट्रांसफ़ॉर्म पूरी तरह से काम करने वाला होना चाहिए. किसी ईमेल पर क्लिक करने से, सूची में मौजूद आइटम बड़ा हो जाता है, जिससे ईमेल की सूची हट जाती है. 'वापस जाएं' बटन दबाने से ईमेल की जानकारी वाली स्क्रीन फिर से सूची में मौजूद हो जाती है, जबकि ईमेल की सूची बड़ी हो जाती है.

बाद में

663e8594319bdee3.gif

6. ईमेल पेज लिखने के लिए, एफ़एबी से कंटेनर ट्रांसफ़ॉर्म का ट्रांज़िशन जोड़ें

कंटेनर ट्रांसफ़ॉर्म करना जारी रखें और फ़्लोटिंग ऐक्शन बटन से ComposePage में ट्रांज़िशन जोड़ें. इसकी मदद से, एफ़एबी को उस नए ईमेल में बड़ा किया जा सकता है जिसे उपयोगकर्ता लिख सकता है. सबसे पहले, ऐप्लिकेशन को फिर से चलाएं और एफ़एबी पर क्लिक करके देखें कि ईमेल लिखने वाली स्क्रीन को लॉन्च करते समय कोई ट्रांज़िशन नहीं हो रहा.

पहले

4aa2befdc5170c60.gif

हम इस ट्रांज़िशन को काफ़ी हद तक ठीक वैसे ही कॉन्फ़िगर करेंगे जैसे हमने पिछले चरण में किया था. ऐसा इसलिए, क्योंकि हम एक ही विजेट क्लास, OpenContainer का इस्तेमाल कर रहे हैं.

home.dart में, फ़ाइल के ऊपर मौजूद package:animations/animations.dart को इंपोर्ट करें और _ReplyFabState build() तरीके में बदलाव करें. आइए, लौटाए गए Material विजेट को 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,
     ...

हमारे पिछले OpenContainer विजेट को कॉन्फ़िगर करने के लिए इस्तेमाल किए गए पैरामीटर के साथ-साथ, onClosed को अब सेट किया जा रहा है. onClosed एक ClosedCallback है. इसे तब कॉल किया जाता है, जब OpenContainer रास्ते की जानकारी पॉप-अप हुई हो या फिर यह बंद हो गई हो. उस लेन-देन की रिटर्न वैल्यू इस फ़ंक्शन में तर्क के तौर पर पास की जाती है. हम अपने ऐप्लिकेशन की सेवा देने वाली कंपनी को यह सूचित करने के लिए Callback का इस्तेमाल करते हैं कि हम ComposePage रूट से बाहर निकल गए हैं, ताकि यह सभी सुनने वालों को सूचना दे सके.

हमने अपने आखिरी चरण की तरह ही, Material विजेट को विजेट से हटा दिया, क्योंकि OpenContainer विजेट, closedColor के साथ closedBuilder से मिले विजेट के रंग को हैंडल करता है. हम अपने InkWell विजेट के onTap में से Navigator.push() कॉल को हटा देंगे और उसे OpenContainer विजेट के closedBuilder से मिले openContainer() Callback से बदल देंगे, क्योंकि अब 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 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,
         ),
       ),
     ),
   );
 },
);

अब कुछ पुराना कोड मिटाएं. अब हमारा OpenContainer विजेट, ऐप्लिकेशन बनाने वाली कंपनी को यह सूचना देता है कि अब हम onClosed ClosedCallback के ज़रिए ComposePage का इस्तेमाल नहीं करते. इसलिए, हम 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);

इस चरण में बस इतना ही! स्क्रीन कंपोज़ करने के लिए, आपको एफ़एबी से कुछ इस तरह ट्रांज़िशन करना होगा:

बाद में

5c7ad1b4b40f9f0c.gif

7. शेयर किए गए Z-ऐक्सिस ट्रांज़िशन को खोज आइकॉन से खोज व्यू पेज पर जोड़ें

इस चरण में, हम खोज आइकॉन से फ़ुल स्क्रीन में खोज व्यू में ट्रांज़िशन जोड़ेंगे. नेविगेशन के इस बदलाव में कोई स्थायी कंटेनर शामिल नहीं है. इसलिए, हम दो स्क्रीन के बीच के संबंध को बेहतर बनाने के लिए, शेयर किए गए Z-ऐक्सिस ट्रांज़िशन का इस्तेमाल कर सकते हैं. साथ ही, ऐप्लिकेशन के क्रम में एक लेवल ऊपर जाने का संकेत दे सकते हैं.

अतिरिक्त कोड जोड़ने से पहले, ऐप्लिकेशन चलाएं और स्क्रीन के नीचे दाएं कोने में खोज आइकॉन पर टैप करें. इससे बिना किसी संक्रमण के खोज दृश्य स्क्रीन आ जाएगी.

पहले

df7683a8ad7b920e.gif

शुरू करने के लिए, आइए अपनी router.dart फ़ाइल पर जाएं. हमारी ReplySearchPath क्लास की परिभाषा के बाद, यह स्निपेट जोड़ें:

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

अब, अपने नए SharedAxisTransitionPageWrapper का इस्तेमाल करके, अपने हिसाब से ट्रांज़िशन करें. आइए, हमारी ReplyRouterDelegate क्लास डेफ़िनिशन के अंदर, pages प्रॉपर्टी के तहत, हमारी खोज स्क्रीन को CustomTransitionPage के बजाय SharedAxisTransitionPageWrapper से रैप करते हैं:

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

अब ऐप्लिकेशन को फिर से चलाकर देखें.

81b3ea098926931.gif

चीज़ें ठीक से दिखने लगी हैं! ऐप्लिकेशन बार में सबसे नीचे मौजूद खोज आइकॉन पर क्लिक करने पर, शेयर किया गया ऐक्सिस ट्रांज़िशन, खोज वाले पेज को बड़ा करके दिखाता है. हालांकि, ध्यान दें कि कैसे होम पेज स्केल आउट नहीं होता और इसके बजाय वह स्थिर रहता है, क्योंकि खोज पेज उसके ऊपर स्केल करता है. इसके अलावा, 'वापस जाएं' बटन दबाने पर, होम पेज बड़े पैमाने पर व्यू में नहीं दिखता. इसके बजाय, वह तब भी स्थिर रहता है, जब खोज वाले पेज का व्यू बड़ा नहीं होता. फ़िलहाल, हमारा काम पूरा नहीं हुआ है.

आइए, दोनों समस्याओं को ठीक करने के लिए, CustomTransitionPage के बजाय HomePage को SharedAxisTransitionWrapper के साथ रैप करें:

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

हो गया! अब ऐप्लिकेशन को फिर से चलाकर देखें और खोज आइकॉन पर टैप करें. होम और खोज व्यू स्क्रीन एक साथ धुंधली होनी चाहिए और Z-ऐक्सिस की गहराई पर स्केल होनी चाहिए. इससे, दोनों स्क्रीन के बीच बिना किसी रुकावट के इफ़ेक्ट लागू हो जाना चाहिए.

बाद में

462d890086a3d18a.gif

8. मेलबॉक्स पेजों के बीच फ़ेड थ्रू ट्रांज़िशन जोड़ना

इस चरण में, हम अलग-अलग मेलबॉक्स के बीच ट्रांज़िशन जोड़ेंगे. हम किसी जगह या पदानुक्रमिक संबंध पर ज़ोर नहीं देना चाहते, इसलिए हम एक आसान "स्वैप" करने के लिए फ़ेड थ्रू का इस्तेमाल करेंगे चुनें.

कोई भी अतिरिक्त कोड जोड़ने से पहले, ऐप्लिकेशन चलाकर देखें. सबसे नीचे ऐप्लिकेशन बार में जवाब दें लोगो पर टैप करें और मेलबॉक्स बदलकर देखें. ईमेल की सूची बिना किसी ट्रांज़िशन के बदलनी चाहिए.

पहले

89033988ce26b92e.gif

शुरू करने के लिए, आइए अपनी mail_view_router.dart फ़ाइल पर जाएं. हमारी MailViewRouterDelegate क्लास की परिभाषा के बाद, यह स्निपेट जोड़ें:

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

हमारे आखिरी चरण की तरह ही, ट्रांज़िशन के लिए अपने नए FadeThroughTransitionPageWrapper का इस्तेमाल करते हैं. हमारी MailViewRouterDelegate क्लास परिभाषा के अंदर, pages प्रॉपर्टी के तहत, हमारी मेलबॉक्स स्क्रीन को CustomTransitionPage के साथ रैप करने के बजाय, 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),
   ),
 ],
);

ऐप्लिकेशन को फिर से चलाएं. नीचे वाला नेविगेशन पैनल खोलने और मेलबॉक्स बदलने पर, ईमेल की मौजूदा सूची फ़ेड और स्केल आउट हो जाएगी, जबकि नई सूची फ़ेड होगी और स्केल हो जाएगी. बढ़िया!

बाद में

8186940082b630d.gif

9. लिखें और जवाब दें एफ़एबी के बीच ट्रांज़िशन के ज़रिए फ़ेड करें

इस चरण में, हम अलग-अलग एफ़एबी आइकॉन के बीच ट्रांज़िशन जोड़ेंगे. हम किसी जगह या पदानुक्रमिक संबंध पर ज़ोर नहीं देना चाहते, इसलिए हम एक आसान "स्वैप" करने के लिए फ़ेड थ्रू का इस्तेमाल करेंगे एफ़एबी में मौजूद आइकॉन के बीच स्विच कर सकता है.

कोई भी अतिरिक्त कोड जोड़ने से पहले, ऐप्लिकेशन को चलाकर देखें, ईमेल पर टैप करें और ईमेल व्यू को खोलें. एफ़एबी आइकॉन को बिना किसी ट्रांज़िशन के बदलना चाहिए.

पहले

d8e3afa0447cfc20.gif

हम कोडलैब के बाकी बचे दिनों में, home.dart में काम करेंगे. इसलिए, ऐनिमेशन पैकेज के लिए इंपोर्ट करने की चिंता न करें, क्योंकि दूसरे चरण में हमने पहले ही home.dart के लिए ऐसा किया था.

अगले कुछ ट्रांज़िशन को कॉन्फ़िगर करने का तरीका काफ़ी मिलता-जुलता होगा. ऐसा इसलिए, क्योंकि वे सभी, फिर से इस्तेमाल की जा सकने वाली क्लास _FadeThroughTransitionSwitcher का इस्तेमाल करेंगे.

home.dart में, आइए _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,
   );
 }
}

अब हमारे _ReplyFabState में, fabSwitcher विजेट खोजें. ईमेल व्यू में है या नहीं, इसके आधार पर fabSwitcher अलग आइकॉन दिखाता है. चलिए, अब इसे _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,
             ),
     );
...

हम अपने _FadeThroughTransitionSwitcher को एक पारदर्शी fillColor देते हैं, ताकि ट्रांज़िशन के दौरान एलिमेंट के बीच कोई बैकग्राउंड न दिखे. हम एक UniqueKey भी बनाते हैं और उसे किसी एक आइकॉन को असाइन करते हैं.

अब इस चरण में, संदर्भ के हिसाब से पूरी तरह से ऐनिमेटेड एफ़एबी होना चाहिए. ईमेल व्यू में जाने से पुराना एफ़एबी आइकॉन धुंधला हो जाएगा और स्केल आउट हो जाएगा. साथ ही, नया एफ़एबी आइकॉन फ़ेड हो जाएगा और बड़ा हो जाएगा.

बाद में

c55bacd9a144ec69.gif

10. गायब हो रहे मेलबॉक्स टाइटल के बीच फ़ेड थ्रू ट्रांज़िशन जोड़ें

इस चरण में, हम ईमेल दृश्य के दौरान दृश्यमान और दृष्टिहीन स्थिति के बीच मेलबॉक्स शीर्षक में फ़ेड थ्रू ट्रांज़िशन जोड़ेंगे. हम किसी जगह या पदानुक्रमिक संबंध पर ज़ोर नहीं देना चाहते, इसलिए हम एक आसान "स्वैप" करने के लिए फ़ेड थ्रू का इस्तेमाल करेंगे Text विजेट के बीच में होना चाहिए जिसमें मेलबॉक्स टाइटल और खाली SizedBox शामिल हों.

कोई भी अतिरिक्त कोड जोड़ने से पहले, ऐप्लिकेशन को चलाकर देखें, ईमेल पर टैप करें और ईमेल व्यू को खोलें. मेलबॉक्स का टाइटल बिना किसी ट्रांज़िशन के गायब हो जाना चाहिए.

पहले

59eb57a6c71725c0.gif

कोडलैब का बाकी हिस्सा जल्दी हो जाएगा, क्योंकि आखिरी चरण में हमने _FadeThroughTransitionSwitcher का ज़्यादातर काम पहले ही कर लिया था.

आइए, अब अपना ट्रांज़िशन जोड़ने के लिए, home.dart में हमारी _AnimatedBottomAppBar क्लास देखते हैं. हम अपने आखिरी चरण से _FadeThroughTransitionSwitcher का फिर से इस्तेमाल करेंगे और अपनी onMailView कंडिशनल को रैप करेंगे. इससे या तो खाली SizedBox दिखता है या फिर मेलबॉक्स का टाइटल दिखता है, जो नीचे वाले पैनल के साथ सिंक हो जाता है:

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

बस इतना ही, हमने यह चरण पूरा कर लिया है!

ऐप्लिकेशन को फिर से चलाएं. जब आप कोई ईमेल खोलते हैं और आपको ईमेल व्यू पर ले जाया जाता है, तो ऐप्लिकेशन बार के नीचे वाला मेलबॉक्स टाइटल धुंधला होना चाहिए और बड़ा हो जाना चाहिए. बहुत बढ़िया!

बाद में

3f1a3db01a481124.gif

11. सबसे नीचे मौजूद ऐप्लिकेशन बार से जुड़ी कार्रवाइयों के बीच, फ़ेड थ्रू ट्रांज़िशन को जोड़ना

इस चरण में, हम ऐप्लिकेशन के संदर्भ के आधार पर सबसे नीचे वाली ऐप्लिकेशन बार कार्रवाइयों को फ़ेड थ्रू करने के लिए, फ़ेड थ्रू ट्रांज़िशन जोड़ेंगे. हम किसी जगह या पदानुक्रमिक संबंध पर ज़ोर नहीं देना चाहते, इसलिए हम एक आसान "स्वैप" करने के लिए फ़ेड थ्रू का इस्तेमाल करेंगे जब ऐप्लिकेशन होम पेज पर होगा, जब सबसे नीचे वाला पैनल दिखेगा, और जब हम ईमेल व्यू पर होंगे.

कोई भी अतिरिक्त कोड जोड़ने से पहले, ऐप्लिकेशन को चलाकर देखें, ईमेल पर टैप करें और ईमेल व्यू को खोलें. आप जवाब दें लोगो पर टैप करके भी देख सकते हैं. ऐप्लिकेशन बार पर नीचे दी जाने वाली कार्रवाइयां, ट्रांज़िशन के बिना बदल जानी चाहिए.

पहले

5f662eac19fce3ed.gif

आखिरी चरण की तरह ही, हम फिर से _FadeThroughTransitionSwitcher का इस्तेमाल करेंगे. अपनी पसंद के मुताबिक ट्रांज़िशन पाने के लिए, _BottomAppBarActionItems क्लास की परिभाषा पर जाएं और build() फ़ंक्शन के रिटर्न विजेट को _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
...

आइए, अब इसे आज़माएं! जब कोई ईमेल खोला जाता है और आपको ईमेल व्यू पर ले जाया जाता है, तो सबसे नीचे वाली ऐप्लिकेशन बार की पुरानी कार्रवाइयां फ़ेड और स्केल आउट हो जाएंगी, जबकि नई कार्रवाइयां फ़ेड और स्केल इन होंगी. बहुत खूब!

बाद में

cff0fa2afa1c5a7f.gif

12. बधाई हो!

Dart कोड की 100 से कम पंक्तियों का उपयोग करके, एनिमेशन पैकेज ने एक ऐसे मौजूदा ऐप्लिकेशन में सुंदर ट्रांज़िशन बनाने में आपकी सहायता की है, जो मटीरियल डिज़ाइन दिशानिर्देशों के अनुरूप है और सभी डिवाइस पर एक जैसा दिखता और व्यवहार भी करता है.

d5637de49eb64d8a.gif

अगले चरण

मटीरियल मोशन सिस्टम के बारे में ज़्यादा जानकारी के लिए, दिशा-निर्देश और पूरे डेवलपर दस्तावेज़ देखना न भूलें. साथ ही, अपने ऐप्लिकेशन में कुछ मटीरियल ट्रांज़िशन जोड़कर देखें!

मटीरियल मोशन आज़माने के लिए धन्यवाद. हमें उम्मीद है कि आपको यह कोडलैब पसंद आया होगा!

मैंने सही समय और मेहनत में इस कोडलैब को पूरा कर लिया

पूरी तरह सहमत सहमत कोई फ़र्क़ नहीं पड़ता असहमत पूरी तरह असहमत

मुझे आने वाले समय में मटीरियल मोशन सिस्टम का इस्तेमाल जारी रखना है

पूरी तरह सहमत सहमत कोई फ़र्क़ नहीं पड़ता असहमत पूरी तरह असहमत

मटीरियल फ़्लटर लाइब्रेरी से मिले विजेट और Flutter फ़्रेमवर्क के इस्तेमाल से जुड़े ज़्यादा डेमो के लिए, Flutter गैलरी में ज़रूर जाएं.

46ba920f17198998.png

6ae8ae284bf4f9fa.png