Theme i ThemeData — co to jest, zasady i konfiguracja stylów

Autor: IT Sectr Opublikowano: 2026-07-03 Czas czytania: 8 min

Theme i ThemeData — centralne mechanizmy motywizacji we Flutter, pozwalające ustawiać kolory, czcionki, odstępy i style komponentów globalnie dla całej aplikacji. ThemeData — obiekt konfiguracyjny przekazywany do MaterialApp przez parametr theme. Według oficjalnej dokumentacji Flutter (2026), prawidłowa konfiguracja ThemeData redukuje ilość powielonego kodu stylizacji o 70–80% i zapewnia jednolity wygląd ekranów. Wbudowany system dziedziczenia pozwala nadpisywać motyw na poziomie poszczególnych widgetów przez Theme.of(context).

Najważniejsze

  • ThemeData — obiekt konfiguracyjny z paletą kolorów, typografią i stylami komponentów
  • Theme.of(context) — dostęp do bieżącego motywu z dowolnego widgetu przez BuildContext
  • Light/Dark — wbudowana obsługa jasnego i ciemnego motywu przez theme i darkTheme
  • Theme — widget dziedziczący motyw do elementów potomnych i pozwalający go nadpisywać
  • ColorScheme — zestaw harmonijnych kolorów generujących style dla całego systemu Material

Czym są Theme i ThemeData

ThemeData — klasa zawierająca pełny opis wizualnego stylu aplikacji Material. Przekazywana do MaterialApp przez parametr theme lub darkTheme i dziedziczona przez wszystkie widgety potomne. Każdy widget Material Design używa ThemeData do pobierania swoich stylów: kolorów przycisków, rozmiarów AppBar, stylów tekstu, odstępów kart i ikon.

U podstaw ThemeData leży ColorScheme, który określa primary, secondary, surface, error i inne kolory. Z tych kolorów automatycznie obliczane są stany (hover, focus, pressed) dla wszystkich komponentów Material. Na przykład ElevatedButton używa koloru primary do tła i onPrimary do tekstu — bez dodatkowej konfiguracji.

Widget Theme — to InheritedWidget, który rozpowszechnia motyw w dół drzewa widgetów. Każdy widget może uzyskać bieżący motyw przez Theme.of(context), a także utworzyć nowy motyw na podstawie istniejącego z nadpisanymi wartościami przez Theme.copyWith lub ThemeData.copyWith.

Podsumowanie: ThemeData — jednolite źródło stylów aplikacji. Rozpocznij tworzenie od zdefiniowania ThemeData — to nadaje wizualny język całemu projektowi i zapobiega rozproszonym stylom w różnych ekranach.

ColorScheme: podstawa palety kolorów

ColorScheme — centralny element motywizacji w Material 3. Zastępuje przestarzałe primarySwatch i primaryColor, dostarczając pełny zestaw kolorów dla wszystkich stanów i komponentów.

ColorScheme zawiera 12 podstawowych kolorów: primary, onPrimary, secondary, onSecondary, surface, onSurface, error, onError, background, onBackground, outline i shadow. Dla każdego koloru automatycznie obliczane są warianty light i dark, a także kolory dla stanów hover, focus, pressed i dragged. Przy podaniu tylko primary, framework generuje pozostałe kolory automatycznie przez ColorScheme.fromSeed.

Metoda ColorScheme.fromSeed (Material 3) przyjmuje bazowy kolor seedColor i generuje pełną harmonijną paletę na podstawie algorytmu Material Design 3. To eliminuje ręczne dopasowywanie kolorów complementary i analogous. Wystarczy przekazać jeden główny kolor marki, a ColorScheme.fromSeed stworzy spójną paletę dla całego systemu Material.

Podsumowanie: w nowych projektach używaj ColorScheme.fromSeed do generowania palety. Dla zgodności z Material 2 — primarySwatch. ColorScheme — obowiązkowe minimum dla Material 3.

Typography i TextTheme: czcionki aplikacji

TextTheme — część ThemeData określająca style tekstu dla wszystkich poziomów nagłówków, podtytułów, body i etykiet. Material Design definiuje 15 stylów tekstowych: headlineLarge, headlineMedium, headlineSmall, titleLarge, titleMedium i inne.

