บทที่ 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
สิ่งที่ต้องทำ:
- สร้าง Switch ในหน้า Settings
- เมื่อกดเปิด ให้เปลี่ยนเป็น
AppTheme.dark() - เมื่อกดปิด ให้เปลี่ยนเป็น
AppTheme.light() - (ใช้ความรู้จากบทที่ 21 เรื่อง Dynamic Theme)
โจทย์ที่ 2: สร้าง Custom Card
สิ่งที่ต้องทำ:
- สร้าง Widget ชื่อ
GlassCard - รับ
childเป็น parameter - ใส่ Effect Glassmorphism ลงไป
- นำไปใช้แทน Card ปกติในแอปหน้า Home
โจทย์ที่ 3: Responsive Layout
สิ่งที่ต้องทำ:
- ทดสอบรันแอปบน iPad หรือ Tablet (Simulator)
- ใช้
LayoutBuilderตรวจสอบความกว้างหน้าจอ - ถ้าจอใหญ่กว่า 600px ให้แสดง Grid 2 แถว (ใช้
SliverGrid) - ถ้าจอมือถือ ให้แสดง List 1 แถวเหมือนเดิม
📌 สรุป
ในบทนี้เราขยับจากการเขียนโค้ด Logic มาสู่การทำ "ศิลปะ" บนหน้าจอ
- Theme: คือหัวใจที่คุมโทนของทั้งแอป
- Colors & Gradients: สร้างอารมณ์และความรู้สึก
- Custom Widgets: ช่วยให้โค้ดสะอาดและนำกลับมาใช้ใหม่ได้ง่าย
ไฟล์ที่เกี่ยวข้อง
🔜 บทถัดไป
บทที่ 12: API Integration (HTTP Requests) เมื่อหน้าตาสวยแล้ว ต่อไปเราจะทำให้แอป "ฉลาด" ขึ้นด้วยการดึงข้อมูลจาก Server!