Tạo bởi Trần Văn Điêp|
Lập Trình Flutter

Widget cơ bản (Text, Image, Button, Column, Row)

Nếu bạn mới bắt đầu hành trình lập trình Flutter, chắc chắn bạn đã nghe đến cụm từ “Widget”. Trong Flutter, mọi thứ – từ một đoạn văn bản, hình ảnh, đến bố cục phức tạp – đều là Widget. Chúng là nền tảng tạo nên toàn bộ giao diện ứng dụng. Hiểu và sử dụng thành thạo các Widget cơ bản không chỉ giúp bạn xây dựng ứng dụng nhanh chóng mà còn giúp bạn tối ưu trải nghiệm người dùng (UI/UX) và hiệu suất ứng dụng.

Bài viết này sẽ hướng dẫn chi tiết về 5 widget cơ bản nhất trong Flutter: Text, Image, Button, ColumnRow. Đây là các khối xây dựng giao diện phổ biến nhất mà bất kỳ lập trình viên Flutter nào cũng phải thành thạo. Bạn sẽ được tìm hiểu về cú pháp, cách sử dụng, ví dụ thực tế và những mẹo nhỏ để áp dụng hiệu quả trong dự án của mình.

Hãy cùng bắt đầu khám phá sức mạnh của widget trong Flutter – nơi mà chỉ với vài dòng code, bạn có thể tạo nên giao diện đẹp mắt, mượt mà và dễ bảo trì.


Tổng quan về Widget trong Flutter

Trong Flutter, Widget là thành phần cơ bản nhất để tạo nên giao diện. Mọi thứ bạn nhìn thấy trên màn hình – từ nút bấm, hình ảnh, văn bản cho đến bố cục – đều được biểu diễn thông qua các widget. Flutter cung cấp hai loại widget chính:

  • StatelessWidget: Giao diện không thay đổi trong suốt vòng đời của nó. Ví dụ: Text, Icon, Image.

  • StatefulWidget: Giao diện có thể thay đổi khi trạng thái thay đổi. Ví dụ: Button có thể đổi màu khi nhấn, ô nhập liệu thay đổi nội dung.

Ưu điểm của widget trong Flutter:

  • Tái sử dụng cao: Dễ dàng kết hợp nhiều widget nhỏ để tạo nên giao diện phức tạp.

  • Cấu trúc rõ ràng: Giúp mã dễ đọc, dễ bảo trì.

  • Hiệu năng mạnh mẽ: Flutter render giao diện trực tiếp, không phụ thuộc vào WebView hay nền tảng gốc.

Bây giờ, hãy cùng đi sâu vào từng loại widget cơ bản để hiểu rõ cách chúng hoạt động và áp dụng trong thực tế.


Widget Text – Hiển thị văn bản trong Flutter

Text là widget cơ bản và phổ biến nhất dùng để hiển thị chuỗi văn bản trên màn hình.

1. Cấu trúc cơ bản của Text:

Text( 'Xin chào Flutter!', style: TextStyle( fontSize: 20, color: Colors.blue, fontWeight: FontWeight.bold, ), );

2. Các thuộc tính quan trọng:

  • style: Định dạng văn bản (kích thước, màu sắc, font chữ, v.v.).

  • textAlign: Căn chỉnh văn bản (center, left, right, justify).

  • maxLines: Giới hạn số dòng hiển thị.

  • overflow: Xử lý văn bản tràn, ví dụ TextOverflow.ellipsis.

3. Ví dụ thực tế:

Text( 'Chào mừng bạn đến với khóa học Flutter cơ bản!', textAlign: TextAlign.center, style: TextStyle( fontSize: 18, color: Colors.black87, ), );

4. Mẹo sử dụng hiệu quả:

  • Sử dụng Google Fonts để tạo phong cách riêng cho ứng dụng.

  • Gộp nhiều đoạn Text trong RichText khi cần hiển thị văn bản nhiều kiểu khác nhau trong cùng dòng.

  • Dùng const khi văn bản không thay đổi để cải thiện hiệu suất.


