Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
MediaQuery в Flutter: управление размерами и адаптивностью
MediaQuery — это один из самых важных инструментов для создания адаптивных приложений в Flutter. Это ваш доступ к информации о device (устройстве) и его текущем состоянии.
Что такое MediaQuery
MediaQuery — это InheritedWidget, предоставляющий информацию о медиа-окружении (размер экрана, ориентация, плотность пиксели, инсеты и т.д.). Это allows вам создавать UI, который адаптируется к разным размерам экранов и конфигурациям.
// Базовое использование
var mediaQuery = MediaQuery.of(context);
var screenSize = mediaQuery.size; // Size(400, 800)
var isPortrait = mediaQuery.orientation == Orientation.portrait;
var devicePixelRatio = mediaQuery.devicePixelRatio; // 2.0 на Retina
Основные свойства MediaQuery
1. Размер экрана (Size)
var size = MediaQuery.of(context).size;
var width = size.width; // ширина в logical pixels
var height = size.height; // высота в logical pixels
// Логические пиксели vs физические пиксели:
// На iPhone 12 (Retina 2x):
// - логические пиксели: 390
// - физические пиксели: 780 (логические * devicePixelRatio)
2. Ориентация (Orientation)
var orientation = MediaQuery.of(context).orientation;
if (orientation == Orientation.portrait) {
// Вертикальная ориентация
} else {
// Горизонтальная ориентация
}
// Практический пример
widget() {
return MediaQuery.of(context).orientation == Orientation.portrait
? _buildPortrait(context)
: _buildLandscape(context);
}
3. Device Pixel Ratio
var dpr = MediaQuery.of(context).devicePixelRatio;
// Например, на iPhone 12:
// dpr = 2.0 (Retina) или 3.0 (Pro Max)
// На Pixel 6:
// dpr = 2.75
// На обычном Android:
// dpr = 1.0
// Используется для загрузки изображений нужного качества
Image.network(
'https://example.com/image@${dpr.toStringAsFixed(0)}x.png',
)
4. Insets (Padding для система UI элементов)
var viewInsets = MediaQuery.of(context).viewInsets;
var bottom = viewInsets.bottom; // высота клавиатуры
var top = viewInsets.top; // высота status bar или notch
// Пример: отодвинуть виджет выше клавиатуры
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: TextField(...),
)
5. Safe Area (Insets для notch'а и safe zones)
var padding = MediaQuery.of(context).padding;
var top = padding.top; // отступ для status bar / notch
var bottom = padding.bottom; // отступ для home indicator
var left = padding.left; // отступ для side notch
var right = padding.right; // отступ для side notch
// или используйте встроенный SafeArea widget
SafeArea(
child: Scaffold(
body: Center(child: Text('Safe area')),
),
)
Практические примеры
Адаптивный layout для разных размеров
class ResponsiveLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
// Телефон: < 600
// Tablet: 600-900
// Desktop: > 900
if (width < 600) {
return _buildPhoneLayout();
} else if (width < 900) {
return _buildTabletLayout();
} else {
return _buildDesktopLayout();
}
}
Widget _buildPhoneLayout() {
return Column(
children: [
_header(),
_content(),
_footer(),
],
);
}
Widget _buildTabletLayout() {
return Row(
children: [
Expanded(flex: 1, child: _sidebar()),
Expanded(flex: 2, child: _content()),
],
);
}
Widget _buildDesktopLayout() {
return Row(
children: [
Expanded(flex: 1, child: _header()),
Expanded(flex: 2, child: _content()),
Expanded(flex: 1, child: _footer()),
],
);
}
// widgets...
}
Адаптивные размеры шрифтов
class ResponsiveText extends StatelessWidget {
final String text;
final TextStyle? style;
const ResponsiveText(this.text, {this.style});
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
// Динамически определяем размер шрифта
double fontSize;
if (width < 600) {
fontSize = 14; // phone
} else if (width < 900) {
fontSize = 16; // tablet
} else {
fontSize = 18; // desktop
}
return Text(
text,
style: (style ?? TextStyle()).copyWith(fontSize: fontSize),
);
}
}
// Использование
ResponsiveText('Hello', style: TextStyle(fontWeight: FontWeight.bold))
Обработка клавиатуры
class FloatingTextField extends StatefulWidget {
@override
State<FloatingTextField> createState() => _FloatingTextFieldState();
}
class _FloatingTextFieldState extends State<FloatingTextField> {
late FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
}
@override
Widget build(BuildContext context) {
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
return Scaffold(
body: Stack(
children: [
// основной контент
SingleChildScrollView(
child: Column(
children: List.generate(
20,
(i) => ListTile(title: Text('Item $i')),
),
),
),
// Text field который плывёт над клавиатурой
Positioned(
bottom: keyboardHeight,
left: 0,
right: 0,
child: Container(
color: Colors.white,
padding: EdgeInsets.all(16),
child: TextField(
focusNode: _focusNode,
decoration: InputDecoration(
hintText: 'Type something...',
border: OutlineInputBorder(),
),
),
),
),
],
),
);
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
}
Определение типа устройства
enum DeviceType { phone, tablet, desktop }
DeviceType getDeviceType(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
return DeviceType.phone;
} else if (width < 900) {
return DeviceType.tablet;
} else {
return DeviceType.desktop;
}
}
// Использование
final deviceType = getDeviceType(context);
switch (deviceType) {
case DeviceType.phone:
return _buildPhoneUI();
case DeviceType.tablet:
return _buildTabletUI();
case DeviceType.desktop:
return _buildDesktopUI();
}
Адаптивные отступы
class ResponsivePadding extends StatelessWidget {
final Widget child;
const ResponsivePadding({required this.child});
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
// На телефоне маленькие отступы, на планшете — большие
final padding = width < 600 ? 16.0 : 32.0;
return Padding(
padding: EdgeInsets.all(padding),
child: child,
);
}
}
Обработка notch и safe area
class NotchAwareWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final hasNotch = mediaQuery.padding.top > 24; // iPhone X и новее
return Column(
children: [
// AppBar с дополнительным padding если есть notch
Container(
height: 56 + (hasNotch ? 16 : 0),
color: Colors.blue,
child: Align(
alignment: Alignment.bottomCenter,
child: Text('App Bar'),
),
),
Expanded(
child: ListView(
children: [...],
),
),
],
);
}
}
Создание helper функций для MediaQuery
// lib/utils/media_query_helper.dart
extension MediaQueryExtension on BuildContext {
/// Ширина экрана
double get screenWidth => MediaQuery.of(this).size.width;
/// Высота экрана
double get screenHeight => MediaQuery.of(this).size.height;
/// Высота клавиатуры
double get keyboardHeight => MediaQuery.of(this).viewInsets.bottom;
/// Плотность пикселей
double get pixelRatio => MediaQuery.of(this).devicePixelRatio;
/// Ориентация
bool get isPortrait => MediaQuery.of(this).orientation == Orientation.portrait;
bool get isLandscape => MediaQuery.of(this).orientation == Orientation.landscape;
/// Тип устройства
bool get isPhone => screenWidth < 600;
bool get isTablet => screenWidth >= 600 && screenWidth < 900;
bool get isDesktop => screenWidth >= 900;
/// Относительные размеры
double percentWidth(double percent) => screenWidth * (percent / 100);
double percentHeight(double percent) => screenHeight * (percent / 100);
}
// Использование
Widget build(BuildContext context) {
return Container(
width: context.percentWidth(50), // 50% от ширины экрана
padding: EdgeInsets.all(context.isPhone ? 16 : 32),
child: Text('Responsive!'),
);
}
MediaQuery vs LayoutBuilder
// MediaQuery — получить информацию о device
var size = MediaQuery.of(context).size;
// LayoutBuilder — получить информацию о доступном space
LayoutBuilder(
builder: (context, constraints) {
return Container(
width: constraints.maxWidth, // доступная ширина
height: constraints.maxHeight, // доступная высота
);
},
)
// Обычно используют LayoutBuilder для более точной адаптивности
Оптимизация производительности
// ❌ Плохо — вызывает MediaQuery каждый раз при rebuild
Widget build(BuildContext context) {
var width = MediaQuery.of(context).size.width; // expensive
return ...
}
// ✅ Лучше — используйте в initState или как parameter
var width = MediaQuery.of(context).size.width;
var isPhone = width < 600;
// ✅ Ещё лучше — используйте extension методы
if (context.isPhone) { ... }
// ✅ Best — используйте LayoutBuilder если возможно
LayoutBuilder(
builder: (context, constraints) {
return constraints.maxWidth < 600 ? ... : ...;
},
)
Выводы
MediaQuery — это критически важный инструмент для:
- Создания адаптивных интерфейсов
- Обработки разных размеров экранов (phone, tablet, desktop)
- Адаптации к клавиатуре и notch'ам
- Выбора правильных размеров шрифтов и отступов
- Оптимизации UI для каждого типа устройства
Комбинируя MediaQuery с LayoutBuilder и создав набор helper расширений, вы сможете создавать адаптивные приложения, которые отлично работают на всех устройствах.