1. บทนำ
Material Components (MDC) ช่วยให้นักพัฒนานำดีไซน์ Material มาใช้ MDC สร้างโดยทีมวิศวกรและนักออกแบบ UX ที่ Google โดยมีคอมโพเนนต์ UI ที่สวยงามและใช้งานได้หลายสิบอย่างและพร้อมใช้งานสำหรับ Android, iOS, เว็บ และ Flutter.material.io/develop |
ใน Codelab MDC-101 คุณใช้คอมโพเนนต์ Material 2 รายการเพื่อสร้างหน้าเข้าสู่ระบบ ได้แก่ ช่องข้อความและปุ่มที่มีระลอกคลื่น ทีนี้ เรามาต่อยอดรากฐานนี้ด้วยการเพิ่มการนำทาง โครงสร้าง และข้อมูล
สิ่งที่คุณจะสร้าง
ใน Codelab นี้ คุณจะได้สร้างหน้าจอหลักสำหรับแอปชื่อ Shrine ซึ่งเป็นแอปอีคอมเมิร์ซที่ขายเสื้อผ้าและของใช้ในบ้าน โดยจะประกอบด้วยข้อมูลต่อไปนี้
- แถบแอปด้านบน
- รายการตารางกริดที่เต็มไปด้วยผลิตภัณฑ์
Android | iOS |
คอมโพเนนต์และระบบย่อย Material Flutter ในโค้ดแล็บนี้
- แถบแอปด้านบน
- ตารางกริด
- การ์ด
คุณจะให้คะแนนระดับประสบการณ์ในการพัฒนาด้วย Flutter เท่าใด
2. ตั้งค่าสภาพแวดล้อมในการพัฒนาซอฟต์แวร์ Flutter
คุณต้องใช้ซอฟต์แวร์ 2 อย่างในการฝึกนี้ ได้แก่ Flutter SDK และเครื่องมือแก้ไข
คุณเรียกใช้ Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาแอป
- โปรแกรมจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องตั้งค่าใน 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
หากต้องการโคลน 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
เปิดโปรเจ็กต์และเรียกใช้แอป
- เปิดโปรเจ็กต์ในเครื่องมือแก้ไขที่ต้องการ
- ทำตามวิธีการเพื่อ "เรียกใช้แอป" ในเริ่มต้นใช้งาน: ทดสอบการใช้งานสำหรับเครื่องมือแก้ไขที่คุณเลือก
สำเร็จ! คุณควรเห็นหน้าการเข้าสู่ระบบ 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
ให้เพิ่มชื่อลงในแถบแอปดังนี้
// 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
ให้ตั้งค่า 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 การดำเนินการเหล่านี้เรียกว่า "การดำเนินการ"
เพิ่มการดำเนินการ
มีพื้นที่สำหรับ IconButtons อีก 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 ใบไว้ใต้แถบแอปด้านบน วิดเจ็ตการ์ดเพียงอย่างเดียวมีข้อมูลไม่เพียงพอที่จะจัดวางตำแหน่งที่เรามองเห็นได้ เราจึงอยากรวมวิดเจ็ตไว้ในวิดเจ็ต 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 จะสร้างชิ้นส่วนที่มีขนาดเดียวกันทั้งหมด
เรามีบัตรใบเดียว แต่บัตรว่างเปล่า มาเพิ่มวิดเจ็ตย่อยลงในการ์ดกัน
จัดเลย์เอาต์เนื้อหา
การ์ดควรมีภูมิภาคสำหรับรูปภาพ ชื่อ และข้อความรอง
อัปเดตรายการย่อยของ 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 2 รายการซ้อนกันแนวตั้งโดยเว้นพื้นที่ว่าง 8 จุดไว้ตรงกลาง (SizedBox) เราสร้างคอลัมน์อีกคอลัมน์หนึ่งไว้ภายใน Padding
บันทึกโปรเจ็กต์
Android | iOS |
ในตัวอย่างนี้ คุณจะเห็นการ์ดถูกฝังจากขอบ โดยมีมุมโค้งมนและเงา (ที่แสดงระดับความสูงของการ์ด) รูปร่างทั้งหมดเรียกว่า "คอนเทนเนอร์" ใน Material (อย่าสับสนกับคลาสวิดเจ็ตจริงที่ชื่อว่าคอนเทนเนอร์)
การ์ดมักจะแสดงในคอลเล็กชันร่วมกับการ์ดอื่นๆ ลองสร้างเป็นคอลเล็กชันในตารางกริด
6. สร้างคอลเล็กชันการ์ด
เมื่อการ์ดหลายใบปรากฏในหน้าจอ ระบบจะจัดกลุ่มการ์ดเหล่านั้นเข้าด้วยกันเป็นคอลเล็กชันอย่างน้อย 1 รายการ การ์ดในคอลเล็กชันจะอยู่ในระนาบเดียวกัน ซึ่งหมายความว่าการ์ดจะอยู่ในระดับเดียวกัน (เว้นแต่จะมีการหยิบหรือลากการ์ด แต่เราจะไม่ทำเช่นนั้นในบทนี้)
เพิ่มการ์ดเป็นคอลเล็กชัน
ขณะนี้ระบบได้สร้างการ์ดในบรรทัดของช่อง 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();
}
หมายเหตุ: ยังไม่คอมไพล์และเรียกใช้ มีการเปลี่ยนแปลงอีก 1 รายการ
นอกจากนี้ ให้เปลี่ยนฟังก์ชัน 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
),
Hot รีสตาร์ทแอป
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 และ Type เราจะปรับแต่งสไตล์ของส่วนประกอบเหล่านี้เพื่อแสดงออกถึงแบรนด์ที่มีชีวิตชีวาและทันสมัย