Kodokon kodokon.com

ธีม: ThemeData, Material 3 และโหมดมืด

รวมสีและการจัดวางตัวอักษรไว้ที่ศูนย์กลางใน ThemeData สร้างพาเลตต์สี Material 3 ด้วย ColorScheme.fromSeed และมอบโหมดมืดได้อย่างไม่ยากเย็น

9 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

การเลิกจัดสไตล์ทีละวิดเจ็ตคือก้าวสำคัญสู่ความเป็นมืออาชีพ ThemeData รวมสี การจัดวางตัวอักษร และรูปทรงของทั้งแอปไว้ที่ศูนย์กลาง ด้วย Material 3 (เปิดใช้เป็นค่าเริ่มต้นตั้งแต่ Flutter 3.16) ทุกอย่างเริ่มต้นจาก ColorScheme โดย ColorScheme.fromSeed จะสกัดพาเลตต์สีที่ครบถ้วน กลมกลืน และเข้าถึงได้ ออกมาจากสี seed เพียงสีเดียว

DART
import 'package:flutter/material.dart';

void main() {
  runApp(const ShopApp());
}

class ShopApp extends StatelessWidget {
  const ShopApp({super.key});

  @override
  Widget build(BuildContext context) {
    const seed = Colors.teal;
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
        ),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
          brightness: Brightness.dark,
        ),
      ),
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(title: const Text('Shop')),
        body: const Center(child: Text('Welcome')),
      ),
    );
  }
}
สองธีมที่ถูกสร้างจากสี seed เพียงสีเดียว

ภายในวิดเจ็ตของคุณ อย่าประดิษฐ์อะไรขึ้นใหม่ จงอ่านธีมโดยรอบด้วย Theme.of(context) ตัว colorScheme เปิดเผย บทบาท (role) เชิงความหมาย (primary, onPrimary, primaryContainer, surface และอื่น ๆ) และ textTheme เปิดเผยสไตล์ที่มีชื่อ (titleMedium, bodyLarge และอื่น ๆ) วิดเจ็ตที่เขียนแบบนี้จะปรับตัวเข้ากับธีมสว่างและธีมมืดได้โดยอัตโนมัติ

DART
import 'package:flutter/material.dart';

class PriceTag extends StatelessWidget {
  const PriceTag({super.key, required this.price});

  final double price;

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    final text = Theme.of(context).textTheme;
    return Container(
      padding: const EdgeInsets.symmetric(
        horizontal: 12,
        vertical: 6,
      ),
      decoration: BoxDecoration(
        color: colors.primaryContainer,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        '$price €',
        style: text.titleMedium?.copyWith(
          color: colors.onPrimaryContainer,
        ),
      ),
    );
  }
}
ไม่มีสีที่เขียนตายตัว วิดเจ็ตจะเป็นไปตามธีมที่กำลังใช้งานอยู่

หากต้องการก้าวไปอีกขั้น จงตั้งค่า ธีมของคอมโพเนนต์ วิดเจ็ตแต่ละตระกูล (แถบแอป ปุ่ม ช่องกรอกข้อมูล) รับธีมเฉพาะตัวใน ThemeData ได้ คุณกำหนดระยะขอบ (padding) ของปุ่มหรือการจัดกึ่งกลางของหัวข้อเพียงครั้งเดียว แล้วทุกอินสแตนซ์จะสืบทอดมันไปโดยไม่ต้องมีสไตล์เฉพาะที่สักบรรทัดเดียว

DART
import 'package:flutter/material.dart';

final ThemeData appTheme = ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.teal,
  ),
  appBarTheme: const AppBarTheme(
    centerTitle: true,
    elevation: 0,
  ),
  filledButtonTheme: FilledButtonThemeData(
    style: FilledButton.styleFrom(
      padding: const EdgeInsets.symmetric(
        horizontal: 24,
        vertical: 12,
      ),
    ),
  ),
);
การตั้งค่าคอมโพเนนต์มีผลทั่วทั้งแอป

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. ColorScheme.fromSeed(seedColor: ...) สร้างอะไรออกมา?
    • สีเดียวที่ทำให้อ่อนลงเล็กน้อย
    • พาเลตต์สี Material 3 ที่ครบถ้วนและสอดคล้องกัน สกัดออกมาจากสี seed
    • ไล่เฉดสี (gradient) สำหรับพื้นหลัง
    • ธีมมืดเท่านั้น
  2. คุณจะทำให้แอปเป็นไปตามการตั้งค่าสว่าง/มืดของระบบได้อย่างไร?
    • ระบุแค่ theme ที่เหลือเป็นอัตโนมัติ
    • ระบุ theme และ darkTheme แล้วตั้ง themeMode เป็น ThemeMode.system
    • เรียก setState เมื่อความสว่างเปลี่ยน
    • อ่าน MediaQuery ในทุกวิดเจ็ต
  3. ทำไมจึงเขียน color: Theme.of(context).colorScheme.primary แทนที่จะเป็น color: Colors.blue?
    • มันเขียนสั้นกว่า
    • สีจะปรับตัวเข้ากับธีมที่กำลังใช้งานและคงความสอดคล้องกันทั่วทั้งแอป
    • Colors.blue เลิกใช้แล้วใน Material 3