[{"data":1,"prerenderedAt":73},["ShallowReactive",2],{"tutorial-drag-drop-tra-liste-diverse-in-flutter-con-draggable-e-dragtarget":3,"comments-tutorial-drag-drop-tra-liste-diverse-in-flutter-con-draggable-e-dragtarget":72},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":15,"difficulty":16,"estimated_minutes":17,"flutter_version":18,"status":19,"published_at":20,"meta_title":21,"meta_description":22,"category":23,"author":27,"steps":29},64,"Drag & drop tra liste diverse in Flutter con Draggable e DragTarget","drag-drop-tra-liste-diverse-in-flutter-con-draggable-e-dragtarget","Impara a costruire un'interfaccia drag & drop in Flutter usando i widget Draggable e DragTarget per spostare elementi tra colonne, come in una board Kanban.","Il drag & drop è un pattern di interazione molto potente per rendere le tue app più dinamiche e intuitive. Pensa a una board Kanban (come Trello) dove trascini le card tra colonne \"Da fare\", \"In corso\" e \"Completato\".\n\nIn questo tutorial useremo i widget nativi di Flutter `Draggable` e `DragTarget`, senza pacchetti esterni, per costruire una mini board Kanban con tre colonne e la possibilità di spostare le card da una colonna all'altra semplicemente trascinandole.\n\nAl termine avrai capito come funziona il feedback visivo durante il trascinamento e come aggiornare lo stato quando un elemento viene rilasciato.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F86a2550f-57d5-4702-bd29-4884ab9d32d5.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1589081939249-272f962229e0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMjczMDl8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Pete Lead","https:\u002F\u002Funsplash.com\u002F@petelead","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fsticky-notes-on-white-wall-kZzkvwoBz-M",null,"intermediate",35,"3.x","published","2026-08-15T04:30:46+00:00","Drag & Drop in Flutter con Draggable e DragTarget","Guida pratica per creare un drag & drop tra liste in Flutter usando Draggable e DragTarget: costruisci una board Kanban passo dopo passo.",{"id":24,"name":25,"slug":26},1,"Guide","guide",{"id":24,"name":28},"Flutter Bot",[30,37,44,51,58,65],{"id":31,"position":24,"title":32,"body":33,"code_snippet":34,"code_language":35,"expected_result":36,"demo_url":15,"video_url":15},427,"Definire il modello dei dati","Iniziamo modellando i dati della nostra board. Ogni card è un semplice oggetto `Task` con un titolo. Ogni colonna è identificata da un enum `TaskStatus`.\n\nUsiamo una `Map` per raggruppare i task per stato: questa struttura ci renderà facile spostare un elemento da una colonna all'altra.","enum TaskStatus { todo, inProgress, done }\n\nextension TaskStatusLabel on TaskStatus {\n  String get label => switch (this) {\n        TaskStatus.todo => 'Da fare',\n        TaskStatus.inProgress => 'In corso',\n        TaskStatus.done => 'Completato',\n      };\n}\n\nclass Task {\n  final String id;\n  final String title;\n\n  const Task({required this.id, required this.title});\n}","dart","Hai un modello Task e un enum TaskStatus con etichette leggibili per ogni colonna.",{"id":38,"position":39,"title":40,"body":41,"code_snippet":42,"code_language":35,"expected_result":43,"demo_url":15,"video_url":15},428,2,"Creare lo StatefulWidget della board","Creiamo un `StatefulWidget` che mantiene lo stato della board: una mappa che associa ogni `TaskStatus` a una lista di `Task`.\n\nDefiniamo anche un metodo `_moveTask` che rimuove un task dalla colonna di origine e lo aggiunge a quella di destinazione, chiamando `setState` per aggiornare l'interfaccia.","class KanbanBoard extends StatefulWidget {\n  const KanbanBoard({super.key});\n\n  @override\n  State\u003CKanbanBoard> createState() => _KanbanBoardState();\n}\n\nclass _KanbanBoardState extends State\u003CKanbanBoard> {\n  final Map\u003CTaskStatus, List\u003CTask>> _tasks = {\n    TaskStatus.todo: [\n      const Task(id: '1', title: 'Progettare la UI'),\n      const Task(id: '2', title: 'Scrivere i test'),\n    ],\n    TaskStatus.inProgress: [\n      const Task(id: '3', title: 'Implementare API'),\n    ],\n    TaskStatus.done: [\n      const Task(id: '4', title: 'Setup progetto'),\n    ],\n  };\n\n  void _moveTask(Task task, TaskStatus from, TaskStatus to) {\n    if (from == to) return;\n    setState(() {\n      _tasks[from]!.removeWhere((t) => t.id == task.id);\n      _tasks[to]!.add(task);\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return const Placeholder();\n  }\n}","Hai lo stato iniziale della board con alcune card distribuite nelle tre colonne.",{"id":45,"position":46,"title":47,"body":48,"code_snippet":49,"code_language":35,"expected_result":50,"demo_url":15,"video_url":15},429,3,"Rendere le card trascinabili con Draggable","Il widget `Draggable\u003CT>` permette di trascinare un widget. I parametri fondamentali sono:\n\n- `data`: il dato associato al trascinamento (qui il `Task`).\n- `feedback`: il widget mostrato sotto il dito durante il trascinamento.\n- `childWhenDragging`: cosa mostrare al posto dell'originale mentre lo trascini.\n- `child`: l'aspetto normale della card.\n\nCostruiamo un metodo `_buildCard` che avvolge la card in un `Draggable`.","Widget _buildCard(Task task) {\n  final card = Card(\n    margin: const EdgeInsets.symmetric(vertical: 6),\n    child: Padding(\n      padding: const EdgeInsets.all(12),\n      child: Text(task.title),\n    ),\n  );\n\n  return Draggable\u003CTask>(\n    data: task,\n    feedback: Material(\n      color: Colors.transparent,\n      child: SizedBox(\n        width: 200,\n        child: Opacity(opacity: 0.9, child: card),\n      ),\n    ),\n    childWhenDragging: Opacity(opacity: 0.3, child: card),\n    child: card,\n  );\n}","Le card ora possono essere trascinate e mostrano un feedback semitrasparente durante il trascinamento.",{"id":52,"position":53,"title":54,"body":55,"code_snippet":56,"code_language":35,"expected_result":57,"demo_url":15,"video_url":15},430,4,"Creare le zone di rilascio con DragTarget","Ogni colonna deve accettare le card trascinate. Usiamo `DragTarget\u003CTask>`, che espone tre callback utili:\n\n- `onWillAcceptWithDetails`: decide se accettare il dato (restituisce un bool).\n- `onAcceptWithDetails`: viene chiamato quando l'elemento viene rilasciato.\n- `builder`: costruisce la colonna e riceve la lista dei candidati in hover per cambiare aspetto visivo.\n\nColoriamo la colonna quando una card sta per essere rilasciata sopra di essa.","Widget _buildColumn(TaskStatus status) {\n  final tasks = _tasks[status]!;\n\n  return Expanded(\n    child: DragTarget\u003CTask>(\n      onWillAcceptWithDetails: (details) => true,\n      onAcceptWithDetails: (details) {\n        final task = details.data;\n        final from = _tasks.entries\n            .firstWhere((e) => e.value.any((t) => t.id == task.id))\n            .key;\n        _moveTask(task, from, status);\n      },\n      builder: (context, candidate, rejected) {\n        final isHovering = candidate.isNotEmpty;\n        return Container(\n          margin: const EdgeInsets.all(8),\n          padding: const EdgeInsets.all(8),\n          decoration: BoxDecoration(\n            color: isHovering\n                ? Colors.blue.withOpacity(0.15)\n                : Colors.grey.withOpacity(0.08),\n            borderRadius: BorderRadius.circular(12),\n            border: Border.all(\n              color: isHovering ? Colors.blue : Colors.transparent,\n              width: 2,\n            ),\n          ),\n          child: Column(\n            crossAxisAlignment: CrossAxisAlignment.stretch,\n            children: [\n              Padding(\n                padding: const EdgeInsets.all(8),\n                child: Text(\n                  status.label,\n                  style: const TextStyle(fontWeight: FontWeight.bold),\n                ),\n              ),\n              Expanded(\n                child: ListView(\n                  children: tasks.map(_buildCard).toList(),\n                ),\n              ),\n            ],\n          ),\n        );\n      },\n    ),\n  );\n}","Ogni colonna evidenzia il bordo blu quando trascini una card sopra di essa ed è pronta ad accettare il rilascio.",{"id":59,"position":60,"title":61,"body":62,"code_snippet":63,"code_language":35,"expected_result":64,"demo_url":15,"video_url":15},431,5,"Assemblare la board nel metodo build","Ora mettiamo insieme le tre colonne in una `Row` all'interno dello `Scaffold`. Ogni colonna è già `Expanded`, quindi occuperanno lo spazio in modo equo.\n\nAggiorniamo il metodo `build` del nostro `_KanbanBoardState`.","@override\nWidget build(BuildContext context) {\n  return Scaffold(\n    appBar: AppBar(title: const Text('Kanban Board')),\n    body: Row(\n      children: TaskStatus.values.map(_buildColumn).toList(),\n    ),\n  );\n}","Vedi tre colonne affiancate e puoi trascinare le card da una all'altra: al rilascio la card cambia colonna.",{"id":66,"position":67,"title":68,"body":69,"code_snippet":70,"code_language":35,"expected_result":71,"demo_url":15,"video_url":15},432,6,"Avviare l'app e provare il drag & drop","Infine colleghiamo la board a una `MaterialApp` e avviamo l'applicazione. Prova a trascinare una card da 'Da fare' a 'In corso' o 'Completato': lo stato si aggiornerà automaticamente grazie a `setState`.\n\n**Suggerimento**: per evitare eccezioni nella ricerca dello stato di origine, assicurati che ogni `Task` abbia un `id` univoco. Per una board più robusta potresti spostare la logica in un gestore di stato come Provider o Riverpod.","import 'package:flutter\u002Fmaterial.dart';\n\nvoid main() => runApp(const MyApp());\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return const MaterialApp(\n      debugShowCheckedModeBanner: false,\n      home: KanbanBoard(),\n    );\n  }\n}","L'app si avvia mostrando la board Kanban funzionante: le card si spostano tra le colonne con il drag & drop.",[],1789120601793]