Cosa sono i Sliver
In Flutter, un Sliver è una porzione di un'area scorrevole. Il termine inglese significa letteralmente "scheggia" o "fettina": ogni Sliver rappresenta infatti una sezione che può comportarsi in modo diverso durante lo scorrimento. Mentre widget come ListView o GridView coprono i casi più comuni, quando hai bisogno di combinare più elementi scorrevoli con effetti sofisticati (app bar che si comprime, header che restano agganciati, liste e griglie nella stessa vista) i Sliver diventano lo strumento giusto.
Il cuore di tutto è il widget CustomScrollView, che accetta una lista di Sliver e li dispone lungo un unico asse di scorrimento condiviso.
Il primo CustomScrollView
Vediamo un esempio base che combina un'app bar collassabile e una lista:
CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Esplora'),
background: Image.network(
'https://picsum.photos/600/300',
fit: BoxFit.cover,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
leading: const Icon(Icons.star),
title: Text('Elemento $index'),
),
childCount: 30,
),
),
],
)
Nota le proprietà di SliverAppBar:
expandedHeight: l'altezza massima quando è completamente espansa.pinned: setrue, l'app bar resta visibile (agganciata) in alto anche dopo il collasso.floating: setrue, l'app bar riappare appena scorri verso l'alto, senza dover arrivare in cima.snap(usabile confloating: true): completa automaticamente l'animazione di apertura/chiusura.
SliverList e SliverGrid
I due Sliver più usati per i contenuti sono SliverList e SliverGrid. Entrambi accettano un delegate che descrive come costruire i figli.
Per una griglia:
SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1,
),
delegate: SliverChildBuilderDelegate(
(context, index) => Container(
color: Colors.teal[(index % 9 + 1) * 100],
alignment: Alignment.center,
child: Text('$index'),
),
childCount: 24,
),
)
Esiste anche il pratico costruttore SliverList.builder e SliverGrid.builder introdotti per semplificare la sintassi, evitando di scrivere esplicitamente il delegate:
SliverList.builder(
itemCount: 30,
itemBuilder: (context, index) => ListTile(
title: Text('Voce $index'),
),
)
Inserire widget non scorrevoli con SliverToBoxAdapter
Spesso vuoi inserire un widget normale (non un Sliver) all'interno di un CustomScrollView, ad esempio un banner o un titolo di sezione. Per farlo si usa SliverToBoxAdapter:
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'In evidenza',
style: Theme.of(context).textTheme.headlineSmall,
),
),
)
Fai attenzione: SliverToBoxAdapter costruisce il suo figlio tutto in una volta, quindi non è adatto a liste lunghe. Per quelle usa sempre SliverList.
Header agganciati con SliverPersistentHeader
Uno degli effetti più richiesti è l'header di sezione che resta "appiccicato" in cima mentre scorri (il classico sticky header). Si ottiene con SliverPersistentHeader e un delegate personalizzato:
class _SezioneHeader extends SliverPersistentHeaderDelegate {
final String titolo;
_SezioneHeader(this.titolo);
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Theme.of(context).colorScheme.surface,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
titolo,
style: Theme.of(context).textTheme.titleMedium,
),
);
}
@override
double get maxExtent => 48;
@override
double get minExtent => 48;
@override
bool shouldRebuild(_SezioneHeader oldDelegate) =>
oldDelegate.titolo != titolo;
}
E poi nel CustomScrollView:
SliverPersistentHeader(
pinned: true,
delegate: _SezioneHeader('Categoria A'),
)
Con pinned: true l'header rimane agganciato in cima; con floating: true ricompare allo scroll verso l'alto.
Aggiungere padding ai Sliver
Non puoi avvolgere un Sliver in un normale Padding: ti serve SliverPadding:
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverList.builder(
itemCount: 10,
itemBuilder: (context, i) => Text('Riga $i'),
),
)
Riempire lo spazio rimanente
Quando una lista è più corta dello schermo e vuoi che un widget occupi lo spazio residuo (utile per stati vuoti), usa SliverFillRemaining:
SliverFillRemaining(
hasScrollBody: false,
child: Center(
child: Text('Non ci sono altri elementi'),
),
)
Best practice
- Usa i builder: con
SliverChildBuilderDelegateo i costruttori.builderi figli vengono creati pigramente (lazy), risparmiando memoria. - Evita
SliverToBoxAdapterper le liste: rende inutile la costruzione lazy. - Un solo asse di scorrimento: tutti i Sliver in un
CustomScrollViewcondividono lo stessoScrollController. Per scroll annidati su assi diversi valutaNestedScrollView. - Definisci bene min/maxExtent nei delegate persistenti: valori errati causano errori di layout.
Conclusioni
I Sliver sono la chiave per costruire interfacce di scorrimento veramente flessibili in Flutter. Con CustomScrollView puoi combinare app bar collassabili, header agganciati, liste, griglie e widget statici in un'unica esperienza fluida. Una volta compreso il meccanismo di delegate e extent, avrai a disposizione un sistema potente per dare carattere alle tue schermate scorrevoli.
