Flutter WrapAlignment: Sắp xếp Widget như Pro, không sợ tràn màn hình!
Flutter

Flutter WrapAlignment: Sắp xếp Widget như Pro, không sợ tràn màn hình!

Author

Admin System

@root

Ngày xuất bản

23 Mar, 2026

Lượt xem

3 Lượt

"WrapAlignment"

Này mấy đứa, hôm nay anh Creyt sẽ "giải mã" một "vũ khí" cực kỳ lợi hại trong kho tàng layout của Flutter: WrapAlignment. Nghe tên có vẻ "khoai" đúng không? Nhưng tin anh đi, nó sẽ là "cứu tinh" cho mấy đứa khi phải vật lộn với mấy cái widget cứ thích "nhảy dù" lung tung.

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

Tưởng tượng thế này, mấy đứa đang có một "đội quân" các widget nhỏ xinh (ví dụ: các nút filter, các thẻ tag, hay mấy cái avatar của bạn bè). Mấy đứa muốn xếp chúng thành một hàng ngang, nhưng khổ nỗi, màn hình điện thoại thì bé tí, mà "đội quân" thì đông. Nếu dùng Row truyền thống, y như rằng "đội quân" sẽ tràn ra ngoài màn hình, gây ra lỗi "overflow" đáng sợ.

Lúc này, "người hùng" Wrap xuất hiện. Wrap giống như một ông chủ nhà hàng siêu tâm lý, ổng nói: "Cứ vào đi các cháu, nếu bàn này không đủ chỗ, ta sẽ tự động xếp các cháu sang bàn mới bên dưới, không lo chen chúc!"

Thế còn WrapAlignment? Nó chính là "nghệ thuật sắp xếp bàn ghế" của ông chủ nhà hàng đó trên mỗi "bàn" (tức là mỗi dòng/cột) mà các widget đang ngồi. Nó định nghĩa cách các widget được căn chỉnh dọc theo trục chính (trục ngang nếu directionhorizontal, hoặc trục dọc nếu directionvertical) trong mỗi dòng hoặc cột đã wrap.

Nói cách khác, WrapAlignment giúp mấy đứa kiểm soát vị trí của các widget bên trong mỗi hàng/cộtWrap đã tạo ra khi chúng không còn đủ chỗ trên một hàng/cột duy nhất.

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

Để dễ hình dung hơn, anh em mình cùng "thực chiến" với một ví dụ đơn giản nhé. Anh sẽ tạo ra một loạt các Container nhỏ và cho chúng "nhảy múa" trong Wrap với các WrapAlignment khác nhau.

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: 'Creyt\'s WrapAlignment Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const WrapAlignmentScreen(),
    );
  }
}

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

  @override
  State<WrapAlignmentScreen> createState() => _WrapAlignmentScreenState();
}

class _WrapAlignmentScreenState extends State<WrapAlignmentScreen> {
  WrapAlignment _currentAlignment = WrapAlignment.start; // Mặc định là start

  // Danh sách các loại WrapAlignment để dễ dàng chuyển đổi
  final List<WrapAlignment> _alignments = [
    WrapAlignment.start,
    WrapAlignment.end,
    WrapAlignment.center,
    WrapAlignment.spaceBetween,
    WrapAlignment.spaceAround,
    WrapAlignment.spaceEvenly,
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WrapAlignment cùng anh Creyt'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // Dropdown để chọn WrapAlignment
            DropdownButton<WrapAlignment>(
              value: _currentAlignment,
              onChanged: (WrapAlignment? newValue) {
                if (newValue != null) {
                  setState(() {
                    _currentAlignment = newValue;
                  });
                }
              },
              items: _alignments.map<DropdownMenuItem<WrapAlignment>>((WrapAlignment alignment) {
                return DropdownMenuItem<WrapAlignment>(
                  value: alignment,
                  child: Text(alignment.toString().split('.').last), // Hiển thị tên enum
                );
              }).toList(),
            ),
            const SizedBox(height: 20),
            // Widget Wrap với WrapAlignment được chọn
            Container(
              color: Colors.grey[200], // Để dễ nhìn ranh giới của Wrap
              padding: const EdgeInsets.all(8.0),
              child: Wrap(
                alignment: _currentAlignment, // Đây là điểm mấu chốt!
                spacing: 10.0, // Khoảng cách giữa các item trên cùng một dòng
                runSpacing: 10.0, // Khoảng cách giữa các dòng
                children: List.generate(
                  15, // Tạo 15 widget con
                  (index) => Container(
                    width: 80,
                    height: 40,
                    color: Colors.blue.shade200.withOpacity((index + 1) / 15),
                    child: Center(
                      child: Text(
                        'Item ${index + 1}',
                        style: const TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 20),
            const Text(
              'Thử thay đổi WrapAlignment và quan sát cách các Item sắp xếp trong từng dòng nhé!',
              textAlign: TextAlign.center,
              style: TextStyle(fontStyle: FontStyle.italic),
            ),
          ],
        ),
      ),
    );
  }
}

Giải thích từng loại WrapAlignment nè:

