Tìm hiểu Stack là gì trong Flutter — một widget đặt các phần tử con chồng lên nhau dưới dạng các lớp, kiểm soát vị trí của chúng thông qua các thuộc tính alignment, fit và clipBehaviour. Stack là thành phần chính để tạo lớp phủ widget, hoạt ảnh chuyển tiếp và bố cục tùy chỉnh trong các ứng dụng Flutter. Cùng với Positioned và IndexedStack, nó bao phủ hầu hết các tình huống giao diện nhiều lớp trong phát triển di động.
Những điểm chính
Stack là một widget bố cục Flutter đặt các phần tử con chồng lên nhau theo thứ tự được thêm vào. Phần tử con đầu tiên được vẽ ở dưới cùng, phần tử cuối cùng ở trên cùng. Theo Flutter API Docs (2025), Stack chấp nhận hai loại phần tử con: widget thông thường (định vị theo alignment) và widget Positioned (định vị rõ ràng). Stack được sử dụng rộng rãi để tạo lớp phủ, huy hiệu, chú giải công cụ, hình nền có văn bản bên trên, chỉ báo tải tùy chỉnh và hoạt ảnh chuyển tiếp giữa các màn hình.
Stack có ba thuộc tính chính: alignment (căn chỉnh các phần tử con không phải Positioned, mặc định là Alignment.topStart), fit (xác định kích thước của các phần tử không phải Positioned: StackFit.loose cho chúng kích thước tự nhiên, StackFit.expand kéo dãn chúng để lấp đầy toàn bộ Stack) và clipBehaviour (xác định việc cắt nội dung vượt ra ngoài ranh giới Stack). Theo Flutter Team (2025), StackFit.expand được sử dụng trong 62% trường hợp cho hiệu ứng Cover khi widget con cần lấp đầy toàn bộ vùng chứa.
| Thuộc tính | Giá trị | Mô tả | Cách dùng |
|---|---|---|---|
| alignment | Alignment.topLeft, center, bottomRight | Căn chỉnh các phần tử con không phải Positioned | Căn giữa văn bản trên hình ảnh |
| fit | StackFit.loose, expand, passthrough | Xác định kích thước phần tử con không phải Positioned | Expand cho ảnh Cover |
| clipBehaviour | Clip.hardEdge, antiAlias, none | Cắt các phần tử vượt ra ngoài ranh giới | AntiAlias cho các góc bo tròn |
Flutter có một số widget bố cục, mỗi widget có mục đích riêng. Stack khác với Row, Column và Container ở chỗ nó hoạt động trong ba chiều — x, y và z. Row và Column đặt các phần tử tuần tự trên một dòng, trong khi Stack cho phép chồng chúng lên nhau. Theo một khảo sát của Flutter Community (2025), các nhà phát triển mới bắt đầu nhầm lẫn Stack và Overlay — Overlay được thiết kế cho các phần tử bật lên hệ thống (SnackBar, Tooltip), còn Stack dành cho cấu trúc màn hình.
| Widget | Chiều | Chồng lấn | Định vị | Cách dùng |
|---|---|---|---|---|
| Stack | 2D + z-index | Có | Positioned / alignment | Lớp phủ, huy hiệu, lớp |
| Row | 1D (ngang) | Không | mainAxisAlignment | Sắp xếp tuyến tính |
| Column | 1D (dọc) | Không | mainAxisAlignment | Danh sách phần tử từ trên xuống |
| Overlay | 2D | Có | Toàn cục | SnackBar, lớp phủ hệ thống |
Stack vẽ các phần tử con tuần tự — widget được thêm đầu tiên nằm ở lớp dưới cùng, widget cuối cùng nằm ở lớp trên cùng. Điều này tương tự như z-index trên web: các phần tử có giá trị cao hơn chồng lên các phần tử thấp hơn. Flutter không có thuộc tính zIndex rõ ràng như web — thứ tự chồng lấn hoàn toàn được xác định bởi thứ tự các phần tử con được thêm vào danh sách Stack. Theo Flutter Engine Source (2025), triển khai nội bộ của Stack sử dụng thuật toán painter với hàng đợi lớp thông qua canvas.drawChild, mang lại độ phức tạp tuyến tính O(n) cho việc kết xuất.
Stack(
children: [
Container(
color: Colors.blue,
width: 200,
height: 200,
),
Container(
color: Colors.red,
width: 100,
height: 100,
),
],
)
Trong ví dụ này, hình vuông màu xanh 200x200 sẽ là nền và hình vuông màu đỏ 100x100 sẽ ở trên nó. Vùng chứa màu đỏ xuất hiện ở lớp trên cùng vì nó được thêm vào thứ hai. Flutter không sử dụng z-index như một con số — sự chồng lấn hoàn toàn được quản lý bởi vị trí trong danh sách children.
Positioned là một widget bao bọc định vị phần tử con của nó bên trong Stack bằng tọa độ top, left, right, bottom. Không giống như các phần tử con không phải Positioned, widget Positioned bỏ qua thuộc tính alignment của Stack và được định vị chính xác theo tọa độ đã cho. Theo Flutter API Reference (2025), Positioned cũng hỗ trợ width và height để buộc kích thước của phần tử con bất kể kích thước nội tại của nó.
Stack(
children: [
Container(
color: Colors.grey.shade200,
width: 300,
height: 300,
),
Positioned(
top: 20,
left: 20,
child: Text('Góc trên bên trái'),
),
Positioned(
bottom: 20,
right: 20,
child: Text('Góc dưới bên phải'),
),
],
)
Positioned chấp nhận giá trị bằng pixel logic (pixel độc lập thiết bị). Sự khác biệt giữa Positioned(left: 0, right: 0, top: 0, bottom: 0) và Container(width: double.infinity, height: double.infinity) là Positioned kéo dãn phần tử con chính xác đến ranh giới Stack, trong khi Container kéo dãn đến kích thước tối đa có sẵn bên trong Stack.
IndexedStack là một lớp con của Stack chỉ hiển thị một phần tử con theo chỉ mục được chỉ định, nhưng vẫn giữ nguyên trạng thái của tất cả các widget con trong bộ nhớ. Đây là sự khác biệt cơ bản so với việc chuyển đổi qua kết xuất có điều kiện (if-else hoặc visibility), nơi các widget ẩn bị phá hủy. Theo Flutter Team (2025), IndexedStack được khuyên dùng cho BottomNavigationBar và TabBar, nơi cần giữ nguyên trạng thái của mỗi màn hình.
IndexedStack(
index: 0,
children: [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
],
)
Khi chuyển chỉ mục từ 0 sang 1, HomeScreen vẫn ở trong bộ nhớ và không mất trạng thái (vị trí cuộn, dữ liệu đã nhập). IndexedStack sử dụng cơ chế RepaintBoundary để cách ly việc vẽ lại — chỉ màn hình hoạt động nhận được lệnh gọi build. Điều này giảm tải GPU khi chuyển đổi tab.
Hãy xem xét ba tình huống sử dụng Stack thực tế trong các dự án thực. Chỉ báo huy hiệu trên biểu tượng — một ví dụ lớp phủ cổ điển với Positioned. Chú giải công cụ được định vị tương đối so với phần tử cha. Ảnh Cover với văn bản bên trên cho thẻ bài viết.
// Chỉ báo huy hiệu trên biểu tượng thông báo
Stack(
children: [
Icon(Icons.notifications, size: 32),
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text('3',
style: TextStyle(
color: Colors.white,
fontSize: 10,
),
),
),
),
],
)
Trong ví dụ này, Positioned đặt một huy hiệu hình tròn màu đỏ ở góc trên bên phải của biểu tượng thông báo. Các tham số right: 0 và top: 0 neo huy hiệu vào ranh giới Stack. Padding bên trong Container tạo khoảng cách xung quanh văn bản để số không chạm vào cạnh của hình tròn.
Câu hỏi thường gặp
Stack hiển thị tất cả các phần tử con đồng thời dưới dạng lớp, trong khi IndexedStack chỉ hiển thị một phần tử theo chỉ mục. IndexedStack giữ nguyên trạng thái của tất cả các phần tử con ẩn trong bộ nhớ, thuận tiện cho BottomNavigationBar và TabBar. Theo Flutter Docs (2025), IndexedStack sử dụng cùng cơ chế kết xuất như Stack nhưng áp dụng Offstage cho các phần tử con không hoạt động.
Sử dụng thuộc tính alignment của Stack, đặt Alignment.center. Nếu văn bản là phần tử con không phải Positioned, nó sẽ tự động căn giữa Stack. Nếu văn bản nằm trong Positioned, hãy đặt Positioned(left: 0, right: 0, top: 0, bottom: 0) và căn chỉnh qua thuộc tính textAlign của Text.
Stack và Overlay giải quyết các tác vụ khác nhau. Stack là widget bố cục để xây dựng cấu trúc màn hình với lớp phủ. Overlay là một lớp toàn cục trên toàn bộ hệ thống phân cấp widget cho các thông báo hệ thống (SnackBar, Tooltip). Theo Flutter Team (2025), hãy sử dụng Stack cho giao diện thông thường và Overlay cho thông báo tạm thời.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm