Flutter BottomSheetController: Bậc Thầy Điều Khiển Cửa Sổ Thông Minh
Flutter

Flutter BottomSheetController: Bậc Thầy Điều Khiển Cửa Sổ Thông Minh

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

2 Lượt

"BottomSheetController"

Chào mừng các bạn đến với buổi học hôm nay, nơi chúng ta sẽ cùng nhau "giải phẫu" một khái niệm nghe có vẻ phức tạp nhưng lại cực kỳ quyền năng trong Flutter: BottomSheetController.

1. BottomSheetController là gì và để làm gì?

Hãy hình dung thế này: trong thế giới ứng dụng của chúng ta, đôi khi bạn cần trưng ra một "tấm bảng thông báo" nhỏ, không quá phô trương, chỉ nhẹ nhàng trượt lên từ dưới màn hình để cung cấp thêm thông tin hoặc yêu cầu một thao tác nhanh. Đó chính là BottomSheet – một "người phục vụ" lịch thiệp, không bao giờ chiếm hết sự chú ý của bạn mà chỉ xuất hiện đúng lúc, đúng chỗ.

Thế còn BottomSheetController? Nó chính là người quản lý sự kiện của "người phục vụ" đó. Nó không phải là bản thân tấm bảng (BottomSheet), mà là công cụ để bạn ra lệnh và điều khiển tấm bảng ấy. Cụ thể hơn, khi bạn sử dụng Scaffold.of(context).showBottomSheet(), Flutter sẽ trả về cho bạn một đối tượng PersistentBottomSheetController. Đây chính là "chìa khóa" giúp bạn tương tác trực tiếp với BottomSheet đó: đóng nó lại, lắng nghe trạng thái của nó, hoặc thậm chí thay đổi hành vi của nó một cách có lập trình.

Vậy, mục đích tối thượng của PersistentBottomSheetController là gì? Nó cho phép chúng ta:

  • Đóng BottomSheet một cách có lập trình: Thay vì chờ người dùng vuốt xuống hoặc nhấn nút Back, bạn có thể tự động đóng BottomSheet sau khi một hành động nào đó hoàn tất (ví dụ: sau khi người dùng nhấn "Lưu" hoặc "Gửi").
  • Kiểm soát trạng thái: Mặc dù ít phổ biến hơn cho việc thay đổi nội dung trực tiếp (thường dùng StatefulWidget bên trong BottomSheet), nhưng nó cung cấp các phương thức để tương tác với lifecycle của BottomSheet.

Lưu ý nhỏ nhưng cực kỳ quan trọng: Flutter có hai loại BottomSheet chính:

  • showModalBottomSheet: Loại này hoạt động như một cửa sổ pop-up (modal), che mờ phần còn lại của màn hình. Nó đóng lại bằng cách gọi Navigator.pop(context). Nó không trả về một PersistentBottomSheetController trực tiếp.
  • Scaffold.of(context).showBottomSheet: Đây là loại persistent (dai dẳng), nó nằm đè lên nội dung chính của Scaffold mà không làm mờ nền, và thường được dùng cho các thanh công cụ hoặc thông tin liên tục. Chính phương thức này trả về PersistentBottomSheetController mà chúng ta đang nói đến.
Illustration

2. Code Ví Dụ Minh Hoạ: Mở, Đóng và Kiểm Soát

Hãy cùng xây dựng một ứng dụng nhỏ nơi chúng ta có thể mở một PersistentBottomSheet và đóng nó lại bằng một nút bấm bên trong chính nó, sử dụng PersistentBottomSheetController.

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 BottomSheet Controller',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // Biến để lưu trữ PersistentBottomSheetController
  PersistentBottomSheetController? _bottomSheetController;

  void _showMyPersistentBottomSheet() {
    // Đảm bảo rằng Scaffold.of(context) có thể tìm thấy Scaffold
    // Nếu bạn gọi showBottomSheet trực tiếp trong build method của Scaffold, 
    // context đó sẽ không chứa Scaffold.
    // Cách an toàn nhất là bọc nó trong Builder hoặc gọi từ một context con.
    _bottomSheetController = Scaffold.of(context).showBottomSheet(
      (BuildContext context) {
        return Container(
          height: 200,
          color: Colors.amber,
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                const Text('Đây là Persistent BottomSheet của bạn!'),
                const SizedBox(height: 20),
                ElevatedButton(
                  child: const Text('Đóng BottomSheet này'),
                  onPressed: () {
                    // Sử dụng controller để đóng BottomSheet
                    _bottomSheetController?.close();
                    // Đặt lại controller về null để tránh rò rỉ bộ nhớ
                    _bottomSheetController = null;
                  },
                ),
              ],
            ),
          ),
        );
      },
    );

    // Bạn có thể lắng nghe trạng thái đóng của BottomSheet
    _bottomSheetController?.closed.whenComplete(() {
      debugPrint('BottomSheet đã đóng hoàn toàn!');
      _bottomSheetController = null; // Đảm bảo gán null khi đóng
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BottomSheet Controller Demo'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _bottomSheetController == null
              ? _showMyPersistentBottomSheet // Chỉ mở khi chưa có BottomSheet nào
              : null, // Vô hiệu hóa nút nếu BottomSheet đang mở
          child: const Text('Mở Persistent BottomSheet'),
        ),
      ),
    );
  }
}

