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 ใน Codelab นี้

  • แถบแอปด้านบน
  • ตารางกริด
  • การ์ด

โปรดให้คะแนนประสบการณ์การใช้งานการพัฒนา 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 มีรูปแบบการนำทางที่ช่วยให้คุณใช้งานได้ง่าย หนึ่งในคอมโพเนนต์ที่เห็นได้ชัดที่สุดคือแถบแอปด้านบน

มาเพิ่มแถบแอปด้านบนกัน เพื่อให้สามารถนำทางและให้ผู้ใช้เข้าถึงการทำงานอื่นๆ ได้อย่างรวดเร็ว

เพิ่มวิดเจ็ต 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

แทนที่ตรงกลางในตัวของนั่งร้านด้วย 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จะกําหนดว่ารูปภาพจะใช้รูปร่างใด ไม่ว่าจะให้รูปภาพประเภทใดก็ตาม

ระยะห่างจากขอบจะดึงข้อความเข้ามาจากด้านข้างเล็กน้อย

วิดเจ็ตข้อความ 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 ดูข้อมูลเพิ่มเติมได้โดยไปที่แคตตาล็อกวิดเจ็ตคอมโพเนนต์เนื้อหา

แม้ว่าแอปจะทำงานได้อย่างเต็มรูปแบบ แต่แอปของเรายังไม่ได้แสดงแบรนด์หรือมุมมองที่เฉพาะเจาะจงใดๆ ใน MDC-103: Material Design Theming with Color, Shape, Elevation และ Type เราจะปรับแต่งสไตล์ของส่วนประกอบเหล่านี้เพื่อแสดงออกถึงแบรนด์ที่มีชีวิตชีวาและทันสมัย

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

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

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

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