MDC-102 Flutter: โครงสร้างวัสดุและเลย์เอาต์

MDC-102 Flutter:
โครงสร้างและเลย์เอาต์ของ Material

เกี่ยวกับ Codelab นี้

subjectอัปเดตล่าสุดเมื่อ มิ.ย. 7, 2023
account_circleเขียนโดย Material Flutter Team

1 บทนำ

logo_components_color_2x_web_96dp.png

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

เปิดโปรเจ็กต์และเรียกใช้แอป

  1. เปิดโปรเจ็กต์ในเครื่องมือแก้ไขที่ต้องการ
  2. ทำตามวิธีการเพื่อ "เรียกใช้แอป" ในเริ่มต้นใช้งาน: ทดสอบการใช้งานสำหรับเครื่องมือแก้ไขที่คุณเลือก

สำเร็จ! คุณควรเห็นหน้าการเข้าสู่ระบบ 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

แถบแอปที่มีชื่อ Shrine

แถบแอปที่มีชื่อ Shrine

แถบแอปจำนวนมากมีปุ่มอยู่ข้างชื่อ มาเพิ่มไอคอนเมนูในแอปของเรากัน

เพิ่มปุ่มไอคอนนำ

ขณะที่ยังอยู่ใน 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 การดำเนินการเหล่านี้เรียกว่า "การดำเนินการ"

เพิ่มการดำเนินการ

มีพื้นที่สำหรับ 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

แถบแอปที่มีชื่อ Shrine และไอคอนเมนูแฮมเบอร์เกอร์ รวมถึงไอคอนการค้นหาและปรับแต่งที่ตามหลัง

แถบแอปที่มี Shrine เป็นชื่อและคำอธิบาย มีไอคอนเมนูแฮมเบอร์เกอร์ ต่อท้ายด้วยการค้นหาและปรับแต่งไอคอน

ตอนนี้แอปจะมีปุ่มนำหน้า ชื่อ และการทำงาน 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 เราจะปรับแต่งสไตล์ของส่วนประกอบเหล่านี้เพื่อแสดงออกถึงแบรนด์ที่มีชีวิตชีวาและทันสมัย

ฉันทำ Codelab นี้เสร็จได้ โดยใช้เวลาและลงแรงพอสมควร

ฉันต้องการใช้คอมโพเนนต์เนื้อหาต่อไปในอนาคต