Trong ví dụ trên:

  • Chúng ta khai báo một biến _bottomSheetController kiểu PersistentBottomSheetController? để lưu trữ đối tượng controller.
  • Khi nút "Mở Persistent BottomSheet" được nhấn, hàm _showMyPersistentBottomSheet được gọi.
  • Trong hàm này, Scaffold.of(context).showBottomSheet được sử dụng để hiển thị BottomSheet. Điều quan trọng là nó trả về PersistentBottomSheetController và chúng ta gán nó vào biến _bottomSheetController.
  • Bên trong BottomSheet, có một nút "Đóng BottomSheet này". Khi nhấn, nó gọi _bottomSheetController?.close(). Đây chính là lúc PersistentBottomSheetController thể hiện quyền năng của nó, ra lệnh cho BottomSheet đóng lại một cách duyên dáng.
  • Chúng ta cũng thêm một listener _bottomSheetController?.closed.whenComplete(...) để biết khi nào BottomSheet đã đóng hoàn toàn, một kỹ thuật rất hữu ích để quản lý trạng thái UI.

3. Mẹo (Best Practices) để ghi nhớ và ứng dụng thực tế

Để sử dụng BottomSheetController (và BottomSheet nói chung) hiệu quả như một "giảng viên lão luyện" của Harvard, bạn cần nắm vững vài "nguyên tắc vàng" sau:

  • Hiểu rõ Persistent vs. Modal: Đây là khác biệt cốt lõi. PersistentBottomSheetController chỉ dành cho showBottomSheet(). Nếu bạn dùng showModalBottomSheet(), việc đóng nó được thực hiện bằng Navigator.pop(context), vì bản chất nó là một route mới được đẩy lên stack điều hướng. Đừng nhầm lẫn hai "người phục vụ" này!
  • Quản lý BuildContext cẩn thận: Khi gọi Scaffold.of(context), context đó phải là con của Scaffold. Nếu bạn gọi nó ngay trong build method của Scaffold chính, bạn sẽ gặp lỗi. Giải pháp: bọc nút gọi showBottomSheet trong một Builder widget, hoặc gọi từ một StatefulWidget con, như trong ví dụ của chúng ta.
  • Giải phóng tài nguyên: Sau khi BottomSheet đóng, hãy gán _bottomSheetController = null; để đảm bảo không có rò rỉ bộ nhớ và ứng dụng của bạn luôn "sạch sẽ" như một thư viện mới tinh. Điều này cũng giúp bạn kiểm soát trạng thái nút "Mở" như trong ví dụ (chỉ cho phép mở khi chưa có BottomSheet nào đang hoạt động).
  • UX là Vua: BottomSheet là để bổ trợ, không phải thay thế toàn bộ màn hình. Đừng nhồi nhét quá nhiều thông tin hay quá nhiều hành động vào đó. Hãy coi nó như một "ghi chú dán" thông minh, cung cấp thông tin nhanh hoặc tác vụ đơn giản.
  • Sử dụng closed Future: _bottomSheetController?.closed trả về một Future. Bạn có thể dùng .whenComplete() để thực hiện các hành động sau khi BottomSheet đã đóng hoàn toàn, ví dụ như cập nhật UI chính hoặc thực hiện một logic nghiệp vụ nào đó.

4. Ví dụ Thực Tế Các Ứng Dụng Đã Ứng Dụng

BottomSheet (và khả năng điều khiển chúng) là một trong những "viên gạch" cơ bản xây dựng nên trải nghiệm người dùng hiện đại. Bạn sẽ thấy chúng ở khắp mọi nơi:

  • Google Maps: Khi bạn chạm vào một địa điểm, một BottomSheet trượt lên hiển thị thông tin chi tiết về địa điểm đó (địa chỉ, giờ mở cửa, đánh giá). Bạn có thể vuốt nó lên cao hơn để xem đầy đủ hoặc vuốt xuống để đóng.
  • Spotify/Apple Music: Mini-player ở cuối màn hình khi bạn đang nghe nhạc. Đây là một ví dụ điển hình của PersistentBottomSheet. Chạm vào nó sẽ mở rộng ra thành giao diện điều khiển nhạc đầy đủ (thường là một BottomSheet lớn hơn hoặc một màn hình mới).
  • Các ứng dụng ngân hàng/thanh toán: Khi bạn thực hiện một giao dịch hoặc cần chọn phương thức thanh toán, thường có một BottomSheet xuất hiện để bạn xác nhận hoặc lựa chọn.
  • Ứng dụng quản lý tác vụ (To-do lists): Khi bạn nhấn nút "+" để thêm một tác vụ mới, thay vì chuyển sang một màn hình hoàn toàn mới, một BottomSheet có thể trượt lên để bạn nhập thông tin nhanh chóng.

Nhớ nhé, BottomSheetController không phải là một "công tắc" thần kỳ cho mọi loại BottomSheet, mà là một "tay điều khiển từ xa" chuyên dụng cho PersistentBottomSheet. Nắm vững nó, bạn sẽ thêm một công cụ mạnh mẽ vào kho vũ khí Flutter của mình để tạo ra những giao diện người dùng mượt mà và trực quan hơn!

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!