Introduzione
Il drag & drop è un'interazione familiare per gli utenti: trascinare un elemento da un punto all'altro rende le interfacce più intuitive e coinvolgenti. Pensiamo a una board in stile Kanban, a un carrello della spesa dove si trascinano prodotti, o a una lista riordinabile.
Flutter offre un supporto nativo e completo per queste interazioni tramite i widget Draggable, DragTarget e LongPressDraggable, oltre al comodo ReorderableListView per i casi più comuni. In questa guida vedremo come usarli in pratica.
I widget fondamentali
Il sistema di drag & drop di Flutter si basa su due componenti principali:
Draggable: rende un widget trascinabile. Trasporta un valore (ildata) che verrà consegnato alla destinazione.DragTarget: definisce una zona in cui è possibile rilasciare gli elementi trascinati, decidendo se accettarli o meno.
Un esempio base con Draggable
Il widget Draggable richiede tre proprietà chiave: data (il valore trasportato), child (l'aspetto normale) e feedback (l'aspetto durante il trascinamento).
Draggable<int>(
data: 10,
feedback: Material(
color: Colors.transparent,
child: Container(
width: 80,
height: 80,
color: Colors.blue.withOpacity(0.7),
child: const Center(
child: Text('Trascina', style: TextStyle(color: Colors.white)),
),
),
),
childWhenDragging: Container(
width: 80,
height: 80,
color: Colors.grey.shade300,
),
child: Container(
width: 80,
height: 80,
color: Colors.blue,
child: const Center(
child: Text('Trascina', style: TextStyle(color: Colors.white)),
),
),
)
Alcune note importanti:
- Il
feedbackviene renderizzato al di fuori dell'albero normale, quindi va spesso avvolto in unMaterialper evitare artefatti visivi come il testo con sottolineatura gialla. childWhenDraggingsostituisce il widget originale durante il trascinamento (opzionale).
Ricevere gli elementi con DragTarget
Il DragTarget costruisce la propria UI in base agli elementi che stanno per essere rilasciati e reagisce quando avviene il drop.
int _totale = 0;
DragTarget<int>(
builder: (context, candidateData, rejectedData) {
final isHovering = candidateData.isNotEmpty;
return Container(
width: 150,
height: 150,
color: isHovering ? Colors.green.shade200 : Colors.green.shade50,
child: Center(
child: Text('Totale: $_totale'),
),
);
},
onWillAcceptWithDetails: (details) => details.data > 0,
onAcceptWithDetails: (details) {
setState(() {
_totale += details.data;
});
},
)
I callback principali sono:
builder: ricevecandidateData(gli elementi accettabili sopra il target) erejectedData(quelli rifiutati). Utile per dare feedback visivo durante l'hover.onWillAcceptWithDetails: decide se accettare o meno l'elemento in arrivo. Restituendofalse, l'elemento finirà tra irejectedData.onAcceptWithDetails: viene invocato quando l'elemento viene effettivamente rilasciato.
Nota: i vecchi callback
onWillAccepteonAcceptsono deprecati a favore delle versioni...WithDetails, che forniscono anche la posizione del drop tramitedetails.offset.
LongPressDraggable per le liste
Quando gli elementi trascinabili sono dentro una lista scorrevole, un Draggable normale entrerebbe in conflitto con lo scroll. La soluzione è LongPressDraggable, che avvia il trascinamento solo dopo una pressione prolungata.
LongPressDraggable<Task>(
data: task,
feedback: Material(
elevation: 6,
child: TaskCard(task: task),
),
child: TaskCard(task: task),
)
Riordinare liste con ReorderableListView
Per il caso comune del riordino di una lista, Flutter offre ReorderableListView, che gestisce internamente tutta la logica di drag & drop.
ReorderableListView(
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
children: [
for (final item in _items)
ListTile(
key: ValueKey(item),
title: Text(item),
),
],
)
Attenzione a due dettagli:
- Ogni figlio deve avere una
keyunivoca, altrimenti Flutter non riesce a tracciare gli spostamenti. - La correzione
newIndex -= 1quandonewIndex > oldIndexè necessaria perché l'elemento viene prima rimosso e poi reinserito.
Best practice
- Feedback chiaro: usa
childWhenDragginge cambia lo stile delDragTargetdurante l'hover per comunicare all'utente cosa sta accadendo. - Material per il feedback: avvolgi sempre il
feedbackin unMaterialper evitare problemi di rendering del testo. - Accessibilità: il drag & drop puro non è sempre accessibile; considera alternative (come pulsanti di spostamento) per gli utenti che usano screen reader.
- Prestazioni: mantieni leggero il widget
feedback, poiché viene ricostruito durante il movimento.
Conclusioni
Flutter rende il drag & drop sorprendentemente semplice da implementare. Con Draggable e DragTarget hai il controllo completo su interazioni personalizzate, mentre ReorderableListView copre il caso d'uso più frequente del riordino con poche righe di codice. Sperimenta con i feedback visivi per creare interfacce davvero fluide e intuitive.
