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

Как создать адаптивный (responsive) интерфейс во Flutter?

2.2 Middle🔥 201 комментариев
#Flutter виджеты#Архитектура Flutter

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

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

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

Как создать адаптивный (responsive) интерфейс во Flutter?

Адаптивный интерфейс во Flutter создаётся путём реагирования на размер экрана, ориентацию устройства и параметры платформы. Существует несколько подходов: от простого использования MediaQuery до сложных решений с LayoutBuilder.

1. Использование MediaQuery (базовый подход)

import "package:flutter/material.dart";

class ResponsiveExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Получаем размеры экрана
    final screenWidth = MediaQuery.of(context).size.width;
    final screenHeight = MediaQuery.of(context).size.height;
    final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
    
    return Column(
      children: [
        // Адаптируем размер текста
        Text(
          "Responsive Text",
          style: TextStyle(
            fontSize: screenWidth > 600 ? 28 : 16,
          ),
        ),
        // Адаптируем отступы
        SizedBox(height: isPortrait ? 16 : 8),
        // Условно показываем элементы
        if (screenWidth > 900)
          Row(
            children: [
              Expanded(child: SideMenu()),
              Expanded(flex: 3, child: MainContent()),
            ],
          )
        else
          Column(
            children: [
              SideMenu(),
              MainContent(),
            ],
          ),
      ],
    );
  }
}

2. LayoutBuilder для более гибкого контроля

LayoutBuilder предоставляет constraints контейнера, что удобнее чем MediaQuery:

class ResponsiveLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // maxWidth доступного пространства
        if (constraints.maxWidth < 600) {
          return MobileLayout();
        } else if (constraints.maxWidth < 1200) {
          return TabletLayout();
        } else {
          return DesktopLayout();
        }
      },
    );
  }
}

class MobileLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AppBar(title: Text("Mobile")),
        Expanded(child: ListView()),
      ],
    );
  }
}

class TabletLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          flex: 1,
          child: SideBar(),
        ),
        Expanded(
          flex: 3,
          child: MainContent(),
        ),
      ],
    );
  }
}

class DesktopLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        SideBar(),
        Expanded(child: MainContent()),
        RightPanel(),
      ],
    );
  }
}

3. Utility класс для breakpoints

Для удобства создайте класс с breakpoints:

class ResponsiveHelper {
  // Определяем breakpoints
  static const double mobileMaxWidth = 600;
  static const double tabletMaxWidth = 1200;
  
  static bool isMobile(BuildContext context) {
    return MediaQuery.of(context).size.width < mobileMaxWidth;
  }
  
  static bool isTablet(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    return width >= mobileMaxWidth && width < tabletMaxWidth;
  }
  
  static bool isDesktop(BuildContext context) {
    return MediaQuery.of(context).size.width >= tabletMaxWidth;
  }
  
  static bool isPortrait(BuildContext context) {
    return MediaQuery.of(context).orientation == Orientation.portrait;
  }
  
  static bool isLandscape(BuildContext context) {
    return MediaQuery.of(context).orientation == Orientation.landscape;
  }
}

// Использование
class MyScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("My App")),
      body: ResponsiveHelper.isDesktop(context)
          ? DesktopLayout()
          : ResponsiveHelper.isTablet(context)
              ? TabletLayout()
              : MobileLayout(),
    );
  }
}

4. Адаптивные размеры и отступы

class AdaptiveSizes {
  // Получаем размер в зависимости от контекста
  static double getHorizontalPadding(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return 16;
    if (width < 1200) return 24;
    return 32;
  }
  
  static double getColumnWidth(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return width;
    if (width < 1200) return width * 0.5;
    return width * 0.33;
  }
  
  static double getFontSize(BuildContext context, double baseSilze) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return baseSize * 0.9;
    if (width < 1200) return baseSize;
    return baseSize * 1.2;
  }
}

// Использование
class AdaptiveText extends StatelessWidget {
  final String text;
  
  const AdaptiveText(this.text);
  
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.symmetric(
        horizontal: AdaptiveSizes.getHorizontalPadding(context),
      ),
      child: Text(
        text,
        style: TextStyle(
          fontSize: AdaptiveSizes.getFontSize(context, 16),
        ),
      ),
    );
  }
}

5. Практический пример: Адаптивная сетка (Grid)

class ResponsiveGrid extends StatelessWidget {
  final List<Widget> items;
  
  const ResponsiveGrid({required this.items});
  
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // Определяем количество колонок
        late int crossAxisCount;
        if (constraints.maxWidth < 600) {
          crossAxisCount = 1;  // Мобайл
        } else if (constraints.maxWidth < 1200) {
          crossAxisCount = 2;  // Планшет
        } else {
          crossAxisCount = 3;  // Десктоп
        }
        
        return GridView.count(
          crossAxisCount: crossAxisCount,
          childAspectRatio: constraints.maxWidth > 1200 ? 0.8 : 1.0,
          children: items,
        );
      },
    );
  }
}

6. SafeArea для безопасных границ

class ResponsiveScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Safe Area")),
      body: SafeArea(
        // Защищает от вырезов и системных элементов
        child: Padding(
          padding: EdgeInsets.all(
            MediaQuery.of(context).size.width > 600 ? 32 : 16,
          ),
          child: Column(
            children: [
              Text("Content"),
            ],
          ),
        ),
      ),
    );
  }
}

7. Ориентация экрана (Orientation Change)

class OrientationExample extends StatefulWidget {
  @override
  State<OrientationExample> createState() => _OrientationExampleState();
}

class _OrientationExampleState extends State<OrientationExample> {
  @override
  Widget build(BuildContext context) {
    final orientation = MediaQuery.of(context).orientation;
    
    return Scaffold(
      body: orientation == Orientation.portrait
          ? Column(
              children: [
                Image.asset("assets/image.png"),
                Expanded(child: Content()),
              ],
            )
          : Row(
              children: [
                Expanded(
                  child: Image.asset("assets/image.png"),
                ),
                Expanded(
                  child: Content(),
                ),
              ],
            ),
    );
  }
}

Best Practices

1. Используйте LayoutBuilder вместо MediaQuery в виджетах

// ✓ Лучше - работает при любом размере родителя
LayoutBuilder(builder: (context, constraints) { })

// ✗ Может не работать во вложенных контейнерах
MediaQuery.of(context).size.width

2. Определите clear breakpoints

const mobileBreakpoint = 600;
const tabletBreakpoint = 1200;

3. Тестируйте на разных размерах

flutter run -d chrome --web-port=8080
# Меняйте размер окна браузера

4. Используйте Expanded и Flexible для гибкого расположения

Row(
  children: [
    Expanded(child: LeftPanel()),    // Занимает оставшееся место
    Flexible(flex: 2, child: Main()), // Управляемое расширение
  ],
)

Адаптивный дизайн — критически важен для хорошего пользовательского опыта на разных устройствах. Сочетание MediaQuery, LayoutBuilder и продуманных breakpoints позволяет создавать действительно responsive приложения.

Как создать адаптивный (responsive) интерфейс во Flutter? | PrepBro