Saltar al contenido principal

LayoutBuilder y diseños adaptativos

Aprende cómo usar el widget LayoutBuilder.

Aprende cómo crear layouts que se adapten a diferentes anchos de pantalla.

Qué lograrás

Crear layouts responsivos con LayoutBuilder
Detectar el tamaño de pantalla para elegir diferentes layouts
Construir una barra lateral y un layout de detalles para pantallas grandes

Pasos

1

Introducción

Modern apps need to work well on screens of all sizes. On this page, you'll learn how to create layouts that adapt to different screen widths. This app shows a sidebar on large screens and a navigation-based UI on small screens. Specifically, this app handles two screen sizes:

  • Pantallas grandes (tablets, escritorio): Shows contact groups and contact details side-by-side.
  • Pantallas pequeñas (teléfonos): Uses navigation to move between contact groups and details.
2

Crear la página de grupos de contactos

Primero, crea la estructura básica del widget ContactGroupsPage para tu pantalla de grupos de contactos. Crea lib/screens/contact_groups.dart y añade la siguiente estructura básica:

dart
import 'package:flutter/cupertino.dart';

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Contact Groups will go here')),
    );
  }
}
3

Crear la página de contactos

Del mismo modo, crea lib/screens/contacts.dart para mostrar eventualmente contactos individuales:

dart
import 'package:flutter/cupertino.dart';

class ContactListsPage extends StatelessWidget {
  const ContactListsPage({super.key, required this.listId});

  final int listId;

  @override
  Widget build(BuildContext context) {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: Center(child: Text('Lists of contacts will go here')),
    );
  }
}

El widget ContaactsListPage y el widget ContactGroupsPage son páginas temporales que se necesitan para implementar el widget de layout adaptativo, lo cual harás a continuación.

4

Construir la base del layout adaptativo

Crea lib/screens/adaptive_layout.dart y comienza con la siguiente estructura básica:

dart
import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return const ContactGroupsPage(); // Temporary placeholder
  }
}

Este es un StatefulWidget porque el layout adaptativo gestionará eventualmente qué grupo de contactos está seleccionado actualmente.

A continuación, añade la lógica de detección de tamaño de pantalla a lib/screens/adaptive_layout.dart:

dart
import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return const Text('Large screen layout'); // Temporary
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}

El widget LayoutBuilder proporciona información sobre las restricciones de tamaño del padre. En el callback builder, recibes un objeto BoxConstraints que te indica el ancho y el alto máximos disponibles.

Al comprobar si constraints.maxWidth > largeScreenMinWidth, puedes decidir qué layout mostrar. El umbral de 600 píxeles es un punto de ruptura común que separa las pantallas de tamaño de teléfono de las de tamaño de tablet.

5

Actualizar la aplicación principal

Actualiza main.dart para usar el layout adaptativo, de modo que puedas ver tus cambios:

dart
import 'package:flutter/cupertino.dart';

import 'data/contact_group.dart';
import 'screens/adaptive_layout.dart';

final contactGroupsModel = ContactGroupsModel();

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

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

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Rolodex',
      theme: CupertinoThemeData(
        barBackgroundColor: CupertinoDynamicColor.withBrightness(
          color: Color(0xFFF9F9F9),
          darkColor: Color(0xFF1D1D1D),
        ),
      ),
      home: AdaptiveLayout(),
    );
  }
}

Si lo estás ejecutando en Chrome, puedes cambiar el tamaño de la ventana del navegador para ver los cambios de layout.

6

Añadir funcionalidad de selección de lista

El layout de pantalla grande necesita rastrear qué grupo de contactos está seleccionado. Actualiza el objeto state en lib/screens/adaptive_layout.dart con el siguiente código:

dart

import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;

  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return const Text('Large screen layout');
        } else {
          return const ContactGroupsPage();
        }
      },
    );
  }
}

La variable selectedListId rastrea el grupo de contactos actualmente seleccionado, y _onContactListSelected actualiza este valor cuando el usuario realiza una elección.

7

Construir el layout para pantalla grande

Ahora, implementa el layout lado a lado para pantallas grandes en lib/screens/adaptive_layout.dart. Primero, reemplaza el texto temporal con un widget que contenga el layout adecuado.

dart

import 'package:flutter/cupertino.dart';

import 'contact_groups.dart';

const largeScreenMinWidth = 600;

class AdaptiveLayout extends StatefulWidget {
  const AdaptiveLayout({super.key});

  @override
  State<AdaptiveLayout> createState() => _AdaptiveLayoutState();
}

