1. מבוא
Material Components (MDC) עוזרים למפתחים להטמיע את Material Design. MDC נוצר על ידי צוות של מהנדסים ומעצבי חוויית משתמש ב-Google, והוא כולל עשרות רכיבי ממשק משתמש יפים ופונקציונליים. הוא זמין ל-Android, ל-iOS, לאינטרנט ול-Flutter.material.io/develop |
ב-Codelab MDC-101 השתמשת בשני רכיבי Material כדי לבנות דף התחברות: שדות טקסט ולחצנים עם גלי דיו. עכשיו נרחיב את הבסיס הזה על ידי הוספת ניווט, מבנה ונתונים.
מה תפַתחו
ב-Codelab הזה תבנו מסך בית לאפליקציה בשם Shrine, אפליקציית מסחר אלקטרוני שמוכרת בגדים ומוצרים לבית. היא תכלול:
- סרגל אפליקציה בחלק העליון
- רשימת רשת מלאה במוצרים
Android | iOS |
רכיבים ותת-מערכות של Material Flutter בקודלאב הזה
- סרגל האפליקציה העליון
- רשתות
- כרטיסים
מה מידת הניסיון שלך בפיתוח ב-Flutter?
2. הגדרת סביבת הפיתוח של Flutter
כדי להשלים את שיעור ה-Lab הזה אתם צריכים שתי תוכנות: Flutter SDK וכלי עריכה.
אפשר להריץ את הקודלאב בכל אחד מהמכשירים הבאים:
- מכשיר 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 לתחילת הדרך
האפליקציה לתחילת הפעולה נמצאת בספרייה material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series
.
...או לשכפל אותו מ-GitHub
כדי להעתיק (clone) את סדנת הקוד הזו מ-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
פותחים את הפרויקט ומפעילים את האפליקציה
- פותחים את הפרויקט בכלי העריכה שבחרתם.
- פועלים לפי ההוראות ל'הפעלת האפליקציה' בקטע תחילת העבודה: נסיעת מבחן בעורך שבחרתם.
הצלחת! בדף שנפתח במכשיר אמור להופיע דף הכניסה של Shrine מהקודלאב של MDC-101.
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
, מוסיפים כותרת לסרגל האפליקציות:
// TODO: Add app bar (102)
appBar: AppBar(
// TODO: Add buttons and title (102)
title: const Text('SHRINE'),
// TODO: Add trailing buttons (102)
שומרים את הפרויקט.
Android | iOS |
בסרגלי אפליקציות רבים מופיע לחצן לצד הכותרת. נוסיף סמל תפריט לאפליקציה.
הוספת 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 |
סמל התפריט (המכונה גם 'המבורגר') מופיע בדיוק במקום שבו הוא אמור להופיע.
אפשר גם להוסיף לחצנים לחלק העורפי של הכותרת. ב-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 |
עכשיו יש לאפליקציה לחצן מוביל, כותרת ושתי פעולות בצד ימין. בסרגל האפליקציות מוצג גם גובה באמצעות צללית עדינה שמראה שהוא נמצא בשכבה שונה מהתוכן.
5. הוספת כרטיס לרשת
עכשיו, אחרי שיש לאפליקציה שלנו מבנה כלשהו, נארגן את התוכן על ידי הצבתו בכרטיסים.
הוספת GridView
נתחיל בהוספת כרטיס אחד מתחת לסרגל האפליקציות העליון. לווידג'ט Card אין מספיק מידע כדי להציג את עצמו במקום שבו נוכל לראות אותו, לכן נצטרך להכיל אותו בווידג'ט 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 |
בתצוגה המקדימה הזו ניתן לראות שהכרטיס מוטמע מהקצה, עם פינות מעוגלות ועם צל (שמבטא את גובה הכרטיס). הצורה כולה נקראת ה"מאגר" בחומר. (לא להתבלבל עם סוג הווידג'ט בפועל שנקרא 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: עיצוב חומרים בעיצוב חדשני עם צבע, צורה, גובה וסוג, נתאים אישית את הסגנון של הרכיבים האלה כדי לבטא מותג מודרני ותוסס.