Flutter TableColumnWidth: Kỹ năng 'cắt đất' cho bảng dữ liệu của bạn!
Flutter

Flutter TableColumnWidth: Kỹ năng 'cắt đất' cho bảng dữ liệu của bạn!

Author

Admin System

@root

Ngày xuất bản

21 Mar, 2026

Lượt xem

2 Lượt

"TableColumnWidth"

Này các Gen Z tương lai của làng code! Hôm nay, anh Creyt sẽ cùng các em 'mổ xẻ' một khái niệm nghe có vẻ khô khan nhưng lại cực kỳ quan trọng khi các em muốn làm chủ cái 'bảng tính Excel thu nhỏ' trong app Flutter của mình: đó là TableColumnWidth.

1. TableColumnWidth là cái gì mà nghe 'drama' thế?

Đơn giản mà nói, TableColumnWidth giống như cái 'quyền sổ đỏ' mà các em dùng để phân chia đất đai cho từng cột trong một cái bàn (widget Table) vậy. Thay vì cứ để ông trời (hay cụ thể hơn là Flutter) tự động phân bổ đất đai theo ý ổng, thì mình, với tư cách là 'chủ đầu tư', có thể chủ động 'cắm cọc' định hình chiều rộng cho từng cột. Khi các em có dữ liệu dạng bảng, việc các cột cứ 'co giãn' vô tội vạ nhìn ngứa mắt lắm, đúng không? TableColumnWidth sinh ra để giải quyết nỗi 'đau đầu' đó, giúp bảng của các em trông gọn gàng, chuyên nghiệp và dễ đọc hơn nhiều.

Nó là một abstract class (một khuôn mẫu trừu tượng), và chúng ta sẽ dùng các 'đứa con' cụ thể của nó để thực hiện nhiệm vụ 'chia đất':

  • FixedColumnWidth: Kiểu 'đất nền' cố định. Cột này tao chốt 100 pixel, khỏi bàn! Dù nội dung dài hay ngắn, nó vẫn cứ giữ nguyên chiều rộng đó. Thích hợp cho các cột có nội dung biết trước kích thước như icon, nút bấm nhỏ.
  • FlexColumnWidth: Kiểu 'chia phần trăm theo tỷ lệ vàng'. Cột này được chia 2 phần, cột kia 1 phần, tổng là 3 phần. Chia đều ra mà sống! Giống như Expanded trong Row/Column hay flex trong CSS ấy. Nó rất linh hoạt, giúp bảng của em tự động co giãn theo kích thước màn hình.
  • FractionColumnWidth: Kiểu 'đất nền' theo phần trăm tổng. Cột này chiếm 30% tổng chiều rộng của bảng, chuẩn chỉ! Dễ hình dung, dễ kiểm soát khi em muốn tỷ lệ chính xác.
  • IntrinsicColumnWidth: Kiểu 'co giãn theo nội dung tự nhiên'. Mày cứ co lại hết cỡ theo nội dung nhỏ nhất đi, để tao xem kích thước 'thật' của mày là bao nhiêu. Thường dùng để làm cho cột có chiều rộng vừa đủ với nội dung dài nhất trong cột đó, nhưng đôi khi có thể gây hiệu suất không tốt nếu bảng quá lớn.
  • MinColumnWidth & MaxColumnWidth: Hai thằng này thường đi đôi với nhau, như 'anh em cây khế' vậy. Cột này ít nhất phải 50, nhiều nhất không quá 200. Mày cứ liệu mà sống! Chúng cho phép em đặt giới hạn trên và dưới cho chiều rộng cột, kết hợp với các loại khác để có sự linh hoạt mà vẫn giữ được trật tự.

2. Code Ví Dụ Minh Hoạ: Cầm tay chỉ việc 'cắm cọc' đất

