Flutter Widget Fundamentals

1. Widget

Trong Flutter, Widget là thành phần cơ bản dùng để xây dựng giao diện người dùng (UI).

Có thể hiểu đơn giản:

Mọi thành phần xuất hiện trên giao diện Flutter đều được xây dựng từ Widget.

Ví dụ:

  • Text → hiển thị văn bản.
  • Image → hiển thị hình ảnh.
  • Icon → hiển thị biểu tượng.
  • Container → tạo một vùng chứa.
  • Row → sắp xếp Widget theo chiều ngang.
  • Column → sắp xếp Widget theo chiều dọc.
  • ElevatedButton → tạo nút bấm.
  • Scaffold → cung cấp cấu trúc cơ bản cho một màn hình.

Ví dụ:

Text('Hello Flutter')

Ở đây Text là một Widget.

Một giao diện phức tạp cũng được tạo bằng cách kết hợp nhiều Widget nhỏ lại với nhau.

2. Widget Tree

Flutter tổ chức giao diện dưới dạng Widget Tree (cây Widget).

Mỗi Widget có thể chứa một hoặc nhiều Widget con.

Ví dụ:

MaterialApp
└── Scaffold
    ├── AppBar
    │   └── Text
    │
    └── Center
        └── Column
            ├── Text
            └── ElevatedButton

Có thể hiểu:

  • MaterialApp là Widget gốc.
  • Scaffold nằm bên trong MaterialApp.
  • AppBar và phần body nằm bên trong Scaffold.
  • Text nằm bên trong AppBar.
  • Column nằm bên trong Center.
  • Text và ElevatedButton nằm bên trong Column.

Ví dụ code

MaterialApp(
  home: Scaffold(
    appBar: AppBar(
      title: const Text('Home'),
    ),
    body: Center(
      child: Column(
        children: [
          const Text('Hello'),
          ElevatedButton(
            onPressed: () {},
            child: const Text('Click'),
          ),
        ],
      ),
    ),
  ),
)

Điểm quan trọng:

Flutter không xây dựng UI bằng cách thao tác trực tiếp từng thành phần giao diện. Thay vào đó, ta mô tả cấu trúc UI bằng một cây Widget.

3. Widget là bất biến (Immutable)

Một đặc điểm quan trọng của Widget trong Flutter là:

Widget thường là immutable object – đối tượng không thể thay đổi sau khi được tạo.

Ví dụ:

Text(
  'Hello',
  style: TextStyle(
    fontSize: 20,
  ),
)

Sau khi Widget Text được tạo, ta không thay đổi trực tiếp giá trị của nó.

Nếu dữ liệu thay đổi, Flutter sẽ tạo ra cấu hình Widget mới và xây dựng lại phần giao diện cần thiết.

Ví dụ:

Text('$count')

Khi count thay đổi từ:

0 → 1

Flutter sẽ tạo cấu hình Widget mới:

Text('1')

thay vì sửa trực tiếp Widget cũ.

4. StatelessWidget

StatelessWidget là Widget không quản lý State có thể thay đổi bên trong chính nó.

Nó phù hợp với những UI phụ thuộc vào dữ liệu không thay đổi trong vòng đời Widget.

Ví dụ:

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

  @override
  Widget build(BuildContext context) {
    return const Text(
      'Welcome to Flutter',
    );
  }
}

Widget này chỉ hiển thị một dòng chữ và không có dữ liệu nội bộ cần thay đổi.

Đặc điểm

StatelessWidget
│
├── Không quản lý State nội bộ
├── Không sử dụng setState()
├── Có phương thức build()
└── Phù hợp với UI tĩnh

Ví dụ thường gặp:

  • Logo.
  • Tiêu đề.
  • Icon.
  • Text.
  • Card hiển thị dữ liệu.
  • Các thành phần UI chỉ nhận dữ liệu từ Widget cha.

5. StatefulWidget

StatefulWidget là Widget có State có thể thay đổi trong quá trình ứng dụng chạy.

Ví dụ:

  • Bộ đếm số.
  • Checkbox.
  • Switch.
  • Form nhập dữ liệu.
  • Animation.
  • Loading.
  • Hiển thị/ẩn password.

