Một bottom sheet trông vừa màn hình khi mở, nhưng nút đóng hoặc ô nhập lại bị bàn phím che. Thay vì thêm một khoảng trống cố định theo chiếc điện thoại đang dùng, hãy đọc phần giao diện bị che từ MediaQuery và cho nội dung một đường cuộn khi không còn đủ chỗ.
Bài này dựng form ghi chú ngắn để bạn thử trên dự án Flutter. Mục tiêu là phân biệt quyền sử dụng chiều cao của sheet, khoảng chừa cho bàn phím và vùng cuộn. Ba phần này giải quyết những vấn đề khác nhau.
Điều kiện và giới hạn kiểm chứng
Đối chiếu API Flutter hiện hành ngày 11/11/2026: showModalBottomSheet, MediaQueryData.viewInsets và SingleChildScrollView. Ví dụ đã được rà theo hợp đồng API, nhưng chưa chạy Flutter SDK, analyzer, emulator, thiết bị hoặc kiểm thử trợ năng. Không có kết quả đo bàn phím hay animation được tuyên bố trong bài.
Hãy đặt code vào lib/main.dart của một dự án Flutter tương thích với các API được dẫn. Kiểm tra phiên bản SDK của dự án khi áp dụng; bài không khẳng định mọi dự án Flutter cũ đều hỗ trợ cùng một tập tham số.
1. Một màn hình và một sheet nhỏ
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: NotePage()));
class NotePage extends StatelessWidget {
const NotePage({super.key});
void _openSheet(BuildContext context) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (sheetContext) {
final keyboard = MediaQuery.of(sheetContext).viewInsets.bottom;
return Padding(
padding: EdgeInsets.only(bottom: keyboard),
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Ghi chú thử nghiệm'),
const SizedBox(height: 12),
const TextField(
minLines: 2,
maxLines: 4,
decoration: InputDecoration(labelText: 'Nội dung'),
),
const SizedBox(height: 12),
FilledButton(
onPressed: () => Navigator.of(sheetContext).pop(),
child: const Text('Đóng'),
),
],
),
),
);
},
);
}
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Bottom sheet demo')),
body: Center(
child: FilledButton(
onPressed: () => _openSheet(context),
child: const Text('Mở ghi chú'),
),
),
);
}
Nút Đóng chỉ đóng ví dụ, không lưu dữ liệu. Không có request, controller hoặc trạng thái ngoài sheet. Nhờ vậy, khi một vùng bị che, bạn có thể tập trung quan sát layout thay vì đồng thời debug việc gửi form.
2. Đọc ba phần của layout
isScrollControlled cho phép route của sheet sử dụng cơ chế chiều cao phù hợp với nội dung có thể cuộn. Nó không phải lệnh tự cộng khoảng cách bằng chiều cao bàn phím vào form. Trong ví dụ, việc chừa phần dưới được thể hiện riêng bằng Padding.
viewInsets.bottom mô tả phần dưới bị system UI che, thường là bàn phím. Code đọc từ sheetContext trong builder thay vì lấy một số cố định khi màn hình đầu tiên được tạo. Khi dữ liệu MediaQuery thay đổi, phần phụ thuộc có thể được build lại theo inset mới.
SingleChildScrollView tạo đường cuộn cho form ngắn khi không gian giảm. Column dùng kích thước chính tối thiểu; không đặt Expanded bên trong vùng cuộn dọc không có giới hạn chiều cao theo cách tùy tiện. Ví dụ chỉ có vài control, không dùng cấu trúc này để dựng hàng nghìn item.
3. Tự kiểm tra trên thiết bị đích
Mở sheet khi bàn phím chưa hiện, chạm ô nhập rồi kiểm tra có cuộn tới nút Đóng được không. Gõ đủ bốn dòng, xoay màn hình và tăng kích thước chữ. Sau đó đóng bàn phím và xem khoảng trống bên dưới có trở về phù hợp không.
Đây là kế hoạch kiểm tra, chưa phải các kết quả đã quan sát. Nên lưu loại thiết bị, phiên bản hệ điều hành, chế độ bàn phím và SDK khi ghi lỗi. Bàn phím nổi hoặc bàn phím ngoài có thể tạo tình huống khác với bàn phím phần mềm gắn dưới màn hình.
Lỗi thường gặp
Thêm một Padding cố định có thể vừa với máy hiện tại nhưng thừa hoặc thiếu ở máy khác. Cộng inset ở nhiều lớp có thể đẩy nội dung lên quá xa. Khi app đã có lớp xử lý bàn phím riêng, hãy kiểm tra tổng khoảng cách trước khi chép thêm đoạn này.
useSafeArea của modal sheet xử lý các vùng xâm lấn theo hợp đồng riêng; không nên coi nó là đồng nghĩa với viewInsets. Ví dụ không đảm bảo toàn bộ layout hệ thống, focus, bàn phím mọi hãng hoặc trợ năng. Hãy kiểm thử các tình huống sản phẩm thực sự hỗ trợ.
Kết luận
Với form ngắn trong bottom sheet, tách rõ chiều cao route, khoảng chừa theo inset và khả năng cuộn. Dùng ví dụ tối thiểu để xác minh trên thiết bị trước khi ghép validation hoặc lưu dữ liệu.

