Widget — nó là gì, các loại và sự kết hợp trong Flutter

Tác giả: IT Sectr Đã đăng: 2026-06-30 Thời gian đọc: 10 phút

Widget là khái niệm trung tâm của framework Flutter, mô tả cấu hình của một phần tử giao diện người dùng. Mọi thành phần trực quan, từ nút bấm đến hoạt ảnh phức tạp, đều là Widget. Không giống như các framework khác nơi UI được mô tả bằng các tệp XML riêng biệt hoặc được vẽ theo mệnh lệnh, Flutter xây dựng giao diện thông qua sự kết hợp Widget — kết hợp các phần tử nhỏ không thể phân chia thành cây phân cấp. Theo Tài liệu Flutter (2025), thư viện Flutter SDK bao gồm hơn 260 Widget tích hợp bao phủ Material Design, Cupertino và các kiểu tùy chỉnh.

Những Điểm Chính

  • Widget — khối xây dựng cơ bản của UI trong Flutter, mô tả cấu hình của một phần tử.
  • Sự kết hợp — UI được xây dựng bằng cách lồng Widget vào nhau, không phải thông qua kế thừa.
  • StatelessWidget — widget không thay đổi sau khi kết xuất (văn bản, biểu tượng, đệm).
  • StatefulWidget — widget với trạng thái có thể thay đổi (biểu mẫu, hoạt ảnh, danh sách dữ liệu).
  • Element tree — Flutter duy trì ba cây: Widget, Element và RenderObject.

Widget trong Flutter là gì

Widget trong Flutter là một mô tả bất biến (immutable) của một phần giao diện người dùng. Mỗi Widget chứa các thuộc tính cấu hình: kích thước, màu sắc, vị trí, văn bản, trình xử lý sự kiện và Widget con. Widget không được kết xuất trực tiếp — chúng là bản thiết kế (blueprint) mà dựa trên đó Flutter Engine tạo ra một RenderObject, đối tượng đồ họa thực tế trên màn hình.

Triết lý của Flutter tuyên bố: “Everything is a Widget”. Điều này có nghĩa là không chỉ các phần tử có thể nhìn thấy (Text, Image, Button) là Widget, mà còn các khối cấu trúc (Padding, Center, Column, Stack), các khối hành vi (GestureDetector, AnimatedBuilder) và thậm chí cả ứng dụng (MaterialApp, CupertinoApp). Cách tiếp cận này đảm bảo tính đồng nhất: bất kỳ phần tử màn hình nào cũng có thể kết hợp với bất kỳ phần tử nào khác thông qua việc lồng ghép đơn giản.

Theo Google I/O 2024 — Flutter Widgets Deep Dive, ứng dụng trung bình trên Flutter chứa từ 200 đến 1500 Widget tại bất kỳ thời điểm nào. Mặc dù số lượng này, Flutter vẫn duy trì 60 FPS ngay cả trên các thiết bị giá rẻ nhờ các tối ưu hóa ở cấp độ động cơ C++ Skia/Impeller. Widget là các đối tượng nhẹ (40–80 byte mỗi cái), vì vậy việc tạo chúng không phải là nút thắt cổ chai về hiệu suất.

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Xin chào, Flutter!'),
        ),
      ),
    );
  }
}

Ba Cây của Flutter: Widget, Element và RenderObject

Để hiểu cách Widget hoạt động, cần hiểu kiến trúc Flutter bao gồm ba cây kết nối với nhau. Cây đầu tiên — Widget tree — mô tả cấu hình UI. Đây là một cây nhẹ có thể được xây dựng lại hoàn toàn mỗi khung hình (bộ thu gom rác loại bỏ Widget cũ và tạo mới). Widget là bất biến (immutable): nếu màu văn bản thay đổi, một Widget Text mới với màu mới được tạo, cái cũ bị loại bỏ.

Cây thứ hai — Element tree — là liên kết giữa Widget và RenderObject. Element chứa tham chiếu đến Widget (cấu hình) và RenderObject (kết xuất). Khi Widget thay đổi, Flutter so sánh Widget mới với Element cũ và quyết định: cập nhật RenderObject hiện có (nếu Widget cùng loại) hoặc tạo mới (nếu loại Widget đã thay đổi). Quá trình này được gọi là Reconciliation và tương tự như React Virtual DOM.

