1. บทนำ
| Material Components (MDC) ช่วยให้นักพัฒนาแอปใช้งาน Material Design ได้ MDC สร้างขึ้นโดยทีมวิศวกรและนักออกแบบ UX ของ Google โดยมีคอมโพเนนต์ UI ที่สวยงามและใช้งานได้หลายสิบรายการ และพร้อมใช้งานสำหรับ Android, iOS, เว็บ และ Flutter.material.io/develop |
ใน Codelab MDC-101 คุณใช้คอมโพเนนต์ Material 2 รายการเพื่อสร้างหน้าลงชื่อเข้าใช้ ได้แก่ ช่องข้อความและปุ่มที่มีระลอกหมึก ตอนนี้มาต่อยอดจากพื้นฐานนี้ด้วยการเพิ่มการนำทาง โครงสร้าง และข้อมูลกัน
สิ่งที่คุณจะสร้าง
ในโค้ดแล็บนี้ คุณจะได้สร้างหน้าจอหลักสำหรับแอปชื่อ Shrine ซึ่งเป็นแอปอีคอมเมิร์ซที่ขายเสื้อผ้าและของใช้ในบ้าน โดยจะประกอบด้วยข้อมูลต่อไปนี้
- แถบแอปด้านบน
- รายการตารางกริดที่เต็มไปด้วยผลิตภัณฑ์
Android | iOS |
|
|
คอมโพเนนต์และระบบย่อยของ Material Flutter ในโค้ดแล็บนี้
- แถบแอปด้านบน
- ตารางกริด
- การ์ด
คุณจะให้คะแนนระดับประสบการณ์ในการพัฒนาแอปด้วย Flutter เท่าไร
2. ตั้งค่าสภาพแวดล้อมในการพัฒนา Flutter
คุณต้องมีซอฟต์แวร์ 2 อย่างเพื่อทำแล็บนี้ให้เสร็จสมบูรณ์ ได้แก่ Flutter SDK และโปรแกรมแก้ไข
คุณเรียกใช้ Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาแอป
- โปรแกรมจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- Android Emulator (ต้องตั้งค่าใน Android Studio)
- เบราว์เซอร์ (ต้องใช้ Chrome สำหรับการแก้ไขข้อบกพร่อง)
- ในรูปแบบแอปพลิเคชันเดสก์ท็อป Windows, Linux หรือ macOS คุณต้องพัฒนาบนแพลตฟอร์มที่วางแผนจะใช้งาน ดังนั้น หากต้องการพัฒนาแอปเดสก์ท็อป Windows คุณต้องพัฒนาบน Windows เพื่อเข้าถึงห่วงโซ่การสร้างที่เหมาะสม มีข้อกำหนดเฉพาะของระบบปฏิบัติการที่อธิบายไว้โดยละเอียดใน 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
หากต้องการโคลนโค้ดแล็บนี้จาก 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 จาก Codelab 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 ให้เพิ่มชื่อใน 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 |
|
|
แถบแอปหลายแถบมีปุ่มข้างชื่อ มาเพิ่มไอคอนเมนูในแอปกัน
เพิ่ม 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 สิ่งเหล่านี้เรียกว่า "การดำเนินการ"
เพิ่มการดำเนินการ
ยังมีพื้นที่สำหรับ IconButton อีก 2 รายการ
เพิ่มลงในอินสแตนซ์ 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 |
|
|
ตอนนี้แอปมีปุ่มนำ ชื่อ และการดำเนินการ 2 อย่างทางด้านขวา แถบแอปยังแสดงการยกระดับโดยใช้เงาที่ละเอียดอ่อนซึ่งแสดงว่าแถบแอปอยู่บนเลเยอร์ที่ต่างจากเนื้อหา
5. เพิ่มการ์ดในตารางกริด
ตอนนี้แอปของเรามีโครงสร้างแล้ว มาจัดระเบียบเนื้อหาโดยใส่ลงในการ์ดกัน
เพิ่ม GridView
มาเริ่มกันด้วยการเพิ่มการ์ด 1 ใบใต้แถบแอปด้านบน วิดเจ็ต Card เพียงอย่างเดียวมีข้อมูลไม่เพียงพอที่จะจัดวางตัวเองในตำแหน่งที่เราจะเห็นได้ ดังนั้นเราจึงต้องห่อหุ้มวิดเจ็ตนี้ไว้ในวิดเจ็ต 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: ระบุจำนวนรายการในแนวนอน เราต้องการ 2 คอลัมน์
padding: ฟิลด์จะให้พื้นที่ทั้ง 4 ด้านของ GridView แน่นอนว่าคุณจะมองไม่เห็นระยะขอบที่ด้านท้ายหรือด้านล่างเนื่องจากยังไม่มีองค์ประกอบย่อย GridView อยู่ข้างๆ
ฟิลด์ childAspectRatio: จะระบุขนาดของรายการตามสัดส่วนการแสดงผล (ความกว้างต่อความสูง)
โดยค่าเริ่มต้น GridView จะสร้างไทล์ที่มีขนาดเท่ากันทั้งหมด
เรามีบัตรอยู่ 1 ใบ แต่ไม่มีเงินในบัตร มาเพิ่มวิดเจ็ตย่อยลงในการ์ดกัน
จัดวางเนื้อหา
การ์ดควรมีพื้นที่สำหรับรูปภาพ ชื่อ และข้อความรอง
อัปเดตองค์ประกอบย่อยของ 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 จะกำหนดรูปร่างของรูปภาพไม่ว่าจะเป็นรูปภาพประเภทใดก็ตาม
ระยะห่างภายในจะเลื่อนข้อความจากด้านข้างเข้ามาเล็กน้อย
วิดเจ็ตข้อความทั้ง 2 รายการซ้อนกันในแนวตั้งโดยมีพื้นที่ว่าง 8 จุดคั่นระหว่างกลาง (SizedBox) เราสร้าง Column อีกอันเพื่อวางไว้ใน Padding
บันทึกโปรเจ็กต์
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. ยินดีด้วย
แอปของเรามีโฟลว์พื้นฐานที่จะนำผู้ใช้จากหน้าจอเข้าสู่ระบบไปยังหน้าจอหลัก ซึ่งผู้ใช้จะดูผลิตภัณฑ์ได้ เราได้เพิ่มแถบแอปด้านบน (พร้อมชื่อและปุ่ม 3 ปุ่ม) และการ์ด (เพื่อนำเสนอเนื้อหาของแอป) ด้วยโค้ดเพียงไม่กี่บรรทัด ตอนนี้หน้าจอหลักของเราเรียบง่ายและใช้งานได้จริง โดยมีโครงสร้างพื้นฐานและเนื้อหาที่ดำเนินการได้
ขั้นตอนถัดไป
ตอนนี้เราได้ใช้คอมโพเนนต์หลัก 4 รายการจากไลบรารี Material Flutter แล้ว ได้แก่ แถบแอปด้านบน การ์ด ช่องข้อความ และปุ่ม คุณดูข้อมูลเพิ่มเติมได้โดยไปที่แคตตาล็อกวิดเจ็ตคอมโพเนนต์ Material
แม้ว่าแอปจะทำงานได้อย่างเต็มที่ แต่ก็ยังไม่ได้แสดงถึงแบรนด์หรือมุมมองใดๆ โดยเฉพาะ ใน MDC-103: Material Design Theming with Color, Shape, Elevation and Type เราจะปรับแต่งสไตล์ของคอมโพเนนต์เหล่านี้เพื่อแสดงถึงแบรนด์ที่ทันสมัยและมีชีวิตชีวา
















