Overlay trong Flutter: Khi Widget Muốn 'Bay Lượn' Ngoài Quy Đạo!
Flutter

Overlay trong Flutter: Khi Widget Muốn 'Bay Lượn' Ngoài Quy Đạo!

Author

Admin System

@root

Ngày xuất bản

20 Mar, 2026

Lượt xem

2 Lượt

"Overlay"

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 OverlayStateremove nó khi không cần nữa.

Illustration

Gợi Ý Đọc Tiếp
NotificationListenerState: 'Thính Giác' của Flutter

3 Lượt xem

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 đến OverlayEntry của mình.
  • Hàm _showOverlay sẽ tạo OverlayEntry với widget Positioned bên trong để định vị tooltip.
  • Overlay.of(context).insert(_overlayEntry!); là phép thuật chèn widget vào lớp Overlay.
  • _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 widget OverlayEntry khô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ảo context bạn truyền vào là của một widget nằm trong cây widget có Overlay (thường là MaterialApp hoặc WidgetsApp đã cung cấp sẵn Overlay). Trong ví dụ trên, anh dùng context của ElevatedButton để định vị tooltip, nhưng Overlay.of(context) vẫn sẽ tìm đến OverlayState gầ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 OverlayEntry 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, SlideTransition, hoặc AnimatedOpacity. 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 Stack hay showDialog/showModalBottomSheet không đủ linh hoạt: showDialogshowModalBottomSheet cũ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. OverlayEntry cho 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é!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!