บทที่ 11mobile|14 KB

บทที่ 11: UI/UX Design และ Theme System

บทที่ 11: UI/UX Design และ Theme System

🎯 เป้าหมายการเรียนรู้

หลังจากเรียนบทนี้แล้ว คุณจะสามารถ:

  • ออกแบบ UI ที่สวยงามและทันสมัยด้วย Material Design 3
  • สร้างระบบ Theme (Light/Dark Mode) ที่สลับได้ทันที
  • เข้าใจทฤษฎีสี (Color Theory) และการเลือกใช้ Palette
  • สร้าง Custom Widgets เพื่อนำกลับมาใช้ซ้ำ (Reusable Components)
  • สร้างเอฟเฟกต์ Glassmorphism (พื้นหลังเบลอ) และ Mesh Gradients

📚 ทฤษฎีและแนวคิด

1. Material Design 3 (MD3)

QUOM App ใช้ดีไซน์ซิสเต็มล่าสุดจาก Google คือ Material Design 3 (Material You) ซึ่งมีจุดเด่นคือ:

  • Dynamic Color: สีของแอปปรับตาม Wallpaper (ใน Android 12+) หรือตามที่เรากำหนด
  • Elevation: การใช้แสงและเงาเพื่อบอกความลึกของวัตถุ
  • Accessibility: การออกแบบที่คำนึงถึงการใช้งานของทุกคน (Contrast, Touch Target)

2. QUOM Design Philosophy (ปรัชญาการออกแบบ)

เราไม่ได้แค่วางปุ่มและข้อความ แต่เราสร้าง "ประสบการณ์" (User Experience):

  • Vivid Colors: ใช้สีเขียว เหลือง แดง ที่สดใสเพื่อกระตุ้นพลังงาน
  • Glassmorphism: ใช้ความโปร่งใสและเบลอ เพื่อให้ความรู้สึก Modern และ Premium
  • Motion: ทุกการขยับต้องมีความหมายและลื่นไหล (Smooth Animations)

💻 การเขียนโค้ด (Implementation)

1. สร้าง Theme System

ไฟล์: mobile/lib/theme/app_theme.dart

เราจะสร้างคลาส AppTheme เพื่อเป็นจุดศูนย์กลางในการจัดการสีและฟอนต์ทั้งหมด

import 'package:flutter/material.dart';

class AppTheme {

อธิบายทีละบรรทัด (Imports):

  • import 'package:flutter/material.dart'
    • จำเป็นที่สุด! เพราะคลาสอย่าง Color, ThemeData, LinearGradient อยู่ในนี้ทั้งหมด

อธิบายโค้ด: // --- 1. Color Palette (ชุดสีหลัก) --- // เราประกาศเป็น static const เพื่อให้เรียกใช้ได้ทั่วแอปโดยไม่ต้องสร้าง object ใหม่ static const vividGreen = Color(0xFF00C853); // สีเขียวหลัก (Success/Action) static const vividYellow = Color(0xFFFFD600); // สีเหลือง (Warning/Highlight) static const vividRed = Color(0xFFFF1744); // สีแดง (Error/Danger)

// Colors for Light Mode static const lightBackground = Color(0xFFFAFAEE); // พื้นหลังขาวครีม สบายตา static const lightCard = Colors.white; static const lightText = Color(0xFF1E293B); // สีดำอมน้ำเงิน (ดีกว่าดำสนิท)

// Colors for Dark Mode static const darkBackground = Color(0xFF0F172A); // พื้นหลังน้ำเงินเข้ม static const darkCard = Color(0xFF1E293B); static const darkText = Colors.white;

// --- 2. Gradients (การไล่สี) --- // ใช้สำหรับพื้นหลังเพื่อให้ดูมีมิติ static const lightGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFFE8F5E9), // เขียวอ่อนมว๊าก Color(0xFFFFFDE7), // เหลืองอ่อนมว๊าก Color(0xFFFFEBEE), // แดงอ่อนมว๊าก ], );

static const darkGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Color(0xFF0F172A), // น้ำเงินเข้มมา Color(0xFF1E1B4B), // น้ำเงินเข้มกว่า Color(0xFF1E293B), // เทาน้ำเงิน ], );

// --- 3. ThemeData (การตั้งค่า Theme รวม) --- static ThemeData light() { // เริ่มจาก base theme ของ Flutter return ThemeData( useMaterial3: true, brightness: Brightness.light,