Một StatefulWidget thường gồm hai phần:

StatefulWidget
      │
      └── State
           ├── lưu dữ liệu thay đổi
           └── build UI

Ví dụ:

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

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('$count'),

        ElevatedButton(
          onPressed: () {
            setState(() {
              count++;
            });
          },
          child: const Text('Increase'),
        ),
      ],
    );
  }
}

Khi người dùng nhấn nút:

setState(() {
  count++;
});

Giá trị count thay đổi.

Flutter nhận biết State đã thay đổi và gọi lại build() để cập nhật UI.

6. State

State là dữ liệu có thể thay đổi và ảnh hưởng đến giao diện của Widget.

Ví dụ:

int count = 0;

count là State nếu giá trị của nó thay đổi và sự thay đổi đó cần được phản ánh lên UI.

Ví dụ:

count = 0
   ↓
User click
   ↓
count = 1
   ↓
UI được cập nhật
   ↓
Text hiển thị "1"

Một số ví dụ về State:

  • Số lượng sản phẩm trong giỏ hàng.
  • Trạng thái đăng nhập.
  • Trạng thái loading.
  • Giá trị TextField.
  • Trạng thái bật/tắt Switch.
  • Tab hiện tại.
  • Danh sách dữ liệu đang hiển thị.

7. setState()

setState() được sử dụng trong StatefulWidget để thông báo cho Flutter rằng State đã thay đổi và UI cần được xây dựng lại.

Ví dụ:

setState(() {
  count++;
});

Nếu thay đổi State nhưng không gọi setState():

count++;

thì Flutter không được thông báo để cập nhật UI ngay theo cách của StatefulWidget.

Nguyên tắc

Không nên viết:

setState(() {
  // Thực hiện các tác vụ quá nặng
});

setState() nên chủ yếu dùng để cập nhật State liên quan đến UI.

8. Build Method

build() là phương thức quan trọng trong Widget.

Nó có nhiệm vụ:

Mô tả giao diện mà Widget cần hiển thị dựa trên State và dữ liệu hiện tại.

Ví dụ:

@override
Widget build(BuildContext context) {
  return const Text(
    'Hello Flutter',
  );
}

build() phải trả về một Widget.

Ví dụ:

Widget build(BuildContext context) {
  return Container();
}

hoặc:

Widget build(BuildContext context) {
  return Column(
    children: [
      const Text('Hello'),
      const Text('Flutter'),
    ],
  );
}

Khi nào build() được gọi?

build() có thể được gọi lại khi:

  • Widget được tạo.
  • State thay đổi.
  • Widget cha rebuild.
  • Một số dữ liệu phụ thuộc thay đổi.

Vì vậy:

Không nên đặt các tác vụ nặng hoặc tác vụ có side effect vào build().

Ví dụ không nên gọi API trực tiếp trong build().

9. BuildContext

BuildContext đại diện cho vị trí của một Widget trong Widget Tree.

Ví dụ:

Widget build(BuildContext context) {
  return Text('Hello');
}

context thường được sử dụng để truy cập các thông tin nằm trong Widget Tree.

Ví dụ:

Theme.of(context)

Lấy Theme hiện tại.

Hoặc:

MediaQuery.of(context)

Lấy thông tin về kích thước màn hình.

Hoặc:

Navigator.of(context)

Truy cập Navigator để chuyển màn hình.

Có thể hiểu đơn giản:

BuildContext giúp Widget biết nó đang nằm ở đâu trong Widget Tree và có thể truy cập các thông tin được cung cấp ở phía trên nó.

10. Parent Widget và Child Widget

Widget có thể có:

  • Parent Widget → Widget cha.
  • Child Widget → Widget con.

Ví dụ:

Center(
  child: Text('Hello'),
)

Ở đây:

Center
└── Text

Center là Parent.

Text là Child.

Một Widget có thể có nhiều Widget con:

Column(
  children: [
    Text('A'),
    Text('B'),
    Text('C'),
  ],
)

Cây:

Column
├── Text A
├── Text B
└── Text C

11. child và children