  • WrapAlignment.start: Giống như mấy đứa xếp hàng vào lớp, tất cả "dồn" về phía bên trái (hoặc phía trên nếu directionvertical) của mỗi dòng/cột. Đây là mặc định.
  • WrapAlignment.end: Ngược lại với start, tất cả "dồn" về phía bên phải (hoặc phía dưới).
  • WrapAlignment.center: Các widget sẽ được căn giữa trên mỗi dòng/cột. Đẹp đẽ, cân đối.
  • WrapAlignment.spaceBetween: Các widget sẽ "giãn" đều ra, sao cho khoảng trống giữa chúng là bằng nhau. Lưu ý: Không có khoảng trống ở hai đầu dòng/cột. Giống như mấy đứa đang đứng giãn cách xã hội trong một hàng, nhưng hai đứa đầu và cuối hàng thì sát tường.
  • WrapAlignment.spaceAround: Tương tự spaceBetween nhưng có thêm khoảng trống ở hai đầu dòng/cột. Khoảng trống ở hai đầu bằng một nửa khoảng trống giữa các widget.
  • WrapAlignment.spaceEvenly: Các widget và khoảng trống giữa chúng, cũng như khoảng trống ở hai đầu, đều được phân bổ đều nhau. Cho ra một bố cục rất cân đối và "đẹp mắt".
Illustration

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

  • Hiểu Rõ Wrap vs Row/Column: Wrap là để xử lý khi các widget không thể chứa hết trên một trục chính và cần "xuống dòng". WrapAlignment chỉ có ý nghĩa khi có nhiều hơn một dòng/cột được tạo ra bởi Wrap. Nếu chỉ có một dòng, WrapAlignment sẽ hoạt động giống MainAxisAlignment của Row/Column.
  • Trực Quan Hoá: Khi dùng, hãy nghĩ đến "đội quân" widget của mấy đứa. start, end, center thì dễ rồi. Với spaceBetween, spaceAround, spaceEvenly, hãy tưởng tượng khoảng trống giữahai đầu các widget.
  • Kết Hợp với runAlignmentrunSpacing:
    • alignment (chính là WrapAlignment): Căn chỉnh các widget trong cùng một dòng/cột.
    • runAlignment: Căn chỉnh các dòng/cột đã wrap với nhau (ví dụ: các dòng con của Wrap sẽ căn giữa theo trục phụ).
    • spacing: Khoảng cách giữa các widget trên cùng một dòng/cột.
    • runSpacing: Khoảng cách giữa các dòng/cột khác nhau. Hiểu rõ 4 thằng này là mấy đứa "nắm trọn" Wrap trong lòng bàn tay!
  • Khi Nào Dùng Wrap?: Khi số lượng item không cố định và mấy đứa muốn chúng tự động "xuống dòng" mà không gây tràn. Ví dụ: danh sách tag, danh sách filter, list avatar bạn bè.

4. Ứng Dụng Thực Tế

Mấy đứa có thể thấy WrapAlignment (thông qua Wrap) ở rất nhiều nơi:

  • TikTok/Instagram/Facebook: Khi mấy đứa xem profile, phần hiển thị các hashtag, sở thích, kỹ năng của một người dùng thường là một loạt các "thẻ" nhỏ. Chúng sẽ tự động xuống dòng khi hết chỗ và thường được căn chỉnh bằng WrapAlignment.start hoặc center.
  • Các trang thương mại điện tử (Shopee, Lazada): Phần bộ lọc sản phẩm (ví dụ: "Màu sắc", "Kích thước", "Thương hiệu"). Mỗi lựa chọn là một nút nhỏ, chúng được xếp hàng ngang và tự động xuống dòng. WrapAlignment giúp chúng trông gọn gàng trên mọi kích thước màn hình.
  • Google Photos/Thư viện ảnh: Khi hiển thị các tag địa điểm, tag người trên ảnh, chúng cũng thường được xếp theo kiểu Wrap.

5. Thử Nghiệm và Hướng Dẫn Nên Dùng cho Case Nào

Anh Creyt đã từng "đau đầu" với việc căn chỉnh các filter button trên một ứng dụng tìm kiếm. Ban đầu dùng Row, tràn màn hình. Chuyển sang Wrap, nhưng lại thấy các nút cứ dồn hết sang một bên. Mãi sau mới "ngộ" ra sức mạnh của WrapAlignment.

  • Dùng WrapAlignment.start (mặc định) hoặc end: Khi mấy đứa muốn các item "dồn" về một phía. Thường dùng cho các danh sách tag, nơi mà thứ tự quan trọng hoặc muốn tiết kiệm không gian.
  • Dùng WrapAlignment.center: Khi mấy đứa muốn các item của mỗi dòng luôn được căn giữa. Phù hợp cho các bộ sưu tập nhỏ, các nút hành động phụ trợ mà mấy đứa muốn chúng trông "cân đối" trên mọi dòng.
  • Dùng WrapAlignment.spaceBetween, spaceAround, spaceEvenly: Đây là "bộ ba quyền lực" khi mấy đứa muốn phân bổ không gian một cách thông minh.
    • spaceBetween: Khi muốn các item trải đều hết chiều rộng của dòng, nhưng không muốn có khoảng trống ở rìa. Ví dụ: Các icon điều hướng trong một thanh công cụ phụ, nơi mấy đứa muốn chúng "bám" sát hai bên.
    • spaceAround: Khi cần một chút "hít thở" ở hai đầu, nhưng vẫn muốn phân bổ đều. Thường dùng khi các item có kích thước tương đối đồng đều và mấy đứa muốn một bố cục "thở" hơn.
    • spaceEvenly: Khi mấy đứa muốn mọi khoảng cách (giữa các item và cả ở hai đầu) đều tăm tắp. Đây là lựa chọn cho bố cục siêu cân đối, thường dùng cho các nút chức năng quan trọng, hoặc các thành phần UI mà tính thẩm mỹ và sự cân bằng được ưu tiên hàng đầu.

Nhớ nhé, WrapAlignment không chỉ là một enum đơn thuần, nó là "chìa khóa" để mấy đứa tạo ra những layout linh hoạt, đẹp mắt và "thở" tốt trên mọi kích thước màn hình. Cứ "nghịch" nhiều vào, mấy đứa sẽ thấy nó "vi diệu" thế nào! Chúc mấy đứa code vui!

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!