Scroll avanzado y slivers
Aprende a implementar scroll de alto rendimiento con slivers.
En esta lección, aprenderás sobre los slivers,
los cuales son widgets especiales que pueden aprovechar el
potente y compostable sistema de scroll de Flutter.
Los slivers te permiten crear efectos de scroll sofisticados,
incluyendo cabeceras colapsables, integración de búsqueda y comportamientos de scroll personalizados.
Al final de esta sección, comprenderás cómo
usar CustomScrollView, crear barras de navegación que se colapsan
y organizar el contenido en secciones con scroll.
Qué lograrás
Pasos
1
Slivers y widgets
Slivers y widgets
Los slivers son áreas con scroll que se pueden componer juntas en un
CustomScrollView u otras vistas de scroll.
Piensa en los slivers como bloques de construcción donde cada uno
aporta una parte del contenido total con scroll.
Aunque los slivers y los widgets son conceptos fundamentales de Flutter, sirven para propósitos diferentes y no son intercambiables.
-
Los Widgets son bloques de construcción generales de la UI que pueden usarse en cualquier lugar de tu árbol de widgets.
-
Los Slivers son widgets especializados diseñados específicamente para diseños con scroll y tienen algunas restricciones:
-
Los slivers solo pueden ser hijos directos de vistas de scroll, como
CustomScrollViewyNestedScrollView. -
Algunas vistas de scroll solo aceptan slivers como hijos. No puedes pasar widgets regulares a
CustomScrollView.slivers. -
Para usar widgets regulares dentro de un contexto de sliver, envuélvelos en
SliverToBoxAdapteroSliverFillRemaining.
Esta separación arquitectónica permite a Flutter optimizar el rendimiento del scroll mientras mantiene límites claros entre los diferentes tipos de componentes de la UI.
2
Añadir una estructura básica de slivers a los grupos de contactos
Añadir una estructura básica de slivers a los grupos de contactos
Primero, reemplaza el contenido del marcador de posición en tu página de grupos de contactos. Para evitar duplicar código entre el diseño del teléfono y la barra lateral de la tablet, puedes crear un widget privado y reutilizable.
Actualiza lib/screens/contact_groups.dart
agregando _ContactGroupsView al final del archivo.
import 'package:flutter/cupertino.dart';
import '../data/contact_group.dart';
import '../main.dart';
class ContactGroupsPage extends StatelessWidget {
const ContactGroupsPage({super.key});
@override
Widget build(BuildContext context) {
return _ContactGroupsView(
selectedListId: 0,
onListSelected: (list) {
debugPrint(list.toString());
},
);
}
}
// ···
class _ContactGroupsView extends StatelessWidget {
const _ContactGroupsView({required this.onListSelected, this.selectedListId});
final int? selectedListId;
final void Function(ContactGroup) onListSelected;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
backgroundColor: CupertinoColors.extraLightBackgroundGray,
child: CustomScrollView(
slivers: [
const CupertinoSliverNavigationBar(largeTitle: Text('Lists')),
SliverFillRemaining(
child: ValueListenableBuilder<List<ContactGroup>>(
valueListenable: contactGroupsModel.listsNotifier,
builder: (context, contactLists, child) {
return CupertinoListSection.insetGrouped(
header: const Text('iPhone'),
children: [
for (final ContactGroup contactList in contactLists)
CupertinoListTile(
title: Text(contactList.label),
onTap: () => onListSelected(contactList),
),
],
);
},
),
),
],
),
);
}
}
Este widget privado contiene la UI compartida para mostrar la lista de grupos de contactos. En pantallas pequeñas, se usará como una página, y en pantallas grandes se usará para llenar la columna izquierda.
Este widget introduce varios slivers:
CupertinoSliverNavigationBar: Una barra de navegación con opinión que se colapsa a medida que se hace scroll en la página.SliverList: Una lista con scroll de elementos.SliverFillRemaining: Un sliver que ocupa el espacio restante en el área de scroll, y cuyo hijo es un widget que no es sliver.
Acepta una función callback, onListSelected, para manejar toques,
lo que la hace adaptable tanto para navegación como para selección en la barra lateral.
Ahora, actualiza ContactGroupsPage en lib/screens/contact_groups.dart para usar tu nuevo
widget _ContactGroupsView:
class ContactGroupsPage extends StatelessWidget {
const ContactGroupsPage({super.key});
@override
Widget build(BuildContext context) {
return _ContactGroupsView(
selectedListId: 0,
onListSelected: (list) {
debugPrint(list.toString());
},
);
}
}
Esta estructura mantiene el ContactGroupsPage limpio y
enfocado en su responsabilidad principal: la navegación,
sobre la cual aprenderás en la siguiente sección de este tutorial.
3
Mejorar la lista con iconos y elementos visuales
Mejorar la lista con iconos y elementos visuales
Ahora, añade iconos y recuentos de contactos para hacer la lista más informativa.
Añade este método auxiliar _buildTrailing a tu
clase _ContactGroupsView en lib/screens/contact_groups.dart:
Widget _buildTrailing(List<Contact> contacts, BuildContext context) {
final TextStyle style = CupertinoTheme.of(
context,
).textTheme.textStyle.copyWith(color: CupertinoColors.systemGrey);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(contacts.length.toString(), style: style),
const Icon(
CupertinoIcons.forward,
color: CupertinoColors.systemGrey3,
size: 18,
),
],
);
}
Este ayudante crea el contenido final para cada elemento de la lista. Muestra el número de contactos y una flecha hacia adelante.
Ahora, actualiza el CupertinoListSection en _ContactGroupsView para
usar iconos y el ayudante final. Actualiza el código dentro del
callback ValueListenableBuilder.builder en el método build:
child: ValueListenableBuilder<List<ContactGroup>>(
valueListenable: contactGroupsModel.listsNotifier,
builder: (context, contactLists, child) {
const groupIcon = Icon(
CupertinoIcons.group,
weight: 900,
size: 32,
);
const pairIcon = Icon(
CupertinoIcons.person_2,
weight: 900,
size: 24,
);
return CupertinoListSection.insetGrouped(
header: const Text('iPhone'),
children: [
for (final ContactGroup contactList in contactLists)
CupertinoListTile(
leading: contactList.id == 0 ? groupIcon : pairIcon,
title: Text(contactList.label),
trailing: _buildTrailing(contactList.contacts, context),
onTap: () => onListSelected(contactList),
),
],
);
},
),
El código actualizado ahora muestra iconos que diferencian entre el grupo principal "All iPhone" y los grupos creados por el usuario, junto con el número de contactos e indicadores de navegación.
4
Crear scroll avanzado para contactos
Crear scroll avanzado para contactos
A continuación, implementarás la página de la lista de contactos.
En la siguiente lección, implementarás la navegación para pantallas pequeñas.
Para ver tu progreso en la página de lista de contactos mientras tanto, primero
actualiza lib/screens/adaptive_layout.dart para mostrar la página de lista de contactos:
import 'package:flutter/cupertino.dart';
import 'contacts.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 {
return const ContactListsPage(listId: 0); // New, temporary
}
},
);
}
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')),
],
),
),
);
}
}
Actualiza lib/screens/contacts.dart añadiendo _ContactListView al
final del archivo:
class _ContactListView extends StatelessWidget {
const _ContactListView({
required this.listId,
this.automaticallyImplyLeading = true,
});
final int listId;
final bool automaticallyImplyLeading;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: ValueListenableBuilder<List<ContactGroup>>(
valueListenable: contactGroupsModel.listsNotifier,
builder: (context, contactGroups, child) {
final contactList = contactGroupsModel.findContactList(listId);
return CustomScrollView(
slivers: [
CupertinoSliverNavigationBar(
largeTitle: Text(contactList.title),
automaticallyImplyLeading: automaticallyImplyLeading,
),
SliverFillRemaining(
child: Center(
child: Text(
'${contactList.contacts.length} contacts in ${contactList.label}',
),
),
),
],
);
},
),
);
}
}
Ahora, actualiza ContactListsPage para usar esta vista:
class ContactListsPage extends StatelessWidget {
const ContactListsPage({super.key, required this.listId});
final int listId;
@override
Widget build(BuildContext context) {
return _ContactListView(listId: listId);
}
}
Esta implementación básica demuestra cómo usar slivers con datos dinámicos en un componente reutilizable.
5
Añadir integración de búsqueda con slivers
Añadir integración de búsqueda con slivers
Ahora, mejora la página de contactos con una UI de funcionalidad de búsqueda integrada.
Actualiza el CustomScrollView en _ContactListView
para usar el
constructor CupertinoSliverNavigationBar.search en lugar del
constructor por defecto CupertinoSliverNavigationBar:
class _ContactListView extends StatelessWidget {
// ···
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: ValueListenableBuilder<List<ContactGroup>>(
valueListenable: contactGroupsModel.listsNotifier,
builder: (context, contactGroups, child) {
final contactList = contactGroupsModel.findContactList(listId);
return CustomScrollView(
slivers: [
// Now using a search bar:
CupertinoSliverNavigationBar.search(
largeTitle: Text(contactList.title),
searchField: const CupertinoSearchTextField(
suffixIcon: Icon(CupertinoIcons.mic_fill),
suffixMode: OverlayVisibilityMode.always,
),
),
SliverFillRemaining(
child: Center(
child: Text(
'${contactList.contacts.length} contacts in ${contactList.label}',
),
),
),
],
);
},
),
);
}
}
El constructor CupertinoSliverNavigationBar.search proporciona
una funcionalidad de búsqueda integrada. A medida que haces scroll hacia abajo,
el campo de búsqueda realiza la transición de forma suave hacia la barra de navegación colapsada.
6
Crear secciones de contactos ordenadas alfabéticamente
Crear secciones de contactos ordenadas alfabéticamente
Las aplicaciones de contactos del mundo real organizan los contactos alfabéticamente.
Para hacer esto, crea secciones para cada letra.
Añade el siguiente widget al final de tu archivo contacts.dart.
Este widget no contiene slivers.
class ContactListSection extends StatelessWidget {
const ContactListSection({
super.key,
required this.lastInitial,
required this.contacts,
});
final String lastInitial;
final List<Contact> contacts;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsetsDirectional.fromSTEB(20, 0, 20, 0),
child: Column(
children: [
const SizedBox(height: 15),
Align(
alignment: AlignmentDirectional.bottomStart,
child: Text(
lastInitial,
style: const TextStyle(
color: CupertinoColors.systemGrey,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
),
CupertinoListSection(
backgroundColor: CupertinoColors.systemBackground,
dividerMargin: 0,
additionalDividerMargin: 0,
topMargin: 4,
children: [
for (final Contact contact in contacts)
CupertinoListTile(
padding: const EdgeInsets.all(0),
title: Text('${contact.firstName} ${contact.lastName}'),
),
],
),
],
),
);
}
}
Este widget crea las secciones alfabéticas familiares que ves en la aplicación Contactos de iOS.
7
Usar SliverList para las secciones alfabetizadas
Usar SliverList para las secciones alfabetizadas
Ahora, reemplaza el contenido del marcador de posición en _ContactListView con
las secciones alfabetizadas:
class _ContactListView extends StatelessWidget {
// ···
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: ValueListenableBuilder<List<ContactGroup>>(
valueListenable: contactGroupsModel.listsNotifier,
builder: (context, contactGroups, child) {
final contactList = contactGroupsModel.findContactList(listId);
final contacts = contactList.alphabetizedContacts;
return CustomScrollView(
slivers: [
CupertinoSliverNavigationBar.search(
largeTitle: Text(contactList.title),
automaticallyImplyLeading: automaticallyImplyLeading,
searchField: const CupertinoSearchTextField(
suffixIcon: Icon(CupertinoIcons.mic_fill),
suffixMode: OverlayVisibilityMode.always,
),
),
SliverList.list(
children: [
const SizedBox(height: 20),
...contacts.keys.map(
(initial) => ContactListSection(
lastInitial: initial,
contacts: contacts[initial]!,
),
),
],
),
],
);
},
),
);
}
}
SliverList.list te permite proporcionar una lista de widgets que
pasan a formar parte del contenido con scroll. Esta es la forma más sencilla de
añadir una lista de widgets normales a un área de sliver con scroll.
En la siguiente lección, aprenderás sobre la navegación basada en pila y actualizarás la UI en pantallas pequeñas para navegar entre la vista de lista de contactos y la vista de contactos.
8
Revisión
Revisión
Qué lograste
Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.Entendí los slivers y cómo difieren de los widgets
Los slivers son widgets especializados para diseños con scroll. Solo pueden ser hijos directos de vistas de scroll como
CustomScrollView. En CustomScrollView y otros contextos de sliver, los widgets regulares deben envolverse en
SliverToBoxAdapter o SliverFillRemaining.
Construí layouts con scroll con CustomScrollView
CustomScrollView te permite componer múltiples slivers juntos. Usaste CupertinoSliverNavigationBar,
SliverFillRemaining y SliverList para crear interfaces con scroll sofisticadas.
Creé barras de navegación colapsables con búsqueda
Utilizaste el constructor CupertinoSliverNavigationBar.search para crear una barra de navegación colapsable con funcionalidad de búsqueda integrada.
Organicé contactos en secciones alfabetizadas
Creaste widgets ContactListSection agrupados por la inicial del apellido, luego usaste SliverList.list
para añadirlos al área con scroll. Esto refleja la experiencia familiar de la aplicación Contactos de iOS.
9
Ponte a prueba
Ponte a prueba
Quiz de slivers
1 / 2Los slivers son más rápidos de renderizar que los widgets normales.
No exactamente.
Ambos están optimizados; la diferencia es su propósito y contexto.
Los slivers pueden tener un número ilimitado de hijos.
No exactamente.
Algunos slivers como SliverList pueden tener muchos hijos, pero eso no es lo que los distingue.
Los slivers manejan automáticamente los gestos del usuario.
No exactamente.
El manejo de gestos es independiente; los slivers se encargan de la composición de layouts con scroll.
Los slivers son widgets especializados diseñados para diseños con scroll y solo pueden ser hijos directos de vistas de scroll.
¡Así es!
Los slivers funcionan dentro de vistas de scroll como CustomScrollView; los widgets regulares se pueden usar en cualquier lugar.
Pásalo a la propiedad
childen lugar deslivers.No exactamente.
CustomScrollView utiliza la propiedad slivers; no hay propiedad child para este propósito.
Envuélvelo en un SliverToBoxAdapter o SliverFillRemaining.
¡Así es!
Estos adaptadores convierten widgets regulares en slivers para que puedan usarse en contextos de sliver.
Solo agrégalo directamente; CustomScrollView acepta cualquier widget.
No exactamente.
CustomScrollView solo acepta slivers; los widgets regulares deben envolverse.
Convierte el widget en un sliver llamando a
.toSliver()en él.No exactamente.
No existe el método
.toSliver(); utilizas widgets adaptadores como SliverToBoxAdapter.
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.