class _AdaptiveLayoutState extends State<AdaptiveLayout> {
  int selectedListId = 0;

  void _onContactListSelected(int listId) {
    setState(() {
      selectedListId = listId;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final isLargeScreen = constraints.maxWidth > largeScreenMinWidth;

        if (isLargeScreen) {
          return _buildLargeScreenLayout();
        } else {
          // For small screens, use the original, navigation-style approach.
          return const ContactGroupsPage();
        }
      },
    );
  }

  Widget _buildLargeScreenLayout() {
    return const CupertinoPageScaffold(
      backgroundColor: CupertinoColors.extraLightBackgroundGray,
      child: SafeArea(child: Row(children: [Text('Sidebar'), Text('Details')])),
    );
  }
}

El layout de pantalla grande utiliza un Row para colocar la barra lateral y los detalles lado a lado. SafeArea garantiza que el contenido no se superponga con elementos de la UI del sistema, como la barra de estado.

Ahora, establece los tamaños de los dos paneles y añade un divisor visual en lib/screens/adaptive_layout.dart:

dart
Widget _buildLargeScreenLayout() {
  return CupertinoPageScaffold(
    backgroundColor: CupertinoColors.extraLightBackgroundGray,
    child: SafeArea(
      child: Row(
        children: [
          const SizedBox(width: 320, child: Text('Sidebar placeholder')),
          Container(width: 1, color: CupertinoColors.separator),
          const Expanded(child: Text('Details placeholder')),
        ],
      ),
    ),
  );
}

Este layout crea lo siguiente:

  • Una barra lateral de ancho fijo (320 píxeles) para grupos de contactos.
  • Un divisor de 1 píxel entre los paneles.
  • Un panel de detalles que utiliza un widget Expanded para ocupar el espacio restante.
8

Probar el layout adaptativo

Usa Hot Reload en tu aplicación y prueba el comportamiento responsivo. Si la estás ejecutando en Chrome, puedes cambiar el tamaño de la ventana del navegador para ver el cambio de layout:

  • Ventana ancha (> 600px): Muestra texto provisional para la barra lateral y los detalles lado a lado.
  • Ventana estrecha (< 600px): Muestra solo la página de grupos de contactos.

Tanto la barra lateral como el área de contenido principal muestran texto provisional por ahora.

En la siguiente lección, implementarás slivers para completar el contenido de la lista de contactos.

9

Revisión

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Layouts responsivos creados con LayoutBuilder

LayoutBuilder proporciona las restricciones de tamaño del padre en su callback builder. Al comprobar constraints.maxWidth, puedes decidir qué layout mostrar en función del espacio disponible.

Tamaño de pantalla detectado para elegir diferentes layouts

Utilizaste un punto de ruptura de 600 píxeles para distinguir las pantallas de tamaño de teléfono de las de tamaño de tablet. Este umbral común ayuda a tu aplicación a adaptar su UI para brindar la mejor experiencia en cada dispositivo.

Construcción de una barra lateral y un layout de detalles para pantallas grandes

En pantallas grandes, mostraste una barra lateral de ancho fijo y un panel de detalles Expanded lado a lado usando un Row. Este patrón clásico maximiza el espacio útil de la pantalla en tablets y escritorios.

10

Ponte a prueba

Quiz de Layout Adaptativo

1 / 2
What information does LayoutBuilder provide to its builder callback?
  1. El sistema operativo del dispositivo y la orientación de la pantalla.

    No exactamente.

    LayoutBuilder proporciona restricciones de tamaño, no información del sistema operativo o de la orientación.

  2. Las restricciones de tamaño del padre, incluyendo el ancho y el alto máximos.

    ¡Así es!

    El builder de LayoutBuilder recibe BoxConstraints que te indican el espacio disponible del padre.

  3. El número de widgets hijos en el árbol.

    No exactamente.

    LayoutBuilder proporciona restricciones de layout, no información sobre el árbol de widgets.

  4. Los colores y la tipografía del tema actual.

    No exactamente.

    Theme data viene de Theme.of(context), no de LayoutBuilder.

In a large screen layout, which widget can be used to place a sidebar and details panel side-by-side?
  1. Column

    No exactamente.

    Column organiza los widgets verticalmente, no lado a lado.

  2. Stack

    No exactamente.

    Stack superpone widgets unos encima de otros, no lado a lado.

  3. ListView

    No exactamente.

    ListView es para listas con desplazamiento, no para layouts lado a lado.

  4. Row

    ¡Así es!

    Row organiza a sus hijos horizontalmente, lo que lo hace ideal para colocar una barra lateral y un panel de detalles lado a lado.