MDC-104 Flutter: मटीरियल ऐडवांस्ड कॉम्पोनेंट

1. परिचय

logo_components_color_2x_web_96dp.png

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

कोड लैब MDC-103 में, आपने अपने ऐप्लिकेशन को स्टाइल करने के लिए, मटीरियल कॉम्पोनेंट (एमडीसी) के रंग, एलिवेशन, टाइपोग्राफ़ी, और आकार को पसंद के मुताबिक बनाया था.

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

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

आपको क्या बनाने को मिलेगा

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

  • आकार
  • मोशन
  • Flutter विजेट (जिन्हें आपने पिछले कोडलैब में इस्तेमाल किया है)

Android

iOS

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

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

मेन्यू में शामिल आइटम की चार कैटगरी

मेन्यू में शामिल आइटम की चार कैटगरी

इस कोडलैब में Material Flutter कॉम्पोनेंट और सबसिस्टम

  • आकार

Flutter डेवलपमेंट के लिए, आपके अनुभव के हिसाब से आपकी रेटिंग क्या होगी?

शुरुआती सामान्य एडवांस

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

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

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

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

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

क्या आपको MDC-103 से आगे बढ़ना है?

अगर आपने MDC-103 पूरा कर लिया है, तो आपका कोड इस कोडलैब के लिए तैयार होना चाहिए. इस चरण पर जाएं: बैकड्रॉप मेन्यू जोड़ना.

क्या आपको नए सिरे से शुरुआत करनी है?

स्टार्टर ऐप्लिकेशन, material-components-flutter-codelabs-104-starter_and_103-complete/mdc_100_series डायरेक्ट्री में मौजूद होता है.

...या इसे GitHub से क्लोन करें

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

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 104-starter_and_103-complete

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

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

हो गया! आपको अपने डिवाइस पर, पिछले कोडलैब का Shrine लॉगिन पेज दिखेगा.

Android

iOS

Shrine का लॉगिन पेज

Shrine का लॉगिन पेज

4. बैकड्रॉप मेन्यू जोड़ना

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

होम ऐप्लिकेशन बार हटाना

होम पेज विजेट, हमारी फ़्रंट लेयर का कॉन्टेंट होगा. फ़िलहाल, इसमें एक ऐप्लिकेशन बार है. हम ऐप्लिकेशन बार को बैक लेयर में ले जाएंगे. साथ ही, होम पेज में सिर्फ़ AsymmetricView शामिल होगा.

home.dart में, build() फ़ंक्शन को बदलकर सिर्फ़ AsymmetricView दिखाने वाला फ़ंक्शन बनाएं:

// TODO: Return an AsymmetricView (104)
return AsymmetricView(products: ProductsRepository.loadProducts(Category.all));

बैकड्रॉप विजेट जोड़ना

बैकड्रॉप नाम का एक विजेट बनाएं, जिसमें frontLayer और backLayer शामिल हों.

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

backdrop.dart नाम की फ़ाइल में नई फ़ाइल जोड़ने के लिए:/lib

import 'package:flutter/material.dart';

import 'model/product.dart';

// TODO: Add velocity constant (104)

class Backdrop extends StatefulWidget {
  final Category currentCategory;
  final Widget frontLayer;
  final Widget backLayer;
  final Widget frontTitle;
  final Widget backTitle;

  const Backdrop({
    required this.currentCategory,
    required this.frontLayer,
    required this.backLayer,
    required this.frontTitle,
    required this.backTitle,
    Key? key,
  }) : super(key: key);

  @override
  _BackdropState createState() => _BackdropState();
}

// TODO: Add _FrontLayer class (104)
// TODO: Add _BackdropTitle class (104)
// TODO: Add _BackdropState class (104)

ध्यान दें कि हमने कुछ प्रॉपर्टी को required के तौर पर मार्क किया है. यह कंस्ट्रक्टर में मौजूद उन प्रॉपर्टी के लिए सबसे सही तरीका है जिनकी कोई डिफ़ॉल्ट वैल्यू नहीं होती और जिन्हें null नहीं किया जा सकता. इसलिए, इन्हें नहीं भूलना चाहिए.

Backdrop क्लास की परिभाषा में, _BackdropState क्लास जोड़ें:

// TODO: Add _BackdropState class (104)
class _BackdropState extends State<Backdrop>
    with SingleTickerProviderStateMixin {
  final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');

  // TODO: Add AnimationController widget (104)

  // TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
  Widget _buildStack() {
    return Stack(
    key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        widget.backLayer,
        widget.frontLayer,
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    var appBar = AppBar(
      elevation: 0.0,
      titleSpacing: 0.0,
      // TODO: Replace leading menu icon with IconButton (104)
      // TODO: Remove leading property (104)
      // TODO: Create title with _BackdropTitle parameter (104)
      leading: Icon(Icons.menu),
      title: Text('SHRINE'),
      actions: <Widget>[
        // TODO: Add shortcut to login screen from trailing icons (104)
        IconButton(
          icon: Icon(
            Icons.search,
            semanticLabel: 'search',
          ),
          onPressed: () {
          // TODO: Add open login (104)
          },
        ),
        IconButton(
          icon: Icon(
            Icons.tune,
            semanticLabel: 'filter',
          ),
          onPressed: () {
          // TODO: Add open login (104)
          },
        ),
      ],
    );
    return Scaffold(
      appBar: appBar,
      // TODO: Return a LayoutBuilder widget (104)
      body: _buildStack(),
    );
  }
}

build() फ़ंक्शन, ऐप्लिकेशन बार के साथ Scaffold दिखाता है. यह ठीक वैसा ही है जैसा HomePage दिखाता था. लेकिन Scaffold का मुख्य हिस्सा एक Stack होता है. स्टैक के चाइल्ड एलिमेंट ओवरलैप हो सकते हैं. हर चाइल्ड का साइज़ और जगह, स्टैक के पैरंट के हिसाब से तय की जाती है.

अब ShrineApp में Backdrop इंस्टेंस जोड़ें.

app.dart में, backdrop.dart और model/product.dart इंपोर्ट करें:

import 'backdrop.dart'; // New code
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart'; // New code
import 'supplemental/cut_corners_border.dart';

app.dart, में, / के रूट में बदलाव करें. इसके लिए, Backdrop को वापस लाएं. इसमें HomePage को frontLayer के तौर पर इस्तेमाल किया गया है:

// TODO: Change to a Backdrop with a HomePage frontLayer (104)
'/': (BuildContext context) => Backdrop(
     // TODO: Make currentCategory field take _currentCategory (104)
     currentCategory: Category.all,
     // TODO: Pass _currentCategory for frontLayer (104)
     frontLayer: HomePage(),
     // TODO: Change backLayer field value to CategoryMenuPage (104)
     backLayer: Container(color: kShrinePink100),
     frontTitle: Text('SHRINE'),
     backTitle: Text('MENU'),
),

अपने प्रोजेक्ट को सेव करें. आपको दिखेगा कि हमारा होम पेज और ऐप्लिकेशन बार दिख रहा है:

Android

iOS

गुलाबी बैकग्राउंड वाला, प्रॉडक्ट का Shrine पेज

गुलाबी बैकग्राउंड वाला, प्रॉडक्ट का Shrine पेज

backLayer, frontLayer होम पेज के पीछे नई लेयर में गुलाबी रंग का हिस्सा दिखाता है.

Flutter Inspector का इस्तेमाल करके, यह पुष्टि की जा सकती है कि स्टैक में, HomePage के पीछे एक कंटेनर है. यह कुछ ऐसा दिखना चाहिए:

92ed338a15a074bd.png

अब दोनों लेयर के डिज़ाइन और कॉन्टेंट में बदलाव किया जा सकता है.

5. कोई शेप जोड़ना

इस चरण में, आपको सबसे ऊपर बाएं कोने में कट जोड़ने के लिए, फ़्रंट लेयर को स्टाइल करना होगा.

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

सबसे ऊपर वाली लेयर में कोई शेप जोड़ना

शाइन ऐप्लिकेशन के लिए, ऐंगल वाले शाइन लोगो से प्रेरणा लेकर शेप स्टोरी बनाई गई है. शेप स्टोरी, ऐप्लिकेशन में इस्तेमाल होने वाले शेप का सामान्य इस्तेमाल है. उदाहरण के लिए, लोगो के शेप को लॉगिन पेज के उन एलिमेंट में दोहराया गया है जिन पर शेप लागू किया गया है. इस चरण में, आपको सामने की लेयर को स्टाइल करना होगा. इसके लिए, ऊपर-बाएं कोने से तिरछा कट लगाएं.

backdrop.dart में, नई क्लास _FrontLayer जोड़ें:

// TODO: Add _FrontLayer class (104)
class _FrontLayer extends StatelessWidget {
  // TODO: Add on-tap callback (104)
  const _FrontLayer({
    Key? key,
    required this.child,
  }) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 16.0,
      shape: const BeveledRectangleBorder(
        borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          // TODO: Add a GestureDetector (104)
          Expanded(
            child: child,
          ),
        ],
      ),
    );
  }
}

