DraggableScrollableSheet: Kéo Thả Sheet, Nâng Tầm UI Flutter!
Flutter

DraggableScrollableSheet: Kéo Thả Sheet, Nâng Tầm UI Flutter!

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

20 Lượt

"DraggableScrollableSheet"

Chào mừng các bạn đến với buổi học hôm nay cùng Creyt! Chủ đề của chúng ta là một “phù thủy” trong việc tạo ra những trải nghiệm UI động và mượt mà: DraggableScrollableSheet. Nghe cái tên đã thấy 'ngầu' rồi đúng không? Đừng lo, Creyt sẽ bóc tách nó dễ hiểu như bóc vỏ chuối vậy.

DraggableScrollableSheet là gì và để làm gì?

Để dễ hình dung, các bạn cứ tưởng tượng thế này: Bạn có một tấm rèm cửa sổ. Tấm rèm này không phải loại cố định mà là loại bạn có thể kéo lên, kéo xuống để lộ ra hoặc che đi phần khung cảnh bên ngoài. Bạn có thể kéo nó chỉ hé một chút, hoặc kéo lên nửa chừng, hay thậm chí là kéo gần hết để nhìn rõ mọi thứ. DraggableScrollableSheet chính là tấm rèm cửa sổ thông minh đó trong Flutter.

Nói một cách chính xác hơn, DraggableScrollableSheet là một widget cho phép bạn hiển thị một phần nội dung (một “sheet” hay “panel”) mà người dùng có thể kéo lên và kéo xuống để điều chỉnh kích thước hiển thị của nó. Nó không chỉ là một BottomSheet đơn thuần, mà còn “thông minh” hơn nhiều vì nó có thể nhớ vị trí, điều chỉnh kích thước theo tỷ lệ và quan trọng nhất là hòa mình vào luồng cuộn của nội dung bên trong.

Vậy tại sao chúng ta cần nó? Đơn giản thôi! Trong kỷ nguyên di động, không gian màn hình là vàng. DraggableScrollableSheet giúp chúng ta:

  1. Tiết kiệm không gian: Hiển thị thông tin bổ sung chỉ khi người dùng cần, thay vì chiếm hết màn hình ngay từ đầu.
  2. Cung cấp ngữ cảnh: Hiện thị chi tiết hơn về một mục nào đó mà không cần chuyển sang màn hình mới, giữ người dùng ở lại ngữ cảnh hiện tại.
  3. Tạo trải nghiệm hiện đại: Mang lại cảm giác tương tác trực quan, mượt mà như các ứng dụng bản đồ, ứng dụng giao đồ ăn mà bạn vẫn dùng hàng ngày.

Cấu trúc và Hoạt động của "Chiếc Rèm Thông Minh"

DraggableScrollableSheet về cơ bản là một widget con của Stack (hoặc các widget có thể xếp chồng lên nhau) và nó sẽ hiển thị ở phía dưới màn hình, trượt lên trên. Điểm mấu chốt để nó hoạt động “thông minh” là nó cần một ScrollableWidget (như ListView, GridView, SingleChildScrollView) làm con của nó. Và đây là lúc chúng ta nói về builderScrollController.

  • builder: Đây là một hàm mà bạn truyền vào DraggableScrollableSheet. Nó nhận hai tham số: BuildContext và một ScrollController. Cái ScrollController này chính là sợi dây thần kinh kết nối tấm rèm với nội dung bên trong. Bạn bắt buộc phải gán ScrollController này cho widget cuộn con của bạn (ví dụ: ListView hoặc SingleChildScrollView) để DraggableScrollableSheet biết khi nào thì kéo chính nó, khi nào thì cho phép nội dung bên trong cuộn.
  • initialChildSize: Kích thước ban đầu của sheet khi nó xuất hiện (tỷ lệ từ 0.0 đến 1.0). Ví dụ, 0.3 nghĩa là sheet chiếm 30% chiều cao màn hình.
  • minChildSize: Kích thước nhỏ nhất mà sheet có thể thu lại khi người dùng kéo xuống (tỷ lệ). Nếu kéo xuống thấp hơn giá trị này, sheet sẽ biến mất hoặc trở về minChildSize tùy thuộc vào cấu hình.
  • maxChildSize: Kích thước lớn nhất mà sheet có thể mở rộng khi người dùng kéo lên (tỷ lệ). 1.0 có nghĩa là nó có thể chiếm toàn bộ màn hình.
  • expand: Mặc định là false. Nếu bạn đặt true, sheet sẽ mở rộng để lấp đầy không gian còn lại theo chiều cao, thường là toàn bộ màn hình nếu maxChildSize1.0. Điều này hữu ích khi bạn muốn sheet tự động chiếm không gian tối đa có thể.
