DismissiblePane: Vuốt nhẹ, bay sạch - Nắm quyền kiểm soát danh sách!
Flutter

DismissiblePane: Vuốt nhẹ, bay sạch - Nắm quyền kiểm soát danh sách!

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

27 Lượt

"DismissiblePane"

Chào các lập trình viên tương lai! Hôm nay, Giảng viên Creyt sẽ dẫn các bạn đi khám phá một "công cụ" cực kỳ lợi hại trong kho vũ khí UI/UX của Flutter: Dismissible (hay chính xác hơn là Dismissible widget, vì DismissiblePane là một concept mở rộng từ nó, nhưng ý tưởng cốt lõi thì như nhau). Đừng lo, tôi sẽ giải thích cặn kẽ như khi bạn đang nhâm nhi ly cà phê sáng, dễ hiểu đến mức bà bạn cũng gật gù!

1. Dismissible là gì và để làm gì? (Vuốt phát là bay!)

Bạn đã bao giờ dùng ứng dụng email hay ứng dụng quản lý công việc chưa? Cái cảm giác vuốt một email sang trái để xóa, hay vuốt sang phải để đánh dấu đã đọc ấy, đó chính là Dismissible đang "làm trò" đó.

Hãy tưởng tượng thế này: bạn có một chồng giấy tờ lộn xộn trên bàn, mỗi tờ là một nhiệm vụ cần làm. Với Dismissible, bạn như có một "cái thùng rác ma thuật" ngay bên cạnh. Chỉ cần vuốt nhẹ tờ giấy nào không cần nữa, "phụt!" nó biến mất. Hoặc vuốt sang hướng khác, "tách!" nó được chuyển vào mục lưu trữ.

Nói một cách hàn lâm hơn, Dismissible trong Flutter là một widget cho phép bạn loại bỏ (dismiss) một widget con bằng cách vuốt nó sang một hướng cụ thể. Nó cực kỳ hữu ích để:

  • Xóa/Lưu trữ các mục trong danh sách: Điển hình nhất là các mục trong ListView (email, tin nhắn, ghi chú, sản phẩm trong giỏ hàng).
  • Cung cấp tương tác trực quan: Nâng cao trải nghiệm người dùng bằng cách cho phép họ thao tác trực tiếp trên các phần tử UI.
  • Giảm bớt nút bấm: Thay vì phải nhấn vào một icon xóa, vuốt luôn tiện lợi hơn nhiều.
Illustration

2. Code Ví Dụ Minh Họa: Danh sách nhiệm vụ "vuốt là bay"

Để các bạn dễ hình dung, chúng ta sẽ xây dựng một danh sách các nhiệm vụ đơn giản. Khi vuốt một nhiệm vụ sang trái, nó sẽ bị xóa. Khi vuốt sang phải, nó sẽ được đánh dấu là "đã hoàn thành".

Các thành phần chính của Dismissible:

  • key: Cực kỳ quan trọng! Flutter dùng key để nhận diện duy nhất từng Dismissible widget. Nếu không có key hoặc key không duy nhất, bạn sẽ gặp lỗi hoặc hành vi không mong muốn. Thường dùng UniqueKey() hoặc ValueKey() với ID của đối tượng.
  • child: Widget mà bạn muốn cho phép người dùng vuốt đi (ví dụ: một ListTile).
  • background: Widget sẽ hiển thị phía sau child khi bạn vuốt từ trái sang phải (hoặc từ trên xuống dưới, tùy direction).
  • secondaryBackground: Tương tự background, nhưng hiển thị khi bạn vuốt từ phải sang trái (hoặc từ dưới lên trên).
  • onDismissed: Hàm callback được gọi khi widget đã được vuốt hoàn toàn và bị loại bỏ. Đây là nơi bạn cập nhật dữ liệu của mình.
  • direction: Chỉ định các hướng vuốt được phép (DismissDirection.horizontal, DismissDirection.startToEnd, DismissDirection.endToStart, v.v.).
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: 'Dismissible Demo', 
      theme: ThemeData( 
        primarySwatch: Colors.blue, 
      ), 
      home: const DismissibleTasksScreen(), 
    ); 
  }
}

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

  @override 
  State<DismissibleTasksScreen> createState() => _DismissibleTasksScreenState(); 
}

class _DismissibleTasksScreenState extends State<DismissibleTasksScreen> { 
  final List<String> _tasks = List.generate( 
    10, 
    (index) => 'Nhiệm vụ số ${index + 1}', 
  ); 

