
Chào mừng các "đệ tử" đến với bài học hôm nay! Giảng viên Creyt sẽ cùng các bạn "mổ xẻ" một khái niệm mà nhiều khi chúng ta bỏ qua, nhưng nó lại là "trái tim" của mọi thứ liên quan đến nhập liệu văn bản trong Flutter: EditableText.
1. EditableText là gì và để làm gì? – "Cái ruột" trần trụi
Các bạn cứ hình dung thế này: Nếu TextField là một chiếc xe hơi đã hoàn thiện, bóng loáng, có đầy đủ ghế da, điều hòa mát lạnh, thì EditableText chính là cái khung sườn (chassis) trần trụi, khối động cơ và hệ thống lái cơ bản của chiếc xe đó. Nó là widget cấp thấp nhất trong Flutter chịu trách nhiệm xử lý việc nhập liệu, chọn văn bản, và di chuyển con trỏ mà không hề có bất kỳ trang trí (decoration) hay hiệu ứng hình ảnh mặc định nào.
Mục đích sinh ra của nó? Đơn giản là để bạn có toàn quyền kiểm soát! Khi bạn cần một trường nhập liệu có giao diện "độc lạ Bình Dương", hoặc một hành vi tương tác mà TextField không thể đáp ứng được (ví dụ, một trình soạn thảo code, một editor rich-text với đủ thứ định dạng), thì EditableText chính là "công cụ" bạn cần để "đẽo gọt" từ đầu.
2. Tại sao không dùng TextField luôn cho rồi?
Câu hỏi hay! TextField là "người anh em" phổ biến hơn nhiều, và trong 99% trường hợp, bạn nên dùng TextField. Nó đã "đóng gói" sẵn EditableText bên trong và thêm vào hàng tá tiện ích như InputDecoration (viền, label, hint text, icon), errorText, padding, scrollPhysics... Nó giống như việc bạn mua một căn nhà đã xây sẵn, đầy đủ tiện nghi, chỉ việc dọn vào ở.
Nhưng đôi khi, bạn không muốn căn nhà xây sẵn đó. Bạn muốn tự mình "thiết kế kiến trúc" từng viên gạch, từng đường dây điện để tạo ra một "kiệt tác" có một không hai. Khi đó, EditableText là "viên gạch" cơ bản nhất để bạn bắt đầu xây dựng.