इसके बाद, _BackdropState के _buildStack() फ़ंक्शन में, फ़्रंट लेयर को _FrontLayer में रैप करें:

  Widget _buildStack() {
    // TODO: Create a RelativeRectTween Animation (104)

    return Stack(
    key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        widget.backLayer,
        // TODO: Add a PositionedTransition (104)
        // TODO: Wrap front layer in _FrontLayer (104)
          _FrontLayer(child: widget.frontLayer),
      ],
    );
  }

फिर से लोड करें.

Android

iOS

कस्टम शेप वाला श्राइन प्रॉडक्ट पेज

कस्टम शेप वाला श्राइन प्रॉडक्ट पेज

हमने Shrine के मुख्य हिस्से को कस्टम शेप दिया है. हालांकि, हम चाहते हैं कि यह ऐप्लिकेशन बार से विज़ुअली कनेक्ट हो.

ऐप्लिकेशन बार का रंग बदलना

app.dart में, _buildShrineTheme() फ़ंक्शन को इनमें से किसी एक में बदलें:

ThemeData _buildShrineTheme() {
  final ThemeData base = ThemeData.light(useMaterial3: true);
  return base.copyWith(
    colorScheme: base.colorScheme.copyWith(
      primary: kShrinePink100,
      onPrimary: kShrineBrown900,
      secondary: kShrineBrown900,
      error: kShrineErrorRed,
    ),
    textTheme: _buildShrineTextTheme(base.textTheme),
    textSelectionTheme: const TextSelectionThemeData(
      selectionColor: kShrinePink100,
    ),
    appBarTheme: const AppBarTheme(
      foregroundColor: kShrineBrown900,
      backgroundColor: kShrinePink100,
    ),
    inputDecorationTheme: const InputDecorationTheme(
      border: CutCornersBorder(),
      focusedBorder: CutCornersBorder(
        borderSide: BorderSide(
          width: 2.0,
          color: kShrineBrown900,
        ),
      ),
      floatingLabelStyle: TextStyle(
        color: kShrineBrown900,
      ),
    ),
  );
}

हॉट रीस्टार्ट. अब आपको रंगीन नया ऐप्लिकेशन बार दिखेगा.

Android

iOS

मंदिर के प्रॉडक्ट पेज पर रंगीन ऐप्लिकेशन बार

मंदिर के प्रॉडक्ट पेज पर रंगीन ऐप्लिकेशन बार

इस बदलाव की वजह से, उपयोगकर्ताओं को यह दिख सकता है कि सफ़ेद रंग की सबसे आगे वाली लेयर के ठीक पीछे कुछ है. आइए, मोशन जोड़ते हैं, ताकि उपयोगकर्ता बैकड्रॉप की बैक लेयर देख सकें.

6. मोशन जोड़ना

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

मेन्यू बटन में रीवील मोशन जोड़ना

backdrop.dart में सबसे ऊपर, किसी भी क्लास या फ़ंक्शन के स्कोप से बाहर, एक कॉन्स्टेंट जोड़ें. यह कॉन्स्टेंट, उस वेलोसिटी को दिखाएगा जिस पर हमें अपना ऐनिमेशन चलाना है:

// TODO: Add velocity constant (104)
const double _kFlingVelocity = 2.0;

_BackdropState में AnimationController विजेट जोड़ें. इसे initState() फ़ंक्शन में इंस्टैंशिएट करें और इसे स्टेट के dispose() फ़ंक्शन में डिस्पोज़ करें:

  // TODO: Add AnimationController widget (104)
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      value: 1.0,
      vsync: this,
    );
  }

  // TODO: Add override for didUpdateWidget (104)

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // TODO: Add functions to get and change front layer visibility (104)

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

