MDC-102 Flutter: بنية المواد والتصميم

1. مقدمة

logo_components_color_2x_web_96dp.png

تساعد Material Components (MDC) المطوّرين في تنفيذ التصميم المتعدد الأبعاد. تم إنشاء MDC بواسطة فريق من المهندسين ومصممي تجربة المستخدم في Google، وتتضمّن عشرات المكوّنات الجميلة والوظيفية لواجهة المستخدم، وهي متاحة على Android وiOS والويب وFlutter.material.io/develop

في الدرس التطبيقي حول الترميز MDC-101، استخدَمت مكوّنَين من "مكوّنات التصميم المتعدد الأبعاد" لإنشاء صفحة تسجيل دخول: حقول نصية وأزرار مع تموّجات الحبر. والآن، لنوسّع نطاق هذا الأساس من خلال إضافة عناصر التنقّل والبنية والبيانات.

ما ستنشئه

في هذا الدرس التطبيقي حول الترميز، ستنشئ شاشة رئيسية لتطبيق يُسمى Shrine، وهو تطبيق للتجارة الإلكترونية يبيع الملابس والسلع المنزلية. ستتضمّن هذه الرسالة ما يلي:

  • شريط تطبيق علوي
  • قائمة شبكية مليئة بالمنتجات

Android

iOS

تطبيق للتجارة الإلكترونية يتضمّن شريط تطبيقات علويًا وشبكة مليئة بالمنتجات

تطبيق للتجارة الإلكترونية يتضمّن شريط تطبيقات علويًا وشبكة مليئة بالمنتجات

مكوّنات Material Flutter والأنظمة الفرعية في هذا الدرس التطبيقي حول الترميز

  • شريط التطبيق العلوي
  • الشبكات
  • بيانات بطاقات الائتمان

ما هو تقييمك لمستوى خبرتك في تطوير تطبيقات Flutter؟

مبتدئ متوسط متقدّم

2. إعداد بيئة تطوير Flutter

تحتاج إلى برنامجَين لإكمال هذا الدرس التطبيقي، وهما حزمة تطوير البرامج (SDK) الخاصة بإطار عمل Flutter ومحرِّر.

يمكنك تشغيل الدرس العملي باستخدام أيّ من الأجهزة التالية:

  • جهاز Android أو iOS فعلي متصل بالكمبيوتر وتم ضبطه على "وضع مطور البرامج"
  • محاكي iOS (يتطلّب تثبيت أدوات Xcode)
  • محاكي Android (يتطلّب الإعداد في "استوديو Android")
  • متصفّح (يجب استخدام Chrome لتصحيح الأخطاء).
  • كتطبيق سطح مكتب على Windows أو Linux أو macOS يجب أن يتم التطوير على النظام الأساسي الذي تخطّط للنشر عليه. لذا، إذا أردت تطوير تطبيق سطح مكتب لنظام التشغيل Windows، يجب أن يتم التطوير على Windows للوصول إلى سلسلة الإنشاء المناسبة. هناك متطلبات خاصة بنظام التشغيل يتم تناولها بالتفصيل على الرابط docs.flutter.dev/desktop.

3- تنزيل تطبيق بدء الدرس التطبيقي حول الترميز

هل سبق لك المشاركة في دورة MDC-101؟

إذا أكملت MDC-101، من المفترض أن يكون الرمز البرمجي جاهزًا لهذا الدرس التطبيقي. انتقِل إلى الخطوة: إضافة شريط تطبيق علوي.

هل تريد البدء من الصفر؟

تنزيل تطبيق الدرس التطبيقي حول الترميز للمبتدئين

يقع تطبيق البداية في دليل material-components-flutter-codelabs-102-starter_and_101-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 102-starter_and_101-complete

افتح المشروع وشغِّل التطبيق

  1. افتح المشروع في المحرِّر الذي تختاره.
  2. اتّبِع التعليمات الواردة في مقالة البدء: تجربة القيادة ضمن القسم "تشغيل التطبيق" في المحرّر الذي اخترته.

اكتمال النقل بنجاح يجب أن تظهر لك صفحة تسجيل الدخول إلى Shrine من الدرس التطبيقي حول الترميز MDC-101 على جهازك.

Android

iOS

صفحة تسجيل الدخول التي تتضمّن حقلَي اسم المستخدم وكلمة المرور وزرَّي الإلغاء والتالي

صفحة تسجيل الدخول التي تتضمّن حقلَي اسم المستخدم وكلمة المرور وزرَّي الإلغاء والتالي

