Kodokon kodokon.com

Temas: ThemeData, Material 3 y modo oscuro

Centraliza los colores y la tipografía en ThemeData, genera una paleta de Material 3 con ColorScheme.fromSeed y ofrece un modo oscuro sin esfuerzo.

9 min · 3 preguntas

Abrir esta lección en Kodokon

Dejar de aplicar estilos widget por widget es un hito profesional: ThemeData centraliza los colores, la tipografía y las formas de toda la aplicación. Con Material 3 (activado por defecto desde Flutter 3.16), todo parte del ColorScheme: ColorScheme.fromSeed deriva una paleta completa, armoniosa y accesible a partir de un único color semilla (seed).

DART
import 'package:flutter/material.dart';

void main() {
  runApp(const ShopApp());
}

class ShopApp extends StatelessWidget {
  const ShopApp({super.key});

  @override
  Widget build(BuildContext context) {
    const seed = Colors.teal;
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
        ),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
          brightness: Brightness.dark,
        ),
      ),
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(title: const Text('Shop')),
        body: const Center(child: Text('Welcome')),
      ),
    );
  }
}
Dos temas generados a partir de un único color semilla.

Dentro de tus widgets, no reinventes nada: lee el tema ambiental con Theme.of(context). El colorScheme expone roles semánticos (primary, onPrimary, primaryContainer, surface, etcétera) y textTheme expone estilos con nombre (titleMedium, bodyLarge, etcétera). Un widget escrito de esta manera se adapta automáticamente tanto al tema claro como al oscuro.

DART
import 'package:flutter/material.dart';

class PriceTag extends StatelessWidget {
  const PriceTag({super.key, required this.price});

  final double price;

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    final text = Theme.of(context).textTheme;
    return Container(
      padding: const EdgeInsets.symmetric(
        horizontal: 12,
        vertical: 6,
      ),
      decoration: BoxDecoration(
        color: colors.primaryContainer,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        '$price €',
        style: text.titleMedium?.copyWith(
          color: colors.onPrimaryContainer,
        ),
      ),
    );
  }
}
Ningún color codificado a mano: el widget sigue el tema activo.

Para ir más allá, configura los temas de componentes: cada familia de widgets (barras de aplicación, botones, campos de entrada) acepta un tema dedicado en ThemeData. Defines el padding de tus botones o el centrado de tus títulos una sola vez, y cada instancia lo hereda sin un solo estilo local.

DART
import 'package:flutter/material.dart';

final ThemeData appTheme = ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.teal,
  ),
  appBarTheme: const AppBarTheme(
    centerTitle: true,
    elevation: 0,
  ),
  filledButtonTheme: FilledButtonThemeData(
    style: FilledButton.styleFrom(
      padding: const EdgeInsets.symmetric(
        horizontal: 24,
        vertical: 12,
      ),
    ),
  ),
);
Los ajustes de componentes se aplican en toda la aplicación.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué produce ColorScheme.fromSeed(seedColor: ...)?
    • Un único color ligeramente aclarado
    • Una paleta de Material 3 completa y coherente derivada del color semilla
    • Un degradado para los fondos
    • Únicamente un tema oscuro
  2. ¿Cómo haces que la aplicación siga el ajuste claro/oscuro del sistema?
    • Proporciona solo theme; el resto es automático
    • Proporciona theme y darkTheme, y establece themeMode en ThemeMode.system
    • Llama a setState cuando el brillo cambia
    • Lee MediaQuery en cada widget
  3. ¿Por qué escribir color: Theme.of(context).colorScheme.primary en lugar de color: Colors.blue?
    • Es más corto de escribir
    • El color se adapta al tema activo y se mantiene coherente en toda la aplicación
    • Colors.blue está obsoleto en Material 3