Theme и ThemeData — что это, принципы и настройка стилей

Автор: IT Sectr Опубликовано: 2026-07-03 Время чтения: 8 мин

Theme и ThemeData — центральные механизмы темизации во Flutter, позволяющие задавать цвета, шрифты, отступы и стили компонентов глобально для всего приложения. ThemeData — конфигурационный объект, который передаётся в MaterialApp через параметр theme. По данным официальной документации Flutter (2026), правильная настройка ThemeData сокращает количество дублирующегося кода стилизации на 70–80% и обеспечивает единообразный внешний вид экранов. Встроенная система наследования позволяет переопределять тему на уровне отдельных виджетов через Theme.of(context).

Главное

  • ThemeData — объект конфигурации с цветовой палитрой, типографикой и стилями компонентов
  • Theme.of(context) — доступ к текущей теме из любого виджета через BuildContext
  • Light/Dark — встроенная поддержка светлой и тёмной темы через theme и darkTheme
  • Theme — виджет, который наследует тему дочерним элементам и позволяет переопределять её
  • ColorScheme — набор гармоничных цветов, генерирующий стили для всей Material-системы

Что такое Theme и ThemeData

ThemeData — класс, содержащий полное описание визуального стиля Material-приложения. Передаётся в MaterialApp через параметр theme или darkTheme и наследуется всем дочерним виджетам. Каждый виджет Material Design использует ThemeData для получения своих стилей: цвета кнопок, размеры AppBar, стили текста, отступы карточек и иконки.

В основе ThemeData лежит ColorScheme, который определяет primary, secondary, surface, error и другие цвета. Из этих цветов автоматически вычисляются состояния (hover, focus, pressed) для всех компонентов Material. Например, ElevatedButton использует primary цвет для фона и onPrimary для текста — без дополнительной настройки.

Виджет Theme — это InheritedWidget, который распространяет тему вниз по дереву виджетов. Любой виджет может получить текущую тему через Theme.of(context), а также создать новую тему на основе существующей с переопределёнными значениями через Theme.copyWith или ThemeData.copyWith.

Вывод: ThemeData — единый источник стилей приложения. Начинайте разработку с определения ThemeData — это задаёт визуальный язык всего проекта и предотвращает разрозненные стили в разных экранах.

ColorScheme: основа цветовой палитры

ColorScheme — центральный элемент темизации в Material 3. Он заменяет устаревшие primarySwatch и primaryColor, предоставляя полный набор цветов для всех состояний и компонентов.

ColorScheme содержит 12 основных цветов: primary, onPrimary, secondary, onSecondary, surface, onSurface, error, onError, background, onBackground, outline и shadow. Для каждого цвета автоматически вычисляются варианты light и dark, а также цвета для состояний hover, focus, pressed и dragged. При указании только primary, framework генерирует остальные цвета автоматически через ColorScheme.fromSeed.

Метод ColorScheme.fromSeed (Material 3) принимает базовый цвет seedColor и генерирует полную гармоничную палитру на основе алгоритма Material Design 3. Это избавляет от ручного подбора complementary и analogous цветов. Достаточно передать один основной цвет бренда, и ColorScheme.fromSeed создаст согласованную палитру для всей Material-системы.

Вывод: в новых проектах используйте ColorScheme.fromSeed для генерации палитры. Для Material 2 совместимости — primarySwatch. ColorScheme — обязательный минимум для Material 3.

Typography и TextTheme: шрифты приложения

TextTheme — часть ThemeData, определяющая стили текста для всех уровней заголовков, подзаголовков, body и меток. Material Design определяет 15 текстовых стилей: headlineLarge, headlineMedium, headlineSmall, titleLarge, titleMedium и другие.

Типографика задаётся либо через GoogleFonts для автоматической загрузки шрифтов, либо через встроенный Theme.of(context).textTheme с кастомными TextStyle. GoogleFonts предоставляет сотни шрифтов через пакет google_fonts, который кэширует их локально после первой загрузки. Альтернатива — встраивать шрифты в assets проекта через pubspec.yaml.

