Widgets

Building blocks ของ UI ใน Flutter — ทุกอย่างที่เห็นบนหน้าจอคือ Widget เหมือนกล่องซ้อนกล่อง (Widget Tree)

Widget Tree

Widget ซ้อนกันเป็นชั้น ๆ — child: มีลูก 1 ตัว / children: มีลูกหลายตัว:

MaterialApp          → root ของแอป (ต้องมี)
  └── Scaffold       → โครงหน้าจอ (AppBar, body, FAB)
       └── Column    → จัดลูกแนวตั้ง
            ├── Image → แสดงรูป
            ├── SizedBox → เว้นระยะ
            └── TextButton → ปุ่มกดได้

StatelessWidget vs StatefulWidget

StatelessWidgetStatefulWidget
เปลี่ยนแปลงได้ไหมไม่ได้ สร้างแล้วคงที่เปลี่ยนได้ตลอด
จำนวน class1 class (มี build())2 class (Widget + State)
update จอต่อเมื่อ parent re-renderเมื่อ setState() ถูกเรียก
ตัวอย่างข้อความ, ไอคอน, gradientกดปุ่มเปลี่ยนข้อความ, toggle
เปรียบเทียบโปสเตอร์ติดผนังป้ายไฟ LED เปลี่ยนได้

วิธีเลือก: ถามตัวเองว่า “widget นี้ต้องเปลี่ยนหน้าตาหลังจากสร้างไหม?” ไม่ → Stateless / ใช่ → Stateful

สร้าง StatelessWidget

class GradientContainer extends StatelessWidget {
  const GradientContainer({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: const BoxDecoration(
        gradient: LinearGradient(colors: [Colors.purple, Colors.blue]),
      ),
      child: const Center(child: Text('Hello!')),
    );
  }
}

สร้าง StatefulWidget (2 classes)

class DiceRoller extends StatefulWidget {
  const DiceRoller({super.key});
 
  @override
  State<DiceRoller> createState() => _DiceRollerState();
}
 
class _DiceRollerState extends State<DiceRoller> {
  var currentDiceRoll = 2;
 
  void rollDice() {
    setState(() {
      currentDiceRoll = Random().nextInt(6) + 1;
    });
  }
 
  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Image.asset('assets/images/dice-$currentDiceRoll.png'),
      TextButton(onPressed: rollDice, child: const Text('Roll Dice')),
    ]);
  }
}

ทำไมต้องแยก 2 class? Flutter จัดการ Widget กับ State แยกกัน — Widget ถูกสร้างใหม่ได้ทุกเมื่อ แต่ State อยู่ถาวรเก็บข้อมูลที่เปลี่ยนได้

setState() Flow

กดปุ่ม → onPressed เรียก function → setState() → Flutter เรียก build() ใหม่ → จอ update

ถ้าไม่ใส่ setState() → ค่าเปลี่ยนใน memory แต่ Flutter ไม่เรียก build() ใหม่ → จอเดิม

Configuration Objects

ไม่ใช่ทุกอย่างใน Widget Tree ที่เป็น Widget — บาง object เป็น configuration ที่ตั้งค่าให้ widget:

Configuration Objectตั้งค่าอะไร
BoxDecorationตกแต่ง Container (gradient, border)
LinearGradientไล่สี
TextStylefont size, weight, color
EdgeInsetspadding/margin
Colorสี
Alignmentตำแหน่ง (topLeft, center, …)
Container(
  decoration: BoxDecoration(        // ← config object
    gradient: LinearGradient(       // ← config object
      colors: [Colors.purple, Colors.blue],
    ),
  ),
  child: Text(
    'Hello!',
    style: TextStyle(fontSize: 28), // ← config object
  ),
)

Built-in Widgets ที่ใช้บ่อย

Layout

Widgetหน้าที่
MaterialApproot ของแอป ให้ Material Design theme
Scaffoldโครงร่างหน้าจอ (AppBar + Body + FAB)
Centerจัดลูกไว้ตรงกลาง
Columnจัดลูกแนวตั้ง ↕
Rowจัดลูกแนวนอน ↔
Containerกล่องตกแต่งได้ (gradient, border, padding)
SizedBoxกำหนดขนาด / เว้นระยะ
ListViewlist เลื่อนได้
Stackซ้อน widget ทับกัน

Content

Widgetหน้าที่
Textแสดงข้อความ
Image.asset()แสดงรูปจาก assets
Image.network()แสดงรูปจาก URL
Iconแสดงไอคอน

Buttons

ปุ่มหน้าตา
ElevatedButtonมีสีพื้นหลัง + เงา
OutlinedButtonไม่มีพื้นหลัง มีแค่ขอบ
TextButtonแค่ข้อความกดได้

ทุกปุ่มต้องมี child: (widget บนปุ่ม) และ onPressed: (function เมื่อกด)

Spacing: SizedBox vs Padding

SizedBoxEdgeInsets (padding)
ทำอะไรเว้นระยะ ระหว่าง widget 2 ตัวเว้นระยะ ภายใน widget ตัวเดียว
เปรียบเทียบเว้นบรรทัดระหว่างย่อหน้าเว้นขอบกระดาษรอบข้อความ

const ประหยัด Memory

const Text('Hello!')  // ใช้ที่แรก → สร้างใน memory
const Text('Hello!')  // ใช้ที่สอง → ชี้ไปก้อนเดิม ไม่สร้างใหม่

ใส่ const หน้า widget ที่ค่าไม่เปลี่ยน → Dart cache ใช้ซ้ำ ประหยัด memory

  • Flutter — framework ที่ Widgets อยู่
  • Dart — ภาษาที่ใช้เขียน Widgets
  • OOP — Widget คือ Object สร้างจาก Class
  • Shared Preferences — เก็บข้อมูลบน local device