LongPressDraggable: Nắm Bắt Thế Giới Kéo Thả Trong Flutter
Flutter

LongPressDraggable: Nắm Bắt Thế Giới Kéo Thả Trong Flutter

Author

Admin System

@root

Ngày xuất bản

19 Mar, 2026

Lượt xem

1 Lượt

"LongPressDraggableState"

Chào mừng các bạn đến với buổi học hôm nay cùng anh Creyt! Chủ đề của chúng ta là một khái niệm tuy đơn giản về mặt ý tưởng nhưng lại cực kỳ mạnh mẽ trong việc tạo ra trải nghiệm người dùng (UX) sống động: LongPressDraggableState trong Flutter.

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

Anh em cứ hình dung thế này, trong thế giới lập trình Flutter, cái từ khóa "LongPressDraggableState" nó không phải là một class cụ thể mà anh em có thể new ra đâu nhé. Nó là cái trạng thái hay hành vi được kiểm soát bởi widget LongPressDraggable và các widget liên quan như DragTarget. Nói nôm na, nó chính là toàn bộ quá trình một vật thể được "nhấn giữ lâu" để kích hoạt chế độ kéo, sau đó di chuyển tự do và cuối cùng "thả" vào một vị trí mong muốn.

Để làm gì ư? Đơn giản thôi! Anh em muốn người dùng có thể sắp xếp lại danh sách công việc trên một ứng dụng Trello mini của riêng mình? Hay muốn họ kéo một icon ứng dụng từ màn hình chính vào thùng rác? Hoặc thậm chí là kéo thả các item vào giỏ hàng? Chính xác, LongPressDraggable là chìa khóa mở cánh cửa đó.

Phép ẩn dụ của Creyt: Hãy tưởng tượng anh em đang chơi một trò chơi xếp hình LEGO. Thay vì chỉ nhấc một viên gạch lên và đặt xuống ngay lập tức (như Draggable thông thường), anh em muốn chọn kỹ một viên gạch bằng cách giữ tay vào nó một lúc (đó là long press), sau đó mới bắt đầu di chuyển nó đến vị trí mới. Cái "trạng thái" của viên gạch khi nó đang được giữ trên tay anh em, lơ lửng giữa không trung, chờ được đặt vào một ô trống nào đó – đó chính là cái tinh thần của LongPressDraggableState. Nó không chỉ là "có đang kéo hay không", mà là "đang được giữ sau một cú nhấn giữ, và đang lơ lửng để tìm nơi hạ cánh".

Illustration

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

Để anh em dễ hình dung, chúng ta sẽ xây dựng một ví dụ đơn giản: kéo thả một hình vuông màu đỏ vào một vùng màu xanh lá cây. Khi thả thành công, vùng màu xanh sẽ thay đổi nội dung.

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: 'LongPressDraggable Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const DragAndDropScreen(),
    );
  }
}

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

  @override
  State<DragAndDropScreen> createState() => _DragAndDropScreenState();
}

class _DragAndDropScreenState extends State<DragAndDropScreen> {
  bool _isDropped = false;
  String _droppedText = 'Thả vật phẩm vào đây!';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Kéo Thả với LongPressDraggable'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            // Vật phẩm có thể kéo (Draggable item)
            LongPressDraggable<
                String>(
              data: 'Vật phẩm màu đỏ',
              feedback: Material(
                color: Colors.red.withOpacity(0.7),
                elevation: 4.0,
                child: SizedBox(
                  width: 100.0,
                  height: 100.0,
                  child: Center(
                    child: Text(
                      'Đang kéo!',
                      style: TextStyle(color: Colors.white, fontSize: 16),
                    ),
                  ),
                ),
              ),
              childWhenDragging: Container( // Widget hiển thị tại vị trí gốc khi đang kéo
                width: 100.0,
                height: 100.0,
                color: Colors.grey.shade300,
                child: const Center(child: Text('Đã rời đi')),
              ),
              child: Container( // Widget gốc
                width: 100.0,
                height: 100.0,
                color: Colors.red,
                child: const Center(child: Text('Kéo tôi!')),
              ),
              onDragStarted: () {
                print('Bắt đầu kéo!');
              },
              onDragEnd: (details) {
                print('Kết thúc kéo tại: ${details.offset}');
              },
            ),
            
            // Vùng có thể nhận vật phẩm (DragTarget)
            DragTarget<
                String>(
              builder: (BuildContext context, List<String?> candidateData, List rejectedData) {
                return Container(
                  width: 200.0,
                  height: 200.0,
                  color: _isDropped ? Colors.green.shade700 : Colors.green.shade300,
                  child: Center(
                    child: Text(
                      _droppedText,
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                      textAlign: TextAlign.center,
                    ),
                  ),
                );
              },
              onWillAccept: (data) {
                // Chỉ chấp nhận nếu data là 'Vật phẩm màu đỏ'
                print('Sẽ chấp nhận data: $data');
                return data == 'Vật phẩm màu đỏ';
              },
              onAccept: (data) {
                // Khi vật phẩm được thả thành công
                setState(() {
                  _isDropped = true;
                  _droppedText = '$data đã được thả thành công!';
                });
                print('Đã chấp nhận data: $data');
              },
              onLeave: (data) {
                // Khi vật phẩm rời khỏi vùng target mà chưa được thả
                print('Vật phẩm rời khỏi target: $data');
              },
            ),
          ],
        ),
      ),
    );
  }
}

