Что такое адаптивная и отзывчивая вёрстка в Flutter и как её реализовать?

«Что такое адаптивная и отзывчивая вёрстка в Flutter и как её реализовать?» — вопрос из категории UI и вёрстка, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Flutter адаптивная и отзывчивая вёрстка — это подход к проектированию пользовательского интерфейса, при котором приложение корректно отображается и удобно используется на устройствах с разными размерами экранов (от маленького телефона до большого планшета или десктопа) и ориентациями.

Основные инструменты и техники:

  1. MediaQuery — для получения характеристик экрана.

    final mediaData = MediaQuery.of(context);
    final screenWidth = mediaData.size.width;
    final screenHeight = mediaData.size.height;
    final pixelRatio = mediaData.devicePixelRatio;
    final isPortrait = screenHeight > screenWidth;
  2. LayoutBuilder — для реагирования на размеры родительского виджета. Более предпочтителен, чем MediaQuery, когда виджет должен адаптироваться к доступному пространству, а не ко всему экрану.

    LayoutBuilder(
      builder: (context, constraints) {
        // constraints.maxWidth — максимальная доступная ширина
        if (constraints.maxWidth > 600) {
          return _buildWideLayout(); // Макет для планшета/десктопа
        } else {
          return _buildNarrowLayout(); // Макет для телефона
        }
      },
    )
  3. Использование гибких виджетов (Flexible, Expanded) внутри Row и Column.

    Row(
      children: [
        Expanded( // Занимает всё оставшееся пространство
          flex: 2,
          child: Container(color: Colors.red),
        ),
        Expanded(
          flex: 1,
          child: Container(color: Colors.blue),
        ),
      ],
    )
  4. OrientationBuilder — для реакции на изменение ориентации.

    OrientationBuilder(
      builder: (context, orientation) {
        return orientation == Orientation.portrait
            ? _buildPortraitScaffold()
            : _buildLandscapeScaffold();
      },
    )
  5. Определение брейкпоинтов (breakpoints) для разных категорий устройств.

    class Breakpoints {
      static const double mobile = 600; // До 600px — телефон
      static const double tablet = 900; // 600-900px — планшет
      // >900px — десктоп
    }
    
    // Использование
    if (screenWidth < Breakpoints.mobile) {
      // Мобильный UI
    } else if (screenWidth < Breakpoints.tablet) {
      // Планшетный UI
    } else {
      // Десктопный UI
    }

Практический пример: адаптивный список деталей (master-detail):

class AdaptiveDetailPage extends StatelessWidget {
  final Widget masterView;
  final Widget detailView;

  const AdaptiveDetailPage({
    super.key,
    required this.masterView,
    required this.detailView,
  });

  @override
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;

    // На широком экране показываем master и detail рядом
    if (screenWidth > 600) {
      return Row(
        children: [
          SizedBox(width: 300, child: masterView),
          Expanded(child: detailView),
        ],
      );
    } 
    // На узком экране используем навигацию между экранами
    else {
      return masterView; // При тапе на элемент открываем detailView на новом экране
    }
  }
}

Ключевые принципы:

  • Относительные единицы: Использовать FractionallySizedBox, проценты, flex вместо фиксированных пикселей.
  • Тестирование: Обязательно тестировать UI на эмуляторах/устройствах с разными диагоналями и плотностями пикселей.
  • Содержание прежде всего: Макет должен адаптироваться, чтобы контент оставался читаемым и доступным.