Slidable e swipe-to-action nelle liste Flutter con flutter_slidable
GuidePrincipiante25 min Flutter 3.x

Slidable e swipe-to-action nelle liste Flutter con flutter_slidable

Introduzione

Le liste sono ovunque nelle app moderne: email, chat, task list. Un pattern UX molto diffuso è quello di rivelare azioni contestuali (elimina, archivia, condividi) facendo scorrere lateralmente un elemento della lista.

In questo tutorial impareremo a usare il pacchetto flutter_slidable, la soluzione più diffusa e flessibile per implementare gli swipe-to-action in Flutter.

Alla fine avremo una lista di attività in cui potremo scorrere ogni elemento verso sinistra o destra per rivelare pulsanti di azione, con animazioni fluide e comportamenti personalizzabili.

  1. 1

    Aggiungere la dipendenza flutter_slidable

    Per prima cosa aggiungiamo il pacchetto al nostro progetto. Apri il terminale nella cartella del progetto ed esegui il comando di installazione, oppure aggiungi manualmente la dipendenza nel file pubspec.yaml.

    Dopo aver salvato, esegui flutter pub get per scaricare il pacchetto.

    flutter pub add flutter_slidable
    
    # oppure in pubspec.yaml:
    # dependencies:
    #   flutter_slidable: ^3.1.0

    Risultato atteso

    Il pacchetto flutter_slidable compare tra le dipendenze e viene scaricato correttamente.

  2. 2

    Preparare un modello dati e la lista di base

    Creiamo un semplice modello Task e uno StatefulWidget che mostra una ListView di attività. Partiamo da una lista standard, senza ancora le azioni scorrevoli, per avere una base su cui lavorare.

    import 'package:flutter/material.dart';
    
    class Task {
      final String id;
      final String title;
      bool archived;
    
      Task({required this.id, required this.title, this.archived = false});
    }
    
    class TaskListPage extends StatefulWidget {
      const TaskListPage({super.key});
    
      @override
      State<TaskListPage> createState() => _TaskListPageState();
    }
    
    class _TaskListPageState extends State<TaskListPage> {
      final List<Task> _tasks = List.generate(
        8,
        (i) => Task(id: '$i', title: 'Attività numero ${i + 1}'),
      );
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Le mie attività')),
          body: ListView.builder(
            itemCount: _tasks.length,
            itemBuilder: (context, index) {
              final task = _tasks[index];
              return ListTile(
                leading: const Icon(Icons.task_alt),
                title: Text(task.title),
              );
            },
          ),
        );
      }
    }

    Risultato atteso

    L'app mostra una lista con 8 attività statiche, senza interazioni di swipe.

  3. 3

    Avvolgere l'elemento con il widget Slidable

    Ora avvolgiamo ogni ListTile in un widget Slidable. Ogni Slidable deve avere una key univoca. Aggiungiamo un endActionPane (che appare scorrendo da destra verso sinistra) con un'azione di eliminazione.

    Ricordati di importare il pacchetto con import 'package:flutter_slidable/flutter_slidable.dart';.

    import 'package:flutter_slidable/flutter_slidable.dart';
    
    // dentro itemBuilder:
    return Slidable(
      key: ValueKey(task.id),
      endActionPane: ActionPane(
        motion: const DrawerMotion(),
        children: [
          SlidableAction(
            onPressed: (context) {
              setState(() => _tasks.removeAt(index));
            },
            backgroundColor: Colors.red,
            foregroundColor: Colors.white,
            icon: Icons.delete,
            label: 'Elimina',
          ),
        ],
      ),
      child: ListTile(
        leading: const Icon(Icons.task_alt),
        title: Text(task.title),
      ),
    );

    Risultato atteso

    Scorrendo un elemento da destra verso sinistra compare un pulsante rosso 'Elimina' che rimuove l'attività dalla lista.

  4. 4

    Aggiungere azioni iniziali (startActionPane)

    Oltre alle azioni finali, possiamo aggiungere azioni che appaiono scorrendo da sinistra verso destra tramite startActionPane. Aggiungiamo un'azione di archiviazione che modifica lo stato del task.

    Ogni ActionPane supporta diverse animazioni tramite la proprietà motion: DrawerMotion, BehindMotion, ScrollMotion e StretchMotion. Prova a cambiarle per vedere l'effetto.

    Slidable(
      key: ValueKey(task.id),
      startActionPane: ActionPane(
        motion: const BehindMotion(),
        children: [
          SlidableAction(
            onPressed: (context) {
              setState(() => task.archived = !task.archived);
            },
            backgroundColor: Colors.blue,
            foregroundColor: Colors.white,
            icon: Icons.archive,
            label: task.archived ? 'Ripristina' : 'Archivia',
          ),
        ],
      ),
      endActionPane: ActionPane(
        motion: const DrawerMotion(),
        children: [
          SlidableAction(
            onPressed: (context) {
              setState(() => _tasks.removeAt(index));
            },
            backgroundColor: Colors.red,
            foregroundColor: Colors.white,
            icon: Icons.delete,
            label: 'Elimina',
          ),
        ],
      ),
      child: ListTile(
        leading: Icon(
          task.archived ? Icons.inventory_2 : Icons.task_alt,
          color: task.archived ? Colors.blue : null,
        ),
        title: Text(task.title),
        subtitle: task.archived ? const Text('Archiviata') : null,
      ),
    )

    Risultato atteso

    Scorrendo da sinistra verso destra appare un pulsante blu che archivia/ripristina l'attività aggiornando l'icona e il sottotitolo.

  5. 5

    Abilitare la conferma con lo swipe completo (DismissiblePane)

    Un'esperienza ancora più fluida consiste nel completare l'azione con uno swipe deciso, senza dover premere il pulsante. Basta assegnare un DismissiblePane all'ActionPane.

    È importante che il callback onDismissed esegua effettivamente la rimozione, altrimenti il widget resterebbe in uno stato incoerente. Mostriamo anche uno SnackBar per informare l'utente.

    endActionPane: ActionPane(
      motion: const DrawerMotion(),
      dismissible: DismissiblePane(
        onDismissed: () {
          final removed = _tasks[index];
          setState(() => _tasks.removeAt(index));
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('${removed.title} eliminata')),
          );
        },
      ),
      children: [
        SlidableAction(
          onPressed: (context) {
            setState(() => _tasks.removeAt(index));
          },
          backgroundColor: Colors.red,
          foregroundColor: Colors.white,
          icon: Icons.delete,
          label: 'Elimina',
        ),
      ],
    ),

    Risultato atteso

    Uno swipe deciso verso sinistra elimina direttamente l'elemento e mostra una SnackBar di conferma.

  6. 6

    Gestire più azioni e chiudere gli altri Slidable

    Possiamo inserire più SlidableAction nello stesso pane (ad esempio Condividi + Elimina) e usare flex per bilanciarne la larghezza. Inoltre, avvolgendo la lista in uno SlidableAutoCloseBehavior, quando apriamo un elemento gli altri si chiudono automaticamente, migliorando l'usabilità.

    // Avvolgi la ListView:
    body: SlidableAutoCloseBehavior(
      child: ListView.builder(
        itemCount: _tasks.length,
        itemBuilder: (context, index) {
          // ... i tuoi Slidable ...
        },
      ),
    ),
    
    // endActionPane con due azioni:
    endActionPane: ActionPane(
      motion: const StretchMotion(),
      children: [
        SlidableAction(
          flex: 1,
          onPressed: (context) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('Condivisione...')),
            );
          },
          backgroundColor: Colors.green,
          foregroundColor: Colors.white,
          icon: Icons.share,
          label: 'Condividi',
        ),
        SlidableAction(
          flex: 1,
          onPressed: (context) => setState(() => _tasks.removeAt(index)),
          backgroundColor: Colors.red,
          foregroundColor: Colors.white,
          icon: Icons.delete,
          label: 'Elimina',
        ),
      ],
    ),

    Risultato atteso

    Ogni elemento mostra due azioni (Condividi ed Elimina) e aprendone uno gli altri Slidable già aperti si chiudono automaticamente.

  7. 7

    Rifiniture finali e best practice

    Conclusioni

    Hai implementato una lista con azioni scorrevoli complete usando flutter_slidable. Ecco alcune buone pratiche da tenere a mente:

    • Chiavi univoche: assegna sempre una key stabile a ogni Slidable, soprattutto se gli elementi vengono riordinati o rimossi.
    • Feedback all'utente: usa SnackBar con azione Annulla per le operazioni distruttive come l'eliminazione.
    • Coerenza dello stato: aggiorna sempre la sorgente dati (_tasks) prima di far ricostruire la lista.
    • Accessibilità: le azioni via swipe non sono sempre scopribili; valuta di affiancare un menu contestuale (long press) per gli stessi comandi.
    • SlidableAutoCloseBehavior: mantiene l'interfaccia pulita chiudendo automaticamente gli elementi non attivi.

    Con questi accorgimenti la tua lista sarà professionale e piacevole da usare.

    // Esempio di SnackBar con azione Annulla
    void _deleteWithUndo(int index) {
      final removed = _tasks[index];
      setState(() => _tasks.removeAt(index));
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('${removed.title} eliminata'),
          action: SnackBarAction(
            label: 'Annulla',
            onPressed: () {
              setState(() => _tasks.insert(index, removed));
            },
          ),
        ),
      );
    }

    Risultato atteso

    L'app permette di eliminare un'attività con possibilità di annullare l'operazione tramite la SnackBar.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!