3. Code Ví Dụ Minh Hoạ – "Mổ xẻ" cái ruột
Để các bạn dễ hình dung, chúng ta hãy cùng xem EditableText hoạt động như thế nào. Các bạn sẽ thấy nó "trần trụi" đến mức nào!
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: 'EditableText Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const EditableTextScreen(),
);
}
}
class EditableTextScreen extends StatefulWidget {
const EditableTextScreen({super.key});
@override
State<EditableTextScreen> createState() => _EditableTextScreenState();
}
class _EditableTextScreenState extends State<EditableTextScreen> {
// 1. Controller: "Người quản lý" nội dung văn bản
late final TextEditingController _textController;
// 2. FocusNode: "Người gác cổng" cho trạng thái tập trung (focus)
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_textController = TextEditingController(text: 'Hello Giảng viên Creyt!');
_focusNode = FocusNode();
}
@override
void dispose() {
_textController.dispose(); // Luôn nhớ "dọn dẹp" controller
_focusNode.dispose(); // Luôn nhớ "dọn dẹp" focus node
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('EditableText Demo của Creyt'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.grey[200],
border: Border.all(color: Colors.blueAccent, width: 2),
borderRadius: BorderRadius.circular(8.0),
),
child: EditableText(
controller: _textController,
focusNode: _focusNode,
style: const TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.bold,
),
cursorColor: Colors.red, // Màu con trỏ
backgroundCursorColor: Colors.blue, // Màu con trỏ khi không focus (ít dùng)
selectionColor: Colors.lightBlue.withOpacity(0.5), // Màu vùng chọn
readOnly: false, // Có cho phép sửa đổi không?
maxLines: 1, // Số dòng tối đa
keyboardType: TextInputType.text,
autofocus: true, // Tự động focus khi widget được tạo
onChanged: (text) {
// Bất cứ khi nào văn bản thay đổi
print('Văn bản đã thay đổi: $text');
},
onSubmitted: (text) {
// Khi người dùng nhấn Enter/Done
print('Người dùng đã submit: $text');
_focusNode.unfocus(); // Bỏ focus sau khi submit
},
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Thao tác với văn bản từ bên ngoài
_textController.text = 'Creyt đã thay đổi nội dung!';
_focusNode.requestFocus(); // Yêu cầu focus lại
},
child: const Icon(Icons.edit),
),
);
}
}
Trong ví dụ trên, các bạn thấy EditableText chỉ cung cấp những thứ cốt lõi nhất: controller, focusNode, style, cursorColor, selectionColor và các callback như onChanged, onSubmitted. Mọi thứ về "khung viền", "nền", "padding" đều phải do bạn tự "đắp" bên ngoài bằng các widget như Container, Padding, BoxDecoration.
4. Mẹo (Best Practices) từ Giảng viên Creyt – "Bí kíp" để không "đi vào vết xe đổ"
-
"Đừng đụng vào nó nếu không cần!": Đây là quy tắc vàng! Luôn bắt đầu với
TextField. Chỉ khi nào bạn gặp phải một yêu cầu UI/UX quá đặc biệt màTextFieldkhông thể đáp ứng, hoặc bạn cần tối ưu hiệu năng cực đoan cho một lượng lớn input, thì mới nghĩ đếnEditableText. Nó giống như việc bạn chỉ nên tự xây nhà khi bạn là kiến trúc sư và thợ xây lành nghề, chứ không phải chỉ vì muốn "thử cho biết". -
Quản lý
TextEditingControllervàFocusNode: Hai "anh bạn" này cực kỳ quan trọng và hay bị quên. Luôn nhớ khai báo chúng bằnglate finalhoặc khởi tạo tronginitStatevà phảidispose()chúng trong phương thứcdispose()củaStatefulWidget. Nếu không, chúng sẽ gây ra rò rỉ bộ nhớ (memory leak), làm ứng dụng của bạn "nặng nề" và "chậm chạp" dần theo thời gian. Đây là "nghiệp vụ" cơ bản mà một lập trình viên "có tâm" phải làm.
-
Tùy biến "đến tận chân răng":
EditableTextcho phép bạn kiểm soát màu con trỏ (cursorColor), màu vùng chọn (selectionColor), và thậm chí cả màu con trỏ khi không focus (backgroundCursorColor). Tận dụng điều này để tạo ra những trải nghiệm nhập liệu độc đáo, phù hợp với branding của ứng dụng bạn. -
Hiệu năng (Performance): Vì
EditableTextít "phụ kiện" hơnTextField, trong những trường hợp cực đoan (ví dụ: một màn hình có hàng trăm ô nhập liệu nhỏ), nó có thể mang lại hiệu năng tốt hơn một chút. Tuy nhiên, đừng "mù quáng" mà hãy luôn dùng công cụ Profile của Flutter để kiểm tra trước khi quyết định "hy sinh" sự tiện lợi củaTextFieldđể đổi lấyEditableText.
5. Ứng dụng thực tế – "EditableText" đang ở đâu ngoài kia?
"Thầy ơi, có ai dùng cái này không hay chỉ mình em học?" – Chắc chắn rồi! EditableText là nền tảng cho nhiều ứng dụng phức tạp mà bạn thấy hàng ngày:
-
Trình soạn thảo mã nguồn (Code Editors): Các ứng dụng như VS Code (phiên bản web), hoặc các editor trên di động thường cần hiển thị cú pháp highlight, đánh số dòng, và các tính năng chỉnh sửa phức tạp.
EditableTextcung cấp cơ chế nhập liệu cơ bản, sau đó các lớp logic khác sẽ "vẽ" thêm các hiệu ứng đó lên trên. -
Trình soạn thảo văn bản đa định dạng (Rich Text Editors): Tưởng tượng các ứng dụng như Google Docs, Notion, hay thậm chí là phần soạn thảo tin nhắn trên các mạng xã hội cho phép bạn in đậm, in nghiêng, chèn link...
EditableTextxử lý phần nhập liệu thô, còn việc áp dụng các định dạng là do các lớp cao hơn quản lý. -
Các thanh tìm kiếm tùy chỉnh (Custom Search Bars): Đôi khi, một thanh tìm kiếm không chỉ đơn thuần là nhập text. Nó có thể có gợi ý đặc biệt, hiệu ứng chuyển động riêng, hoặc tích hợp trực tiếp vào một phần của UI game.
EditableTextlà "viên gạch" lý tưởng để xây dựng những thanh tìm kiếm như vậy từ đầu. -
Ứng dụng game hoặc tương tác cao: Trong một số game, bạn có thể cần một ô nhập tên người chơi hoặc mật khẩu mà nó phải hòa quyện hoàn hảo vào phong cách đồ họa của game, không hề có một chút "mùi" của widget hệ thống.
EditableTextlà lựa chọn tuyệt vời cho các tình huống này.
Vậy là các bạn đã "thấm" được phần nào về EditableText rồi chứ? Hãy nhớ, nó là một "công cụ" mạnh mẽ, nhưng hãy sử dụng nó một cách thông minh và có mục đích. Đừng bao giờ ngại "mổ xẻ" các khái niệm cơ bản để hiểu sâu hơn về cách Flutter vận hành nhé! Hẹn gặp lại trong bài học tiếp theo!
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é!