Typografika jest ustawiana albo przez GoogleFonts do automatycznego ładowania czcionek, albo przez wbudowany Theme.of(context).textTheme z niestandardowymi TextStyle. GoogleFonts udostępnia setki czcionek przez pakiet google_fonts, który cachuje je lokalnie po pierwszym załadowaniu. Alternatywa — osadzać czcionki w assets projektu przez pubspec.yaml.

Dla adaptacyjnej typografiki TextTheme obsługuje MediaQuery.textScaleFactor — przy zwiększeniu systemowej czcionki wszystkie style tekstowe skalują się proporcjonalnie. Dodatkowo można ustawić różne rozmiary czcionki dla różnych form faktorów przez isTest w zależności od MediaQuery.size.width.

Podsumowanie: definiuj TextTheme w ThemeData i używaj Theme.of(context).textTheme dla wszystkich elementów tekstowych. To zapewnia jednolitość czcionek i upraszcza globalne zmiany typografiki.

Dziedziczenie i nadpisywanie motywu

Theme — InheritedWidget, który rozpowszechnia motyw od rodzica do dzieci. Każdy widget potomny może zarówno odczytać bieżący motyw przez Theme.of(context), jak i utworzyć własną kopię ze zmianami przez ThemeData.copyWith.

Mechanizm dziedziczenia: jeśli widget jest owinięty w Theme z nowymi danymi, wszyscy jego potomkowie widzą nadpisany motyw, a widgety poza tym owinięciem — oryginalny. Pozwala to na przykład izolować style dla konkretnego ekranu: nagłówek używa niestandardowego motywu ze zmienionym AppBarTheme, a reszta aplikacji — standardowego.

Zagnieżdżanie Theme może być nieograniczone. Flutter używa hierarchicznego wyszukiwania: przy wywołaniu Theme.of(context) framework podnosi się w górę drzewa do pierwszego Theme i zwraca jego motyw. Jeśli nie zostanie znaleziony żaden Theme — zgłaszany jest wyjątek. Dlatego MaterialApp zawsze owija aplikację w Theme.

Podsumowanie: dziedziczenie Theme — elastyczny mechanizm do izolacji stylów. Używaj nadpisywania motywu dla poszczególnych ekranów bez powielania ogólnej konfiguracji.

Dark theme: ciemny motyw aplikacji

Dark theme — wbudowana możliwość Flutter do przełączania między jasnym a ciemnym motywem. ThemeData jest przechowywana w parametrach theme (jasny) i darkTheme (ciemny) MaterialApp. Parametr themeMode zarządza trybem: ThemeMode.light (zawsze jasny), ThemeMode.dark (zawsze ciemny) lub ThemeMode.system (zgodny z ustawieniami systemowymi).

Przy implementacji ciemnego motywu nie trzeba tworzyć wszystkich stylów od nowa. Wystarczy przekazać ColorScheme.fromSeed(seedColor, brightness: Brightness.dark) w darkTheme. Framework automatycznie skoryguje kolory dla ciemnego tła: surface stanie się ciemnoszary, tekst — jasny. Pozostałe podmotywy (AppBarTheme, CardTheme, BottomNavigationBarTheme) dziedziczą z jasnej wersji, jeśli nie zostały jawnie nadpisane.

Dla płynnego przejścia między motywami Flutter udostępnia animację przez AnimatedTheme. Przy zmianie themeMode kolory i style są animowane, tworząc wizualnie przyjemne przełączanie. AnimatedTheme używa Duration i Curve do ustawienia szybkości animacji.

Podsumowanie: dark theme jest implementowane przez darkTheme i ColorScheme.fromSeed z brightness: Brightness.dark. Używaj ThemeMode.system do zgodności z ustawieniami systemowymi użytkownika.

Przykłady kodu z Theme i ThemeData

Przykład 1 — globalna konfiguracja motywu przez MaterialApp z użyciem 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(),
);

W tym przykładzie tworzony jest ThemeData z Material 3, schematem kolorów opartym na indigo i niestandardowymi stylami AppBar i Card. useMaterial3 włącza nowy system motywów Material 3, a ColorScheme.fromSeed generuje pełną paletę.

Przykład 2 — dostęp do motywu i nadpisanie dla części aplikacji.

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

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

Drugi przykład pokazuje użycie Theme.of(context) do odczytu stylów oraz Theme() z copyWith do nadpisania motywu w części drzewa bez zmiany globalnej konfiguracji. SpecialSection wewnątrz Theme otrzyma pomarańczowy kolor primary, reszta aplikacji — oryginalny indigo.

