Bỏ qua để đến nội dung

📐 Constraints

Flutter layout tuân theo quy tắc đơn giản nhưng rất quan trọng:

“Constraints go down. Sizes go up. Parent sets position.”

(Constraints đi xuống. Kích thước đi lên. Parent đặt vị trí.)


Constraints định nghĩa kích thước tối thiểu và tối đa mà một widget có thể có:

BoxConstraints(
minWidth: 0,
maxWidth: 300,
minHeight: 0,
maxHeight: 200,
)

┌─────────────────────────────────────────┐
│ 1. Parent gửi constraints xuống child │
│ "Bạn có thể rộng 0-300, cao 0-200" │
└───────────────────┬─────────────────────┘
┌─────────────────────────────────────────┐
│ 2. Child quyết định kích thước của nó │
│ "Tôi sẽ rộng 150, cao 100" │
└───────────────────┬─────────────────────┘
┌─────────────────────────────────────────┐
│ 3. Parent đặt vị trí cho child │
│ "Bạn sẽ ở vị trí (75, 50)" │
└─────────────────────────────────────────┘

Min = Max, widget buộc phải có kích thước cụ thể:

BoxConstraints.tight(Size(100, 100))
// minWidth = maxWidth = 100
// minHeight = maxHeight = 100

Min = 0, widget có thể chọn bất kỳ kích thước nào từ 0 đến max:

BoxConstraints.loose(Size(100, 100))
// minWidth = 0, maxWidth = 100
// minHeight = 0, maxHeight = 100

Khi maxWidth/maxHeight là vô hạn:

// Trong ListView, height là unbounded
// Trong Row, width là unbounded (cho children)

⚠️ Đây là nguyên nhân gây ra nhiều lỗi layout!


// ❌ Lỗi: Column trong Row, cả hai đều cố gắng expand
Row(
children: [
Column(
children: [Text('Item 1'), Text('Item 2')],
),
],
)
// ✅ Fix: Wrap với Expanded hoặc set kích thước
Row(
children: [
Expanded(
child: Column(
children: [Text('Item 1'), Text('Item 2')],
),
),
],
)

“Vertical viewport was given unbounded height”

Phần tiêu đề ““Vertical viewport was given unbounded height””
// ❌ Lỗi: ListView trong Column (cả hai expand vô hạn theo height)
Column(
children: [
ListView(...),
],
)
// ✅ Fix 1: Wrap ListView với Expanded
Column(
children: [
Expanded(
child: ListView(...),
),
],
)
// ✅ Fix 2: Hoặc set shrinkWrap
Column(
children: [
ListView(
shrinkWrap: true, // ListView co theo nội dung
),
],
)

Đọc constraints trong runtime:

LayoutBuilder(
builder: (context, constraints) {
print('Max width: ${constraints.maxWidth}');
print('Max height: ${constraints.maxHeight}');
if (constraints.maxWidth > 600) {
return WideLayout();
} else {
return NarrowLayout();
}
},
)

Cho children của Row/Column có cùng kích thước:

// Tất cả children trong Row có cùng height
IntrinsicHeight(
child: Row(
children: [
Container(color: Colors.red, width: 50, height: 100),
Container(color: Colors.green, width: 50), // Sẽ có height = 100
Container(color: Colors.blue, width: 50), // Sẽ có height = 100
],
),
)

⚠️ IntrinsicHeight/Width tốn performance, dùng hạn chế!


Widget Behavior
Container Nếu không có child, expand full. Có child thì co theo child
SizedBox Đặt constraints cụ thể
ConstrainedBox Thêm constraints
UnconstrainedBox Bỏ constraints (child có thể overflow)
FractionallySizedBox Kích thước theo % của parent

// Xem constraints trong debug
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
debugPrint('Constraints: $constraints');
return MyWidget();
},
);
}

Hoặc sử dụng Flutter DevTools → Layout Explorer.


Khái niệm Mô tả
Constraints Giới hạn min/max của kích thước
Tight min = max (kích thước cố định)
Loose min = 0 (có thể nhỏ hơn)
Unbounded max = infinity (nguy hiểm!)

Quy tắc vàng:

  1. Constraints đi từ parent xuống child
  2. Sizes đi từ child lên parent
  3. Parent quyết định position của child