Để TableColumnWidth hoạt động, chúng ta sẽ dùng nó bên trong thuộc tính columnWidths của widget Table.

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: 'TableColumnWidth Demo',
      theme: ThemeData(primarySwatch: Colors.blueGrey),
      home: const TableColumnWidthScreen(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Anh Creyt dạy TableColumnWidth'),
        centerTitle: true,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              'Ví dụ 1: Kết hợp Fixed và Flex ColumnWidth',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Table(
              border: TableBorder.all(color: Colors.grey.shade300),
              columnWidths: const {
                0: FixedColumnWidth(80.0), // Cột 0 cố định 80px
                1: FlexColumnWidth(2),    // Cột 1 chiếm 2 phần
                2: FlexColumnWidth(1),    // Cột 2 chiếm 1 phần
              },
              children: _buildTableRows(Colors.blueAccent),
            ),
            const SizedBox(height: 30),
            const Text(
              'Ví dụ 2: Sử dụng Fraction ColumnWidth',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Table(
              border: TableBorder.all(color: Colors.grey.shade300),
              columnWidths: const {
                0: FractionColumnWidth(0.2), // Cột 0 chiếm 20% tổng chiều rộng
                1: FractionColumnWidth(0.5), // Cột 1 chiếm 50%
                2: FractionColumnWidth(0.3), // Cột 2 chiếm 30%
              },
              children: _buildTableRows(Colors.greenAccent),
            ),
            const SizedBox(height: 30),
            const Text(
              'Ví dụ 3: Intrinsic ColumnWidth - "Co giãn theo nội dung"',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            // IntrinsicColumnWidth thường dùng trong Table để các cột có chiều rộng
            // vừa đủ với nội dung dài nhất trong cột đó. Tuy nhiên, nó có thể
            // tốn hiệu năng nếu bảng quá lớn vì phải đo lường nhiều lần.
            Table(
              border: TableBorder.all(color: Colors.grey.shade300),
              columnWidths: const {
                0: IntrinsicColumnWidth(), // Cột 0 co theo nội dung dài nhất
                1: IntrinsicColumnWidth(), // Cột 1 co theo nội dung dài nhất
                2: IntrinsicColumnWidth(), // Cột 2 co theo nội dung dài nhất
              },
              children: [ 
                TableRow(
                  decoration: BoxDecoration(color: Colors.orange.shade100),
                  children: const [
                    Padding(padding: EdgeInsets.all(8.0), child: Text('ID')), 
                    Padding(padding: EdgeInsets.all(8.0), child: Text('Tên sản phẩm siêu dài')), 
                    Padding(padding: EdgeInsets.all(8.0), child: Text('Giá')), 
                  ],
                ),
                TableRow(
                  children: const [
                    Padding(padding: EdgeInsets.all(8.0), child: Text('1')), 
                    Padding(padding: EdgeInsets.all(8.0), child: Text('Áo thun')), 
                    Padding(padding: EdgeInsets.all(8.0), child: Text('150k')), 
                  ],
                ),
                TableRow(
                  children: const [
                    Padding(padding: EdgeInsets.all(8.0), child: Text('2')), 
                    Padding(padding: EdgeInsets.all(8.0), child: Text('Quần jeans rách gối cá tính cực chất')), 
                    Padding(padding: EdgeInsets.all(8.0), child: Text('300k')), 
                  ],
                ),
              ],
            ),
            const SizedBox(height: 30),
            const Text(
              'Ví dụ 4: Kết hợp Min/Max ColumnWidth với Flex',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Table(
              border: TableBorder.all(color: Colors.grey.shade300),
              columnWidths: const {
                0: FixedColumnWidth(60.0), // ID cố định
                1: MinColumnWidth(FixedColumnWidth(100.0), FlexColumnWidth(2)), // Tối thiểu 100, sau đó flex 2
                2: MaxColumnWidth(FixedColumnWidth(150.0), FlexColumnWidth(1)), // Tối đa 150, sau đó flex 1
              },
              children: _buildTableRows(Colors.purpleAccent),
            ),
          ],
        ),
      ),
    );
  }

  List<TableRow> _buildTableRows(Color headerColor) {
    return [
      TableRow(
        decoration: BoxDecoration(color: headerColor.withOpacity(0.2)),
        children: const [
          Padding(padding: EdgeInsets.all(8.0), child: Text('STT', style: TextStyle(fontWeight: FontWeight.bold))), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('Mô tả sản phẩm', style: TextStyle(fontWeight: FontWeight.bold))), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('Số lượng', style: TextStyle(fontWeight: FontWeight.bold))), 
        ],
      ),
      TableRow(
        children: const [
          Padding(padding: EdgeInsets.all(8.0), child: Text('1')), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('Bàn phím cơ RGB xịn xò')), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('1')), 
        ],
      ),
      TableRow(
        children: const [
          Padding(padding: EdgeInsets.all(8.0), child: Text('2')), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('Chuột gaming không dây siêu nhẹ')), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('2')), 
        ],
      ),
      TableRow(
        children: const [
          Padding(padding: EdgeInsets.all(8.0), child: Text('3')), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('Màn hình cong 240Hz cho game thủ pro')), 
          Padding(padding: EdgeInsets.all(8.0), child: Text('1')), 
        ],
      ),
    ];
  }
}
Illustration