Podsumowanie: globalny motyw jest ustawiany w MaterialApp, dostęp przez Theme.of(context), nadpisanie przez Theme z copyWith. Dla Material 3 używaj ColorScheme.fromSeed.

Typowe błędy przy konfiguracji motywu

Błąd 1: używanie primarySwatch zamiast ColorScheme w Material 3. primarySwatch jest przestarzałe i nieobsługiwane w Material 3. Przy użyciu useMaterial3: true z primarySwatch Flutter wyświetla ostrzeżenie. Rozwiązanie: przejść na ColorScheme.fromSeed do generowania palety.

Błąd 2: sztywne ustawianie kolorów w widgetach zamiast używania Theme.of(context). Programiści piszą Colors.blue bezpośrednio w Container, zamiast koloru primary lub secondary z motywu. Przy zmianie motywu takie kolory pozostają niezmienione, naruszając jednolity styl. Rozwiązanie: zawsze używać Theme.of(context).colorScheme.primary lub innych kolorów z motywu.

Błąd 3: brak darkTheme. Jeśli aplikacja nie definiuje darkTheme, przy włączeniu ciemnego motywu na urządzeniu Flutter próbuje dostosować jasny motyw automatycznie, co często daje niskiej jakości rezultat. Rozwiązanie: zawsze przekazywać darkTheme w MaterialApp, nawet jeśli ciemny motyw nie jest priorytetem — to ważne UX dla użytkowników.

Błąd 4: tworzenie nowego motywu przy każdym build. Jeśli ThemeData jest tworzone wewnątrz metody build, każde przebudowanie widgetu tworzy nowy obiekt motywu, łamiąc optymalizację InheritedWidget. Rozwiązanie: wynieść ThemeData do pola statycznego lub konstruktora const poza build.

Podsumowanie: prawidłowa motywizacja wymaga jednolitego scentralizowanego podejścia. Wynoś ThemeData do stałych, używaj schematów kolorów z motywu i zawsze definiuj oba motywy — jasny i ciemny.

Często zadawane pytania

Czym różni się Theme od ThemeData?

ThemeData — to dane konfiguracyjne (kolory, czcionki, style). Theme — InheritedWidget, który przekazuje ThemeData w dół drzewa widgetów. Theme.of(context) zwraca ThemeData, który najbliższy Theme rozpowszechnia w bieżącym kontekście.

Jak dynamicznie przełączyć motyw aplikacji?

Zarządzaj themeMode w MaterialApp przez stan aplikacji. Zmień stan z ThemeMode.light na ThemeMode.dark, a Flutter przebuduje całe drzewo z nowym motywem. Do płynnej animacji używaj AnimatedTheme.

Czym jest ColorScheme.fromSeed?

ColorScheme.fromSeed — metoda Material 3, która przyjmuje jeden bazowy kolor (seedColor) i generuje pełną harmonijną paletę: primary, secondary, tertiary, surface, error i ich on-warianty. Pozwala uzyskać spójny schemat kolorów z jednego koloru marki.

Jak używać niestandardowych czcionek z Theme?

Przez TextTheme wewnątrz ThemeData: wskaż TextStyle z fontFamily dla każdego poziomu. Dla Google Fonts używaj pakietu google_fonts, przekazując fontFamily bezpośrednio w TextTheme. Dla lokalnych czcionek — podłącz je przez pubspec.yaml w sekcji fonts.

Czy można nadpisać motyw dla jednego ekranu?

Tak. Owiń ekran w Theme z nadpisanymi danymi przez copyWith. Wszystkie widgety potomne wewnątrz tego Theme otrzymają zmienione style, a reszta aplikacji zachowa oryginalny motyw. Nie tworzy to dodatkowych narzutów.

Podsumowanie

  • ThemeData — obiekt konfiguracyjny z paletą kolorów, typografią i stylami wszystkich komponentów Material
  • ColorScheme.fromSeed — główny sposób generowania palety kolorów w Material 3 z jednego koloru marki
  • Theme.of(context) — dostęp do bieżącego motywu do odczytu stylów bez powielania kodu
  • Theme — InheritedWidget do dziedziczenia i nadpisywania motywu w poddrzewie widgetów
  • Dark theme — osobny ThemeData z brightness: Brightness.dark, zarządzany przez themeMode
  • Zalecenie: wynoś ThemeData do static lub const, zawsze ustawiaj theme i darkTheme w MaterialApp

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również