Ir al contenido

Guía de Flutter para novatos - Miguel Páramos

FAQ sobre Flutter pal que no tiene ni idea de Flutter | 2º DAM · PMDM

Arquitectura limpia de temas: archivo MiTema, estilos globales para botones y textos, y soporte para Modo Oscuro

La arquitectura más limpia, profesional y escalable en Flutter para la gestión de estilos consiste en centralizar la configuración visual en un archivo de tema dedicado (lib/themes/mi_tema.dart) y conectarlo directamente a MaterialApp en main.dart.

Este enfoque permite definir estilos globales para botones, tipografías y contenedores de una sola vez, además de proporcionar soporte integrado para modo claro y modo oscuro respetando las preferencias del sistema.

1. El archivo de configuración de tema: lib/themes/mi_tema.dart

Sección titulada «1. El archivo de configuración de tema: lib/themes/mi_tema.dart»
import 'package:flutter/material.dart'; // Importa la librería base de componentes y temas de Material
import 'paleta.dart'; // Importa la paleta de colores corporativa centralizada
class MiTema { // Clase estática para la gestión y configuración centralizada de temas
static ThemeData get lightTheme { // Getter que construye y devuelve el tema claro
return ThemeData( // Objeto principal de configuración de tema de Flutter
useMaterial3: true, // Activa las directrices y comportamientos modernos de Material 3
colorScheme: ColorScheme.fromSeed( // Genera la paleta armónica a partir del color base
seedColor: Paleta.primario, // Color semilla corporativo definido en la paleta
brightness: Brightness.light, // Establece la luminosidad clara
), // Fin de ColorScheme.fromSeed
scaffoldBackgroundColor: Paleta.fondo, // Color de fondo predeterminado para las pantallas
elevatedButtonTheme: ElevatedButtonThemeData( // Estilo homogéneo para todos los botones elevados
style: ElevatedButton.styleFrom( // Define las propiedades visuales compartidas del botón
backgroundColor: Paleta.primario, // Fondo azul corporativo por defecto
foregroundColor: Colors.white, // Texto e iconos en color blanco
elevation: 2, // Sombra sutil de elevación
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), // Relleno interior uniforme
shape: RoundedRectangleBorder( // Geometría de bordes del botón
borderRadius: BorderRadius.circular(8), // Curvatura de bordes redondeados
), // Fin de RoundedRectangleBorder
), // Fin de ElevatedButton.styleFrom
), // Fin de ElevatedButtonThemeData
textTheme: const TextTheme( // Jerarquía tipográfica global de la app
headlineMedium: TextStyle( // Estilo para encabezados medianos
fontSize: 22, // Tamaño de fuente en píxeles lógicos
fontWeight: FontWeight.bold, // Grosor tipográfico en negrita
color: Paleta.textoPrincipal, // Color de texto principal de alto contraste
), // Fin de TextStyle
bodyMedium: TextStyle( // Estilo para textos de cuerpo y párrafos
fontSize: 15, // Tamaño de fuente base de lectura
color: Paleta.textoSecundario, // Color atenuado para texto secundario
), // Fin de TextStyle
), // Fin de TextTheme
); // Fin de ThemeData
} // Fin de lightTheme
static ThemeData get darkTheme { // Getter que construye y devuelve el tema oscuro
return ThemeData( // Objeto de configuración de tema para modo noche
useMaterial3: true, // Aplica Material 3 en modo oscuro
colorScheme: ColorScheme.fromSeed( // Genera la paleta tonal oscura
seedColor: Paleta.primario, // Color semilla base
brightness: Brightness.dark, // Establece la luminosidad oscura
), // Fin de ColorScheme.fromSeed
scaffoldBackgroundColor: const Color(0xFF0F172A), // Fondo pizarra oscuro para evitar fatiga visual
elevatedButtonTheme: ElevatedButtonThemeData( // Estilo adaptado para botones en modo oscuro
style: ElevatedButton.styleFrom( // Define propiedades del botón
backgroundColor: Paleta.secundario, // Tono secundario visible sobre fondo oscuro
foregroundColor: Colors.white, // Texto blanco de alto contraste
shape: RoundedRectangleBorder( // Geometría de bordes
borderRadius: BorderRadius.circular(8), // Esquinas redondeadas
), // Fin de RoundedRectangleBorder
), // Fin de ElevatedButton.styleFrom
), // Fin de ElevatedButtonThemeData
); // Fin de ThemeData
} // Fin de darkTheme
} // Fin de la clase MiTema

2. Aplicación limpia en main.dart con soporte automático de Modo Oscuro

Sección titulada «2. Aplicación limpia en main.dart con soporte automático de Modo Oscuro»

En el archivo main.dart, inyectamos el tema claro, el tema oscuro y le indicamos a Flutter que siga automáticamente las preferencias del sistema operativo del usuario:

import 'package:flutter/material.dart'; // Importa la librería Material
import 'themes/mi_tema.dart'; // Importa la definición centralizada de temas claro y oscuro
import 'pantalla_principal.dart'; // Importa la pantalla principal
void main() { // Punto de arranque oficial del programa
runApp(const MainApp()); // Arranca la app con el widget de configuración
} // Fin de la función main
class MainApp extends StatelessWidget { // Widget raíz inmutable que configura el MaterialApp
const MainApp({super.key}); // Constructor constante
@override // Sobreescribe el método build
Widget build(BuildContext context) { // Retorna la estructura principal
return MaterialApp( // Contenedor global de la aplicación
title: 'Mi App con Temas', // Título de la app
theme: MiTema.lightTheme, // Asigna el tema que se activará en modo claro
darkTheme: MiTema.darkTheme, // Asigna el tema que se activará en modo oscuro
themeMode: ThemeMode.system, // Alterna automáticamente según las preferencias del sistema
home: const PantallaPrincipal(), // Pantalla de inicio de la app
); // Fin de MaterialApp
} // Fin del método build
} // Fin de la clase MainApp

3. Cómo sobrescribir el estilo de un botón puntual

Sección titulada «3. Cómo sobrescribir el estilo de un botón puntual»

Gracias a esta arquitectura centralizada, todos los botones de la aplicación heredan automáticamente el estilo, espaciado y bordes predeterminados. Si en una pantalla puntual se requiere un botón con una variante específica (por ejemplo, un botón verde de confirmación o un botón rojo de cancelación), basta con sobrescribir las propiedades deseadas con .styleFrom(...):

ElevatedButton( // Botón con personalización puntual de estilo
style: ElevatedButton.styleFrom( // Sobreescribe únicamente los valores indicados
backgroundColor: Paleta.botonVerde, // Aplica color de fondo verde específico para este botón
foregroundColor: Paleta.textoBotonVerde, // Aplica color de texto blanco para este botón
), // Fin de ElevatedButton.styleFrom
onPressed: () {}, // Callback al pulsar el botón
child: const Text('Confirmar Operación'), // Etiqueta del botón
) // Fin del widget ElevatedButton