
Overlay trong Flutter: Khi Widget Muốn 'Bay Lượn' Ngoài Quy Đạo!
Chào các bạn GenZ, anh Creyt đây! Hôm nay chúng ta sẽ cùng "mổ xẻ" một khái niệm mà nhiều bạn hay nhầm lẫn hoặc chưa khai thác hết tiềm năng của nó trong Flutter: Overlay. Nghe tên thì có vẻ "deep" nhưng thực ra nó cực kỳ gần gũi và hữu ích, đặc biệt khi các em muốn tạo ra những trải nghiệm UI "đỉnh của chóp" mà không bị ràng buộc bởi bố cục thông thường.
1. Overlay là gì và để làm gì? – "Kỹ thuật tàng hình" cho Widget!
Các em cứ hình dung thế này: Khi các em đang xem một bộ phim bom tấn, bỗng dưng có một cái thông báo "Tin nóng!" hiện ra ngay giữa màn hình, hoặc một cái nút "Like" bay lơ lửng theo ngón tay các em khi chạm vào. Những thứ "bay lượn" độc lập, không nằm trong luồng giao diện chính đó chính là ứng dụng của Overlay.
Trong Flutter, Overlay là một cơ chế cho phép chúng ta "chèn" các widget (gọi là OverlayEntry) lên trên các widget khác, thậm chí là lên trên toàn bộ ứng dụng, mà không bị ảnh hưởng bởi cấu trúc cây widget thông thường. Nó giống như việc các em có một tấm kính trong suốt, rồi vẽ vời đủ thứ lên đó, và tấm kính đó được đặt ngay trước mắt người xem, che phủ toàn bộ khung cảnh phía sau.
Để làm gì ư? Đơn giản là để tạo ra những hiệu ứng UI mà Stack, Positioned hay Align không thể làm được một cách dễ dàng. Khi các em cần một widget "nổi" lên trên tất cả, ví dụ:
- Một cái loading spinner toàn màn hình.
- Một tooltip "xịn xò" hiện ra khi người dùng giữ tay vào một icon.
- Một menu ngữ cảnh (context menu) xuất hiện ngay tại vị trí con trỏ chuột.
- Hay thậm chí là một "hint" hướng dẫn người dùng lần đầu sử dụng app.
2. Code Ví Dụ Minh Hoạ: "Bật mí" cách dùng Overlay
Để hiểu rõ hơn, chúng ta hãy cùng xem cách "triệu hồi" một OverlayEntry đơn giản nhé. Anh sẽ làm một ví dụ kinh điển: tạo một tooltip đơn giản khi nhấn nút.
Đầu tiên, chúng ta cần một OverlayEntry để chứa widget mà mình muốn "bay lượn". Sau đó, chúng ta sẽ insert nó vào OverlayState và remove nó khi không cần nữa.

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: 'Flutter Overlay 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ữ OverlayEntry
void _showOverlay(BuildContext context) {
// Nếu có overlay cũ đang hiển thị, thì xóa nó đi trước
_overlayEntry?.remove();
// Lấy RenderBox của widget mà chúng ta muốn overlay xuất hiện gần đó
final RenderBox renderBox = context.findRenderObject() as RenderBox;
final Size size = renderBox.size;
final Offset offset = renderBox.localToGlobal(Offset.zero);
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
left: offset.dx + size.width / 2 - 50, // Căn giữa tooltip theo chiều ngang
top: offset.dy - 40, // Đặt tooltip phía trên nút một chút
child: Material(
elevation: 4.0,
borderRadius: BorderRadius.circular(8.0),
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(8.0),
),
child: const Text(
'Đây là Tooltip!',
style: TextStyle(color: Colors.white, fontSize: 14.0),
),
),
),
),
);
// Chèn OverlayEntry vào OverlayState
Overlay.of(context).insert(_overlayEntry!);
// Tự động ẩn overlay sau 2 giây
Future.delayed(const Duration(seconds: 2), () {
_overlayEntry?.remove();
_overlayEntry = null; // Đặt lại null sau khi xóa
});
}
@override
void dispose() {
_overlayEntry?.remove(); // Đảm bảo overlay được xóa khi widget bị dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Overlay Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Nhấn nút bên dưới để xem Overlay:',
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => _showOverlay(context), // Truyền context của nút
child: const Text('Hiện Tooltip'),
),
],
),
),
);
}
}
Giải thích nhanh:
- Chúng ta dùng
OverlayEntry? _overlayEntry;để giữ tham chiếu đếnOverlayEntrycủa mình. - Hàm
_showOverlaysẽ tạoOverlayEntryvới widgetPositionedbên trong để định vị tooltip. Overlay.of(context).insert(_overlayEntry!);là phép thuật chèn widget vào lớpOverlay._overlayEntry?.remove();là cách chúng ta "thu hồi" widget về. Quan trọng là phải gọi nó khi không cần nữa để tránh rò rỉ bộ nhớ.- Anh dùng
RenderBoxđể lấy vị trí và kích thước của nút, từ đó tính toán vị trí cho tooltip một cách chính xác.
3. Mẹo (Best Practices) từ Creyt để "cầm cưa" Overlay hiệu quả!
- Quản lý lifecycle là chìa khóa: Luôn nhớ
_overlayEntry?.remove()khi widgetOverlayEntrykhông còn cần thiết nữa (ví dụ: khi màn hình bị đóng, hoặc sau một thời gian nhất định). Nếu không, nó sẽ "bay lơ lửng" mãi mãi trong bộ nhớ và gây ra lỗi hiển thị hoặc rò rỉ bộ nhớ. Coi chừng đấy, nó như "hồn ma" của widget vậy! - Context đúng chỗ, đúng lúc: Khi gọi
Overlay.of(context), hãy đảm bảocontextbạn truyền vào là của một widget nằm trong cây widget cóOverlay(thường làMaterialApphoặcWidgetsAppđã cung cấp sẵnOverlay). Trong ví dụ trên, anh dùngcontextcủaElevatedButtonđể định vị tooltip, nhưngOverlay.of(context)vẫn sẽ tìm đếnOverlayStategần nhất trong cây. - Không lạm dụng: Overlay mạnh mẽ nhưng không phải là giải pháp cho mọi vấn đề. Nếu chỉ cần xếp chồng các widget trong một khu vực nhất định, hãy ưu tiên dùng
Stack,Positioned. Overlay nên dùng cho những tình huống thực sự cần "thoát ly" khỏi bố cục cha mẹ. - Kết hợp với Animation: Để các
OverlayEntryxuấ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,SlideTransition, hoặcAnimatedOpacity. Nó sẽ biến trải nghiệm người dùng từ "ổn" thành "wow"! - Accessibility (Khả năng tiếp cận): Đảm bảo rằng các overlay của bạn không làm gián đoạn trải nghiệm của người dùng có nhu cầu đặc biệt (ví dụ: người dùng trình đọc màn hình). Cân nhắc cách họ sẽ tương tác và đóng các overlay này.
4. Ví dụ thực tế: "Ứng dụng của Overlay ở khắp mọi nơi!"
Các em có thể không nhận ra, nhưng Overlay đã và đang được sử dụng rất nhiều trong các ứng dụng mà các em dùng hàng ngày:
- Facebook/Instagram: Khi các em thấy một thông báo "Đã thích bài viết" nhỏ gọn hiện lên và tự động biến mất, hoặc khi một loading spinner toàn màn hình xuất hiện khi chuyển trang.
- Google Maps/Apple Maps: Các tooltip thông tin địa điểm xuất hiện khi các em chạm vào một điểm trên bản đồ.
- Các ứng dụng chỉnh sửa ảnh/video: Các menu ngữ cảnh nhỏ gọn hiện ra khi các em giữ tay vào một đối tượng, cho phép chỉnh sửa nhanh.
- Ứng dụng học tập/Onboarding: Các "tour" hướng dẫn người dùng mới, với các mũi tên và chú thích "bay" trên các nút chức năng.
5. Thử nghiệm của Creyt và lời khuyên chân thành
Anh nhớ hồi mới "vọc" Flutter, anh từng "đau đầu" với việc làm sao để một cái loading spinner nó "phủ" lên toàn bộ màn hình, dù màn hình đó có scrollable hay không, có nhiều lớp widget phức tạp đến mấy. Dùng Stack thì nó cứ bị giới hạn trong phạm vi của Stack đó, không "thoát" ra được.
Rồi anh tìm đến Overlay, và "à ố" ra rằng nó chính là "chân ái" cho những trường hợp cần một widget "đè" lên mọi thứ khác mà không bị ràng buộc bởi cây widget cha mẹ. Nó cho phép anh tạo ra một "lớp" riêng biệt, độc lập hoàn toàn với nội dung bên dưới.
Nên dùng Overlay cho case nào ư?
- Khi bạn cần một widget "bay lơ lửng" trên mọi thứ: Ví dụ như loading indicator toàn màn hình, custom toast message, floating context menu.
- Khi
StackhayshowDialog/showModalBottomSheetkhông đủ linh hoạt:showDialogvàshowModalBottomSheetcũng tạo ra các overlay, nhưng chúng có những hành vi mặc định (ví dụ: modal barrier, hiệu ứng đóng mở) mà đôi khi bạn muốn tùy chỉnh hoàn toàn.OverlayEntrycho bạn quyền kiểm soát tối đa. - Khi bạn cần điều khiển sự xuất hiện/biến mất của widget một cách độc lập: Không phụ thuộc vào việc rebuild của các widget cha.
Nhớ nhé, Overlay là một công cụ mạnh mẽ, nhưng như mọi công cụ mạnh mẽ khác, hãy dùng nó một cách có trách nhiệm và hiểu rõ cơ chế của nó. Chúc các em "code" ra những UI "chất lừ" với Overlay! Hẹn gặp lại trong bài học tiếp theo!
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é!