بعد أن أصبح شكل شاشة تسجيل الدخول جيدًا، لنملأ التطبيق ببعض المنتجات.

4. إضافة شريط التطبيق العلوي

في الوقت الحالي، إذا نقرت على الزر "التالي"، ستتمكّن من رؤية الشاشة الرئيسية التي تعرض الرسالة "أحسنت!". رائع. ولكن الآن لا يمكن للمستخدم اتّخاذ أي إجراءات، ولا يمكنه معرفة مكانه في التطبيق. وللمساعدة، حان الوقت لإضافة عناصر التنقّل.

يوفّر التصميم المتعدد الأبعاد أنماط تنقّل تضمن درجة عالية من سهولة الاستخدام. أحد أكثر المكوّنات وضوحًا هو شريط التطبيق العلوي.

لتوفير التنقّل ومنح المستخدمين إمكانية الوصول السريع إلى إجراءات أخرى، لنضِف شريط تطبيق علويًا.

إضافة أداة AppBar

في home.dart، أضِف AppBar إلى Scaffold وأزِل const المميّز:

return const Scaffold(
  // TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
  ),

من خلال إضافة AppBar إلى حقل appBar: في Scaffold، نحصل على تصميم مثالي مجانًا، مع إبقاء AppBar في أعلى الصفحة والنص الأساسي في الأسفل.

إضافة أداة نص

في home.dart، أضِف عنوانًا إلى شريط التطبيقات:

// TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
    title: const Text('SHRINE'),
    // TODO: Add trailing buttons (102)

احفظ مشروعك.

Android

iOS

شريط تطبيق يحمل اسم Shrine كعنوان

شريط تطبيق يحمل اسم Shrine كعنوان

تحتوي العديد من أشرطة التطبيقات على زر بجانب العنوان. لنضِف رمز قائمة في تطبيقنا.

إضافة IconButton في المقدّمة