Widget Image – Hiển thị hình ảnh dễ dàng

Hình ảnh là yếu tố quan trọng trong việc tạo nên giao diện sinh động và hấp dẫn. Flutter cung cấp nhiều cách để hiển thị hình ảnh thông qua widget Image.

1. Cách sử dụng cơ bản:

Image.asset('assets/images/logo.png');

Ngoài ra, bạn có thể hiển thị ảnh từ mạng hoặc bộ nhớ:

Image.network('https://example.com/flutter.png'); Image.file(File('/storage/emulated/0/Download/photo.jpg'));

2. Các thuộc tính thường dùng:

  • width / height: Kích thước hình ảnh.

  • fit: Cách hình ảnh hiển thị trong khung (BoxFit.cover, contain, fill, fitWidth, fitHeight).

  • color & colorBlendMode: Áp dụng hiệu ứng màu cho hình ảnh.

  • alignment: Căn chỉnh vị trí hình ảnh trong khung.

3. Ví dụ thực tế:

Image.network( 'https://flutter.dev/images/flutter-logo-sharing.png', width: 150, height: 150, fit: BoxFit.contain, );

4. Lời khuyên:

  • Đặt ảnh tĩnh vào thư mục assets/ và khai báo trong pubspec.yaml.

  • Dùng CachedNetworkImage để tải ảnh mạng hiệu quả, tránh tải lại nhiều lần.

  • Hạn chế sử dụng ảnh có dung lượng lớn để tránh lag hoặc crash trên thiết bị yếu.


Widget Button – Tạo tương tác trong ứng dụng

Nút bấm là thành phần không thể thiếu giúp người dùng tương tác với ứng dụng. Flutter hỗ trợ nhiều loại nút khác nhau như TextButton, ElevatedButton, và OutlinedButton.

1. Ví dụ cơ bản:

ElevatedButton( onPressed: () { print('Button clicked!'); }, child: Text('Nhấn vào tôi'), );

2. Các loại Button phổ biến:

  • TextButton: Nút dạng chữ, không có nền.

  • ElevatedButton: Nút nổi, thường dùng để tạo điểm nhấn.

  • OutlinedButton: Nút viền, phù hợp khi muốn giao diện tinh tế hơn.

  • IconButton: Nút chỉ có biểu tượng.

3. Ví dụ kết hợp:

Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () {}, child: Text('Đăng nhập'), ), SizedBox(width: 10), OutlinedButton( onPressed: () {}, child: Text('Đăng ký'), ), ], );

4. Lời khuyên sử dụng:

  • Dùng theme để đồng bộ màu sắc và kích thước các nút trong toàn app.

  • Đảm bảo các nút có đủ kích thước để dễ bấm trên thiết bị di động.

  • Gắn feedback (ví dụ snackbar, animation) khi người dùng thao tác để tăng trải nghiệm.


Widget Column – Tạo bố cục theo chiều dọc

Column là widget bố cục (layout widget) cho phép sắp xếp các phần tử theo chiều dọc (vertical). Đây là widget cực kỳ quan trọng khi bạn muốn hiển thị nội dung nhiều lớp từ trên xuống dưới.

1. Cấu trúc cơ bản:

Column( children: [ Text('Tiêu đề'), Image.asset('assets/banner.png'), ElevatedButton(onPressed: () {}, child: Text('Bắt đầu')), ], );

2. Các thuộc tính thường dùng:

  • mainAxisAlignment: Căn chỉnh trục dọc (start, center, end, spaceBetween).

  • crossAxisAlignment: Căn chỉnh trục ngang (start, center, end).

  • children: Danh sách widget con bên trong Column.

3. Ví dụ minh họa:

Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Xin chào Flutter!'), SizedBox(height: 10), ElevatedButton(onPressed: () {}, child: Text('Click Me')), ], );

