Constraints & Layout
กฎเดียว
หัวข้อที่มีชื่อว่า “กฎเดียว”ถ้าคุณจำเรื่อง layout ของ Flutter ได้แค่อย่างเดียว จำอันนี้:
Constraints ลงล่าง size ขึ้นบน parent วางตำแหน่ง
layout คือ pass เดียวที่ไล่ลง tree แล้วกลับขึ้นมา parent ส่ง constraints ให้ child แต่ละตัว — เป็น BoxConstraints ที่มี min/max ของ width และ height child แต่ละตัวต้องเลือก size ที่อยู่ในขอบเขตนั้นแล้วรายงานกลับขึ้นไป จากนั้น parent จึง วางตำแหน่ง child ลงในพื้นที่ของตัวเอง
flowchart TB parent["Parent widget"] -->|"constraints: min/max w, min/max h"| child["Child widget"] child -->|"chosen size (within constraints)"| parent parent -->|"offset (where to place child)"| placed["Child placed"]
นั่นคือทั้งหมดของ algorithm ที่เหลือเป็นแค่รายละเอียดว่า widget แต่ละตัวเลือก size อย่างไร และส่ง constraints แบบไหน ต่อลงไป
ตัวอย่างเดินทีละขั้น
หัวข้อที่มีชื่อว่า “ตัวอย่างเดินทีละขั้น”Center( child: Container( width: 100, height: 100, color: Colors.blue, ),)อ่านจากบนลงล่าง:
Centerรับ constraints จาก parent (สมมติว่าเป็นทั้งหน้าจอ: ขนาดเท่าหน้าจอพอดี)Centerส่ง constraints แบบ loose ให้ child — “เธอจะขนาดเท่าไรก็ได้ตั้งแต่ 0 จนถึงเท่าหน้าจอ”Containerอยากมีขนาด100x100พอดี ซึ่งอยู่ในขอบเขต จึง size เป็น100x100แล้วรายงานขึ้นไปCenterใหญ่เท่าหน้าจอ แล้ววาง child100x100ไว้ตรงกลาง
เปลี่ยน Center เป็น Align, Padding หรือ SizedBox มีแค่ขั้นที่ 2 เท่านั้นที่เปลี่ยน — คือ constraints ที่ส่งลงไป กฎไม่เคยเปลี่ยน
tight vs loose constraints
หัวข้อที่มีชื่อว่า “tight vs loose constraints”constraint เป็น tight เมื่อ min เท่ากับ max (widget ต้องมีขนาดนั้นเป๊ะ) และเป็น loose เมื่อ min เป็น 0 (widget จะเป็นขนาดเท่าไรก็ได้จนถึง max)
// SizedBox บังคับ constraint แบบ tight — child ต้องเป็น 50x50SizedBox(width: 50, height: 50, child: coloredBox)
// Center บังคับ constraint แบบ loose — child จะใหญ่ได้ถึงขนาด parentCenter(child: coloredBox)นี่คือเหตุผลที่ widget เดียวกันทำงานต่างกันภายใต้ parent ต่างกัน: Container(color: Colors.red) ที่ไม่ระบุ size จะเต็ม parent ภายใต้ constraint แบบ tight แต่หดจนหายภายใต้แบบ loose widget ไม่ได้เปลี่ยน — constraints ต่างหากที่เปลี่ยน
ทำไม unbounded constraints ถึง crash
หัวข้อที่มีชื่อว่า “ทำไม unbounded constraints ถึง crash”widget บางตัวส่ง constraints แบบ unbounded — max เป็น infinity — แปลว่า “เธอจะใหญ่แค่ไหนก็ได้” ListView แนวตั้งเช่น ให้ child มี height แบบ unbounded (ทิศทาง scroll ยาวได้ไม่จำกัด)
เอา Column ไปใส่ใน ListView นั้นแล้วคุณจะเจอ error คลาสสิก: Column พยายามสูงเท่าที่ constraints ยอมให้ แต่ constraint เป็น infinite จึงเลือก height ที่จำกัดไม่ได้
ListView( children: [ Column( // ❌ RenderFlex: unbounded height children: [ Text('a'), Text('b') ], ), ],)วิธีแก้คือให้ child มี height ที่ bounded — wrap child ไว้, ใช้ shrinkWrap หรือปรับโครงสร้างให้ตัว scrollable เป็น widget ชั้นนอก พออ่าน constraints เป็น error พวกนี้อ่านเหมือนประโยคภาษาคน ไม่ใช่ปริศนา