
Chào các "dev-er" tương lai của vũ trụ số! Giảng viên Creyt đây, hôm nay chúng ta sẽ cùng khám phá một khái niệm nghe thì có vẻ cao siêu nhưng thực chất lại cực kỳ thú vị và quyền năng trong Flutter: OverlayState. Nghe cái tên đã thấy mùi "trên trời" rồi phải không?
OverlayState là gì mà Gen Z phải biết?
Các bạn cứ hình dung thế này: Ứng dụng Flutter của chúng ta như một sân khấu kịch hoành tráng. Mỗi Widget là một diễn viên, một đạo cụ trên sân khấu đó, tất cả đều tuân thủ kịch bản, vị trí của mình trong "cây widget" (widget tree). Nhưng đôi khi, đạo diễn (chính là bạn đó) muốn có một hiệu ứng đặc biệt, một ánh đèn spotlight rọi từ trên cao xuống, một dòng chữ chạy ngang màn hình, hay một bong bóng thoại bất ngờ xuất hiện trên tất cả các diễn viên và đạo cụ khác mà không làm xáo trộn bố cục sân khấu chính.
Đó chính là lúc OverlayState ra tay! Nó như một lớp kính trong suốt phủ lên toàn bộ sân khấu của bạn. Bạn có thể "dán" bất kỳ widget nào lên tấm kính này, và chúng sẽ xuất hiện trên mọi thứ khác, bất kể chúng đang ở đâu trong cái cây widget rậm rạp kia. "À à, vậy là mình có thể làm mấy cái pop-up, tooltip xịn sò mà không sợ bị đè bởi các widget khác đúng không thầy?" - Chính xác!
Nói một cách hàn lâm hơn, OverlayState là một State quản lý một stack các OverlayEntry. Mỗi OverlayEntry chính là "tấm vé VIP" cho widget của bạn được xuất hiện trên lớp kính trong suốt kia. Khi bạn "insert" một OverlayEntry, nó sẽ được thêm vào stack đó và hiển thị. Khi bạn "remove", nó biến mất.
Dùng để làm gì?
OverlayState là "vũ khí bí mật" cho những trường hợp bạn cần một widget:
- Nổi trên mọi thứ: Không bị giới hạn bởi
parentwidget hayclipcủa các widget khác. - Xuất hiện ở vị trí tùy ý: Bạn có thể định vị nó theo màn hình, không theo vị trí tương đối của cha mẹ.
- Tương tác độc lập: Nó có thể nhận sự kiện chạm mà không ảnh hưởng đến các widget bên dưới.