  @override 
  Widget build(BuildContext context) { 
    return Scaffold( 
      appBar: AppBar( 
        title: const Text('Danh Sách Nhiệm Vụ (Vuốt là bay!)'), 
      ), 
      body: ListView.builder( 
        itemCount: _tasks.length, 
        itemBuilder: (context, index) { 
          final item = _tasks[index]; 
          return Dismissible( 
            // 1. **Key là bắt buộc và phải duy nhất!** 
            key: Key(item), // Sử dụng item làm key, đảm bảo duy nhất 
            direction: DismissDirection.horizontal, // Cho phép vuốt ngang 

            // 2. Background khi vuốt từ trái sang phải (đánh dấu hoàn thành) 
            background: Container( 
              color: Colors.green, 
              alignment: Alignment.centerLeft, 
              padding: const EdgeInsets.symmetric(horizontal: 20.0), 
              child: const Icon(Icons.check, color: Colors.white), 
            ), 
            // 3. Background khi vuốt từ phải sang trái (xóa) 
            secondaryBackground: Container( 
              color: Colors.red, 
              alignment: Alignment.centerRight, 
              padding: const EdgeInsets.symmetric(horizontal: 20.0), 
              child: const Icon(Icons.delete, color: Colors.white), 
            ), 

            // 4. Widget con được vuốt 
            child: ListTile( 
              title: Text(item), 
              subtitle: const Text('Trạng thái: Đang chờ'), 
            ), 

            // 5. Hàm callback khi widget bị loại bỏ 
            onDismissed: (direction) { 
              // Xóa item khỏi danh sách dữ liệu 
              setState(() { 
                _tasks.removeAt(index); 
              }); 

              // Hiển thị Snackbar thông báo hành động 
              ScaffoldMessenger.of(context).showSnackBar( 
                SnackBar( 
                  content: Text( 
                    direction == DismissDirection.startToEnd 
                        ? '$item đã hoàn thành!' 
                        : '$item đã bị xóa.', 
                  ), 
                  action: SnackBarAction( 
                    label: 'Hoàn tác', 
                    onPressed: () { 
                      // Thường thì bạn sẽ không hoàn tác xóa ngay lập tức 
                      // mà có thể lưu vào một danh sách 'đã xóa' tạm thời. 
                      // Trong ví dụ này, chúng ta đơn giản là thêm lại. 
                      setState(() { 
                        _tasks.insert(index, item); 
                      }); 
                    }, 
                  ), 
                ), 
              ); 
            }, 
          ); 
        }, 
      ), 
    ); 
  }
}

Giải thích nhanh:

  • Chúng ta có một List<String> _tasks để lưu trữ dữ liệu.
  • Mỗi ListTile được bọc trong một Dismissible.
  • Key(item) đảm bảo mỗi Dismissible có một khóa duy nhất.
  • backgroundsecondaryBackground cung cấp phản hồi trực quan khi người dùng vuốt.
  • Trong onDismissed, chúng ta xóa mục khỏi _tasks và dùng setState để cập nhật UI. Một SnackBar cũng được dùng để thông báo và cung cấp tùy chọn hoàn tác (dù cho xóa thực sự thì việc hoàn tác phức tạp hơn nhiều).

3. Mẹo (Best Practices) từ "lão làng" Creyt

  • Key là Vua: Tôi nhắc lại lần nữa, Key là linh hồn của Dismissible. Luôn đảm bảo nó là duy nhất cho mỗi item. Nếu không, Flutter sẽ không biết item nào đang bị vuốt, dẫn đến những hành vi khó chịu như item sai bị xóa hoặc lỗi.
    • Mẹo nhỏ: Nếu dữ liệu của bạn có ID duy nhất (như từ database), hãy dùng ValueKey(yourItem.id). Nếu không, UniqueKey() là lựa chọn an toàn cho các widget động.
  • Phản hồi Trực quan là Tiền: Luôn cung cấp backgroundsecondaryBackground rõ ràng. Người dùng cần biết hành động của họ sẽ dẫn đến kết quả gì trước khi họ hoàn thành thao tác vuốt.
  • Xử lý dữ liệu cẩn thận: Hàm onDismissed là nơi bạn thực sự xóa hoặc cập nhật dữ liệu. Luôn gọi setState sau khi thay đổi danh sách dữ liệu để UI được cập nhật.
  • Hoàn tác (Undo) là ân huệ: Đối với các hành động phá hủy (như xóa), hãy cân nhắc cung cấp một cơ chế hoàn tác ngắn hạn (thường là qua SnackBar). Điều này giúp người dùng sửa chữa sai lầm và tăng sự tự tin khi sử dụng ứng dụng của bạn.
  • Xác nhận cho hành động "nghiêm trọng": Nếu việc xóa có thể gây mất mát dữ liệu lớn, hãy cân nhắc hiển thị một AlertDialog để xác nhận trước khi thực sự loại bỏ item. Dismissible có một callback confirmDismiss cho mục đích này.

4. Ứng dụng thực tế: Bạn thấy Dismissible ở đâu?

Dismissible không phải là một công nghệ mới lạ mà nó đã trở thành một chuẩn mực trong thiết kế UI/UX hiện đại. Bạn sẽ thấy nó ở khắp mọi nơi:

  • Gmail / Outlook / Apple Mail: Vuốt email để lưu trữ, xóa, đánh dấu đã đọc. Đây là ví dụ kinh điển nhất!
  • Todoist / Google Tasks: Vuốt nhiệm vụ để đánh dấu hoàn thành hoặc xóa bỏ.
  • WhatsApp / Telegram: Vuốt cuộc trò chuyện để lưu trữ hoặc xóa.
  • Spotify / Apple Music: Vuốt bài hát trong danh sách phát để xóa khỏi danh sách.
  • Ứng dụng mua sắm (Shopping Cart): Vuốt sản phẩm trong giỏ hàng để xóa khỏi giỏ.

Nhìn chung, bất cứ khi nào bạn có một danh sách các mục và muốn người dùng có thể nhanh chóng loại bỏ hoặc thực hiện một hành động nhanh trên từng mục mà không cần phải nhấn vào nhiều nút, Dismissible chính là "người hùng" bạn cần triệu hồi.

Chúc mừng bạn đã nắm vững một trong những kỹ thuật tương tác người dùng hiệu quả nhất trong Flutter! Giờ thì hãy tự tin áp dụng nó vào các dự án của mình nhé. 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!