  // กำหนดสีหลักของแอป
  colorScheme: ColorScheme.fromSeed(
    seedColor: vividGreen, // ให้ Flutter คำนวณสีอื่นจากสีนี้
    brightness: Brightness.light,
    primary: vividGreen,
    secondary: vividYellow,
    error: vividRed,
  ),

  // ปรับแต่ง Scaffold (พื้นหลังหน้าจอ)
  scaffoldBackgroundColor: lightBackground,
  
  // ปรับแต่ง AppBar
  appBarTheme: const AppBarTheme(
    backgroundColor: Colors.transparent, // โปร่งใส
    foregroundColor: lightText, // สีข้อความ/ไอคอน
    elevation: 0, // ไม่มีเงา
  ),
  
  // ปรับแต่ง Card
  cardTheme: CardThemeData(
    color: lightCard,
    elevation: 0, // แบนราบ (Modern style)
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(20), // มุมโค้งมน
      side: BorderSide(color: vividGreen.withOpacity(0.1)), // ขอบจางๆ
    ),
  ),
  
  // ปรับแต่ง Input Field
  inputDecorationTheme: InputDecorationTheme(
    filled: true,
    fillColor: Colors.white,
    contentPadding: const EdgeInsets.all(16),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16),
      borderSide: BorderSide(color: vividGreen.withOpacity(0.2)),
    ),
    // เมื่อกดที่ช่อง:
    focusedBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(16),
      borderSide: const BorderSide(color: vividGreen, width: 2),
    ),
  ),
);

}

// สร้าง Dark Theme ในลักษณะเดียวกัน... static ThemeData dark() { // ... } }


### 2. สร้างพื้นหลังแบบ Mesh Gradient

ไฟล์: `mobile/lib/widgets/mesh_background.dart`

Widget นี้จะสร้างพื้นหลังที่มี "ลูกเล่น" เป็นวงกลมเบลอๆ ลอยอยู่ (Mesh Gradient) ซึ่งนิยมมากในดีไซน์สมัยใหม่