Code Ví Dụ Minh Hoạ: "Toast" thông báo siêu tốc
Để các bạn dễ hình dung, chúng ta sẽ tạo một cái "toast" thông báo nhỏ nhắn, xinh xắn, bay ra giữa màn hình rồi tự động biến mất – y hệt như mấy cái notification trên Instagram hay TikTok vậy.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'OverlayState Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
OverlayEntry? _overlayEntry; // Biến để giữ tham chiếu đến OverlayEntry
void _showOverlay(BuildContext context) {
// Bước 1: Đảm bảo không có overlay cũ nào đang hiển thị
_overlayEntry?.remove();
// Bước 2: Tạo một OverlayEntry mới
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
// Định vị widget của bạn trên màn hình
top: 100.0, // Cách mép trên 100px
left: MediaQuery.of(context).size.width * 0.1, // Cách mép trái 10%
width: MediaQuery.of(context).size.width * 0.8, // Chiếm 80% chiều rộng
child: Material( // Material giúp widget có elevation và design đẹp hơn
color: Colors.transparent, // Nền trong suốt để chỉ hiển thị nội dung
child: Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.black87, // Nền đen mờ
borderRadius: BorderRadius.circular(8.0),
boxShadow: const [
BoxShadow(
color: Colors.black26,
blurRadius: 10.0,
offset: Offset(0, 4),
),
],
),
child: const Text(
'Bạn vừa kích hoạt Overlay! Nó nằm trên mọi thứ đấy!',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 16.0),
),
),
),
),
);
// Bước 3: Thêm OverlayEntry vào OverlayState của ứng dụng
// Overlay.of(context) sẽ tìm OverlayState gần nhất trong cây widget
Overlay.of(context).insert(_overlayEntry!);
// Bước 4: Tự động remove overlay sau 3 giây (tùy chỉnh thời gian)
Future.delayed(const Duration(seconds: 3), () {
_overlayEntry?.remove(); // Xóa overlay khỏi màn hình
_overlayEntry = null; // Đặt lại về null để sẵn sàng cho lần hiển thị tiếp theo
});
}
@override
void dispose() {
// Đảm bảo overlay được remove khi widget cha bị dispose
_overlayEntry?.remove();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter OverlayState Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Đây là nội dung chính của ứng dụng.',
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => _showOverlay(context),
child: const Text('Hiện thông báo Overlay'),
),
const SizedBox(height: 20),
// Widget này sẽ bị Overlay che khi nó xuất hiện
Container(
height: 100,
width: 200,
color: Colors.green,
child: const Center(
child: Text('Widget này sẽ bị Overlay che', style: TextStyle(color: Colors.white)),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_showOverlay(context);
},
child: const Icon(Icons.add),
),
);
}
}
Giải thích Code:
OverlayEntry? _overlayEntry;: Đây là biến để chúng ta giữ "tấm vé VIP" cho widget của mình. Quan trọng là phải giữ nó để sau này còn biết đường mà "thu vé" lại (remove)._showOverlay(BuildContext context): Hàm này là nơi "phép thuật" xảy ra. Nó nhậncontextđể có thể tìm đượcOverlayStatecủa ứng dụng._overlayEntry?.remove();: Luôn kiểm tra và remove overlay cũ nếu có, tránh tình trạng "chồng chéo" các lớp kính lên nhau.OverlayEntry(...): Chúng ta tạo mộtOverlayEntrymới. Bên trong nó là mộtbuilderfunction, nơi bạn định nghĩa widget mà mình muốn hiển thị "trên trời".Positioned(...): Thường thì các widget trongOverlayEntrysẽ được bọc bởiPositionedđể bạn có thể định vị chính xác chúng trên màn hình (dùngtop,left,right,bottom,width,height).Material(...): Nên bọc widget của bạn trongMaterialđể nó thừa hưởng các thuộc tính Material Design nhưelevation(tạo bóng đổ) haysplash effectnếu có tương tác.Overlay.of(context).insert(_overlayEntry!);: Đây là câu lệnh mấu chốt! Nó lấyOverlayStategần nhất trong cây widget (thường là củaMaterialApphoặcWidgetsApp) và "insert" tấm vé VIP của bạn vào. Thế là widget của bạn bay lên!Future.delayed(...): Để tạo hiệu ứng "toast" tự biến mất, chúng ta dùngFuture.delayedđể sau một khoảng thời gian nhất định, gọi_overlayEntry?.remove();để "gỡ" widget xuống.dispose(): Đừng quênremove_overlayEntrytrongdispose()củaStateđể tránh rò rỉ bộ nhớ khi widget bị hủy. Đây là một best practice cực kỳ quan trọng!
Mẹo hay từ Creyt (Best Practices)
- Context là chìa khóa: Để truy cập
Overlay.of(context),contextcủa bạn phải nằm bên dưới mộtOverlaytrong cây widget.MaterialApphayWidgetsApptự động cung cấpOverlaycho bạn, nên thường dùngcontexttừScaffoldhoặc bất kỳ widget con nào của nó là được. - Quản lý vòng đời (Lifecycle): Luôn nhớ
remove()OverlayEntrykhi không còn cần nữa. Nếu không, widget đó sẽ mãi mãi hiển thị (hoặc chiếm bộ nhớ) ngay cả khi bạn đã chuyển sang màn hình khác. Cứ nghĩ nó như việc bạn bật đèn thì phải biết tắt đèn vậy. Đặc biệt trongdispose()! - Hiệu suất:
OverlayStatemạnh mẽ, nhưng không phải lúc nào cũng là giải pháp tối ưu. Đối với các UI đơn giản chỉ cần xếp chồng trong một khu vực cụ thể, hãy dùngStackvàPositionedthay vìOverlay.Overlaydành cho những thứ cần nổi toàn cục. - Khả năng truy cập (Accessibility): Khi sử dụng overlay, hãy cân nhắc cách người dùng khuyết tật (ví dụ, dùng trình đọc màn hình) sẽ tương tác với nội dung của bạn. Đảm bảo trải nghiệm vẫn mượt mà và dễ hiểu.
- Animation: Để các overlay xuất hiện và biến mất mượt mà hơn, hãy kết hợp chúng với các widget animation như
FadeTransition,SlideTransitionhoặcAnimatedOpacity. Nó sẽ biến một cái "pop-up" thô cứng thành một "hiệu ứng" có hồn ngay!
Ứng dụng thực tế các website/ứng dụng đã dùng
OverlayState (hoặc các cơ chế tương tự trong các framework khác) được sử dụng rất nhiều:
- Tooltips (Gợi ý công cụ): Khi bạn hover chuột hoặc nhấn giữ một icon, một dòng chữ nhỏ hiện ra giải thích chức năng. Flutter có
Tooltipwidget, nhưng nếu bạn muốn custom "hết nấc" thìOverlayStatelà lựa chọn. - Context Menus (Menu ngữ cảnh): Nhấn giữ một item trên màn hình, một menu nhỏ hiện ra ngay tại vị trí bạn nhấn.
PopupMenuButtoncủa Flutter đã dùng cơ chế tương tự. - Custom Notifications/Snackbars: Các thông báo tùy chỉnh không theo chuẩn Material Design, bay từ trên xuống hoặc từ dưới lên, như ví dụ "toast" của chúng ta.
- Drag-and-Drop Feedback: Khi bạn kéo một item, một bản sao mờ của item đó bay theo con trỏ chuột để hiển thị bạn đang kéo gì và đi đâu.
- In-app Guides/Onboarding: Các mũi tên, pop-up hướng dẫn người dùng lần đầu sử dụng ứng dụng, chỉ ra các nút bấm quan trọng.
Thử nghiệm đã từng và Hướng dẫn nên dùng cho case nào
Với kinh nghiệm "chinh chiến" qua bao dự án, Creyt đã thấy OverlayState được dùng trong nhiều tình huống cực kỳ sáng tạo. Hồi xưa, có lần tôi cần xây dựng một hệ thống "baloon tip" (bong bóng gợi ý) cực kỳ phức tạp, có mũi tên chỉ vào đủ mọi hướng, animation bay ra bay vào đủ kiểu. Dùng OverlayState là giải pháp duy nhất để nó không bị các widget khác cắt xén hay đè lên.
Nên dùng khi:
- Bạn cần một widget xuất hiện trên mọi thứ trong route hiện tại, không bị giới hạn bởi bất kỳ parent nào.
- Bạn muốn định vị widget đó theo tọa độ tuyệt đối của màn hình, không phải tương đối trong một container.
- Bạn đang xây dựng các thành phần UI rất đặc thù như
tooltipcustom,context menucustom,floating notificationđộc đáo, hoặconboarding flowcó các phần tử nổi.
Không nên dùng khi:
- Bạn chỉ cần xếp chồng các widget trong một khu vực nhỏ của màn hình (hãy dùng
Stack). - Bạn muốn hiển thị một dialog đơn giản,
showDialog()của Flutter đã làm rất tốt việc này (và nó cũng dùngOverlaybên trong, nhưng đã được trừu tượng hóa cho bạn rồi). - Bạn đang cố gắng thay thế toàn bộ hệ thống navigation hoặc
ScaffoldbằngOverlayState. Đừng làm phức tạp hóa vấn đề!
OverlayState là một công cụ mạnh mẽ, nhưng như mọi công cụ quyền năng khác, nó cần được sử dụng đúng lúc, đúng chỗ. Đừng biến nó thành "mớ bòng bong" chỉ vì bạn thấy nó "ngầu". Hãy dùng nó một cách thông minh, và bạn sẽ thấy ứng dụng của mình "bay" cao hơn đấy!
Chúc các bạn code vui vẻ và tạo ra những hiệu ứng "trên trời" thật đỉnh!
Thuộc Series: Flutter
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!