← Назад к вопросам

Что такое MediaQuery?

1.0 Junior🔥 201 комментариев
#Flutter виджеты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 расширений, вы сможете создавать адаптивные приложения, которые отлично работают на всех устройствах.