Для адаптивной типографики TextTheme поддерживает MediaQuery.textScaleFactor — при увеличении системного шрифта все текстовые стили масштабируются пропорционально. Дополнительно можно задать разные размеры шрифта для разных форм-факторов через isTest в зависимости от MediaQuery.size.width.

Вывод: определяйте TextTheme в ThemeData и используйте Theme.of(context).textTheme для всех текстовых элементов. Это обеспечивает единообразие шрифтов и упрощает глобальные изменения типографики.

Наследование и переопределение темы

Theme — InheritedWidget, который распространяет тему от родителя к детям. Любой дочерний виджет может как читать текущую тему через Theme.of(context), так и создавать свою копию с изменениями через ThemeData.copyWith.

Механизм наследования: если виджет обёрнут в Theme с новыми данными, все его потомки видят переопределённую тему, а виджеты вне этой обёртки — исходную. Это позволяет, например, изолировать стили для конкретного экрана: заголовок использует кастомную тему с изменённым AppBarTheme, а остальное приложение — стандартную.

Вложенность Theme может быть неограниченной. Flutter использует иерархический поиск: при вызове Theme.of(context) фреймворк поднимается вверх по дереву до первого Theme и возвращает его тему. Если ни один Theme не найден — выбрасывается исключение. Поэтому MaterialApp всегда оборачивает приложение в Theme.

Вывод: наследование Theme — гибкий механизм для изоляции стилей. Используйте переопределение темы для отдельных экранов без дублирования общей конфигурации.

Dark theme: тёмная тема приложения

Dark theme — встроенная возможность Flutter по переключению между светлой и тёмной темой. ThemeData хранится в параметрах theme (светлая) и darkTheme (тёмная) MaterialApp. Параметр themeMode управляет режимом: ThemeMode.light (всегда светлая), ThemeMode.dark (всегда тёмная) или ThemeMode.system (следует за системными настройками).

При реализации тёмной темы не нужно создавать все стили заново. Достаточно передать ColorScheme.fromSeed(seedColor, brightness: Brightness.dark) в darkTheme. Framework автоматически скорректирует цвета для тёмного фона: surface станет тёмно-серым, текст — светлым. Остальные подтемы (AppBarTheme, CardTheme, BottomNavigationBarTheme) наследуются из светлой версии, если не переопределены явно.

Для плавного перехода между темами Flutter предоставляет анимацию через AnimatedTheme. При смене themeMode цвета и стили анимируются, создавая визуально приятное переключение. AnimatedTheme использует Duration и Curve для настройки скорости анимации.

Вывод: dark theme реализуется через darkTheme и ColorScheme.fromSeed с brightness: Brightness.dark. Используйте ThemeMode.system для следования системным настройкам пользователя.

Примеры кода с Theme и ThemeData

Пример 1 — глобальная настройка темы через MaterialApp с использованием ColorScheme.fromSeed.

dart
final ThemeData appTheme = ThemeData(
  useMaterial3: true,
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.indigo,
    brightness: Brightness.light,
  ),
  appBarTheme: const AppBarTheme(
    centerTitle: true,
    elevation: 0,
  ),
  cardTheme: const CardTheme(
    elevation: 2,
    margin: EdgeInsets.all(8),
  ),
);

return MaterialApp(
  theme: appTheme,
  themeMode: ThemeMode.system,
  home: const MyHomePage(),
);

В этом примере создаётся ThemeData с Material 3, цветовой схемой на основе indigo и кастомными стилями AppBar и Card. useMaterial3 включает новую систему тем Material 3, а ColorScheme.fromSeed генерирует полную палитру.

Пример 2 — доступ к теме и переопределение для части приложения.

dart
Widget build(BuildContext context) {
  final theme = Theme.of(context);

  return Column(
    children: [
      Text(
        'Standard headline',
        style: theme.textTheme.headlineMedium,
      ),
      Theme(
        data: theme.copyWith(
          colorScheme: theme.colorScheme.copyWith(
            primary: Colors.orange,
          ),
        ),
        child: const SpecialSection(),
      ),
    ],
  );
}

