Một ô mã đơn hàng cần hiển thị nội dung cố định nhưng vẫn có nút thông tin ở cuối. Bạn đặt enabled: false để ngăn gõ chữ, rồi thấy nút ấy cũng không phản hồi. Nguyên nhân là trạng thái vô hiệu hóa áp dụng cả các widget con bên trong trường, không chỉ thao tác nhập văn bản.
Bài đối chiếu Flutter API ngày 08/11/2026. Đây là ví dụ Material được rà theo tài liệu; chưa chạy Flutter SDK, build ứng dụng, widget test hoặc kiểm tra trên thiết bị. Khi áp dụng vào dự án, kiểm tra lại ví dụ trên phiên bản SDK đang dùng và trên từng nền tảng mục tiêu.
Chọn khả năng cần giữ lại
Theo readOnly, đặt true ngăn người dùng sửa chữ bằng bàn phím hoặc shortcut nhưng vẫn cho chọn nội dung. Theo enabled, đặt false khiến trường bỏ qua tap và vô hiệu hóa cả suffixIcon.
Nếu không muốn người dùng thay đổi vùng chọn, xét thêm enableInteractiveSelection. Ba thuộc tính này giải quyết ba yêu cầu khác nhau. Trước khi chọn, hãy ghi rõ màn hình cần cho phép sửa, chọn chữ và bấm nút phụ ở mức nào.
Dựng hai trường để so sánh trực tiếp
Ví dụ dưới dùng hai controller có cùng nội dung ban đầu. Thay nội dung lib/main.dart của dự án Material thử nghiệm bằng code này để kiểm tra bằng tay:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: FieldDemo()));
class FieldDemo extends StatefulWidget {
const FieldDemo({super.key});
@override
State<FieldDemo> createState() => _FieldDemoState();
}
class _FieldDemoState extends State<FieldDemo> {
final readOnlyCode = TextEditingController(text: 'AE-2048');
final disabledCode = TextEditingController(text: 'AE-2048');
@override
void dispose() {
readOnlyCode.dispose();
disabledCode.dispose();
super.dispose();
}
Widget codeField(TextEditingController controller, bool enabled) {
return TextField(
controller: controller,
enabled: enabled,
readOnly: true,
enableInteractiveSelection: true,
decoration: InputDecoration(
labelText: enabled ? 'Chỉ đọc' : 'Vô hiệu hóa',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
tooltip: 'Thông tin mã',
icon: const Icon(Icons.info_outline),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Mã dùng để tra cứu đơn')),
);
},
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hai trạng thái TextField')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
codeField(readOnlyCode, true),
const SizedBox(height: 24),
codeField(disabledCode, false),
],
),
),
);
}
}
Cả hai trường đều đặt readOnly để ví dụ chỉ thay một biến so sánh: enabled. Trường đầu vẫn có cơ hội nhận tương tác chọn chữ và bấm nút; trường sau mô tả trạng thái vô hiệu hóa. Mã đơn là dữ liệu giả phục vụ giao diện, không gắn với API hoặc quyền truy cập đơn hàng.
Lập kế hoạch kiểm tra theo thao tác của người dùng
Thử nhập thêm ký tự vào trường đầu: nội dung không được thay đổi bởi thao tác nhập. Sau đó thử chọn phần mã và bấm nút thông tin; nút cần mở SnackBar. Ở trường thứ hai, kiểm tra cả tap vào nội dung lẫn nút suffix để xác nhận trạng thái vô hiệu hóa theo hợp đồng API.
Tiếp theo, chỉ đổi enableInteractiveSelection của trường đầu thành false. Kiểm tra lại việc chọn chữ và menu thao tác văn bản, đồng thời xác nhận nút thông tin vẫn đáp ứng yêu cầu màn hình. Đây là danh sách ca kiểm tra cần thực hiện trên môi trường của bạn, không phải kết quả thiết bị đã đo trong bài.
Đừng gộp trạng thái giao diện với dữ liệu
TextEditingController quản lý nội dung và cần được dispose khi không dùng nữa. Một trường readOnly vẫn có thể nhận thay đổi do code cập nhật controller. Vì vậy, ngăn thao tác nhập không phải là cách khóa dữ liệu ở tầng nghiệp vụ hoặc bảo vệ một API.
Trong sản phẩm, hãy tách câu hỏi “người dùng có thể gõ ở đây không?” khỏi “nguồn dữ liệu nào được phép cập nhật mã?”. Nếu mã thay đổi sau khi tải lại đơn, controller cần theo đúng luồng dữ liệu đã thiết kế. Ví dụ này chỉ giữ mã cố định để làm rõ tương tác UI.
Lỗi thường gặp và bước tiếp theo
- Đặt enabled false rồi mong suffixIcon hoạt động: xem lại yêu cầu và dùng readOnly khi phù hợp.
- Đặt readOnly true rồi giả định không còn thao tác chọn chữ: kiểm tra thêm thuộc tính selection.
- Tạo controller mới ở mỗi lần build: nội dung ví dụ dùng controller thuộc State và giải phóng trong dispose.
- Chỉ kiểm tra bằng chuột: bổ sung bàn phím, thao tác chọn chữ và trình đọc màn hình trên nền tảng mục tiêu.
Hãy giữ trường ở trạng thái enabled khi cần nút phụ hoạt động, rồi cấu hình riêng khả năng sửa và chọn nội dung. Cách tách này giúp yêu cầu UX được thể hiện trực tiếp trong props và dễ kiểm tra từng phần.