```dart
import 'dart:ui';
import 'package:flutter/material.dart';
import '../theme/app_theme.dart';

class MeshBackground extends StatelessWidget {
  final Widget child; // เนื้อหาที่จะแสดงทับพื้นหลัง

  const MeshBackground({required this.child, super.key});

  @override
  Widget build(BuildContext context) {
    // เช็คว่าเป็น Dark Mode หรือไม่
    final isDark = Theme.of(context).brightness == Brightness.dark;

    return Stack( // ซ้อน Layer
      children: [
        // Layer 1: พื้นหลังไล่สีเรียบๆ
        Container(
          decoration: BoxDecoration(
            gradient: isDark ? AppTheme.darkGradient : AppTheme.lightGradient,
          ),
        ),
        
        // Layer 2: วงกลมฟุ้งๆ (Blob) ด้านบนซ้าย
        Positioned(
          top: -100, // ยื่นออกไปนอกจอ
          left: -50,
          child: ImageFiltered(
            // Key Technique: ใช้ Blur แรงๆ (sigma 50-100)
            imageFilter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
            child: Container(
              width: 300,
              height: 300,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                // สีเขียวจางๆ
                color: AppTheme.vividGreen.withOpacity(0.4),
              ),
            ),
          ),
        ),
        
        // Layer 3: วงกลมฟุ้งๆ ด้านล่างขวา
        Positioned(
          bottom: -100,
          right: -50,
          child: ImageFiltered(
            imageFilter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
            child: Container(
              width: 350,
              height: 350,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                // สีแดงจางๆ
                color: AppTheme.vividRed.withOpacity(0.3),
              ),
            ),
          ),
        ),
        
        // Layer 4: เนื้อหาหลัก (App content)
        // ต้องวางไว้บนสุด
        child,
      ],
    );
  }
}

3. เทคนิค Glassmorphism (กระจกฝ้า)

เรามักใช้เทคนิคนี้กับ Card หรือ Modal เพื่อให้เห็นพื้นหลังลางๆ

Container(
  decoration: BoxDecoration(
    color: Colors.white.withOpacity(0.2), // พื้นหลังขาวโปร่งใส
    borderRadius: BorderRadius.circular(16),
    border: Border.all(
      color: Colors.white.withOpacity(0.1), // ขอบขาวจางๆ
    ),
  ),
  child: BackdropFilter( // ตัวทำเบลอ
    filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Text("เนื้อหาบนกระจก"),
    ),
  ),
)

🎨 Design Patterns ที่ควรจำ

การใช้สี (Color Usage)

  • 60-30-10 Rule:
    • 60% สีพื้นหลัง (Background)
    • 30% สีรอง (Secondary/Surface)
    • 10% สีหลัก (Primary/Accent) เช่น ปุ่ม, ไอคอนสำคัญ
  • Contrast: ตรวจสอบเสมอว่า text อ่านออกบนพื้นหลังนั้นๆ (เช่น ตัวหนังสือสีดำบนพื้นเหลือง)

Typography (ตัวหนังสือ)

ใน QUOM เรากำหนดขนาดมาตรฐานไว้:

  • Display: 32px (หัวข้อใหญ่หน้าแรก)
  • Title: 20px (หัวข้อ Section)
  • Body: 16px (เนื้อหาทั่วไป)
  • Caption: 12px (คำอธิบายเล็กๆ)

Spacing (ระยะห่าง)

เราใช้ระบบ 8-point grid:

  • 8, 16, 24, 32, 48 pixels
  • ห้ามใช้เลขแปลกๆ เช่น 13px หรือ 7px เพื่อความสม่ำเสมอ

📝 แบบฝึกหัด

โจทย์ที่ 1: สร้าง "Night Mode" Toggle

สิ่งที่ต้องทำ:

  1. สร้าง Switch ในหน้า Settings
  2. เมื่อกดเปิด ให้เปลี่ยนเป็น AppTheme.dark()
  3. เมื่อกดปิด ให้เปลี่ยนเป็น AppTheme.light()
  4. (ใช้ความรู้จากบทที่ 21 เรื่อง Dynamic Theme)

โจทย์ที่ 2: สร้าง Custom Card

สิ่งที่ต้องทำ:

  1. สร้าง Widget ชื่อ GlassCard
  2. รับ child เป็น parameter
  3. ใส่ Effect Glassmorphism ลงไป
  4. นำไปใช้แทน Card ปกติในแอปหน้า Home

โจทย์ที่ 3: Responsive Layout

สิ่งที่ต้องทำ:

  1. ทดสอบรันแอปบน iPad หรือ Tablet (Simulator)
  2. ใช้ LayoutBuilder ตรวจสอบความกว้างหน้าจอ
  3. ถ้าจอใหญ่กว่า 600px ให้แสดง Grid 2 แถว (ใช้ SliverGrid)
  4. ถ้าจอมือถือ ให้แสดง List 1 แถวเหมือนเดิม

📌 สรุป

ในบทนี้เราขยับจากการเขียนโค้ด Logic มาสู่การทำ "ศิลปะ" บนหน้าจอ

  • Theme: คือหัวใจที่คุมโทนของทั้งแอป
  • Colors & Gradients: สร้างอารมณ์และความรู้สึก
  • Custom Widgets: ช่วยให้โค้ดสะอาดและนำกลับมาใช้ใหม่ได้ง่าย

ไฟล์ที่เกี่ยวข้อง


🔜 บทถัดไป

บทที่ 12: API Integration (HTTP Requests) เมื่อหน้าตาสวยแล้ว ต่อไปเราจะทำให้แอป "ฉลาด" ขึ้นด้วยการดึงข้อมูลจาก Server!