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
| StatelessWidget | StatefulWidget | |
|---|---|---|
| เปลี่ยนแปลงได้ไหม | ไม่ได้ สร้างแล้วคงที่ | เปลี่ยนได้ตลอด |
| จำนวน class | 1 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 | ไล่สี |
TextStyle | font size, weight, color |
EdgeInsets | padding/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 | หน้าที่ |
|---|---|
MaterialApp | root ของแอป ให้ Material Design theme |
Scaffold | โครงร่างหน้าจอ (AppBar + Body + FAB) |
Center | จัดลูกไว้ตรงกลาง |
Column | จัดลูกแนวตั้ง ↕ |
Row | จัดลูกแนวนอน ↔ |
Container | กล่องตกแต่งได้ (gradient, border, padding) |
SizedBox | กำหนดขนาด / เว้นระยะ |
ListView | list เลื่อนได้ |
Stack | ซ้อน widget ทับกัน |
Content
| Widget | หน้าที่ |
|---|---|
Text | แสดงข้อความ |
Image.asset() | แสดงรูปจาก assets |
Image.network() | แสดงรูปจาก URL |
Icon | แสดงไอคอน |
Buttons
| ปุ่ม | หน้าตา |
|---|---|
ElevatedButton | มีสีพื้นหลัง + เงา |
OutlinedButton | ไม่มีพื้นหลัง มีแค่ขอบ |
TextButton | แค่ข้อความกดได้ |
ทุกปุ่มต้องมี child: (widget บนปุ่ม) และ onPressed: (function เมื่อกด)
Spacing: SizedBox vs Padding
SizedBox | EdgeInsets (padding) | |
|---|---|---|
| ทำอะไร | เว้นระยะ ระหว่าง widget 2 ตัว | เว้นระยะ ภายใน widget ตัวเดียว |
| เปรียบเทียบ | เว้นบรรทัดระหว่างย่อหน้า | เว้นขอบกระดาษรอบข้อความ |
const ประหยัด Memory
const Text('Hello!') // ใช้ที่แรก → สร้างใน memory
const Text('Hello!') // ใช้ที่สอง → ชี้ไปก้อนเดิม ไม่สร้างใหม่ใส่ const หน้า widget ที่ค่าไม่เปลี่ยน → Dart cache ใช้ซ้ำ ประหยัด memory
Related
- Flutter — framework ที่ Widgets อยู่
- Dart — ภาษาที่ใช้เขียน Widgets
- OOP — Widget คือ Object สร้างจาก Class
- Shared Preferences — เก็บข้อมูลบน local device