Как создать адаптивный (responsive) интерфейс во Flutter?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать адаптивный (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 приложения.