Flutter thường sử dụng hai thuộc tính:

child

Dùng khi Widget chỉ nhận một Widget con.

Center(
  child: Text('Hello'),
)

children

Dùng khi Widget nhận nhiều Widget con.

Column(
  children: [
    Text('A'),
    Text('B'),
    Text('C'),
  ],
)

Một số Widget thường dùng child:

  • Center
  • Container
  • Padding
  • SizedBox
  • Expanded

Một số Widget thường dùng children:

  • Column
  • Row
  • Stack
  • Một số Widget chứa danh sách con.

12. Layout Widget

Layout Widget được sử dụng để sắp xếp vị trí và kích thước của các Widget.

Một số Widget quan trọng:

Container

Dùng để tạo vùng chứa và thiết lập:

  • width
  • height
  • padding
  • margin
  • color
  • decoration

Ví dụ:

Container(
  width: 200,
  height: 100,
  padding: const EdgeInsets.all(16),
  child: const Text('Hello'),
)

Row

Sắp xếp Widget theo chiều ngang.

Row(
  children: [
    Icon(Icons.home),
    Text('Home'),
  ],
)
Icon → Text

Column

Sắp xếp Widget theo chiều dọc.

Column(
  children: [
    Text('Name'),
    Text('Email'),
    Text('Phone'),
  ],
)
Name
 ↓
Email
 ↓
Phone

Stack

Cho phép đặt các Widget chồng lên nhau.

Stack(
  children: [
    Image.network('image-url'),
    const Text('Product'),
  ],
)

Thường dùng để:

  • Badge.
  • Overlay.
  • Text trên hình ảnh.
  • Floating elements.

13. Padding và SizedBox

Padding

Dùng để tạo khoảng cách bên trong một vùng.

Padding(
  padding: const EdgeInsets.all(16),
  child: Text('Hello'),
)

SizedBox

Dùng để tạo kích thước hoặc khoảng cách.

Column(
  children: [
    Text('Hello'),
    SizedBox(height: 20),
    Text('Flutter'),
  ],
)

14. MaterialApp

MaterialApp thường được sử dụng làm Widget gốc của ứng dụng Flutter sử dụng Material Design.

Ví dụ:

void main() {
  runApp(
    MaterialApp(
      home: HomePage(),
    ),
  );
}

MaterialApp cung cấp nhiều chức năng quan trọng như:

  • Theme.
  • Navigation.
  • Localization.
  • Material Design.
  • Cấu hình màn hình ban đầu.

15. Scaffold

Scaffold cung cấp cấu trúc cơ bản cho một màn hình.

Ví dụ:

Scaffold(
  appBar: AppBar(
    title: const Text('Home'),
  ),
  body: const Center(
    child: Text('Hello'),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  ),
)

Một số thành phần thường dùng:

Scaffold
├── appBar
├── body
├── drawer
├── bottomNavigationBar
└── floatingActionButton

16. Widget Composition

Flutter khuyến khích Composition (kết hợp Widget) thay vì tạo những thành phần UI quá lớn.

Ví dụ thay vì tạo một màn hình có hàng trăm dòng code:

class HomePage extends StatelessWidget {
  ...
}

có thể chia thành:

HomePage
├── AppHeader
├── SearchBar
├── CategoryList
├── ProductList
└── BottomNavigation

Mỗi phần có thể là một Widget riêng:

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

  @override
  Widget build(BuildContext context) {
    return Card(
      child: const Text('Product'),
    );
  }
}

Ưu điểm:

  • Code dễ đọc.
  • Dễ tái sử dụng.
  • Dễ bảo trì.
  • Dễ kiểm thử.
  • Giảm độ phức tạp của từng Widget.

17. Widget Lifecycle cơ bản

Đối với StatefulWidget, State có một vòng đời.

Một số phương thức quan trọng:

createState()
    ↓
initState()
    ↓
build()
    ↓
didUpdateWidget()
    ↓
build()
    ↓
dispose()

initState()

Được gọi một lần khi State được tạo.

Thường dùng để:

  • Khởi tạo dữ liệu.
  • Tạo Controller.
  • Đăng ký Listener.
  • Thực hiện một số thao tác khởi tạo.

