Flutter ExpansionPanel: Mở khóa UI linh hoạt, tối ưu trải nghiệm
Flutter

Flutter ExpansionPanel: Mở khóa UI linh hoạt, tối ưu trải nghiệm

Author

Admin System

@root

Ngày xuất bản

18 Mar, 2026

Lượt xem

24 Lượt

"ExpansionPanel"

Chào mừng các bạn đến với buổi học hôm nay cùng Giảng viên Creyt! Hôm nay, chúng ta sẽ cùng nhau "bóc tách" một widget cực kỳ hữu ích trong Flutter, đó là ExpansionPanel.

ExpansionPanel là gì? Để làm gì?

Để dễ hình dung, các bạn cứ tưởng tượng thế này: ExpansionPanel giống như một "chiếc rèm sân khấu mini" trên ứng dụng của bạn vậy. Ban đầu, nó chỉ là một cái tiêu đề nhỏ gọn, kín đáo – cái "rèm" đang buông xuống. Nhưng khi người dùng "kéo rèm lên" (tức là chạm vào tiêu đề), "vở diễn" (nội dung chi tiết) bên trong sẽ từ từ hiện ra, bung nở đầy đủ. Khi không cần nữa, "rèm" lại buông xuống, trả lại không gian gọn gàng cho sân khấu màn hình.

Vậy nó để làm gì? Đơn giản là để giải quyết bài toán không gian và sự tập trung của người dùng. Trong một thế giới di động mà diện tích màn hình là vàng, việc nhồi nhét mọi thứ vào cùng một lúc sẽ khiến người dùng "ngộp thở". ExpansionPanel giúp bạn:

  1. Tiết kiệm không gian: Chỉ hiển thị những gì cần thiết ngay lập tức (tiêu đề), giữ cho giao diện luôn thoáng đãng.
  2. Tổ chức nội dung: Nhóm các thông tin liên quan lại với nhau một cách logic, dễ quản lý.
  3. Cải thiện trải nghiệm người dùng (UX): Giảm tải nhận thức (cognitive load). Người dùng chỉ cần tập trung vào thông tin họ muốn xem, và có thể "ẩn" nó đi khi không cần nữa. Đây là một nguyên tắc thiết kế UI/UX cốt lõi, giúp người dùng cảm thấy kiểm soát được ứng dụng.

Nói theo kiểu Harvard một chút, ExpansionPanel là một ví dụ điển hình của "progressive disclosure" (tiết lộ dần dần) – một kỹ thuật thiết kế giao diện giúp giảm độ phức tạp bằng cách chỉ hiển thị thông tin khi người dùng yêu cầu. Nó giúp duy trì sự đơn giản ở cấp độ bề mặt, nhưng vẫn cung cấp chiều sâu khi cần thiết.

Illustration

Code Ví Dụ Minh Hoạ Rõ Ràng

Để sử dụng ExpansionPanel, chúng ta thường dùng nó trong một danh sách gọi là ExpansionPanelList. Widget này sẽ quản lý nhiều ExpansionPanel con. Chúng ta cần một StatefulWidget để quản lý trạng thái mở/đóng của từng panel.

Đây là một ví dụ đơ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: 'ExpansionPanel Demo của Creyt',
      theme: ThemeData(primarySwatch: Colors.blueGrey),
      home: const ExpansionPanelScreen(),
    );
  }
}

// Lớp dữ liệu cho mỗi item trong ExpansionPanelList
class Item {
  Item({
    required this.expandedValue,
    required this.headerValue,
    this.isExpanded = false,
  });

  String expandedValue;
  String headerValue;
  bool isExpanded;
}

List<Item> generateItems(int numberOfItems) {
  return List<Item>.generate(numberOfItems, (int index) {
    return Item(
      headerValue: 'Tiêu đề Panel ${index + 1}',
      expandedValue: 'Đây là nội dung chi tiết của Panel ${index + 1}. Bạn có thể đặt bất kỳ widget nào vào đây, từ văn bản đến hình ảnh hay các form nhập liệu phức tạp.',
    );
  });
}

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

  @override
  State<ExpansionPanelScreen> createState() => _ExpansionPanelScreenState();
}