4. Mẹo thực tế:

  • Sử dụng SingleChildScrollView bọc Column để tránh lỗi tràn nội dung trên màn hình nhỏ.

  • Kết hợp Expanded hoặc Flexible để chia tỷ lệ không gian giữa các phần tử.

  • Không nên đặt quá nhiều widget trong một Column – hãy tách nhỏ ra bằng các widget riêng biệt để dễ bảo trì.


Widget Row – Tạo bố cục theo chiều ngang

Tương tự như Column, Row giúp sắp xếp các phần tử theo chiều ngang (horizontal). Đây là widget quan trọng để hiển thị các thành phần cạnh nhau, ví dụ như biểu tượng và văn bản, hoặc nhiều nút bấm.

1. Ví dụ cơ bản:

Row( children: [ Icon(Icons.star, color: Colors.orange), SizedBox(width: 8), Text('Đánh giá: 5.0'), ], );

2. Các thuộc tính quan trọng:

  • mainAxisAlignment: Căn chỉnh theo chiều ngang.

  • crossAxisAlignment: Căn chỉnh theo chiều dọc.

  • children: Danh sách widget con trong Row.

3. Ví dụ kết hợp bố cục:

Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton(onPressed: () {}, child: Text('Lưu')), ElevatedButton(onPressed: () {}, child: Text('Hủy')), ], );

4. Lưu ý khi sử dụng Row:

  • Không nên để quá nhiều widget trong một Row – sẽ gây lỗi tràn ngang (RenderFlex overflowed).

  • Dùng Expanded để widget tự co giãn theo không gian có sẵn.

  • Khi cần bố cục linh hoạt, hãy kết hợp RowColumn trong cùng một cây widget.


Demo

Dưới đây là hướng dẫn chi tiết + code mẫu Flutter để bạn tạo ứng dụng có 2 màn hình Login và Register, giao diện đẹp – hiện đại – responsive, dùng Material 3animation nhẹ.
Phù hợp cho người mới bắt đầu học Flutter hoặc muốn có base UI chuẩn để phát triển dự án thực tế.


🧩 Cấu trúc dự án

lib/ ┣ main.dart ┣ screens/ ┃ ┣ login_screen.dart ┃ ┗ register_screen.dart ┗ widgets/ ┗ custom_textfield.dart

📱 main.dart

import 'package:flutter/material.dart'; import 'screens/login_screen.dart'; import 'screens/register_screen.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Auth UI', debugShowCheckedModeBanner: false, theme: ThemeData( useMaterial3: true, colorSchemeSeed: Colors.blueAccent, brightness: Brightness.light, ), initialRoute: '/login', routes: { '/login': (context) => const LoginScreen(), '/register': (context) => const RegisterScreen(), }, ); } }

🧑‍💻 custom_textfield.dart

import 'package:flutter/material.dart'; class CustomTextField extends StatelessWidget { final String label; final bool obscure; final TextEditingController controller; final IconData icon; const CustomTextField({ super.key, required this.label, this.obscure = false, required this.controller, required this.icon, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: TextField( controller: controller, obscureText: obscure, decoration: InputDecoration( prefixIcon: Icon(icon, color: Colors.blueAccent), labelText: label, filled: true, fillColor: Colors.grey[100], border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), ), ); } }

🔐 login_screen.dart