أثناء تواجدك في home.dart، اضبط IconButton للحقل leading: في AppBar. (ضَعها قبل الحقل title: لمحاكاة الترتيب من البداية إلى النهاية):

    // TODO: Add buttons and title (102)
    leading: IconButton(
      icon: const Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

احفظ مشروعك.

Android

iOS

شريط تطبيق يحمل اسم Shrine كعنوان ورمز قائمة هامبرغر

شريط تطبيق يحمل اسم Shrine كعنوان ورمز قائمة هامبرغر

يظهر رمز القائمة (المعروف أيضًا باسم "الهامبرغر") في المكان المتوقّع.

يمكنك أيضًا إضافة أزرار إلى الجانب الأخير من العنوان. في Flutter، تُعرف هذه الإجراءات باسم "actions".

إضافة إجراءات

يمكن إضافة زرَّين آخرَين من النوع IconButton.

أضِفها إلى مثيل AppBar بعد العنوان:

// TODO: Add trailing buttons (102)
actions: <Widget>[
  IconButton(
    icon: const Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: const Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

احفظ مشروعك. يجب أن تبدو شاشتك الرئيسية على النحو التالي:

Android

iOS

شريط تطبيق يتضمّن Shrine كعنوان وأيقونة قائمة هامبرغر، بالإضافة إلى أيقونتي البحث والتخصيص في النهاية

شريط تطبيق يتضمّن Shrine كعنوان وأيقونة قائمة هامبرغر، بالإضافة إلى أيقونتي البحث والتخصيص في النهاية

يحتوي التطبيق الآن على زر رئيسي وعنوان وإجراءَين على الجانب الأيسر. يعرض شريط التطبيق أيضًا الارتفاع باستخدام ظل خفيف يشير إلى أنّه يقع في طبقة مختلفة عن المحتوى.

5- إضافة بطاقة في شبكة

بعد أن أصبح تطبيقنا يتضمّن بعض البنية، لننظّم المحتوى من خلال وضعه في بطاقات.

إضافة GridView

لنبدأ بإضافة بطاقة واحدة أسفل شريط التطبيقات العلوي. لا تحتوي أداة البطاقة وحدها على معلومات كافية لتحديد موضعها على الشاشة، لذا سنحتاج إلى تضمينها في أداة GridView.

استبدِل العنصر Center في نص Scaffold بالعنصر GridView:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: Build a grid of cards (102)
  children: <Widget>[Card()],
),

دعونا نحلّل هذا الرمز. يستدعي GridView الدالة الإنشائية count() لأنّ عدد العناصر التي يعرضها قابل للعد وليس لانهائيًا. ولكنّه يحتاج إلى مزيد من المعلومات لتحديد تخطيطه.

تحدّد السمة crossAxisCount: عدد العناصر المعروضة بشكل أفقي. نريد عمودَين.

يوفر الحقل padding: مساحة على جميع الجوانب الأربعة من GridView. بالطبع، لا يمكنك رؤية المساحة المتروكة على الجانبين الأخير أو السفلي لأنّه لا توجد عناصر GridView ثانوية بجانبهما بعد.

يحدّد الحقل childAspectRatio: حجم السلع استنادًا إلى نسبة العرض إلى الارتفاع (العرض مقسومًا على الارتفاع).

تنشئ GridView تلقائيًا مربّعات متساوية الحجم.

لدينا بطاقة واحدة ولكنّها فارغة. لنضِف الآن عناصر واجهة مستخدم فرعية إلى البطاقة.

تنسيق المحتوى

يجب أن تتضمّن البطاقات أقسامًا للصورة والعنوان والنص الثانوي.

عدِّل عناصر GridView الفرعية:

// TODO: Build a grid of cards (102)
children: <Widget>[
  Card(
    clipBehavior: Clip.antiAlias,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              const SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],

يضيف هذا الرمز أداة Column مستخدَمة لتنسيق الأدوات الفرعية عموديًا.

تحدّد crossAxisAlignment: field القيمة CrossAxisAlignment.start، ما يعني "محاذاة النص إلى الحافة الأمامية".

يحدّد التطبيق المصغّر AspectRatio شكل الصورة بغض النظر عن نوع الصورة المقدَّمة.

تؤدي المسافة الداخلية إلى إزاحة النص قليلاً من الجانب.

يتم ترتيب أداتَي النص عموديًا مع ترك مساحة فارغة بينهما بمقدار 8 نقاط (SizedBox). ننشئ عمودًا آخر لتضمينها داخل المساحة المتروكة.

احفظ مشروعك.

Android

iOS

عنصر واحد يتضمّن صورة وعنوانًا ونصًا ثانويًا

عنصر واحد يتضمّن صورة وعنوانًا ونصًا ثانويًا

في هذه المعاينة، يمكنك ملاحظة أنّ البطاقة مضمّنة من الحافة، مع زوايا مستديرة وظل (يعبّر عن ارتفاع البطاقة). يُطلق على الشكل الكامل اسم "الحاوية" في Material. (يجب عدم الخلط بينه وبين فئة الأداة الفعلية المسماة Container).

عادةً ما يتم عرض البطاقات في مجموعة مع بطاقات أخرى. لنرتّبها كمجموعة في شبكة.

6. إنشاء مجموعة بطاقات

عندما تظهر بطاقات متعدّدة على الشاشة، يتم تجميعها معًا في مجموعة واحدة أو أكثر. تكون البطاقات في المجموعة على المستوى نفسه، ما يعني أنّ البطاقات تشترك في مستوى الارتفاع نفسه (إلا إذا تم التقاط البطاقات أو سحبها، ولكن لن نفعل ذلك هنا).

مضاعفة البطاقة في مجموعة

في الوقت الحالي، يتم إنشاء البطاقة بشكل مضمّن في الحقل children: الخاص بـ GridView. هذا الكثير من الرموز البرمجية المتداخلة التي قد يصعب قراءتها. لنستخرجه في دالة يمكنها إنشاء أكبر عدد ممكن من البطاقات الفارغة، وتعرض قائمة بالبطاقات.

أنشئ دالة خاصة جديدة فوق الدالة build() (تذكَّر أنّ الدوال التي تبدأ بشرطة سفلية هي واجهة برمجة تطبيقات خاصة):

// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) {
      return Card(
        clipBehavior: Clip.antiAlias,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            AspectRatio(
              aspectRatio: 18.0 / 11.0,
              child: Image.asset('assets/diamond.png'),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const <Widget>[
                  Text('Title'),
                  SizedBox(height: 8.0),
                  Text('Secondary Text'),
                ],
              ),
            ),
          ],
        ),
      );
    },
  );
  return cards;
}

عيِّن البطاقات التي تم إنشاؤها إلى حقل children في GridView. تذكَّر استبدال كل ما يتضمّنه GridView بهذا الرمز الجديد:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

احفظ مشروعك.

Android

iOS

شبكة من العناصر تتضمّن صورة وعنوانًا ونصًا ثانويًا

شبكة من العناصر تتضمّن صورة وعنوانًا ونصًا ثانويًا

تظهر البطاقات، ولكنّها لا تعرض أي معلومات بعد. حان الوقت الآن لإضافة بيانات المنتجات.