3. Mẹo (Best Practices) để 'cắm cọc' đất không bị 'quy hoạch treo'

  • Start Simple, Then Scale: Ban đầu, cứ dùng FixedColumnWidth hoặc FlexColumnWidth cho dễ. Khi nào thấy cần độ phức tạp hơn thì mới nghĩ đến Fraction hay Min/Max.
  • FlexColumnWidth là 'Bạn thân' của Responsive: Khi em muốn bảng của mình tự động điều chỉnh theo kích thước màn hình (ví dụ, trên điện thoại và trên tablet), FlexColumnWidth là lựa chọn số 1. Nó sẽ chia đều không gian còn lại theo tỷ lệ em đặt ra.
  • Cẩn trọng với IntrinsicColumnWidth: Thằng này hữu ích khi em muốn cột tự động co giãn vừa đúng với nội dung dài nhất. Tuy nhiên, nó có thể làm giảm hiệu suất đáng kể nếu bảng có quá nhiều hàng hoặc cột, vì Flutter phải đo lường kích thước của từng ô để tìm ra kích thước tối ưu. Chỉ dùng khi thực sự cần thiết và bảng không quá lớn.
  • Kết hợp là 'nghệ thuật': Em có thể kết hợp các loại TableColumnWidth lại với nhau. Ví dụ, cột đầu tiên là FixedColumnWidth cho số thứ tự, các cột tiếp theo là FlexColumnWidth để nội dung tự co giãn. Hoặc dùng MinColumnWidth(FixedColumnWidth(50), FlexColumnWidth(1)) để đảm bảo cột không bao giờ nhỏ hơn 50px nhưng vẫn có thể co giãn linh hoạt.
  • Index của cột: Nhớ rằng columnWidths nhận một Map<int, TableColumnWidth>, trong đó int là index của cột (bắt đầu từ 0). Nếu em không định nghĩa cho một cột nào đó, nó sẽ mặc định dùng FlexColumnWidth(1).

4. Ứng dụng thực tế: Bảng biểu ở khắp mọi nơi

Các em thấy TableColumnWidth được dùng ở đâu không? Nhiều lắm chứ! Bất cứ đâu có dữ liệu dạng bảng mà cần sự gọn gàng, có cấu trúc đều có thể áp dụng:

  • Ứng dụng quản lý tài chính: Hiển thị danh sách giao dịch, sao kê ngân hàng, danh mục đầu tư (cột ngày, mô tả, số tiền).
  • Ứng dụng thương mại điện tử: Giỏ hàng (cột ảnh sản phẩm, tên, số lượng, giá), bảng so sánh sản phẩm.
  • Dashboard quản trị: Thống kê số liệu, danh sách người dùng, báo cáo bán hàng.
  • Ứng dụng danh bạ/quản lý liên hệ: Hiển thị tên, số điện thoại, email.

Thực ra, những ứng dụng như Excel, Google Sheets, hay các trang web hiển thị bảng dữ liệu đều phải có cơ chế tương tự để quản lý chiều rộng cột, chỉ là tên gọi và cách triển khai khác thôi.

5. Thử nghiệm và lời khuyên từ Creyt

Anh Creyt đã từng 'vật lộn' với layout bảng biểu không biết bao nhiêu lần rồi. Có những lúc bảng dữ liệu trông như 'bãi chiến trường' vì các cột cứ nhảy múa lung tung. Hồi đó, anh hay 'hardcode' mọi thứ bằng SizedBox hoặc Container với width cố định, nhưng đổi màn hình cái là 'toang' ngay.

Sau này, khi hiểu sâu hơn về TableColumnWidth, việc 'cắm cọc' cho các cột trở nên dễ dàng hơn nhiều. Anh thường dùng FlexColumnWidth cho hầu hết các cột chứa nội dung động, và FixedColumnWidth cho các cột 'ăn chắc mặc bền' như icon, checkbox, hoặc số thứ tự. FractionColumnWidth thì dành cho những bảng cần tỷ lệ chính xác như biểu đồ mini trong bảng.

Khi nào nên dùng?

  • Khi em cần một bảng dữ liệu có cấu trúc rõ ràng và dễ đọc.
  • Khi em muốn kiểm soát chính xác chiều rộng từng cột, không muốn Flutter tự động tính toán.
  • Khi em muốn bảng của mình trông chuyên nghiệp, không bị 'vỡ layout' trên các kích thước màn hình khác nhau (kết hợp với FlexColumnWidth).

Khi nào nên cân nhắc giải pháp khác?

  • Nếu dữ liệu của em không thực sự cần định dạng bảng (ví dụ, chỉ là một danh sách đơn giản), ListView.builder hoặc Column với các Row lồng nhau có thể đơn giản và hiệu quả hơn.
  • Với IntrinsicColumnWidth, nếu bảng của em có hàng trăm hoặc hàng ngàn dòng, hãy cân nhắc kỹ hoặc tìm cách tối ưu hóa (ví dụ, phân trang dữ liệu) để tránh giật lag.

Nhớ nhé, TableColumnWidth không chỉ là một công cụ, nó là một phần của 'nghệ thuật sắp đặt' UI. Nắm vững nó, các em sẽ có những bảng dữ liệu 'chill phết' và 'đỉnh của chóp'!

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!