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

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 นี้เสร็จได้ โดยใช้เวลาและลงแรงพอสมควร

เห็นด้วยอย่างยิ่ง เห็นด้วย เป็นกลาง ไม่เห็นด้วย ไม่เห็นด้วยอย่างยิ่ง

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

เห็นด้วยอย่างยิ่ง เห็นด้วย เฉยๆ ไม่เห็นด้วย ไม่เห็นด้วยอย่างยิ่ง