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.

Типографіка та 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) framework піднімається вгору по дереву до першого 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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