Giải thích Code:

  • LongPressDraggable<String>: Đây là widget cho phép một child của nó có thể được kéo sau một cú nhấn giữ. Kiểu String trong dấu &lt;> chỉ ra kiểu dữ liệu mà nó sẽ mang theo khi được kéo.

    • data: Dữ liệu mà vật phẩm này mang theo. Khi kéo, DragTarget sẽ nhận được dữ liệu này.
    • feedback: Widget sẽ hiển thị dưới ngón tay người dùng trong suốt quá trình kéo. Anh em nên làm cho nó hơi trong suốt hoặc khác biệt so với child gốc để người dùng biết họ đang kéo cái gì.
    • childWhenDragging: Widget sẽ hiển thị tại vị trí gốc của LongPressDraggable khi nó đang được kéo. Thường dùng để tạo hiệu ứng "chỗ trống" hoặc "mờ đi" ở vị trí cũ.
    • child: Widget gốc ban đầu mà người dùng tương tác để kéo.
    • onDragStarted, onDragEnd: Các callback được gọi khi quá trình kéo bắt đầu và kết thúc.
  • DragTarget<String>: Đây là widget định nghĩa một vùng mà các Draggable có thể được thả vào.

    • builder: Hàm xây dựng giao diện của DragTarget. Nó nhận vào context, candidateData (danh sách các vật phẩm đang lơ lửng trên target này), và rejectedData (danh sách các vật phẩm không được chấp nhận).
    • onWillAccept: Callback này được gọi khi một Draggable lơ lửng trên DragTarget. Nó trả về true nếu DragTarget muốn chấp nhận vật phẩm đó, false nếu không. Đây là nơi anh em có thể kiểm tra data của vật phẩm kéo.
    • onAccept: Callback này được gọi khi một Draggable được thả thành công vào DragTarget (tức là onWillAccept trả về true).
    • onLeave: Callback này được gọi khi một Draggable rời khỏi DragTarget mà chưa được thả.

3. Mẹo (Best Practices) để Ghi Nhớ và Dùng Thực Tế

Để sử dụng LongPressDraggable như một pro, anh em cần nhớ vài "chiêu" sau:

  1. Phản hồi trực quan là Vàng: Luôn cung cấp feedback rõ ràng và khác biệt so với child gốc. Người dùng cần biết họ đang kéo cái gì và cái gì đang chờ ở vị trí cũ. Thêm một chút opacity hoặc elevation cho feedback là một ý hay.
  2. Quản lý data thông minh: Dữ liệu mà anh em truyền qua data là cực kỳ quan trọng. Nó giúp DragTarget biết được vật phẩm nào đang được kéo và có nên chấp nhận hay không. Hãy dùng các đối tượng (object) hoặc enum phức tạp hơn String nếu cần để truyền nhiều thông tin.
  3. Xử lý các sự kiện kéo thả đầy đủ: Đừng bỏ qua onDragStarted, onDragEnd, onWillAccept, onAccept, onLeave. Mỗi callback này đều là một cơ hội để anh em cập nhật UI hoặc logic nghiệp vụ, tạo ra trải nghiệm mượt mà và thông báo cho người dùng về trạng thái hiện tại.
  4. Tối ưu hiệu suất: Nếu anh em có nhiều LongPressDraggable hoặc DragTarget trong một danh sách dài, hãy cẩn thận với việc rebuild quá nhiều widget. Đôi khi, việc sử dụng ValueNotifier hoặc các giải pháp quản lý state cục bộ có thể giúp giảm thiểu rebuild không cần thiết.
  5. Kích thước và khoảng cách: Đảm bảo các vùng kéo và thả đủ lớn để người dùng dễ dàng tương tác, đặc biệt trên màn hình cảm ứng. Không ai thích cái cảm giác phải "nhắm" mãi mới trúng đích cả.

4. Ứng Dụng Thực Tế Các Ứng Dụng/Website Đã Ứng Dụng

LongPressDraggable và cơ chế kéo thả không phải là thứ gì đó xa lạ đâu anh em. Nó xuất hiện nhan nhản trong cuộc sống số của chúng ta:

  • Trello/Jira và các ứng dụng quản lý dự án: Việc kéo thả các thẻ công việc giữa các cột "To Do", "In Progress", "Done" là một ví dụ kinh điển. Nó giúp người dùng trực quan hóa và sắp xếp công việc cực kỳ hiệu quả.
  • Sắp xếp lại icon ứng dụng trên điện thoại: Anh em nhấn giữ một icon trên màn hình chính của Android hay iOS, sau đó kéo nó đi khắp nơi để sắp xếp lại, tạo thư mục. Đó chính là một dạng của LongPressDraggable đấy!
  • Xây dựng giao diện (UI Builder) dạng kéo thả: Các công cụ cho phép người dùng tự thiết kế trang web hay ứng dụng bằng cách kéo các thành phần (nút, hình ảnh, văn bản) vào một "canvas" cũng sử dụng cơ chế này. Ví dụ như Webflow, Bubble.io.
  • Giỏ hàng trong ứng dụng mua sắm: Một số ứng dụng cho phép người dùng kéo trực tiếp sản phẩm từ danh sách vào biểu tượng giỏ hàng để thêm vào. Tiện lợi phải không?
  • Game ghép hình, game giải đố: Rất nhiều game yêu cầu người chơi kéo các mảnh ghép vào đúng vị trí để hoàn thành bức tranh hoặc giải đố.

Hy vọng với bài giảng này, anh em đã "nắm bắt" được LongPressDraggable và có thể tự tin triển khai các tính năng kéo thả "chất như nước cất" trong các dự án Flutter của mình. Nhớ nhé, lập trình không chỉ là code, mà còn là tạo ra trải nghiệm tuyệt vời cho người dùng!

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!