[{"data":1,"prerenderedAt":73},["ShallowReactive",2],{"tutorial-menu-contestuali-e-azioni-al-tocco-prolungato-in-flutter-con-gesturedetector-e-popupmenubutton":3,"comments-tutorial-menu-contestuali-e-azioni-al-tocco-prolungato-in-flutter-con-gesturedetector-e-popupmenubutton":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},58,"Menu contestuali e azioni al tocco prolungato in Flutter con GestureDetector e PopupMenuButton","menu-contestuali-e-azioni-al-tocco-prolungato-in-flutter-con-gesturedetector-e-popupmenubutton","Impara a intercettare tap, long press e gesti personalizzati in Flutter per mostrare menu contestuali dinamici, proprio come nelle app native.","I gesti sono il cuore dell'interazione in un'app mobile. In questo tutorial vedremo come gestire i vari tipi di tocco in Flutter usando `GestureDetector`, come mostrare un menu contestuale con `PopupMenuButton` e come costruire un menu che appare al tocco prolungato in una posizione arbitraria dello schermo tramite `showMenu`.\n\nAlla fine avrai una lista di elementi con azioni contestuali (modifica, condividi, elimina) attivabili con un long press, un pattern usato in moltissime app reali.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F7b1a1242-dde7-4013-a00e-b52f052359fc.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1550921464-9f3de7011af0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMjcyOTF8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Enrique Vidal Flores","https:\u002F\u002Funsplash.com\u002F@enriquisimotv","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fperson-touching-nintendo-switch-_mKKCGtwI94",null,"intermediate",35,"3.x","published","2026-08-09T04:30:49+00:00","Menu contestuali e gesti in Flutter con GestureDetector","Guida pratica ai gesti in Flutter: gestisci tap e long press con GestureDetector e mostra menu contestuali con PopupMenuButton e showMenu.",{"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},390,"Capire i gesti disponibili con GestureDetector","`GestureDetector` è un widget invisibile che avvolge un figlio e ne intercetta i gesti. I callback più usati sono:\n\n- `onTap`: tocco singolo\n- `onDoubleTap`: doppio tocco\n- `onLongPress`: tocco prolungato\n- `onLongPressStart`: fornisce la posizione globale del tocco\n\nPartiamo con un esempio semplice che reagisce ai tre gesti principali mostrando uno `SnackBar`.","GestureDetector(\n  onTap: () => _mostraMessaggio(context, 'Tap singolo'),\n  onDoubleTap: () => _mostraMessaggio(context, 'Doppio tap'),\n  onLongPress: () => _mostraMessaggio(context, 'Long press'),\n  child: Container(\n    padding: const EdgeInsets.all(24),\n    color: Colors.indigo.shade50,\n    child: const Text('Prova i gesti qui'),\n  ),\n);\n\nvoid _mostraMessaggio(BuildContext context, String testo) {\n  ScaffoldMessenger.of(context).showSnackBar(\n    SnackBar(content: Text(testo), duration: const Duration(milliseconds: 800)),\n  );\n}","dart","Toccando il riquadro appare uno SnackBar diverso a seconda del gesto eseguito.",{"id":38,"position":39,"title":40,"body":41,"code_snippet":42,"code_language":35,"expected_result":43,"demo_url":15,"video_url":15},391,2,"Preparare il modello dati e la lista","Creiamo una semplice lista di note su cui applicheremo i menu contestuali. Definiamo un modello `Nota` e uno `StatefulWidget` che mantiene la lista in memoria.","class Nota {\n  final int id;\n  String titolo;\n  Nota({required this.id, required this.titolo});\n}\n\nclass NotePage extends StatefulWidget {\n  const NotePage({super.key});\n  @override\n  State\u003CNotePage> createState() => _NotePageState();\n}\n\nclass _NotePageState extends State\u003CNotePage> {\n  final List\u003CNota> _note = [\n    Nota(id: 1, titolo: 'Spesa settimanale'),\n    Nota(id: 2, titolo: 'Idee per il progetto'),\n    Nota(id: 3, titolo: 'Chiamare il dentista'),\n  ];\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Le mie note')),\n      body: ListView.builder(\n        itemCount: _note.length,\n        itemBuilder: (context, index) {\n          final nota = _note[index];\n          return ListTile(title: Text(nota.titolo));\n        },\n      ),\n    );\n  }\n}","Viene visualizzata una schermata con tre note in una lista scorrevole.",{"id":45,"position":46,"title":47,"body":48,"code_snippet":49,"code_language":35,"expected_result":50,"demo_url":15,"video_url":15},392,3,"Aggiungere un menu contestuale con PopupMenuButton","`PopupMenuButton` è il modo più rapido per aggiungere un menu di azioni tramite un'icona (di solito i tre puntini). Aggiungiamolo come `trailing` di ogni `ListTile`. Il callback `onSelected` riceve il valore dell'elemento scelto.","ListTile(\n  title: Text(nota.titolo),\n  trailing: PopupMenuButton\u003CString>(\n    icon: const Icon(Icons.more_vert),\n    onSelected: (valore) => _gestisciAzione(valore, nota),\n    itemBuilder: (context) => const [\n      PopupMenuItem(\n        value: 'modifica',\n        child: ListTile(leading: Icon(Icons.edit), title: Text('Modifica')),\n      ),\n      PopupMenuItem(\n        value: 'condividi',\n        child: ListTile(leading: Icon(Icons.share), title: Text('Condividi')),\n      ),\n      PopupMenuDivider(),\n      PopupMenuItem(\n        value: 'elimina',\n        child: ListTile(\n          leading: Icon(Icons.delete, color: Colors.red),\n          title: Text('Elimina', style: TextStyle(color: Colors.red)),\n        ),\n      ),\n    ],\n  ),\n)","Ogni nota mostra un'icona a tre puntini che, se toccata, apre un menu con le azioni Modifica, Condividi ed Elimina.",{"id":52,"position":53,"title":54,"body":55,"code_snippet":56,"code_language":35,"expected_result":57,"demo_url":15,"video_url":15},393,4,"Implementare la logica delle azioni","Ora scriviamo il metodo `_gestisciAzione` che reagisce alla scelta dell'utente. Per l'eliminazione mostriamo un dialogo di conferma, per la modifica un semplice `AlertDialog` con un campo di testo.","void _gestisciAzione(String azione, Nota nota) {\n  switch (azione) {\n    case 'modifica':\n      _modificaNota(nota);\n      break;\n    case 'condividi':\n      ScaffoldMessenger.of(context).showSnackBar(\n        SnackBar(content: Text('Condivido: ${nota.titolo}')),\n      );\n      break;\n    case 'elimina':\n      _confermaEliminazione(nota);\n      break;\n  }\n}\n\nFuture\u003Cvoid> _confermaEliminazione(Nota nota) async {\n  final conferma = await showDialog\u003Cbool>(\n    context: context,\n    builder: (ctx) => AlertDialog(\n      title: const Text('Eliminare la nota?'),\n      content: Text('Vuoi eliminare \"${nota.titolo}\"?'),\n      actions: [\n        TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Annulla')),\n        TextButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Elimina')),\n      ],\n    ),\n  );\n  if (conferma == true) {\n    setState(() => _note.removeWhere((n) => n.id == nota.id));\n  }\n}\n\nFuture\u003Cvoid> _modificaNota(Nota nota) async {\n  final controller = TextEditingController(text: nota.titolo);\n  final nuovo = await showDialog\u003CString>(\n    context: context,\n    builder: (ctx) => AlertDialog(\n      title: const Text('Modifica nota'),\n      content: TextField(controller: controller, autofocus: true),\n      actions: [\n        TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Annulla')),\n        TextButton(onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('Salva')),\n      ],\n    ),\n  );\n  if (nuovo != null && nuovo.trim().isNotEmpty) {\n    setState(() => nota.titolo = nuovo.trim());\n  }\n}","Selezionando Modifica si può rinominare la nota, Condividi mostra uno SnackBar ed Elimina chiede conferma prima di rimuovere l'elemento.",{"id":59,"position":60,"title":61,"body":62,"code_snippet":63,"code_language":35,"expected_result":64,"demo_url":15,"video_url":15},394,5,"Mostrare il menu al long press con showMenu","Un pattern molto comune è far comparire il menu contestuale premendo a lungo sull'elemento, esattamente nel punto in cui l'utente ha toccato. Usiamo `onLongPressStart` per catturare la posizione globale e la funzione `showMenu`, che accetta un `RelativeRect` per posizionare il popup.\n\nAvvolgiamo il `ListTile` in un `GestureDetector` e riutilizziamo la logica delle azioni.","Future\u003Cvoid> _mostraMenuContestuale(Offset posizione, Nota nota) async {\n  final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;\n  final valore = await showMenu\u003CString>(\n    context: context,\n    position: RelativeRect.fromRect(\n      posizione & const Size(40, 40),\n      Offset.zero & overlay.size,\n    ),\n    items: const [\n      PopupMenuItem(value: 'modifica', child: Text('Modifica')),\n      PopupMenuItem(value: 'condividi', child: Text('Condividi')),\n      PopupMenuItem(value: 'elimina', child: Text('Elimina')),\n    ],\n  );\n  if (valore != null) _gestisciAzione(valore, nota);\n}\n\n\u002F\u002F Nell'itemBuilder della ListView:\nreturn GestureDetector(\n  onLongPressStart: (dettagli) =>\n      _mostraMenuContestuale(dettagli.globalPosition, nota),\n  child: ListTile(title: Text(nota.titolo)),\n);","Tenendo premuto su una nota, il menu contestuale appare vicino al punto toccato con le stesse azioni.",{"id":66,"position":67,"title":68,"body":69,"code_snippet":70,"code_language":35,"expected_result":71,"demo_url":15,"video_url":15},395,6,"Feedback aptico e considerazioni finali","Per rendere l'esperienza più naturale, aggiungiamo un leggero feedback aptico all'apertura del menu tramite `HapticFeedback`, incluso nel pacchetto `services` di Flutter.\n\n**Best practice:**\n- Preferisci `PopupMenuButton` per un menu sempre visibile (icona), `showMenu` + long press per azioni \"nascoste\".\n- Non abusare del doppio tap: molti utenti non lo scoprono.\n- Fornisci sempre un feedback visivo o aptico per confermare che il gesto è stato riconosciuto.\n- Ricorda che `GestureDetector` non aggiunge effetti ripple: se vuoi il feedback Material usa `InkWell`.","import 'package:flutter\u002Fservices.dart';\n\nonLongPressStart: (dettagli) {\n  HapticFeedback.mediumImpact();\n  _mostraMenuContestuale(dettagli.globalPosition, nota);\n},","Il long press produce una leggera vibrazione prima di mostrare il menu, migliorando la percezione dell'interazione.",[],1789205528261]