Ví dụ:

@override
void initState() {
  super.initState();

  // Khởi tạo
}

dispose()

Được gọi khi Widget bị loại khỏi Widget Tree.

Thường dùng để giải phóng:

  • Controller.
  • AnimationController.
  • StreamSubscription.
  • Listener.

Ví dụ:

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

18. Widget Rebuild

Rebuild nghĩa là Flutter chạy lại build() để tạo cấu hình Widget mới.

Ví dụ:

User click button
       ↓
setState()
       ↓
State thay đổi
       ↓
build() được gọi lại
       ↓
Widget configuration mới
       ↓
Flutter cập nhật UI cần thiết

Quan trọng:

Rebuild không có nghĩa là Flutter xóa toàn bộ màn hình rồi vẽ lại mọi thứ từ đầu.

Flutter sử dụng cơ chế so sánh và quản lý Widget/Element/RenderObject để cập nhật phần cần thiết.

19. Widget, Element và RenderObject

Flutter có kiến trúc gồm nhiều lớp.

Có thể hiểu đơn giản:

Widget
  ↓
Element
  ↓
RenderObject
  ↓
Giao diện trên màn hình

Widget

Mô tả UI.

Element

Quản lý Widget trong Widget Tree và duy trì vị trí của Widget trong cây.

RenderObject

Chịu trách nhiệm về:

  • Layout.
  • Painting.
  • Kích thước.
  • Vị trí.
  • Hiển thị.

Đây là kiến thức nền quan trọng để hiểu cách Flutter hoạt động sâu hơn.

20. Key

Key được sử dụng để xác định và nhận diện Widget trong Widget Tree.

Ví dụ:

ListTile(
  key: const ValueKey(1),
  title: const Text('Product'),
)

Key đặc biệt hữu ích khi:

  • Làm việc với danh sách.
  • Widget được reorder.
  • Widget có State.
  • Cần giữ State của Widget khi cấu trúc cây thay đổi.

Một số loại Key:

  • ValueKey
  • ObjectKey
  • UniqueKey
  • GlobalKey

21. Const Widget

Flutter khuyến khích sử dụng const khi Widget và dữ liệu của nó có thể được xác định tại compile time.

Ví dụ:

const Text('Hello');

Thay vì:

Text('Hello');

const giúp Flutter có thể tái sử dụng các object bất biến trong những trường hợp phù hợp.

Ví dụ:

const Column(
  children: [
    Text('Hello'),
    Text('Flutter'),
  ],
)

22. Ví dụ tổng hợp