إضافة بيانات المنتج

يحتوي التطبيق على بعض المنتجات التي تتضمّن صورًا وأسماءً وأسعارًا. لنضِف ذلك إلى التطبيقات المصغّرة المتوفّرة في البطاقة حاليًا

بعد ذلك، في home.dart، استورِد حزمة جديدة وبعض الملفات التي قدّمناها لنموذج بيانات:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

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

أخيرًا، غيِّر _buildGridCards() لجلب معلومات المنتج، واستخدِم هذه البيانات في البطاقات:

// TODO: Make a collection of cards (102)

// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products.isEmpty) {
    return const <Card>[];
  }

  final ThemeData theme = Theme.of(context);
  final NumberFormat formatter = NumberFormat.simpleCurrency(
      locale: Localizations.localeOf(context).toString());

  return products.map((product) {
    return Card(
      clipBehavior: Clip.antiAlias,
      // TODO: Adjust card heights (103)
      child: Column(
        // TODO: Center items on the card (103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: Adjust the box size (102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: Align labels to the bottom and center (103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: Change innermost Column (103)
                children: <Widget>[
                 // TODO: Handle overflowing labels (103)
                 Text(
                    product.name,
                    style: theme.textTheme.titleLarge,
                    maxLines: 1,
                  ),
                  const SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.titleSmall,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}

ملاحظة: لن يتم تجميع البرنامج وتشغيله بعد. لدينا تغيير آخر.

عليك أيضًا تغيير الدالة build() لتمرير BuildContext إلى _buildGridCards() قبل محاولة التجميع:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

أعِد تشغيل التطبيق.

Android

iOS

شبكة من العناصر تتضمّن صورة وعنوان المنتج وسعره

شبكة من العناصر تتضمّن صورة وعنوان المنتج وسعره

قد تلاحظ أنّنا لا نضيف أي مساحة عمودية بين البطاقات. ويرجع ذلك إلى أنّها تتضمّن تلقائيًا 4 نقاط من الهامش في أعلى وأسفل الصفحة.

احفظ مشروعك.

تظهر بيانات المنتج، ولكن تتضمّن الصور مساحة إضافية حولها. يتم رسم الصور باستخدام BoxFit بقيمة .scaleDown تلقائيًا (في هذه الحالة). لنغيّرها إلى .fitWidth لكي يتم تكبيرها قليلاً وإزالة المسافة البيضاء الإضافية.

أضِف الحقل fit: إلى الصورة بالقيمة BoxFit.fitWidth:

  // TODO: Adjust the box size (102)
  fit: BoxFit.fitWidth,

Android

iOS

شبكة من العناصر مع صورة مقصوصة وعنوان المنتج وسعره

تظهر منتجاتنا الآن في التطبيق بشكلٍ مثالي.

7. تهانينا!

يتضمّن تطبيقنا مسارًا أساسيًا ينقل المستخدم من شاشة تسجيل الدخول إلى شاشة رئيسية يمكن فيها عرض المنتجات. من خلال بضعة أسطر من الرمز البرمجي، أضفنا شريط تطبيق علويًا (يتضمّن عنوانًا وثلاثة أزرار) وبطاقات (لعرض محتوى تطبيقنا). أصبحت شاشتنا الرئيسية بسيطة وعملية، وتتضمّن بنية أساسية ومحتوى قابلاً للتنفيذ.

الخطوات التالية

باستخدام شريط التطبيق العلوي والبطاقة وحقل النص والزر، نكون قد استخدمنا الآن أربعة مكوّنات أساسية من مكتبة Material Flutter. يمكنك استكشاف المزيد من خلال الانتقال إلى كتالوج التطبيقات المصغّرة لمكوّنات Material.

على الرغم من أنّ تطبيقنا يعمل بكامل طاقته، إلا أنّه لا يعبّر بعد عن أي علامة تجارية أو وجهة نظر معيّنة. في MDC-103: التصميم المتعدد الأبعاد باستخدام اللون والشكل والارتفاع والنوع، سنخصّص نمط هذه المكوّنات للتعبير عن علامة تجارية عصرية وحيوية.

تمكّنتُ من إكمال هذا الدرس التطبيقي حول الترميز خلال فترة زمنية معقولة وبجهد معقول

أوافق بشدة أوافق لا أوافق ولا أعارض لا أوافق لا أوافق أبدًا

أريد مواصلة استخدام "مكوّنات Material" في المستقبل

أوافق بشدة أوافق لا أوافق ولا أعارض لا أوافق لا أوافق أبدًا