Stack Widget trong Flutter
1. Giới thiệu
Stack cho phép xếp chồng widgets lên nhau:
Stack(
children: [
Container(width: 200, height: 200, color: Colors.red), // Dưới cùng
Container(width: 150, height: 150, color: Colors.green), // Giữa
Container(width: 100, height: 100, color: Colors.blue), // Trên cùng
],
)Nguyên lý xếp chồng (Layering)
Stack vẽ các children theo thứ tự: First-in, Bottom-out (Cái nào vào trước nằm dưới cùng).
STACK VISUALIZATION
┌──────────────┐
│ Top (3) │ ← Last Child (Blue)
┌─┴────────────┐ │
│ Middle (2) │ │ ← Middle Child (Green)
┌─┴────────────┐ │ │
│ Bottom (1) │ │ │ ← First Child (Red)
└──────────────┘─┴─┘
(3) che khuất (2)
(2) che khuất (1)2. Alignment
Stack(
alignment: Alignment.center, // Căn tất cả children vào giữa
children: [
Container(width: 200, height: 200, color: Colors.grey),
Container(width: 100, height: 100, color: Colors.blue),
],
)3. Positioned Widget
Định vị chính xác child trong Stack:
Stack(
children: [
Container(width: 200, height: 200, color: Colors.grey),
Positioned(
top: 10,
left: 10,
child: Container(width: 50, height: 50, color: Colors.red),
),
Positioned(
bottom: 10,
right: 10,
child: Container(width: 50, height: 50, color: Colors.blue),
),
],
)Positioned properties
| Property | Mô tả |
|---|---|
top | Khoảng cách từ top |
bottom | Khoảng cách từ bottom |
left | Khoảng cách từ left |
right | Khoảng cách từ right |
width | Chiều rộng cố định |
height | Chiều cao cố định |
4. Positioned.fill
Kéo dãn child full Stack:
Stack(
children: [
Positioned.fill(
child: Container(color: Colors.grey),
),
Center(
child: Text('Centered'),
),
],
)5. fit Property
Stack(
fit: StackFit.expand, // Kéo dãn children không có Positioned
children: [...],
)| Giá trị | Mô tả |
|---|---|
loose | Children có thể nhỏ hơn Stack (mặc định) |
expand | Kéo dãn non-positioned children full Stack |
passthrough | Giữ constraints từ parent |
6. clipBehavior
Stack(
clipBehavior: Clip.none, // Cho phép children tràn ra ngoài
children: [
Container(width: 100, height: 100, color: Colors.grey),
Positioned(
top: -20,
left: -20,
child: Container(width: 50, height: 50, color: Colors.red),
),
],
)7. Ví dụ thực tế
Badge trên Icon
Stack(
clipBehavior: Clip.none,
children: [
Icon(Icons.notifications, size: 30),
Positioned(
top: -5,
right: -5,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'3',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
),
],
)Image với Overlay
Stack(
children: [
Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
fit: BoxFit.cover,
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(8),
color: Colors.black54,
child: Text(
'Image Title',
style: TextStyle(color: Colors.white),
),
),
),
],
)Floating Action Button Style
Stack(
children: [
// Main content
ListView.builder(
itemCount: 20,
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
),
// FAB positioned
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
),
],
)Profile Avatar với Status
Stack(
children: [
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://...'),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
)8. IndexedStack
Chỉ hiển thị một child tại một thời điểm:
IndexedStack(
index: currentIndex, // 0, 1, hoặc 2
children: [
Container(color: Colors.red),
Container(color: Colors.green),
Container(color: Colors.blue),
],
)Hữu ích cho tab switching mà muốn giữ state.
📝 Tóm tắt
| Widget/Property | Mục đích |
|---|---|
Stack | Xếp chồng widgets |
Positioned | Định vị chính xác trong Stack |
alignment | Căn chỉnh mặc định cho children |
fit | Cách sizing children |
clipBehavior | Cho phép/không cho tràn |
IndexedStack | Hiển thị một child theo index |
Last updated on