
MediaQueryData: Thám Tử Màn Hình Giúp App Flutter Của Bạn "Biết Điều"
Chào các chiến thần code tương lai của anh Creyt! Hôm nay, chúng ta sẽ cùng "soi" một khái niệm nghe có vẻ hàn lâm nhưng lại cực kỳ thực chiến trong Flutter: MediaQueryData.
1. MediaQueryData là gì và để làm gì? (Theo hướng GenZ)
Nói một cách dễ hiểu, MediaQueryData giống như một "thám tử" chuyên nghiệp hoặc một "bản đồ thông minh" mà app của bạn dùng để biết chính xác nó đang "sống" trong môi trường nào. Em cứ hình dung thế này: bạn đang xây một căn nhà (app Flutter của bạn) và muốn nó phải đẹp, tiện nghi dù khách của bạn là "người khổng lồ" (máy tính bảng màn hình to đùng), "người tí hon" (điện thoại nhỏ xíu), hay "người thuận tay trái" (thiết bị xoay ngang).
MediaQueryData chính là cái "bản đồ" cung cấp tất tần tật thông tin về cái "lô đất" mà app của em đang chiếm dụng. Nó cho em biết:
- Kích thước màn hình: Chiều rộng (width), chiều cao (height) của toàn bộ màn hình.
- Hướng màn hình: Đang xoay dọc (portrait) hay xoay ngang (landscape)?
- Mật độ pixel: Màn hình này "sắc nét" cỡ nào? (ví dụ:
devicePixelRatio). - Padding của hệ thống: Các vùng mà UI của em không nên "chạm" vào, như tai thỏ (notch), thanh trạng thái (status bar) ở trên, hay thanh điều hướng ảo (navigation bar) ở dưới.
- Vân vân mây mây các thông tin khác về font scale, keyboard status...
Tóm lại: Nó là "bộ não" giúp app của em không bị "vỡ trận" hay "xấu ma chê quỷ hờn" khi chạy trên các thiết bị khác nhau. Muốn app "biết điều", tự động điều chỉnh giao diện cho phù hợp với mọi loại màn hình? Chính là nó chứ ai!
2. Code Ví Dụ Minh Họa Rõ Ràng
Giờ thì, lý thuyết suông hoài cũng chán, mình vào thực hành luôn cho "nóng"! Anh Creyt sẽ show cho em một ví dụ đơn giản để thấy MediaQueryData hoạt động như thế 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: 'Creyt\'s MediaQueryData Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
// Đây chính là lúc "thám tử" MediaQuery bắt đầu làm việc!
// Phương thức MediaQuery.of(context) sẽ trả về một đối tượng MediaQueryData
final mediaQueryData = MediaQuery.of(context);
// Lấy kích thước màn hình
final screenWidth = mediaQueryData.size.width;
final screenHeight = mediaQueryData.size.height;
// Lấy hướng xoay màn hình (ngang hay dọc)
final orientation = mediaQueryData.orientation;
// Lấy padding của hệ thống (ví dụ: tai thỏ, thanh trạng thái, thanh điều hướng)
final topPadding = mediaQueryData.padding.top;
final bottomPadding = mediaQueryData.padding.bottom;
return Scaffold(
appBar: AppBar(
title: const Text('Thám Tử Màn Hình: MediaQueryData'),
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Chào mừng đến với lớp của anh Creyt!',
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
// Hiển thị thông tin cơ bản
_buildInfoRow('Chiều rộng màn hình:', '${screenWidth.toStringAsFixed(2)} px'),
_buildInfoRow('Chiều cao màn hình:', '${screenHeight.toStringAsFixed(2)} px'),
_buildInfoRow('Hướng màn hình:', orientation == Orientation.portrait ? 'Dọc (Portrait)' : 'Ngang (Landscape)'),
_buildInfoRow('Padding trên (Status bar, Notch):', '${topPadding.toStringAsFixed(2)} px'),
_buildInfoRow('Padding dưới (Navigation bar):', '${bottomPadding.toStringAsFixed(2)} px'),
const SizedBox(height: 30),
// Một ví dụ nhỏ về responsive UI:
// Cái container này sẽ thay đổi màu và kích thước dựa vào hướng màn hình
Text(
'Thử xoay màn hình xem nào!',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 15),
Container(
width: orientation == Orientation.portrait ? screenWidth * 0.8 : screenWidth * 0.4,
height: orientation == Orientation.portrait ? screenHeight * 0.2 : screenHeight * 0.4,
color: orientation == Orientation.portrait ? Colors.deepPurpleAccent : Colors.teal,
alignment: Alignment.center,
child: Text(
'Anh Creyt đây!',
style: TextStyle(
color: Colors.white,
fontSize: orientation == Orientation.portrait ? 24 : 30,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 20),
Text(
'Container này "biết điều" lắm, nó tự điều chỉnh theo hướng màn hình đó em. Nó co giãn như con mèo vậy!',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value),
],
),
);
}
}
Trong ví dụ trên, anh Creyt đã sử dụng MediaQuery.of(context) để lấy các thông tin về màn hình và sau đó dùng chúng để:
- Hiển thị thông tin kích thước, hướng màn hình.
- Thay đổi kích thước và màu sắc của một
Containerdựa trên hướng màn hình (dọc hay ngang). Đây chính là cách đơn giản nhất để làm UI "responsive" đó em.