Illustration

Code Ví Dụ Minh Họa Rõ Ràng

Không nói suông, giờ chúng ta cùng xem một ví dụ thực tế về cách Creyt sử dụng DraggableScrollableSheet để tạo một sheet thông tin có thể kéo thả trên một màn hình nền đơn giản.

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: 'DraggableScrollableSheet Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const DraggableSheetExample(),
    );
  }
}

class DraggableSheetExample extends StatelessWidget {
  const DraggableSheetExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sheet Kéo Thả Của Creyt'),
        backgroundColor: Colors.deepPurple,
        foregroundColor: Colors.white,
      ),
      body: Stack(
        children: <Widget>[
          // Nội dung chính bên dưới sheet (ví dụ: một hình ảnh nền)
          Positioned.fill(
            child: Image.network(
              'https://picsum.photos/id/1025/800/600', // Một ảnh nền minh họa
              fit: BoxFit.cover,
              colorBlendMode: BlendMode.darken,
              color: Colors.black.withOpacity(0.3), // Làm tối ảnh nền
            ),
          ),
          // Đây chính là "chiếc rèm cửa thông minh" của chúng ta!
          DraggableScrollableSheet(
            initialChildSize: 0.3, // Ban đầu chiếm 30% chiều cao màn hình
            minChildSize: 0.1,    // Thu nhỏ tối thiểu còn 10%
            maxChildSize: 0.8,    // Mở rộng tối đa 80%
            expand: true,         // Sheet sẽ lấp đầy không gian còn lại theo chiều cao
            builder: (BuildContext context, ScrollController scrollController) {
              return Container(
                decoration: const BoxDecoration(
                  color: Colors.white, // Màu nền của sheet
                  borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black26,
                      blurRadius: 10,
                      offset: Offset(0, -5), // Tạo bóng đổ nhẹ phía trên
                    ),
                  ],
                ),
                child: Column(
                  children: [
                    // "Tay nắm" để người dùng biết là có thể kéo được
                    Padding(
                      padding: const EdgeInsets.symmetric(vertical: 10.0),
                      child: Container(
                        width: 40,
                        height: 5,
                        decoration: BoxDecoration(
                          color: Colors.grey[300],
                          borderRadius: BorderRadius.circular(10),
                        ),
                      ),
                    ),
                    Expanded(
                      child: ListView.builder(
                        controller: scrollController, // RẤT QUAN TRỌNG: Gán controller này!
                        itemCount: 50,
                        itemBuilder: (BuildContext context, int index) {
                          return ListTile(
                            leading: Icon(Icons.article, color: Colors.deepPurpleAccent),
                            title: Text(
                              'Mục thông tin số ${index + 1}',
                              style: TextStyle(fontWeight: FontWeight.bold),
                            ),
                            subtitle: Text('Chi tiết hơn về mục này, số liệu, mô tả...'),
                          );
                        },
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

Trong ví dụ trên, các bạn thấy DraggableScrollableSheet được đặt trong một Stack để nó có thể nằm trên một nội dung khác (ở đây là một hình ảnh nền). Điểm nhấn chính là cách chúng ta sử dụng scrollController được cung cấp bởi builder và gán nó cho ListView.builder. Đây là chìa khóa để Flutter biết khi nào thì kéo cả sheet, khi nào thì cuộn nội dung bên trong sheet.

Mẹo Vặt (Best Practices) từ Giảng viên Creyt

Để sử dụng DraggableScrollableSheet một cách hiệu quả và mượt mà nhất, Creyt có vài "mẹo vặt" muốn chia sẻ với các bạn:

  1. "Đừng quên sợi dây liên kết!": Luôn, luôn và luôn truyền scrollController từ hàm builder vào ScrollableWidget con của bạn (ví dụ: ListView, GridView, SingleChildScrollView). Đây là sợi dây thần kinh giúp DraggableScrollableSheet phân biệt giữa cử chỉ kéo sheet và cử chỉ cuộn nội dung. Nếu quên, bạn sẽ thấy hành vi cuộn rất "lạ" hoặc không hoạt động đúng.

  2. "Tối ưu hóa tầm nhìn": Thiết lập minChildSizemaxChildSize một cách hợp lý. minChildSize quá nhỏ có thể khiến người dùng khó kéo lên, hoặc không nhận ra sheet đang tồn tại. Ngược lại, maxChildSize quá lớn (ví dụ 1.0) mà nội dung ít thì sẽ tạo ra không gian trống không cần thiết. Hãy cân nhắc kỹ về trải nghiệm người dùng mong muốn.

  3. "Cho người dùng biết họ đang ở đâu": Thêm một "handle" (một thanh kéo nhỏ) ở phía trên cùng của sheet (như ví dụ code đã làm). Điều này giúp người dùng dễ dàng nhận biết rằng đây là một thành phần có thể kéo được và cung cấp một điểm neo trực quan để tương tác.

  4. "Hiệu suất là vàng": Nếu nội dung bên trong sheet của bạn rất nhiều hoặc phức tạp, hãy ưu tiên sử dụng các widget cuộn "xây dựng theo yêu cầu" như ListView.builder hoặc GridView.builder. Chúng chỉ render các item khi chúng hiển thị trên màn hình, giúp tối ưu hiệu suất và tránh lãng phí tài nguyên.

  5. "Thân thiện với bàn phím": Nếu sheet của bạn chứa các trường nhập liệu (TextField), hãy cân nhắc cách nó tương tác với bàn phím ảo. DraggableScrollableSheet có thể tự điều chỉnh khi bàn phím xuất hiện, nhưng đôi khi bạn cần tinh chỉnh thêm với MediaQuery.of(context).viewInsets.bottom để có trải nghiệm hoàn hảo.

Ứng Dụng Thực Tế

DraggableScrollableSheet không phải là một thứ gì đó xa vời, mà nó đang hiện diện khắp nơi trong các ứng dụng bạn dùng hàng ngày:

  • Google Maps / Apple Maps: Khi bạn tìm kiếm một địa điểm, một sheet thông tin sẽ trượt lên từ dưới. Bạn có thể kéo nó lên để xem chi tiết địa điểm, lộ trình, đánh giá, hoặc kéo xuống để ẩn bớt thông tin và tập trung vào bản đồ.
  • Grab / Uber: Sau khi chọn điểm đến, một sheet nhỏ hiện ra với thông tin tài xế, giá cả. Bạn có thể kéo sheet này lên để xem thêm các lựa chọn xe, chi tiết chuyến đi.
  • Spotify / Apple Music: Khi bạn đang nghe nhạc, thanh phát nhạc ở dưới cùng màn hình có thể được kéo lên thành một sheet lớn hơn để hiển thị lời bài hát, danh sách phát hoặc các điều khiển nâng cao.
  • Ứng dụng quản lý tác vụ / Ghi chú: Một số ứng dụng cho phép bạn kéo một sheet từ dưới lên để nhanh chóng thêm tác vụ mới hoặc xem chi tiết một ghi chú mà không cần rời khỏi danh sách chính.

Kết Luận

DraggableScrollableSheet là một công cụ mạnh mẽ và linh hoạt trong bộ công cụ Flutter, giúp bạn tạo ra các giao diện người dùng động, trực quan và hiệu quả. Nắm vững cách sử dụng nó, đặc biệt là mối liên kết giữa builderScrollController, bạn sẽ có thể "phù phép" ra những trải nghiệm người dùng mượt mà và hiện đại. Hãy thực hành thật nhiều để biến "tấm rèm thông minh" này thành của riêng bạn nhé! Hẹn gặp lại trong bài học tiếp theo của Creyt!

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!