class _ExpansionPanelScreenState extends State<ExpansionPanelScreen> {
  final List<Item> _data = generateItems(3); // Tạo 3 panel mẫu

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ExpansionPanel của Creyt'),
      ),
      body: SingleChildScrollView(
        child: ExpansionPanelList(
          expansionCallback: (int index, bool isExpanded) {
            setState(() {
              _data[index].isExpanded = !isExpanded;
            });
          },
          children: _data.map<ExpansionPanel>((Item item) {
            return ExpansionPanel(
              headerBuilder: (BuildContext context, bool isExpanded) {
                return ListTile(
                  title: Text(item.headerValue),
                  leading: Icon(isExpanded ? Icons.arrow_drop_up : Icons.arrow_drop_down),
                );
              },
              body: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(item.expandedValue),
              ),
              isExpanded: item.isExpanded,
            );
          }).toList(),
        ),
      ),
    );
  }
}

Trong đoạn code trên:

  • Chúng ta tạo một lớp Item để quản lý dữ liệu cho mỗi panel, bao gồm tiêu đề (headerValue), nội dung (expandedValue) và trạng thái mở/đóng (isExpanded).
  • ExpansionPanelList là widget chính chứa các ExpansionPanel.
  • expansionCallback là hàm được gọi khi người dùng chạm vào tiêu đề của một panel. Tại đây, chúng ta setState để cập nhật trạng thái isExpanded của item tương ứng, khiến panel đóng/mở.
  • Mỗi ExpansionPanelheaderBuilder (xây dựng phần tiêu đề) và body (nội dung khi mở).
  • isExpanded là thuộc tính quan trọng để điều khiển trạng thái mở hay đóng của panel.

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

  1. Quản lý trạng thái là chìa khóa: Đừng bao giờ quên isExpanded! Nó là "tay lái" điều khiển "chiếc rèm sân khấu" của bạn. Luôn setState đúng cách khi trạng thái thay đổi để UI được cập nhật.
  2. Đừng lạm dụng: Mặc dù ExpansionPanel rất tiện lợi, nhưng nếu bạn có quá nhiều panel với nội dung cực kỳ dài hoặc phức tạp, hãy cân nhắc giải pháp khác như chuyển sang một màn hình riêng hoặc sử dụng tab. Performance có thể bị ảnh hưởng nếu bạn render quá nhiều widget ẩn.
  3. Nội dung header phải rõ ràng: Tiêu đề của mỗi panel phải đủ súc tích và mô tả được nội dung bên trong, để người dùng không cần mở ra cũng biết đại khái nó nói về cái gì. "Quy tắc 3 giây" – người dùng nên hiểu ngay lập tức.
  4. Tùy biến linh hoạt: headerBuilderbody đều nhận vào Widget, nghĩa là bạn có thể đặt bất cứ thứ gì vào đó – từ text đơn giản đến các form phức tạp, hình ảnh, hay thậm chí là một ListView con. Hãy sáng tạo!
  5. ExpansionPanelList.radio: Nếu bạn chỉ muốn một panel được mở tại một thời điểm (kiểu "radio button"), hãy dùng ExpansionPanelList.radio thay vì ExpansionPanelList thông thường. Nó tự động quản lý việc đóng các panel khác khi một panel mới được mở.

Ví Dụ Thực Tế Các Ứng Dụng/Website Đã Ứng Dụng

ExpansionPanel, hay các biến thể của nó (còn gọi là Accordion trong web design), được sử dụng rộng rãi đến mức bạn có thể thấy nó ở khắp mọi nơi:

  • Trang FAQ (Câu hỏi thường gặp): Đây là ứng dụng kinh điển nhất. Một danh sách các câu hỏi, mỗi câu khi click vào sẽ hiện câu trả lời chi tiết. Tiết kiệm không gian cực hiệu quả.
  • Trang Cài đặt (Settings/Preferences): Trong các ứng dụng di động, các nhóm cài đặt thường được gom lại thành các panel có thể mở rộng. Ví dụ: "Cài đặt tài khoản", "Cài đặt thông báo", "Cài đặt bảo mật".
  • Trang Chi tiết sản phẩm trên E-commerce: Các phần như "Thông số kỹ thuật", "Mô tả sản phẩm", "Đánh giá" thường được đặt trong các panel có thể mở rộng để trang sản phẩm không quá dài.
  • Các bước hướng dẫn (Tutorials/Onboarding): Khi bạn cần hướng dẫn người dùng qua nhiều bước, mỗi bước có thể là một panel, mở ra từng bước một.
  • Menu điều hướng phức tạp: Trong một số trường hợp, menu có nhiều cấp con cũng có thể sử dụng ExpansionPanel để tổ chức.

Như vậy, ExpansionPanel không chỉ là một widget đơn thuần, mà nó là một công cụ mạnh mẽ giúp bạn thiết kế giao diện thông minh, thân thiện và hiệu quả. Hãy vận dụng nó thật khéo léo 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!