3. Mẹo (Best Practices) để ghi nhớ hoặc dùng thực tế
Để trở thành một dev "xịn xò", em cần biết vài mẹo vặt này:
- Đừng lạm dụng
MediaQuery.of(context)quá mức: Mỗi khiMediaQueryDatathay đổi (ví dụ: người dùng xoay màn hình), widget của bạn sẽ được build lại. Nếu gọi quá nhiều chỗ không cần thiết có thể ảnh hưởng đến hiệu năng. Hãy chỉ gọi ở những widget thực sự cần thông tin responsive thôi nhé. - Sử dụng
LayoutBuildercho responsive cục bộ: Nếu em chỉ muốn một phần nhỏ của UI responsive với kích thước của widget cha (chứ không phải toàn bộ màn hình), hãy dùngLayoutBuilder. Nó cung cấpBoxConstraintscủa widget cha, chính xác và hiệu quả hơn cho các trường hợp cụ thể. - Cẩn thận với
padding: Luôn nhớ dùngmediaQueryData.paddingđể xử lý các vùng an toàn như tai thỏ (notch), thanh trạng thái (status bar) hoặc thanh điều hướng ảo (navigation bar) trên Android. Nếu không, UI của em có thể bị che mất hoặc trông "lệch pha" lắm. - Theme và Constants: Thay vì hardcode các giá trị responsive (ví dụ:
screenWidth * 0.8), hãy định nghĩa các hằng số hoặc sử dụng theme để dễ quản lý và nhất quán hơn trong toàn bộ app.
4. Ví dụ thực tế các ứng dụng/website đã ứng dụng
Em có biết không, hầu hết các ứng dụng di động và website "xịn sò" ngày nay đều phải responsive. MediaQueryData (hoặc các cơ chế tương tự trong web) là xương sống của điều đó:
- TikTok, Instagram, Facebook: Em để ý khi xoay ngang điện thoại hoặc dùng trên máy tính bảng, giao diện của chúng sẽ tự động điều chỉnh không? Ảnh, video, comment đều phải hiển thị đẹp và tối ưu không gian.
- Các ứng dụng đọc sách/báo (ví dụ: Kindle, Google News): Tùy chỉnh kích thước font, số cột văn bản, hay cách hiển thị hình ảnh khi em xoay màn hình hoặc đổi từ điện thoại sang tablet.
- Ecommerce Apps (Shopee, Lazada): Trên điện thoại, danh sách sản phẩm thường hiển thị dạng lưới 2 cột. Nhưng trên tablet, chúng có thể hiển thị 3-4 cột để tận dụng không gian màn hình lớn hơn, giúp người dùng xem được nhiều sản phẩm cùng lúc.
5. Thử nghiệm đã từng và hướng dẫn nên dùng cho case nào
Anh Creyt đã từng "đau đầu" với việc làm một cái dashboard quản lý kho hàng. Trên điện thoại thì cần hiển thị dạng list gọn gàng, chỉ show những thông tin chính. Nhưng trên tablet lại muốn hiển thị dạng bảng chi tiết với nhiều cột hơn, có cả biểu đồ thống kê. MediaQueryData chính là "vị cứu tinh" giúp anh dễ dàng chuyển đổi layout giữa hai kịch bản này chỉ với vài dòng code kiểm tra screenWidth và orientation.
Nên dùng MediaQueryData khi nào?
- Điều chỉnh layout tổng thể: Khi em muốn toàn bộ giao diện app thay đổi đáng kể dựa trên kích thước hoặc hướng màn hình (ví dụ: từ layout 1 cột sang 2 cột, hoặc thay đổi vị trí các thành phần chính).
- Xử lý các vùng an toàn (Safe Area): Đảm bảo UI của em không bị che bởi tai thỏ, thanh trạng thái, hoặc các phím điều hướng ảo. Đây là một trong những ứng dụng quan trọng nhất của
mediaQueryData.padding. - Thay đổi kích thước chữ, hình ảnh, hoặc khoảng cách: Để tối ưu trải nghiệm đọc/xem trên các màn hình khác nhau, tránh chữ quá to trên điện thoại nhỏ hoặc quá bé trên tablet lớn.
- Phân biệt giữa điện thoại và máy tính bảng: Để cung cấp các tính năng hoặc luồng người dùng khác nhau cho từng loại thiết bị, tối ưu hóa trải nghiệm cho từng "hệ sinh thái" màn hình.
Hy vọng qua bài này, em đã nắm rõ được MediaQueryData là gì, làm được gì và dùng nó như thế nào để app của mình "biết điều" hơn trên mọi thiết bị. Hãy thực hành thật nhiều để biến kiến thức thành kỹ năng nhé! Chúc em code cháy máy!
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é!