Vista previa — comportamiento adaptativo
Guías aplicadas — Do's & Don'ts
LayoutBuilder
Evalúa constraints.maxWidth para decidir qué árbol de widgets devolver. Mide el espacio real disponible, no el dispositivo.
ConstrainedBox + Center
Envuelve formularios y listas en ConstrainedBox(maxWidth: 560) centrado para evitar estiramiento en pantallas grandes.
GridView.builder lazy
Con SliverGridDelegateWithMaxCrossAxisExtent para que el número de columnas se ajuste automáticamente al ancho disponible.
MediaQuery.orientationOf
No uses la orientación del dispositivo para cambiar layouts. Un iPad en landscape puede estar en modo split-screen con poco ancho.
Detección de hardware
No compruebes "es phone" vs "es tablet". Flutter corre en ventanas redimensionables. Usa siempre el ancho disponible real.
Bloquear orientación
Bloquear la orientación causa letterboxing en dispositivos plegables y viola los requisitos de Android Large Format Tiers.
Código clave — DashboardScreen
// LayoutBuilder detecta espacio disponible // Breakpoint único: 600px const double kLargeScreenMinWidth = 600.0; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final bool isLarge = constraints.maxWidth > kLargeScreenMinWidth; if (isLarge) { return _buildLargeLayout(); } else { return _buildSmallLayout(); } }, ); }
// Tablet/Desktop: NavigationRail lateral Widget _buildLargeLayout() { return Scaffold( body: Row( children: [ // Sidebar 200px extendido NavigationRail( selectedIndex: _selectedIndex, extended: true, minExtendedWidth: 200, destinations: _railDestinations, onDestinationSelected: (i) => setState( () => _selectedIndex = i), ), const VerticalDivider(width: 1), // Contenido ocupa el resto Expanded(child: _buildContent()), ], ), ); }
// GridView lazy con columnas automáticas GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: metrics.length, gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 220, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 1.3, ), itemBuilder: (ctx, i) => MetricCard(metric: metrics[i]), )
// Formulario centrado con maxWidth // Evita estiramiento en desktop Center( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 560.0, ), child: const QuickLogForm(), ), ) // Perfil también restringido Center( child: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 480.0, ), child: const ProfileTab(), ), )
Flujo de trabajo — Construcción del layout adaptativo
Identificar el widget raíz que necesita comportamiento adaptativo
En NutriTrack Pro, el widget es DashboardScreen. Necesita mostrar NavigationRail (tablet) o NavigationBar (móvil) según el espacio disponible.
Envolver en LayoutBuilder y extraer constraints.maxWidth
Definir el breakpoint como constante global
Devolver árbol distinto según breakpoint
Si maxWidth > 600: Row con NavigationRail + Expanded(content). Si no: Scaffold con body + NavigationBar bottom.
Optimizar listas: GridView.builder con SliverGridDelegateWithMaxCrossAxisExtent
Las métricas pasan de 2 columnas en móvil a 3-4 en tablet automáticamente, sin condicionales adicionales.
Restringir formularios y perfiles con ConstrainedBox + Center
maxWidth: 560 para el formulario de registro, maxWidth: 480 para el perfil. Evitan estiramiento en desktop sin romper el responsive.
Validar: redimensionar la ventana y revisar transiciones de layout
Probar en Flutter DevTools arrastrando el tamaño de ventana. Verificar sin errores de overflow. Confirmar lazy rendering de la lista.