Cây thứ ba — RenderObject tree — chịu trách nhiệm kết xuất thực tế trên màn hình. RenderObject chứa kích thước, vị trí và phương thức vẽ cụ thể. Flutter Engine (C++ Skia hoặc Impeller) duyệt qua RenderObject tree và kết xuất từng nút. RenderObject tree là cây nặng nhất, vì vậy Flutter giảm thiểu các thay đổi của nó bằng cách tái sử dụng RenderObject khi chuyển sang Widget cùng loại.

CâyMục đíchBất biến?Vòng đời
WidgetCấu hình UI (bản thiết kế)Được tạo lại mỗi lần build
ElementLiên kết Widget ↔ RenderObjectKhôngTồn tại khi widget còn trong cây
RenderObjectKết xuất và bố cụcKhôngNặng, được tái sử dụng khi có thể

StatelessWidget vs StatefulWidget

Flutter chia Widget thành hai loại cơ bản: StatelessWidgetStatefulWidget. StatelessWidget là widget không chứa trạng thái có thể thay đổi. Giao diện của StatelessWidget hoàn toàn được xác định bởi hàm tạo của nó và không thể thay đổi sau khi kết xuất. Ví dụ: Text, Icon, Divider, Padding. Tất cả thuộc tính của StatelessWidget được khai báo là final trong hàm tạo và chỉ có thể đọc.

StatefulWidget là widget với trạng thái có thể thay đổi. Nó bao gồm hai lớp: Widget (cấu hình bất biến, giống StatelessWidget) và State (trạng thái có thể thay đổi). Tách Widget khỏi State là một quyết định kiến trúc quan trọng trong Flutter. Widget được tạo lại mỗi lần build, nhưng đối tượng State tiếp tục tồn tại trong suốt vòng đời của widget trong cây, giữ nguyên trạng thái của nó.

Khi setState() được gọi, Flutter đánh dấu State là “bẩn” và trong khung hình tiếp theo gọi phương thức build() để xây dựng lại cây con. Quan trọng: setState() không tạo lại Widget — nó chỉ kích hoạt lệnh gọi build() trên State hiện tại. Điều này có nghĩa là StatefulWidget có thể cập nhật UI mà không mất trạng thái của Widget con, miễn là khóa (Key) của các phần tử con vẫn ổn định.

dart
// StatelessWidget — giao diện không bao giờ thay đổi
class GreetingWidget extends StatelessWidget {
  const GreetingWidget({super.key, required this.name});
  final String name;

  @override
  Widget build(BuildContext context) {
    return Text('Xin chào, $name');
  }
}

// StatefulWidget — bộ đếm với trạng thái có thể thay đổi
class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Đếm: $_count'),
        ElevatedButton(
          onPressed: () => setState(() => _count++),
          child: const Text('Tăng'),
        ),
      ],
    );
  }
}

Các Loại Widget: layout, painting, interactive

Tất cả Widget trong Flutter có thể được chia thành ba loại chính theo mục đích chức năng. Layout Widget — chịu trách nhiệm định vị các phần tử con trên màn hình. Row và Column sắp xếp con thành một hàng, Stack đặt cái này lên trên cái khác, Expanded và Flexible phân phối không gian có sẵn. Layout Widget không có đại diện trực quan riêng — chúng quản lý vị trí và kích thước của widget con.

Painting Widget — chịu trách nhiệm về kiểu dáng trực quan. Container kết hợp trang trí (màu sắc, gradient, bóng, viền) với thuộc tính bố cục. Padding thêm khoảng cách, DecoratedBox vẽ nền, Transform áp dụng các phép biến đổi (xoay, tỷ lệ). Painting Widget là các khối xây dựng của phong cách trực quan và thường được sử dụng cùng với Layout Widget để đạt được giao diện mong muốn.

Interactive Widget — xử lý đầu vào của người dùng. GestureDetector phát hiện cử chỉ (chạm, vuốt, chụm), InkWell thêm hiệu ứng gợn sóng Material, TextField chấp nhận nhập văn bản, Slider và Switch cung cấp các phần tử điều khiển tiêu chuẩn. Interactive Widget nâng sự kiện thông qua các hàm callback được truyền vào hàm tạo hoặc xử lý thông qua các nhà cung cấp trạng thái.