Một màn hình Flutter đơ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(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Fundamentals'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Hello Flutter',
                style: TextStyle(
                  fontSize: 24,
                ),
              ),

              const SizedBox(height: 20),

              ElevatedButton(
                onPressed: () {},
                child: const Text('Click Me'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Widget Tree:

MyApp
└── MaterialApp
    └── Scaffold
        ├── AppBar
        │   └── Text
        │
        └── Padding
            └── Column
                ├── Text
                ├── SizedBox
                └── ElevatedButton
                    └── Text

Qua ví dụ này có thể thấy Flutter UI được xây dựng bằng cách lồng các Widget vào nhau.

23. Các nguyên tắc quan trọng

1. Mọi UI đều được xây dựng từ Widget

UI
↓
Widget
↓
Widget
↓
Widget

2. Widget nên nhỏ và có trách nhiệm rõ ràng

Không nên tạo một Widget khổng lồ chứa toàn bộ logic và UI của ứng dụng.

3. Ưu tiên Composition

Kết hợp các Widget nhỏ để tạo Widget lớn.

4. Không đặt logic nặng trong build()

build() có thể được gọi nhiều lần.

5. Dùng StatelessWidget khi không cần State

Không nên dùng StatefulWidget nếu UI không có State cần quản lý.

6. Dùng setState() khi thay đổi State cục bộ

setState(() {
  count++;
});

7. Giải phóng tài nguyên

Các Controller, Subscription hoặc Listener cần được dispose khi không còn sử dụng.

24. Các khái niệm cần nhớ

Khái niệm Ý nghĩa
Widget Thành phần xây dựng UI
Widget Tree Cấu trúc cây của các Widget
StatelessWidget Widget không quản lý State thay đổi
StatefulWidget Widget có State thay đổi
State Dữ liệu có thể thay đổi và ảnh hưởng UI
setState() Thông báo State thay đổi
build() Mô tả UI
BuildContext Vị trí Widget trong Widget Tree
child Một Widget con
children Nhiều Widget con
Parent Widget cha
Child Widget con
Key Định danh Widget
Composition Kết hợp nhiều Widget
Rebuild Chạy lại quá trình build
Widget Tree Cấu trúc UI dạng cây
RenderObject Thành phần thực hiện layout và rendering

25. Tóm tắt

Flutter Widget Fundamentals là nền tảng quan trọng nhất khi bắt đầu học Flutter.

Cần nắm chắc các ý chính:

Flutter UI
    ↓
Widget
    ↓
Widget Tree
    ↓
StatelessWidget / StatefulWidget
    ↓
State
    ↓
setState()
    ↓
build()
    ↓
Rebuild
    ↓
UI được cập nhật

Có thể ghi nhớ ngắn gọn:

Widget là building block của Flutter UI. Các Widget được kết hợp thành Widget Tree. StatelessWidget dùng cho UI không quản lý State nội bộ, StatefulWidget dùng khi UI có State thay đổi. Khi State thay đổi thông qua setState(), Flutter rebuild Widget và cập nhật giao diện cần thiết. BuildContext đại diện cho vị trí của Widget trong Widget Tree và cho phép truy cập các thông tin từ Widget Tree.

Bài viết khác

Flutter Layout & Responsive UI

Flutter Layout & Responsive UI 1. Flutter Layout Layout trong Flutter là quá trình xác định kích thước và vị trí của các Widget trên màn hình. Flutter sử dụng hệ thống Widget để xây dựng giao diện. Các Widget Layout quyết định: Widget nằm ở đâu. Widget có kích thước bao nhiêu. Các Widget […]

StatelessWidget vs StatefulWidget

StatelessWidget vs StatefulWidget 1. Khái niệm Trong Flutter, StatelessWidget và StatefulWidget là hai loại Widget cơ bản dùng để xây dựng giao diện. Điểm khác biệt quan trọng nhất nằm ở State (trạng thái). StatelessWidget: Widget không có State nội bộ có thể thay đổi. StatefulWidget: Widget có State nội bộ có thể thay đổi trong quá […]

Isolate & Concurrency

Isolate & Concurrency Isolate và Concurrency (đồng thời) là cơ chế trong Dart dùng để thực hiện nhiều công việc mà không làm một công việc phải chờ hoàn thành hoàn toàn mới có thể xử lý công việc khác. Lý thuyết 1. Concurrency Concurrency (xử lý đồng thời) là khả năng chương trình quản […]

Null Safety trong Dart

Null Safety là cơ chế phân biệt rõ ràng giữa kiểu dữ liệu có thể chứa null (Nullable) và kiểu dữ liệu không thể chứa null (Non-nullable) ngay ở mức ngôn ngữ lập trình, giúp trình biên dịch phát hiện và bắt lỗi liên quan đến null ngay trong quá trình viết mã thay vì […]

Test Scenario(Kịch bản kiểm thử)

1. Lời mở đầu và vị trí chiến lược của Test Scenario trong quy trình kiểm thử: Trong bức tranh tổng thể của vòng đời kiểm thử phần mềm (STLC), sau khi đã hoàn thành việc phân tích yêu cầu và lập kế hoạch kiểm thử (Test Plan), công việc tiếp theo mang tính chất […]

Dart OOP

OOP là từ viết tắt của cụm từ Object Oriented Programming. Nó có nghĩa là lập trình hướng đối tượng. Đây là một phương pháp lập trình dựa trên những khái niệm về đối tượng và lớp. OOP thường tập trung vào những đối tượng thao tác hơn là tập trung vào logic để có […]

Leave a Reply

Your email address will not be published. Required fields are marked *