MDC-102 Flutter: מבנה ופריסה של החומר

1. מבוא

logo_components_color_2x_web_96dp.png

Material Components (MDC) עוזר למפתחים להטמיע Material Design. MDC נוצר על ידי צוות של מהנדסים ומעצבי חוויית המשתמש ב-Google, שכולל עשרות רכיבים יפים ופונקציונליים של ממשק המשתמש. זמין ל-Android, ל-iOS, לאינטרנט ול-Flutter.material.io/develop

ב-codelab MDC-101 השתמשת בשני רכיבי Material כדי לבנות דף התחברות: שדות טקסט ולחצנים עם גלי דיו. עכשיו נרחיב את הבסיס הזה על ידי הוספת ניווט, מבנה ונתונים.

מה תפַתחו

ב-Codelab הזה תבנו מסך בית לאפליקציה בשם Shrine, אפליקציית מסחר אלקטרוני שמוכרת בגדים ומוצרים לבית. היא תכלול:

  • סרגל עליון באפליקציה
  • רשימת רשת מלאה במוצרים

Android

iOS

אפליקציית מסחר אלקטרוני עם סרגל אפליקציות מוביל ורשת מלאה במוצרים

אפליקציית מסחר אלקטרוני עם סרגל אפליקציות מוביל ורשת מלאה במוצרים

רכיבים ומערכות משנה של Material Flutter ב-Codelab הזה

  • סרגל האפליקציה העליון
  • רשתות
  • כרטיסים

איזה דירוג מגיע לדעתך לרמת הניסיון שלך בפיתוח Flutter?

מתחילים בינונית בקיאים

2. הגדרת סביבת הפיתוח של Flutter

כדי להשלים את שיעור ה-Lab הזה אתם צריכים שתי תוכנות: Flutter SDK וכלי עריכה.

אפשר להריץ את Codelab באמצעות כל אחד מהמכשירים הבאים:

  • מכשיר פיזי שמשמש ל-Android או ל-iOS שמחובר למחשב ומוגדר ל'מצב פיתוח'.
  • הסימולטור של iOS (צריך להתקין כלים של Xcode).
  • האמולטור של Android (נדרשת הגדרה ב-Android Studio).
  • דפדפן (Chrome נדרש לניפוי באגים).
  • בתור אפליקציית Windows , Linux או macOS למחשב. צריך לפתח בפלטפורמה שבה אתם מתכננים לפרוס. לכן, כדי לפתח אפליקציה למחשב של Windows, צריך לפתח את האפליקציה ב-Windows כדי לגשת לשרשרת ה-build המתאימה. יש דרישות ספציפיות למערכת ההפעלה שמפורטות בהרחבה בכתובת docs.flutter.dev/desktop.

3. הורדת האפליקציה לתחילת העבודה של Codelab

ממשיכים לעבור מ-MDC-101?

אם השלמתם את התהליך MDC-101, הקוד שלכם אמור להיות מוכן ל-Codelab הזה. דלגו לשלב: הוספת סרגל אפליקציה עליון.

מתחילים מאפס?

הורדה של האפליקציה לתחילת פעולה של Codelab

האפליקציה לתחילת הפעולה נמצאת בספרייה material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series.

...או לשכפל אותו מ-GitHub

כדי לשכפל את ה-Codelab הזה מ-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. פועלים לפי ההוראות ל'הפעלת האפליקציה'. בקטע שנתחיל?: נסיעת מבחן בכלי העריכה שבחרתם.

הצלחת! דף ההתחברות לאתר אמור להופיע ב-MDC-101 Codelab במכשיר.

Android

iOS

דף התחברות עם השדות 'שם משתמש' ו'סיסמה', 'ביטול' ו'הבא'

דף התחברות עם השדות 'שם משתמש' ו'סיסמה', 'ביטול' ו'הבא'

עכשיו, כשמסך ההתחברות נראה טוב, כדאי לאכלס את האפליקציה במוצרים.

4. הוספת סרגל אפליקציה עליון

עכשיו, אם תלחצו על "הבא", תוכל לראות את מסך הבית שבו כתוב 'הצלחת!'. נהדר! אבל עכשיו למשתמש שלנו אין שום פעולה לבצע, ואין לו מושג איפה הוא נמצא באפליקציה. כדי לעזור, הגיע הזמן להוסיף ניווט.

עיצוב חדשני (Material Design) כולל דפוסי ניווט שמבטיחים רמה גבוהה של נוחות השימוש. אחד הרכיבים הבולטים ביותר הוא סרגל האפליקציה העליון.

כדי לאפשר ניווט ולתת למשתמשים גישה מהירה לפעולות אחרות, כדאי להוסיף סרגל עליון באפליקציה.

הוספת ווידג'ט של 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, מוסיפים כותרת ל-AppBar:

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

שומרים את הפרויקט.

Android

iOS

סרגל אפליקציות עם שם ה'מקדש'

סרגל אפליקציות עם שם ה'מקדש'

בסרגלי אפליקציות רבים מופיע לחצן לצד הכותרת. שנוסיף סמל תפריט באפליקציה שלנו.

הוספת לחצן סמל מוביל

עדיין בתוך home.dart, צריך להגדיר לחצן Icon לשדה leading: של ה-AppBar. (יש להוסיף אותו לפני השדה title: כדי לחקות את סדר ההמשך):

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

שומרים את הפרויקט.

Android

iOS