LoạiVí dụ WidgetMục đích
LayoutRow, Column, Stack, Expanded, Flexible, AlignĐịnh vị và kích thước các phần tử con
PaintingContainer, Padding, DecoratedBox, RotatedBoxMàu sắc, nền, viền, bóng, biến đổi
InteractiveGestureDetector, InkWell, TextField, SliderXử lý chạm, nhập liệu, cử chỉ
PlatformMaterialApp, CupertinoApp, Theme, MediaQueryTích hợp nền tảng, chủ đề, thích ứng
AsyncFutureBuilder, StreamBuilder, ValueListenableBuilderCập nhật phản ứng từ dữ liệu bất đồng bộ

Theo Flutter Widget of the Week (Google, 2025), cộng đồng Flutter tích cực sử dụng sự kết hợp của layout + painting + interactive Widget để xây dựng hầu như bất kỳ giao diện nào. Ví dụ, một nút bấm: InkWell (interactive) + Container (painting) + Text (static) + Padding (layout). Tính mô-đun này cho phép tái sử dụng các khối tiêu chuẩn trong các ngữ cảnh khác nhau mà không trùng lặp mã.

Sự Kết Hợp Widget và BuildContext

Sự kết hợp Widget là quá trình xây dựng UI bằng cách lồng một số Widget vào trong những Widget khác. Không giống như kế thừa cổ điển (extends), nơi lớp con kế thừa hành vi của lớp cha, Flutter sử dụng sự tập hợp: mỗi Widget chứa các Widget khác thông qua tham số child (cho một) hoặc children (cho nhiều). Cách tiếp cận này mang lại sự linh hoạt và khả năng tái sử dụng cao hơn.

BuildContext là khái niệm quan trọng thứ hai sau Widget. BuildContext là một bộ mô tả vị trí của Widget trong cây phần tử. Thông qua BuildContext, Widget có thể truy cập các widget tổ tiên (Theme.of(context), MediaQuery.of(context), Navigator.of(context)). BuildContext được truyền vào phương thức build() và được sử dụng để tương tác với các phần tử cha và con. Mỗi Widget có chính xác một BuildContext, xác định duy nhất vị trí của nó trong cây.

Theo Tổng quan Kiến trúc Flutter (Google, 2025), BuildContext là nền tảng cho InheritedWidget — một cơ chế cho phép truyền dữ liệu xuống cây mà không cần truyền rõ ràng qua các hàm tạo. Theme, MediaQuery, Navigator và Provider sử dụng InheritedWidget bên trong. Bất kỳ Widget lồng sâu nào cũng có thể truy cập dữ liệu tổ tiên thông qua BuildContext.dependOnInheritedWidgetOfExactType, làm cho BuildContext trở thành chìa khóa của kiến trúc phản ứng của Flutter.

dart
// Kết hợp Widget qua lồng ghép
Scaffold(
  appBar: AppBar(title: const Text('Ứng dụng của tôi')),
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text('Chào mừng đến với Flutter',
          style: TextStyle(fontSize: 24)),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: () { /* nav */ },
          child: const Text('Bắt đầu'),
        ),
      ],
    ),
  ),
)

// Truy cập chủ đề qua BuildContext
Text(
  'Văn bản có Kiểu',
  style: Theme.of(context).textTheme.headlineMedium,
)

Lỗi Thường Gặp với Widget

Lỗi đầu tiên và phổ biến nhất là sử dụng StatefulWidget khi StatelessWidget là đủ. Nhiều nhà phát triển Flutter mới bắt đầu tạo StatefulWidget cho tất cả các widget, ngay cả khi trạng thái được lưu trữ trong nhà cung cấp bên ngoài (Provider, Riverpod, BLoC). Điều này là quá mức và làm giảm hiệu suất. Quy tắc: sử dụng StatelessWidget nếu trạng thái được quản lý từ bên ngoài hoặc nếu widget không có trạng thái có thể thay đổi riêng.

Lỗi thứ hai — tạo Widget bên trong phương thức build mà không có hàm tạo const. Mỗi Widget được tạo mà không có const được cấp phát lại mỗi lần build. Nếu bạn tạo Widget với hàm tạo const bên trong build(), Flutter có thể tái sử dụng cùng một phiên bản, giảm tải cho bộ thu gom rác. Thêm const bất cứ khi nào có thể — đặc biệt cho Text, Icon, SizedBox, Padding và các widget stateless khác.