ऐसे फ़ंक्शन जोड़ें जो सबसे ऊपर वाली लेयर के दिखने की सेटिंग तय करते हैं और उसे बदलते हैं:

  // TODO: Add functions to get and change front layer visibility (104)
  bool get _frontLayerVisible {
    final AnimationStatus status = _controller.status;
    return status == AnimationStatus.completed ||
        status == AnimationStatus.forward;
  }

  void _toggleBackdropLayerVisibility() {
    _controller.fling(
        velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
  }

backLayer को ExcludeSemantics विजेट में रैप करें. जब बैक लेयर नहीं दिखती है, तब यह विजेट, बैकलेयर के मेन्यू आइटम को सिमैंटिक्स ट्री से हटा देगा.

    return Stack(
      key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        ExcludeSemantics(
          child: widget.backLayer,
          excluding: _frontLayerVisible,
        ),
      ...

_buildStack() फ़ंक्शन को बदलकर, BuildContext और BoxConstraints को शामिल करें. इसके अलावा, एक PositionedTransition भी शामिल करें, जो RelativeRectTween ऐनिमेशन लेता है:

  // TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
  Widget _buildStack(BuildContext context, BoxConstraints constraints) {
    const double layerTitleHeight = 48.0;
    final Size layerSize = constraints.biggest;
    final double layerTop = layerSize.height - layerTitleHeight;

    // TODO: Create a RelativeRectTween Animation (104)
    Animation<RelativeRect> layerAnimation = RelativeRectTween(
      begin: RelativeRect.fromLTRB(
          0.0, layerTop, 0.0, layerTop - layerSize.height),
      end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
    ).animate(_controller.view);

    return Stack(
      key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        ExcludeSemantics(
          child: widget.backLayer,
          excluding: _frontLayerVisible,
        ),
        // TODO: Add a PositionedTransition (104)
        PositionedTransition(
          rect: layerAnimation,
          child: _FrontLayer(
            // TODO: Implement onTap property on _BackdropState (104)
            child: widget.frontLayer,
          ),
        ),
      ],
    );
  }

आखिर में, Scaffold के मुख्य हिस्से के लिए _buildStack फ़ंक्शन को कॉल करने के बजाय, LayoutBuilder विजेट दिखाएं. यह विजेट, _buildStack को बिल्डर के तौर पर इस्तेमाल करता है:

    return Scaffold(
      appBar: appBar,
      // TODO: Return a LayoutBuilder widget (104)
      body: LayoutBuilder(builder: _buildStack),
    );

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

build() फ़ंक्शन में, ऐप्लिकेशन बार में मौजूद लीडिंग मेन्यू आइकॉन को IconButton में बदलें. साथ ही, बटन टैप करने पर फ़्रंट लेयर की दृश्यता को टॉगल करने के लिए इसका इस्तेमाल करें.

      // TODO: Replace leading menu icon with IconButton (104)
      leading: IconButton(
        icon: const Icon(Icons.menu),
        onPressed: _toggleBackdropLayerVisibility,
      ),

सिम्युलेटर को फिर से लोड करें. इसके बाद, मेन्यू बटन पर टैप करें.

Android

iOS

दो गड़बड़ियों वाला खाली श्राइन मेन्यू

दो गड़बड़ियों वाला खाली श्राइन मेन्यू

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

प्रॉडक्ट कॉलम को ListView में रैप करें

supplemental/product_columns.dart में, OneProductCardColumn में मौजूद कॉलम को ListView से बदलें:

class OneProductCardColumn extends StatelessWidget {
  const OneProductCardColumn({required this.product, Key? key}) : super(key: key);

  final Product product;

  @override
  Widget build(BuildContext context) {
    // TODO: Replace Column with a ListView (104)
    return ListView(
      physics: const ClampingScrollPhysics(),
      reverse: true,
      children: <Widget>[
        ConstrainedBox(
          constraints: const BoxConstraints(
            maxWidth: 550,
          ),
          child: ProductCard(
            product: product,
          ),
        ),
        const SizedBox(
          height: 40.0,
        ),

      ],
    );
  }
}

कॉलम में MainAxisAlignment.end शामिल है. लेआउट को सबसे नीचे से शुरू करने के लिए, reverse: true को मार्क करें. बदलाव की वजह से, बच्चों के लिए किए गए ऑर्डर को वापस कर दिया जाता है, ताकि उन्हें इसका मुआवज़ा मिल सके.

पेज को फिर से लोड करें और मेन्यू बटन पर टैप करें.

Android

iOS

एक गड़बड़ी के साथ खाली श्राइन मेन्यू

एक गड़बड़ी के साथ खाली श्राइन मेन्यू

OneProductCardColumn पर दिखने वाली ग्रे रंग की ओवरफ़्लो चेतावनी अब नहीं दिखती! अब हम दूसरी समस्या को ठीक करते हैं.

supplemental/product_columns.dart में, imageAspectRatio के हिसाब लगाने का तरीका बदलें. साथ ही, TwoProductCardColumn में मौजूद कॉलम को ListView से बदलें:

      // TODO: Change imageAspectRatio calculation (104)
      double imageAspectRatio = heightOfImages >= 0.0
          ? constraints.biggest.width / heightOfImages
          : 49.0 / 33.0;
      // TODO: Replace Column with a ListView (104)
      return ListView(
        physics: const ClampingScrollPhysics(),
        children: <Widget>[
          Padding(
            padding: const EdgeInsetsDirectional.only(start: 28.0),
            child: top != null
                ? ProductCard(
                    imageAspectRatio: imageAspectRatio,
                    product: top!,
                  )
                : SizedBox(
                    height: heightOfCards,
                  ),
          ),
          const SizedBox(height: spacerHeight),
          Padding(
            padding: const EdgeInsetsDirectional.only(end: 28.0),
            child: ProductCard(
              imageAspectRatio: imageAspectRatio,
              product: bottom,
            ),
          ),
        ],
      );

हमने imageAspectRatio में कुछ सुरक्षा सुविधाएं भी जोड़ी हैं.

फिर से लोड करें. इसके बाद, मेन्यू बटन पर टैप करें.

Android

iOS

खाली तीर्थ मेन्यू

खाली तीर्थ मेन्यू

अब कोई और आइटम नहीं है.

7. बैक लेयर पर मेन्यू जोड़ना

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

मेन्यू जोड़ना

मेन्यू को सबसे आगे वाली लेयर में और इंटरैक्टिव बटन को सबसे पीछे वाली लेयर में जोड़ें.

lib/category_menu_page.dart नाम की नई फ़ाइल बनाएं:

import 'package:flutter/material.dart';

import 'colors.dart';
import 'model/product.dart';

class CategoryMenuPage extends StatelessWidget {
  final Category currentCategory;
  final ValueChanged<Category> onCategoryTap;
  final List<Category> _categories = Category.values;

  const CategoryMenuPage({
    Key? key,
    required this.currentCategory,
    required this.onCategoryTap,
  }) : super(key: key);

  Widget _buildCategory(Category category, BuildContext context) {
    final categoryString =
        category.toString().replaceAll('Category.', '').toUpperCase();
    final ThemeData theme = Theme.of(context);

    return GestureDetector(
      onTap: () => onCategoryTap(category),
      child: category == currentCategory
        ? Column(
            children: <Widget>[
              const SizedBox(height: 16.0),
              Text(
                categoryString,
                style: theme.textTheme.bodyLarge,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 14.0),
              Container(
                width: 70.0,
                height: 2.0,
                color: kShrinePink400,
              ),
            ],
          )
      : Padding(
        padding: const EdgeInsets.symmetric(vertical: 16.0),
        child: Text(
          categoryString,
          style: theme.textTheme.bodyLarge!.copyWith(
              color: kShrineBrown900.withAlpha(153)
            ),
          textAlign: TextAlign.center,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: const EdgeInsets.only(top: 40.0),
        color: kShrinePink100,
        child: ListView(
          children: _categories
            .map((Category c) => _buildCategory(c, context))
            .toList()),
      ),
    );
  }
}

यह एक GestureDetector है, जो Column को रैप करता है. इसके चाइल्ड, कैटगरी के नाम होते हैं. चुनी गई कैटगरी को दिखाने के लिए, अंडरलाइन का इस्तेमाल किया जाता है.

app.dart में, ShrineApp विजेट को स्टेटलेस से स्टेटफ़ुल में बदलें.

  1. हाइलाइट ShrineApp.
  2. अपने आईडीई के हिसाब से, कोड से जुड़ी कार्रवाइयां दिखाएं:
  3. Android Studio: macOS पर ⌥Enter या alt + enter दबाएं
  4. VS Code: macOS पर ⌘ दबाएं या Ctrl+. दबाएं.
  5. "Convert to StatefulWidget" चुनें.
  6. ShrineAppState क्लास को प्राइवेट (_ShrineAppState) में बदलें. ShrineAppState पर राइट क्लिक करें. इसके बाद,
  7. Android Studio: Refactor > Rename को चुनें
  8. VS Code: 'सिंबल का नाम बदलें' विकल्प चुनें
  9. क्लास को निजी बनाने के लिए, _ShrineAppState डालें.

app.dart में, चुनी गई कैटगरी के लिए _ShrineAppState में एक वैरिएबल जोड़ें. साथ ही, जब उस पर टैप किया जाए, तब एक कॉलबैक जोड़ें:

class _ShrineAppState extends State<ShrineApp> {
  Category _currentCategory = Category.all;

  void _onCategoryTap(Category category) {
    setState(() {
      _currentCategory = category;
    });
  }

इसके बाद, बैक लेयर को CategoryMenuPage में बदलें.

app.dart में, CategoryMenuPage इंपोर्ट करें:

import 'backdrop.dart';
import 'category_menu_page.dart';
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart';
import 'supplemental/cut_corners_border.dart';

build() फ़ंक्शन में, backLayer फ़ील्ड को CategoryMenuPage में बदलें. साथ ही, currentCategory फ़ील्ड को इंस्टेंस वैरिएबल में बदलें.

'/': (BuildContext context) => Backdrop(
              // TODO: Make currentCategory field take _currentCategory (104)
              currentCategory: _currentCategory,
              // TODO: Pass _currentCategory for frontLayer (104)
              frontLayer: HomePage(),
              // TODO: Change backLayer field value to CategoryMenuPage (104)
              backLayer: CategoryMenuPage(
                currentCategory: _currentCategory,
                onCategoryTap: _onCategoryTap,
              ),
              frontTitle: const Text('SHRINE'),
              backTitle: const Text('MENU'),
            ),

पेज को फिर से लोड करें और मेन्यू बटन पर टैप करें.

Android

iOS

मंदिर के मेन्यू में चार कैटगरी

मंदिर के मेन्यू में चार कैटगरी

मेन्यू के किसी विकल्प पर टैप करने से, अभी कुछ नहीं होता. चलिए, इसे ठीक करते हैं।

home.dart में, कैटगरी के लिए एक वैरिएबल जोड़ें और उसे AsymmetricView में पास करें.

import 'package:flutter/material.dart';

import 'model/product.dart';
import 'model/products_repository.dart';
import 'supplemental/asymmetric_view.dart';

class HomePage extends StatelessWidget {
  // TODO: Add a variable for Category (104)
  final Category category;

  const HomePage({this.category = Category.all, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: Pass Category variable to AsymmetricView (104)
    return AsymmetricView(
      products: ProductsRepository.loadProducts(category),
    );
  }
}

app.dart में, frontLayer के लिए _currentCategory पास करें:.

// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(category: _currentCategory),

फिर से लोड करें. सिम्युलेटर में मेन्यू बटन पर टैप करें और कोई कैटगरी चुनें.

Android

iOS

फ़िल्टर किए गए प्रॉडक्ट वाला Shrine पेज

फ़िल्टर किए गए प्रॉडक्ट वाला Shrine पेज

इन्हें फ़िल्टर किया गया है!

मेन्यू चुनने के बाद सबसे ऊपर वाली लेयर बंद करना

backdrop.dart में, _BackdropState में मौजूद didUpdateWidget() फ़ंक्शन के लिए ओवरराइड जोड़ें. यह फ़ंक्शन तब कॉल किया जाता है, जब विजेट के कॉन्फ़िगरेशन में बदलाव होता है:

  // TODO: Add override for didUpdateWidget() (104)
  @override
  void didUpdateWidget(Backdrop old) {
    super.didUpdateWidget(old);

    if (widget.currentCategory != old.currentCategory) {
      _toggleBackdropLayerVisibility();
    } else if (!_frontLayerVisible) {
      _controller.fling(velocity: _kFlingVelocity);
    }
  }

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

सामने वाली लेयर को टॉगल करें

backdrop.dart में, बैकड्रॉप लेयर में ऑन-टैप कॉलबैक जोड़ें:

class _FrontLayer extends StatelessWidget {
  // TODO: Add on-tap callback (104)
  const _FrontLayer({
    Key? key,
    this.onTap, // New code
    required this.child,
  }) : super(key: key);
 
  final VoidCallback? onTap; // New code
  final Widget child;

इसके बाद, _FrontLayer's child: Column's children: में GestureDetector जोड़ें.

      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          // TODO: Add a GestureDetector (104)
          GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: onTap,
            child: Container(
              height: 40.0,
              alignment: AlignmentDirectional.centerStart,
            ),
          ),
          Expanded(
            child: child,
          ),
        ],
      ),

इसके बाद, _buildStack() फ़ंक्शन में _BackdropState पर नई onTap प्रॉपर्टी लागू करें:

          PositionedTransition(
            rect: layerAnimation,
            child: _FrontLayer(
              // TODO: Implement onTap property on _BackdropState (104)
              onTap: _toggleBackdropLayerVisibility,
              child: widget.frontLayer,
            ),
          ),

फिर से लोड करें और सबसे ऊपर वाली लेयर पर टैप करें. सबसे ऊपर वाली लेयर पर टैप करने पर, लेयर खुलनी और बंद होनी चाहिए.

8. ब्रैंड का आइकॉन जोड़ना

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

मेन्यू बटन का आइकॉन बदलना

Android

iOS

ब्रैंड के आइकॉन के साथ मंदिर के प्रॉडक्ट का पेज

ब्रैंड के आइकॉन के साथ मंदिर के प्रॉडक्ट का पेज

backdrop.dart में, _BackdropTitle नाम की नई क्लास बनाएं.

// TODO: Add _BackdropTitle class (104)
class _BackdropTitle extends AnimatedWidget {
  final void Function() onPress;
  final Widget frontTitle;
  final Widget backTitle;

  const _BackdropTitle({
    Key? key,
    required Animation<double> listenable,
    required this.onPress,
    required this.frontTitle,
    required this.backTitle,
  }) : _listenable = listenable, 
       super(key: key, listenable: listenable);

  final Animation<double> _listenable;

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = _listenable;

    return DefaultTextStyle(
      style: Theme.of(context).textTheme.titleLarge!,
      softWrap: false,
      overflow: TextOverflow.ellipsis,
      child: Row(children: <Widget>[
        // branded icon
        SizedBox(
          width: 72.0,
          child: IconButton(
            padding: const EdgeInsets.only(right: 8.0),
            onPressed: this.onPress,
            icon: Stack(children: <Widget>[
              Opacity(
                opacity: animation.value,
                child: const ImageIcon(AssetImage('assets/slanted_menu.png')),
              ),
              FractionalTranslation(
                translation: Tween<Offset>(
                  begin: Offset.zero,
                  end: const Offset(1.0, 0.0),
                ).evaluate(animation),
                child: const ImageIcon(AssetImage('assets/diamond.png')),
              )]),
          ),
        ),
        // Here, we do a custom cross fade between backTitle and frontTitle.
        // This makes a smooth animation between the two texts.
        Stack(
          children: <Widget>[
            Opacity(
              opacity: CurvedAnimation(
                parent: ReverseAnimation(animation),
                curve: const Interval(0.5, 1.0),
              ).value,
              child: FractionalTranslation(
                translation: Tween<Offset>(
                  begin: Offset.zero,
                  end: const Offset(0.5, 0.0),
                ).evaluate(animation),
                child: backTitle,
              ),
            ),
            Opacity(
              opacity: CurvedAnimation(
                parent: animation,
                curve: const Interval(0.5, 1.0),
              ).value,
              child: FractionalTranslation(
                translation: Tween<Offset>(
                  begin: const Offset(-0.25, 0.0),
                  end: Offset.zero,
                ).evaluate(animation),
                child: frontTitle,
              ),
            ),
          ],
        )
      ]),
    );
  }
}

_BackdropTitle एक कस्टम विजेट है. यह AppBar विजेट के title पैरामीटर के लिए, सामान्य Text विजेट की जगह लेगा. इसमें ऐनिमेटेड मेन्यू आइकॉन है. साथ ही, सामने और पीछे के टाइटल के बीच ऐनिमेटेड ट्रांज़िशन हैं. ऐनिमेटेड मेन्यू आइकॉन में नई ऐसेट का इस्तेमाल किया जाएगा. pubspec.yaml में नए slanted_menu.png का रेफ़रंस जोड़ा जाना चाहिए.

assets:
    - assets/diamond.png
    # TODO: Add slanted menu asset (104)
    - assets/slanted_menu.png
    - packages/shrine_images/0-0.jpg

AppBar बिल्डर में जाकर, leading प्रॉपर्टी हटाएं. कस्टम ब्रैंड किए गए आइकॉन को leading विजेट की मूल जगह पर रेंडर करने के लिए, आइकॉन को हटाना ज़रूरी है. ब्रैंड किए गए आइकॉन के लिए, ऐनिमेशन listenable और onPress हैंडलर को _BackdropTitle पर पास किया जाता है. frontTitle और backTitle को भी पास किया जाता है, ताकि इन्हें बैकड्रॉप के टाइटल में रेंडर किया जा सके. AppBar का title पैरामीटर ऐसा दिखना चाहिए:

// TODO: Create title with _BackdropTitle parameter (104)
title: _BackdropTitle(
  listenable: _controller.view,
  onPress: _toggleBackdropLayerVisibility,
  frontTitle: widget.frontTitle,
  backTitle: widget.backTitle,
),

ब्रैंडेड आइकॉन को _BackdropTitle. में बनाया गया है. इसमें ऐनिमेटेड आइकॉन का Stack शामिल है: एक तिरछा मेन्यू और एक डायमंड, जिसे IconButton में रैप किया गया है, ताकि इसे दबाया जा सके. इसके बाद, IconButton को SizedBox में रैप किया जाता है, ताकि आइकॉन को हॉरिज़ॉन्टल तरीके से मूव किया जा सके.

Flutter के "हर चीज़ एक विजेट है" आर्किटेक्चर की मदद से, डिफ़ॉल्ट AppBar के लेआउट में बदलाव किया जा सकता है. इसके लिए, पूरी तरह से नया कस्टम AppBar विजेट बनाने की ज़रूरत नहीं होती. title पैरामीटर को ज़्यादा जटिल _BackdropTitle से बदला जा सकता है. यह पैरामीटर, मूल रूप से Text विजेट होता है. _BackdropTitle में कस्टम आइकॉन भी शामिल होता है. इसलिए, यह leading प्रॉपर्टी की जगह ले लेता है. अब इसे हटाया जा सकता है. इस तरह से विजेट को आसानी से बदला जा सकता है. इसके लिए, किसी अन्य पैरामीटर में बदलाव करने की ज़रूरत नहीं होती. जैसे, ऐक्शन आइकॉन. ये आइकॉन पहले की तरह ही काम करते रहते हैं.

लॉगिन स्क्रीन पर वापस जाने का शॉर्टकट जोड़ना

backdrop.dart,ऐप्लिकेशन बार में मौजूद आखिर के दो आइकॉन से, लॉगिन स्क्रीन पर वापस जाने का शॉर्टकट जोड़ें: आइकॉन के सिमैंटिक लेबल बदलकर, उनके नए मकसद को दिखाएं.

        // TODO: Add shortcut to login screen from trailing icons (104)
        IconButton(
          icon: const Icon(
            Icons.search,
            semanticLabel: 'login', // New code
          ),
          onPressed: () {
            // TODO: Add open login (104)
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => LoginPage()),
            );
          },
        ),
        IconButton(
          icon: const Icon(
            Icons.tune,
            semanticLabel: 'login', // New code
          ),
          onPressed: () {
            // TODO: Add open login (104)
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => LoginPage()),
            );
          },
        ),

फिर से लोड करने की कोशिश करने पर, आपको गड़बड़ी का मैसेज मिलेगा. गड़बड़ी ठीक करने के लिए, login.dart इंपोर्ट करें:

import 'login.dart';

ऐप्लिकेशन को फिर से लोड करें. इसके बाद, लॉगिन स्क्रीन पर वापस जाने के लिए, खोजें या ट्यून करें बटन पर टैप करें.

9. बधाई हो!

इन चार कोडलैब के दौरान, आपने Material Components का इस्तेमाल करके, यूज़र इंटरफ़ेस (यूआई) को बेहतर बनाने का तरीका सीखा. इससे आपको अपने ब्रैंड की पहचान और स्टाइल को दिखाने में मदद मिलेगी.

अगले चरण

यह कोडलैब, MDC-104, कोडलैब की इस सीरीज़ को पूरा करता है. Material Components widgets catalog पर जाकर, Material Flutter में मौजूद अन्य कॉम्पोनेंट के बारे में भी जाना जा सकता है.

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

आपकी दिलचस्पी के हिसाब से, आज़माने के लिए कई अन्य Flutter कोडलैब उपलब्ध हैं. हमारे पास Material के लिए एक और कोडलैब है, जिसमें आपकी दिलचस्पी हो सकती है: Flutter के लिए Material Motion की मदद से बेहतरीन ट्रांज़िशन बनाना.

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

पूरी तरह सहमत सहमत न तो सहमत, न ही असहमत असहमत पूरी तरह असहमत

मुझे आने वाले समय में, Material Components का इस्तेमाल जारी रखना है

पूरी तरह सहमत सहमत न तो सहमत, न ही असहमत असहमत पूरी तरह असहमत