Funciones avanzadas de UI
Una introducción suave a las funciones avanzadas de UI: layouts adaptativos, slivers, desplazamiento, navegación.
Obtén una vista previa de la aplicación Rolodex que construirás y configura un proyecto basado en Cupertino con modelos de datos.
Qué lograrás
Pasos
1
Introducción
Introducción
In this third installment of the Flutter tutorial series, you'll use Flutter's Cupertino library to build a partial clone of the iOS Contacts app.
Al final de este tutorial, habrás aprendido a crear layouts adaptativos, implementar un sistema de temas completo, construir patrones de navegación y utilizar técnicas avanzadas de desplazamiento.
Qué aprenderás
#Este tutorial explora los siguientes temas:
- Construcción de layouts responsivos con
LayoutBuilder. - Uso de desplazamiento avanzado con slivers y búsqueda.
- Implementación de patrones de navegación basados en stack.
- Creación de temas completos con
CupertinoThemeData. - Soporte para temas tanto claro como oscuro.
- Creación de una UI al estilo iOS usando widgets de Cupertino.
Este tutorial asume que has completado los tutoriales de Flutter anteriores y te sientes cómodo con la composición básica de widgets, el state management y la estructura del proyecto Flutter.
2
Crear un nuevo proyecto Flutter
Crear un nuevo proyecto Flutter
Para crear una aplicación Flutter, primero necesitas un proyecto Flutter. Puedes crear una nueva aplicación con la herramienta CLI de Flutter, que se instala como parte del Flutter SDK.
Abre tu terminal preferida y ejecuta el siguiente comando para crear un nuevo proyecto Flutter:
flutter create rolodex --empty
cd rolodex
Este comando crea un nuevo proyecto Flutter que utiliza la plantilla mínima "vacía".
3
Añadir la dependencia de Cupertino Icons
Añadir la dependencia de Cupertino Icons
Este proyecto utiliza el paquete cupertino_icons,
un paquete oficial de Flutter.
Añádelo como dependencia ejecutando el siguiente comando:
flutter pub add cupertino_icons
4
Configurar la estructura del proyecto
Configurar la estructura del proyecto
Primero, crea la estructura de directorios básica para tu aplicación.
En el directorio lib de tu proyecto, crea las siguientes carpetas:
mkdir lib/data lib/screens lib/theme
Este comando crea carpetas para organizar tu código en secciones lógicas: modelos de datos, widgets de pantalla y configuración del tema.
5
Reemplazar el código inicial
Reemplazar el código inicial
En tu IDE, abre el archivo lib/main.dart y reemplaza todo su
contenido con el siguiente código inicial:
import 'package:flutter/cupertino.dart';
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: CupertinoPageScaffold(child: Center(child: Text('Hello Rolodex!'))),
);
}
}
A diferencia de los dos tutoriales anteriores,
esta aplicación utiliza CupertinoApp en lugar de MaterialApp.
El sistema de diseño Cupertino proporciona widgets y estilos al estilo iOS,
lo cual es perfecto para crear aplicaciones que se sientan nativas en dispositivos Apple.
6
Ejecutar tu aplicación
Ejecutar tu aplicación
En tu terminal en la raíz de tu aplicación Flutter, ejecuta el siguiente comando:
flutter run -d chrome
La aplicación se compila y se inicia en una nueva instancia de Chrome. Muestra "Hello Rolodex!" en el centro de la pantalla.
7
Crear los modelos de datos
Crear los modelos de datos
Antes de construir la UI, crea las estructuras de datos y los datos de muestra que utilizará la aplicación. Esta sección se explica brevemente porque no es el foco de este tutorial.
Datos de contacto
#Crea un nuevo archivo, lib/data/contact.dart, y añade la clase básica Contact:
class Contact {
Contact({
required this.id,
required this.firstName,
this.middleName,
required this.lastName,
this.suffix,
});
final int id;
final String firstName;
final String lastName;
final String? middleName;
final String? suffix;
}
final johnAppleseed = Contact(id: 0, firstName: 'John', lastName: 'Appleseed');
final kateBell = Contact(id: 1, firstName: 'Kate', lastName: 'Bell');
final annaHaro = Contact(id: 2, firstName: 'Anna', lastName: 'Haro');
final danielHiggins = Contact(
id: 3,
firstName: 'Daniel',
lastName: 'Higgins',
suffix: 'Jr.',
);
final davidTaylor = Contact(id: 4, firstName: 'David', lastName: 'Taylor');
final hankZakroff = Contact(
id: 5,
firstName: 'Hank',
middleName: 'M.',
lastName: 'Zakroff',
);
final alexAnderson = Contact(id: 6, firstName: 'Alex', lastName: 'Anderson');
final benBrown = Contact(id: 7, firstName: 'Ben', lastName: 'Brown');
final carolCarter = Contact(id: 8, firstName: 'Carol', lastName: 'Carter');
final dianaDevito = Contact(id: 9, firstName: 'Diana', lastName: 'Devito');
final emilyEvans = Contact(id: 10, firstName: 'Emily', lastName: 'Evans');
final frankFisher = Contact(id: 11, firstName: 'Frank', lastName: 'Fisher');
final graceGreen = Contact(id: 12, firstName: 'Grace', lastName: 'Green');
final henryHall = Contact(id: 13, firstName: 'Henry', lastName: 'Hall');
final isaacIngram = Contact(id: 14, firstName: 'Isaac', lastName: 'Ingram');
final juliaJackson = Contact(id: 15, firstName: 'Julia', lastName: 'Jackson');
final kevinKelly = Contact(id: 16, firstName: 'Kevin', lastName: 'Kelly');
final lindaLewis = Contact(id: 17, firstName: 'Linda', lastName: 'Lewis');
final michaelMiller = Contact(id: 18, firstName: 'Michael', lastName: 'Miller');
final nancyNewman = Contact(id: 19, firstName: 'Nancy', lastName: 'Newman');
final oliverOwens = Contact(id: 20, firstName: 'Oliver', lastName: 'Owens');
final penelopeParker = Contact(
id: 21,
firstName: 'Penelope',
lastName: 'Parker',
);
final quentinQuinn = Contact(id: 22, firstName: 'Quentin', lastName: 'Quinn');
final rachelReed = Contact(id: 23, firstName: 'Rachel', lastName: 'Reed');
final samuelSmith = Contact(id: 24, firstName: 'Samuel', lastName: 'Smith');
final tessaTurner = Contact(id: 25, firstName: 'Tessa', lastName: 'Turner');
final umbertoUpton = Contact(id: 26, firstName: 'Umberto', lastName: 'Upton');
final victoriaVance = Contact(id: 27, firstName: 'Victoria', lastName: 'Vance');
final williamWilson = Contact(id: 28, firstName: 'William', lastName: 'Wilson');
final xavierXu = Contact(id: 29, firstName: 'Xavier', lastName: 'Xu');
final yasmineYoung = Contact(id: 30, firstName: 'Yasmine', lastName: 'Young');
final zacharyZimmerman = Contact(
id: 31,
firstName: 'Zachary',
lastName: 'Zimmerman',
);
final elizabethMJohnson = Contact(
id: 32,
firstName: 'Elizabeth',
middleName: 'M.',
lastName: 'Johnson',
);
final robertLWilliamsSr = Contact(
id: 33,
firstName: 'Robert',
middleName: 'L.',
lastName: 'Williams',
suffix: 'Sr.',
);
final margaretAnneDavis = Contact(
id: 34,
firstName: 'Margaret',
middleName: 'Anne',
lastName: 'Davis',
);
final williamJamesBrownIII = Contact(
id: 35,
firstName: 'William',
middleName: 'James',
lastName: 'Brown',
suffix: 'III',
);
final maryElizabethClark = Contact(
id: 36,
firstName: 'Mary',
middleName: 'Elizabeth',
lastName: 'Clark',
);
final drSarahWatson = Contact(
id: 37,
firstName: 'Dr. Sarah',
lastName: 'Watson',
);
final jamesRSmithEsq = Contact(
id: 38,
firstName: 'James',
middleName: 'R.',
lastName: 'Smith',
suffix: 'Esq.',
);
final mariaCruz = Contact(id: 39, firstName: 'Maria', lastName: 'Cruz');
final pierreMartin = Contact(id: 40, firstName: 'Pierre', lastName: 'Martin');
final yukiTanaka = Contact(id: 41, firstName: 'Yuki', lastName: 'Tanaka');
final hansSchmidt = Contact(id: 42, firstName: 'Hans', lastName: 'Schmidt');
final priyaPatel = Contact(id: 43, firstName: 'Priya', lastName: 'Patel');
final carlosGarcia = Contact(id: 44, firstName: 'Carlos', lastName: 'Garcia');
final ninaVolkova = Contact(id: 45, firstName: 'Nina', lastName: 'Volkova');
final jenniferAdams = Contact(id: 46, firstName: 'Jennifer', lastName: 'Adams');
final michaelBaker = Contact(id: 47, firstName: 'Michael', lastName: 'Baker');
final sarahCooper = Contact(id: 48, firstName: 'Sarah', lastName: 'Cooper');
final christopherDaniel = Contact(
id: 49,
firstName: 'Christopher',
lastName: 'Daniel',
);
final jessicaEdwards = Contact(
id: 50,
firstName: 'Jessica',
lastName: 'Edwards',
);
final Set<Contact> allContacts = {
johnAppleseed,
kateBell,
annaHaro,
danielHiggins,
davidTaylor,
hankZakroff,
alexAnderson,
benBrown,
carolCarter,
dianaDevito,
emilyEvans,
frankFisher,
graceGreen,
henryHall,
isaacIngram,
juliaJackson,
kevinKelly,
lindaLewis,
michaelMiller,
nancyNewman,
oliverOwens,
penelopeParker,
quentinQuinn,
rachelReed,
samuelSmith,
tessaTurner,
umbertoUpton,
victoriaVance,
williamWilson,
xavierXu,
yasmineYoung,
zacharyZimmerman,
elizabethMJohnson,
robertLWilliamsSr,
margaretAnneDavis,
williamJamesBrownIII,
maryElizabethClark,
drSarahWatson,
jamesRSmithEsq,
mariaCruz,
pierreMartin,
yukiTanaka,
hansSchmidt,
priyaPatel,
carlosGarcia,
ninaVolkova,
jenniferAdams,
michaelBaker,
sarahCooper,
christopherDaniel,
jessicaEdwards,
};
Estos datos de muestra incluyen contactos con y sin segundos nombres y sufijos. Esto te brinda una variedad de datos con los que trabajar a medida que construyes la UI.
Datos del grupo de contacto
#Ahora, crea los grupos de contactos que organizan tus contactos en listas.
Crea un nuevo archivo lib/data/contact_group.dart y
añade la clase ContactGroup:
import 'dart:collection';
import 'package:flutter/cupertino.dart';
import 'contact.dart';
class ContactGroup {
factory ContactGroup({
required int id,
required String label,
bool permanent = false,
String? title,
List<Contact>? contacts,
}) {
final contactsCopy = contacts ?? <Contact>[];
_sortContacts(contactsCopy);
return ContactGroup._internal(
id: id,
label: label,
permanent: permanent,
title: title,
contacts: contactsCopy,
);
}
ContactGroup._internal({
required this.id,
required this.label,
this.permanent = false,
String? title,
List<Contact>? contacts,
}) : title = title ?? label,
_contacts = contacts ?? const <Contact>[];
final int id;
final bool permanent;
final String label;
final String title;
final List<Contact> _contacts;
List<Contact> get contacts => _contacts;
AlphabetizedContactMap get alphabetizedContacts {
final contactsMap = AlphabetizedContactMap();
for (final contact in _contacts) {
final lastInitial = contact.lastName[0].toUpperCase();
if (contactsMap.containsKey(lastInitial)) {
contactsMap[lastInitial]!.add(contact);
} else {
contactsMap[lastInitial] = [contact];
}
}
return contactsMap;
}
}
Un ContactGroup representa una colección de contactos,
como "Todos los contactos" o "Favoritos".
Añade el siguiente código auxiliar y los datos de muestra a lib/data/contact_group.dart:
typedef AlphabetizedContactMap = SplayTreeMap<String, List<Contact>>;
/// Sorts a list of [contacts] alphabetically by
/// last name, then first name, then middle name.
/// If names are identical, sorts by contact ID to ensure consistent ordering.
void _sortContacts(List<Contact> contacts) {
contacts.sort((a, b) {
final checkLastName = a.lastName.compareTo(b.lastName);
if (checkLastName != 0) {
return checkLastName;
}
final checkFirstName = a.firstName.compareTo(b.firstName);
if (checkFirstName != 0) {
return checkFirstName;
}
if (a.middleName != null && b.middleName != null) {
final checkMiddleName = a.middleName!.compareTo(b.middleName!);
if (checkMiddleName != 0) {
return checkMiddleName;
}
} else if (a.middleName != null || b.middleName != null) {
return a.middleName != null ? 1 : -1;
}
// If both contacts have the exact same name, order by first created.
return a.id.compareTo(b.id);
});
}
final allPhone = ContactGroup(
id: 0,
permanent: true,
label: 'All iPhone',
title: 'iPhone',
contacts: allContacts.toList(),
);
final friends = ContactGroup(
id: 1,
label: 'Friends',
contacts: [allContacts.elementAt(3)],
);
final work = ContactGroup(id: 2, label: 'Work');
List<ContactGroup> generateSeedData() {
return [allPhone, friends, work];
}
Este código crea tres grupos de muestra y una función para generar los datos iniciales para la aplicación.
Finalmente, añade una clase que gestione los cambios de state a lib/data/contact_group.dart:
class ContactGroupsModel {
ContactGroupsModel() : _listsNotifier = ValueNotifier(generateSeedData());
final ValueNotifier<List<ContactGroup>> _listsNotifier;
ValueNotifier<List<ContactGroup>> get listsNotifier => _listsNotifier;
List<ContactGroup> get lists => _listsNotifier.value;
ContactGroup findContactList(int id) {
return lists[id];
}
void dispose() {
_listsNotifier.dispose();
}
}
Si no estás familiarizado con ValueNotifier,
deberías completar el tutorial anterior sobre el state
antes de continuar,
el cual cubre el State Management.
8
Conectar los datos a tu aplicación
Conectar los datos a tu aplicación
Actualiza tu main.dart para incluir el state global e
importar el nuevo archivo de datos:
import 'package:flutter/cupertino.dart';
import 'data/contact_group.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: CupertinoPageScaffold(child: Center(child: Text('Hello Rolodex!'))),
);
}
}
Con todo el código adicional fuera del camino, en la próxima lección, comenzarás a construir la aplicación en serio.
9
Revisión
Revisión
Qué lograste
Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.Vista previa de la aplicación Rolodex
Estás comenzando una nueva sección de tutoriales centrada en funciones avanzadas de UI. Para hacer que tu aplicación se sienta pulida y nativa en cualquier dispositivo, aprenderás sobre layouts adaptativos, slivers, navegación y tematización.
Configuración de un proyecto con widgets de Cupertino
A diferencia de las lecciones anteriores, esta aplicación utiliza CupertinoApp en lugar de MaterialApp. El sistema de diseño Cupertino proporciona widgets de estilo iOS que se sienten nativos en dispositivos Apple.
Modelos de datos creados para contactos y grupos
Creaste las clases Contact y ContactGroup con datos de muestra, además de un ContactGroupsModel
para el State Management. Esta base sustenta la UI que construirás en las próximas lecciones.
10
Ponte a prueba
Ponte a prueba
Quiz de Configuración Avanzada de UI
1 / 2CupertinoApp solo funciona en dispositivos iOS.
No exactamente.
CupertinoApp puede ejecutarse en cualquier plataforma; simplemente proporciona widgets al estilo iOS.
CupertinoApp proporciona widgets y estilos al estilo iOS, mientras que MaterialApp proporciona widgets de Material Design.
¡Así es!
CupertinoApp utiliza widgets del sistema de diseño Cupertino que coinciden con el aspecto y la sensación de iOS.
CupertinoApp es más ligera y tiene un mejor rendimiento.
No exactamente.
Ambas tienen un rendimiento similar; difieren en el estilo visual, no en la velocidad.
MaterialApp requiere más configuración para configurarse.
No exactamente.
Ambas tienen requisitos de configuración similares; simplemente utilizan diferentes sistemas de diseño.
Para validar los valores de entrada del usuario.
No exactamente.
ValueNotifier almacena y notifica sobre cambios de valor, no realiza validaciones.
Para almacenar valores permanentemente en el almacenamiento local.
No exactamente.
ValueNotifier mantiene los valores en memoria; la persistencia requiere una implementación separada.
Para convertir valores entre diferentes tipos de datos.
No exactamente.
La conversión de tipos no es el propósito de ValueNotifier.
Para mantener un único valor y notificar a los escuchas cuando ese valor cambia.
¡Así es!
ValueNotifier es un ChangeNotifier simple que envuelve un único valor y notifica a los escuchas cuando cambia.
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.