import 'package:flutter/material.dart'; import '../widgets/custom_textfield.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State<LoginScreen> createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { final emailCtrl = TextEditingController(); final passCtrl = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 40), const Text( "Welcome Back 👋", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( "Login to continue your journey with Flutter", style: TextStyle(color: Colors.grey[600]), ), const SizedBox(height: 40), CustomTextField( label: "Email", controller: emailCtrl, icon: Icons.email_outlined, ), CustomTextField( label: "Password", obscure: true, controller: passCtrl, icon: Icons.lock_outline, ), const SizedBox(height: 24), ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( backgroundColor: Colors.blueAccent, foregroundColor: Colors.white, minimumSize: const Size.fromHeight(50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: const Text( "Login", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), ), const SizedBox(height: 16), Center( child: GestureDetector( onTap: () => Navigator.pushNamed(context, '/register'), child: const Text.rich( TextSpan( text: "Don’t have an account? ", style: TextStyle(color: Colors.black54), children: [ TextSpan( text: "Register now", style: TextStyle( color: Colors.blueAccent, fontWeight: FontWeight.bold, ), ) ], ), ), ), ), const SizedBox(height: 40), Center( child: Image.network( 'https://flutter.dev/assets/homepage/carousel/slide_1-layer_0-6b7a6c99e2b95a52dbf1a203b12b3d06.png', height: 160, ), ), ], ), ), ), ); } }

📝 register_screen.dart

import 'package:flutter/material.dart'; import '../widgets/custom_textfield.dart'; class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @override State<RegisterScreen> createState() => _RegisterScreenState(); } class _RegisterScreenState extends State<RegisterScreen> { final nameCtrl = TextEditingController(); final emailCtrl = TextEditingController(); final passCtrl = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 40), const Text( "Create Account ✨", style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( "Join our Flutter community today!", style: TextStyle(color: Colors.grey[600]), ), const SizedBox(height: 40), CustomTextField( label: "Full Name", controller: nameCtrl, icon: Icons.person_outline, ), CustomTextField( label: "Email", controller: emailCtrl, icon: Icons.email_outlined, ), CustomTextField( label: "Password", obscure: true, controller: passCtrl, icon: Icons.lock_outline, ), const SizedBox(height: 24), ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( backgroundColor: Colors.blueAccent, foregroundColor: Colors.white, minimumSize: const Size.fromHeight(50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), child: const Text( "Register", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), ), const SizedBox(height: 16), Center( child: GestureDetector( onTap: () => Navigator.pushNamed(context, '/login'), child: const Text.rich( TextSpan( text: "Already have an account? ", style: TextStyle(color: Colors.black54), children: [ TextSpan( text: "Login here", style: TextStyle( color: Colors.blueAccent, fontWeight: FontWeight.bold, ), ) ], ), ), ), ), const SizedBox(height: 40), Center( child: Image.network( 'https://flutter.dev/assets/homepage/carousel/slide_1-layer_2-6076f807d8ed776b15d8224f10d08c2c.png', height: 160, ), ), ], ), ), ), ); } }

🌈 Điểm nổi bật của thiết kế

  • UI hiện đại, màu sắc tươi sáng, dùng Material 3.

  • Tương thích trên mọi màn hình (điện thoại, tablet, emulator).

  • Cấu trúc tách biệt rõ ràng (screens, widgets).

  • ✅ Dễ dàng mở rộng thêm API login/register, Firebase Auth, hoặc Google Sign-In.

  • ✅ Có animation nhẹhình minh họa sinh động giúp giao diện thân thiện.

Kết luận

Qua bài viết này, bạn đã hiểu rõ về các widget cơ bản trong Flutter gồm: Text, Image, Button, ColumnRow. Đây là những viên gạch đầu tiên giúp bạn xây dựng giao diện ứng dụng từ đơn giản đến phức tạp. Khi đã nắm vững cách hoạt động và thuộc tính của các widget này, bạn sẽ dễ dàng kết hợp chúng để tạo nên giao diện tinh tế, mượt mà và thân thiện với người dùng.

Hãy luyện tập bằng cách tạo các giao diện nhỏ mỗi ngày – ví dụ như màn hình đăng nhập, trang giới thiệu hoặc danh sách sản phẩm. Khi làm quen, bạn sẽ nhận ra Flutter thực sự mạnh mẽ nhờ cơ chế widget thống nhất và khả năng tùy biến linh hoạt.

Phản hồi từ học viên

5

Tổng 0 đánh giá

Đăng nhập để làm bài kiểm tra

Chưa có kết quả nào trước đó