סרגל אפליקציות עם הכותרת 'מקדש' וסמל של תפריט המבורגר

סרגל אפליקציות עם הכותרת 'מקדש' וסמל של תפריט המבורגר

סמל התפריט (שנקרא גם 'המבורגר') מופיע בדיוק במקום שבו ציפיתם.

אפשר גם להוסיף לחצנים בסוף הכותרת. ב-Flutter, הפעולות האלה נקראות 'פעולות'.

הוספת פעולות

יש מקום לשני לחצני סמלים נוספים.

מוסיפים אותם למופע של 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

סרגל אפליקציות עם כותרת בשם &#39;מקדש&#39; וסמל של תפריט המבורגר, ובסוף החיפוש ניתן לבצע חיפוש והתאמה אישית של סמלים

סרגל אפליקציות עם כותרת בשם &#39;מקדש&#39; וסמל של תפריט המבורגר, ובסוף החיפוש ניתן לבצע חיפוש והתאמה אישית של סמלים

עכשיו יש לאפליקציה לחצן מוביל, כותרת ושתי פעולות בצד ימין. בסרגל האפליקציה מוצג גם גובה באמצעות צל עדין שמראה שהתוכן נמצא בשכבה שונה מהתוכן.

5. איך מוסיפים כרטיס במשבצות

עכשיו, אחרי שיש לאפליקציה שלנו מבנה כלשהו, נארגן את התוכן על ידי הצבתו בכרטיסים.

הוספת GridView

כדי להתחיל, מוסיפים כרטיס אחד מתחת לסרגל האפליקציות העליון. לווידג'ט הכרטיס לבדו אין מספיק מידע כדי להציג אותו במיקום שבו נוכל להציג אותו, לכן כדאי להקיף אותו בווידג'ט מסוג GridView.

מחליפים את המרכז בגוף הפיגום ב-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 מפעילה את ה-constructor של count() כי מספר הפריטים שמוצג בו אפשר לספירה ולא אינסופי. אבל נדרש מידע נוסף כדי להגדיר את הפריסה.

השדה crossAxisCount: מציין את מספר הפריטים בכל הפריטים. אנחנו רוצים ליצור 2 עמודות.

בשדה padding: יש רווח בכל 4 הצדדים של 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'),
            ],
          ),
        ),
      ],
    ),
  )
],

הקוד הזה מוסיף ווידג'ט של עמודה, שמשמש לפריסת הווידג'טים של הצאצאים במאונך.

הערך crossAxisAlignment: field מציין את הערך CrossAxisAlignment.start, כלומר "יש להתאים את הטקסט לקצה העליון".

הווידג'ט AspectRatio קובע את הצורה שבה התמונה תוצג, בלי קשר לסוג התמונה שמתקבלת.

המרווח הפנימי מכניס קצת את הטקסט מהצד.

שני הווידג'טים של Text (טקסט) מוערם באופן אנכי עם 8 נקודות של רווח ריק ביניהם (SizedBox). אנחנו יוצרים עמודה נוספת כדי לאחסן אותם בתוך הריפוד.

שומרים את הפרויקט.

Android

iOS

פריט יחיד עם תמונה, כותרת וטקסט משני

פריט יחיד עם תמונה, כותרת וטקסט משני

בתצוגה המקדימה הזו ניתן לראות שהכרטיס מוטמע מהקצה, עם פינות מעוגלות ועם צל (שמבטא את גובה הכרטיס). הצורה כולה נקראת "מאגר" ב-Material. (להבדיל מסיווג הווידג'ט עצמו שנקרא Container).

בדרך כלל הכרטיסים מוצגים באוסף יחד עם כרטיסים אחרים. נפרוס אותם כאוסף ברשת.

6. יצירת אוסף כרטיסים

בכל פעם שיש כמה כרטיסים במסך, הם מקובצים יחד לאוסף אחד או יותר. קלפים באוסף הם על אותו מישור, כלומר קלפים בעלי אותו גובה במנוחה (אלא אם אוספים או גוררים את הקלפים, אבל לא נעשה זאת כאן).

מכפילים את הכרטיס לאוסף

כרגע הכרטיס שלנו מובנה בתוך השדה children: ב-GridView. יש המון קוד בתוך הקוד שקשה לקרוא. צריך לחלץ את הנתונים לפונקציה שיכולה ליצור כמה כרטיסים ריקים שרוצים, ולהחזיר רשימה של כרטיסים.

יוצרים פונקציה פרטית חדשה מעל הפונקציה build() (חשוב לזכור שפונקציות שמתחילות בקו תחתון הן API פרטי):

// 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! ניתן לעיין במידע נוסף בקטלוג הווידג'טים של רכיבי החומר.

היא פועלת באופן מלא, אבל האפליקציה שלנו עדיין לא מביעה שום מותג או נקודת מבט מסוימת. במשחק MDC-103: עיצוב חומרים בעיצוב חדשני עם צבע, צורה, גובה וסוג, נתאים אישית את הסגנון של הרכיבים האלה כדי לבטא מותג מודרני ותוסס.

הצלחתי להשלים את ה-Codelab הזה תוך השקעה של זמן ומאמץ סבירים

נכון מאוד נכון נייטרלי לא נכון לא נכון בכלל

ארצה להמשיך להשתמש ברכיבי Material Materials בעתיד

נכון מאוד נכון נייטרלי לא נכון לא נכון בכלל