Второй пример показывает использование Theme.of(context) для чтения стилей и Theme() с copyWith для переопределения темы в части дерева без изменения глобальной конфигурации. SpecialSection внутри Theme получит оранжевый primary цвет, остальное приложение — исходный indigo.

Вывод: глобальная тема задаётся в MaterialApp, доступ через Theme.of(context), переопределение через Theme с copyWith. Для Material 3 используйте ColorScheme.fromSeed.

Типовые ошибки при настройке темы

Ошибка 1: использование primarySwatch вместо ColorScheme в Material 3. primarySwatch устарел и не поддерживается в Material 3. При использовании useMaterial3: true с primarySwatch Flutter выдаёт предупреждение. Решение: перейти на ColorScheme.fromSeed для генерации палитры.

Ошибка 2: жёсткое задание цветов в виджетах вместо использования Theme.of(context). Разработчики пишут Colors.blue напрямую в Container, вместо primary или secondary цвета из темы. При смене темы такие цвета остаются неизменными, нарушая единый стиль. Решение: всегда использовать Theme.of(context).colorScheme.primary или другие цвета из темы.

Ошибка 3: отсутствие darkTheme. Если приложение не определяет darkTheme, при включении тёмной темы на устройстве Flutter пытается адаптировать светлую тему автоматически, что часто даёт некачественный результат. Решение: всегда передавать darkTheme в MaterialApp, даже если dark theme не является приоритетом — это важный UX для пользователей.

Ошибка 4: создание новой темы при каждом build. Если ThemeData создаётся внутри build метода, каждый перестройка виджета создаёт новый объект темы, ломая оптимизацию InheritedWidget. Решение: выносить ThemeData в статическое поле или const-конструктор за пределы build.

Вывод: правильная темизация требует единого централизованного подхода. Выносите ThemeData в константы, используйте цветовые схемы из темы и всегда определяйте обе темы — светлую и тёмную.

Часто задаваемые вопросы

Чем отличается Theme от ThemeData?

ThemeData — это данные конфигурации (цвета, шрифты, стили). Theme — InheritedWidget, который передаёт ThemeData вниз по дереву виджетов. Theme.of(context) возвращает ThemeData, который ближайший Theme распространяет в текущем контексте.

Как переключить тему приложения динамически?

Управляйте themeMode в MaterialApp через состояние приложения. Измените состояние с ThemeMode.light на ThemeMode.dark, и Flutter перестроит всё дерево с новой темой. Для плавной анимации используйте AnimatedTheme.

Что такое ColorScheme.fromSeed?

ColorScheme.fromSeed — метод Material 3, который принимает один базовый цвет (seedColor) и генерирует полную гармоничную палитру: primary, secondary, tertiary, surface, error и их on-варианты. Позволяет получить согласованную цветовую схему из одного цвета бренда.

Как использовать кастомные шрифты с Theme?

Через TextTheme внутри ThemeData: укажите TextStyle с fontFamily для каждого уровня. Для Google Fonts используйте пакет google_fonts, передавая fontFamily напрямую в TextTheme. Для локальных шрифтов — подключите их через pubspec.yaml в секцию fonts.

Можно ли переопределить тему для одного экрана?

Да. Оберните экран в Theme с переопределёнными данными через copyWith. Все дочерние виджеты внутри этого Theme получат изменённые стили, а остальное приложение сохранит исходную тему. Это не создаёт дополнительных накладных расходов.

Итоги

  • ThemeData — конфигурационный объект с цветовой палитрой, типографикой и стилями всех Material-компонентов
  • ColorScheme.fromSeed — основной способ генерации цветовой палитры в Material 3 из одного цвета бренда
  • Theme.of(context) — доступ к текущей теме для чтения стилей без дублирования кода
  • Theme — InheritedWidget для наследования и переопределения темы в поддереве виджетов
  • Dark theme — отдельная ThemeData с brightness: Brightness.dark, управляется через themeMode
  • Рекомендация: выносите ThemeData в static или const, всегда задавайте theme и darkTheme в MaterialApp

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также