Bài 5. Bố cục giao diện (Layout)

Bố cục là kỹ năng dùng nhiều nhất trong Flutter. Bài này dạy nguyên lý ràng buộc kích thước và các widget bố cục: Row, Column, Stack, Expanded, Padding, SizedBox, cùng cách làm giao diện co giãn.

60 phútCơ bản
Bạn sẽ học được
  • Hiểu nguyên tắc “constraints go down, sizes go up”
  • Sắp xếp bằng Row, Column, Stack
  • Chia không gian bằng Expanded, Flexible, Spacer
  • Làm giao diện responsive với LayoutBuilder
Thuật ngữ cần biết
Constraints
Giới hạn kích thước tối thiểu/tối đa cha truyền cho con.
Main axis
Trục chính: ngang với Row, dọc với Column.
Overflow
Nội dung vượt khung, hiện sọc vàng đen.
Breakpoint
Mốc chiều rộng để đổi bố cục.

Khái niệm và lý thuyết

Quy tắc vàng của bố cục Flutter: ràng buộc đi xuống, kích thước đi lên, cha quyết định vị trí. Widget cha truyền giới hạn cho con; con chọn kích thước trong giới hạn đó và báo lại; cha đặt con vào vị trí. Hiểu quy tắc này giúp giải thích hầu hết lỗi bố cục như “RenderFlex overflowed” hay “unbounded height”.

WidgetVai trò
Row / ColumnXếp con theo hàng ngang / cột dọc
Stack + PositionedChồng lớp, đặt vị trí tuyệt đối
Expanded / FlexibleChiếm phần không gian còn lại theo tỉ lệ flex
Padding, SizedBox, SpacerKhoảng đệm, khoảng cách
WrapTự xuống dòng khi hết chỗ

Row và Column

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    const Text('Khóa học Flutter', style: TextStyle(fontSize: 22)),
    const SizedBox(height: 8),
    Row(
      children: [
        const Icon(Icons.star, color: Colors.amber),
        const SizedBox(width: 4),
        const Text('4.9'),
        const Spacer(),
        FilledButton(onPressed: () {}, child: const Text('Học ngay')),
      ],
    ),
  ],
)
Thuộc tínhÝ nghĩa
mainAxisAlignmentCăn theo trục chính: start, center, spaceBetween...
crossAxisAlignmentCăn theo trục phụ
mainAxisSizemin: co theo nội dung; max: chiếm hết

Expanded và Stack

Row(children: [
  Expanded(flex: 2, child: Container(height: 60, color: Colors.green)),
  Expanded(flex: 1, child: Container(height: 60, color: Colors.orange)),
]);  // tỉ lệ 2:1

Stack(children: [
  Image.network('https://picsum.photos/400/200'),
  const Positioned(bottom: 8, left: 8,
    child: Text('Ảnh bìa', style: TextStyle(color: Colors.white))),
]);

Responsive

LayoutBuilder(builder: (context, c) {
  if (c.maxWidth >= 840) return const BoCucMayTinh();   // 3 cột
  if (c.maxWidth >= 600) return const BoCucMayTinhBang(); // 2 cột
  return const BoCucDienThoai();                          // 1 cột
});
// Kích thước màn hình: MediaQuery.sizeOf(context).width
Tình huống: Thẻ sản phẩm

Dựng thẻ gồm ảnh (Stack có nhãn “Giảm 20%”), tên sản phẩm, hàng chứa giá và nút giỏ hàng dùng Spacer đẩy nút sang phải. Bọc thẻ trong Padding 12 và Card bo góc.

Mẹo hay

Bật Flutter Inspector trong DevTools và chọn “Show guidelines” để thấy khung, padding, trục của từng widget khi gỡ lỗi bố cục.

Lưu ý

Đặt ListView trong Column mà không bọc Expanded sẽ gây lỗi “unbounded height” vì ListView muốn cao vô hạn.

Thực hành
  1. Dựng màn hình hồ sơ: ảnh tròn (CircleAvatar), tên, hàng 3 số liệu cách đều.
  2. Tạo thẻ sản phẩm như tình huống trên.
  3. Dùng Wrap hiển thị 10 thẻ kỹ năng (Chip).
  4. Làm lưới 1/2/3 cột theo chiều rộng bằng LayoutBuilder.
Tóm tắt bài học
  • Constraints đi xuống, sizes đi lên, cha đặt vị trí.
  • Row/Column/Stack là ba bố cục chính.
  • Expanded chia không gian; SizedBox/Spacer tạo khoảng cách.
  • LayoutBuilder và MediaQuery cho giao diện co giãn.