Lỗi thứ ba — làm việc không đúng với khóa (Key). Flutter sử dụng Key để xác định Widget khi xây dựng lại cây. Nếu danh sách Widget được xây dựng lại mà không có Key, Flutter có thể nhầm lẫn thứ tự các phần tử, dẫn đến hoạt ảnh không chính xác hoặc mất trạng thái. Luôn thêm Key (ví dụ: ValueKey hoặc ObjectKey) cho các phần tử trong danh sách, đặc biệt khi sử dụng ListView.builder với dữ liệu động.

Câu Hỏi Thường Gặp

Sự khác biệt giữa StatelessWidget và StatefulWidget là gì?

StatelessWidget là widget không có trạng thái có thể thay đổi; giao diện của nó hoàn toàn được xác định bởi hàm tạo. StatefulWidget là widget có trạng thái có thể thay đổi, được lưu trữ trong một đối tượng State riêng biệt và có thể được cập nhật qua setState() mà không cần tạo lại widget. Sử dụng StatelessWidget bất cứ khi nào có thể, StatefulWidget — khi cần trạng thái cục bộ.

Tại sao Widget được gọi là bất biến (immutable)?

Tính bất biến của Widget là quyết định kiến trúc của Flutter cho hiệu suất. Nếu Widget có thể thay đổi, Flutter không thể so sánh an toàn cấu hình cũ và mới mỗi lần build. Tính bất biến cho phép Flutter nhanh chóng xác định liệu Widget có thay đổi hay không (thông qua toán tử ==) và tái sử dụng RenderObject hiện có, giảm thiểu các thao tác kết xuất tốn kém.

BuildContext là gì và tại sao cần nó?

BuildContext là bộ mô tả vị trí của Widget trong cây phần tử. Thông qua nó, Widget có quyền truy cập vào các widget tổ tiên (Theme, MediaQuery, Navigator) và InheritedWidget. BuildContext cũng được sử dụng cho điều hướng (Navigator.of(context)), hiển thị SnackBar và tương tác với Provider. Mỗi Widget nhận BuildContext thông qua phương thức build() và truyền nó cho các phần tử con cháu.

Làm thế nào để chọn giữa Row, Column và Stack cho bố cục?

Sử dụng Row cho bố cục ngang của các phần tử, Column cho bố cục dọc, Stack để xếp chồng các phần tử lên nhau. Row và Column hoạt động theo nguyên tắc flexbox: các phần tử con chiếm không gian theo mainAxisSize, mainAxisAlignment và crossAxisAlignment. Stack sử dụng các phần tử con được định vị để đặt chính xác so với các cạnh hoặc trung tâm.

Làm thế nào Flutter duy trì 60 FPS với hàng ngàn Widget?

Flutter đạt được hiệu suất cao thông qua ba cơ chế: (1) Widget rẻ — các đối tượng bất biến nhẹ (40–80 byte), việc tạo chúng không gây áp lực lên GC. (2) Tái sử dụng RenderObject — khi chuyển sang Widget cùng loại, RenderObject được tái sử dụng, tránh việc tạo lại tốn kém. (3) Động cơ Skia/Impeller — kết xuất ở cấp độ C++ với các lệnh gọi vẽ được giảm thiểu thông qua ranh giới vẽ lại.

Tóm Tắt

  • Widget — cấu hình UI bất biến trong Flutter, khối xây dựng cơ bản mô tả giao diện và hành vi.
  • Ba cây — Flutter sử dụng Widget tree (cấu hình), Element tree (liên kết), RenderObject tree (kết xuất) để kết xuất tối ưu.
  • Stateless vs Stateful — StatelessWidget không có trạng thái, StatefulWidget với State có thể thay đổi và phương thức setState() để cập nhật UI.
  • Sự kết hợp — UI được xây dựng bằng cách lồng Widget qua child/children, không kế thừa, mang lại sự linh hoạt và tái sử dụng.
  • BuildContext — bộ mô tả vị trí Widget trong cây để truy cập Theme, Navigator, MediaQuery và InheritedWidget.
  • Hiệu suất — Widget được tạo lại mỗi lần build (60 FPS), nhưng RenderObject được tái sử dụng khi loại khớp.
  • Loại — Widget được chia thành layout (Row, Column, Stack), painting (Container, Padding), interactive (GestureDetector, TextField) và platform (MaterialApp, Theme).

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm