[{"data":1,"prerenderedAt":85},["ShallowReactive",2],{"tutorial-gestire-scorciatoie-e-azioni-da-tastiera-in-flutter-con-shortcuts-e-actions":3,"comments-tutorial-gestire-scorciatoie-e-azioni-da-tastiera-in-flutter-con-shortcuts-e-actions":84},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"video":16,"difficulty":20,"estimated_minutes":21,"flutter_version":22,"status":23,"published_at":24,"meta_title":25,"meta_description":26,"category":27,"author":31,"steps":33},56,"Gestire scorciatoie e azioni da tastiera in Flutter con Shortcuts e Actions","gestire-scorciatoie-e-azioni-da-tastiera-in-flutter-con-shortcuts-e-actions","Impara a intercettare combinazioni di tasti in Flutter usando i widget Shortcuts, Actions e Intent per creare app desktop e web con supporto completo alla tastiera.","Le applicazioni desktop e web professionali richiedono un supporto solido alla tastiera: scorciatoie come `Ctrl+S` per salvare o `Ctrl+Z` per annullare sono ormai uno standard che gli utenti si aspettano.\n\nFlutter offre un sistema dichiarativo ed elegante per gestire le scorciatoie da tastiera basato su tre concetti chiave: **Shortcuts** (mappa combinazioni di tasti a `Intent`), **Actions** (mappa `Intent` a logica eseguibile) e **Intent** (l'intenzione astratta di un'azione).\n\nIn questo tutorial costruiremo un piccolo editor di testo con contatore che risponde a scorciatoie personalizzate, mostrandoti come separare in modo pulito la definizione dei tasti dalla logica applicativa.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F11b59927-0952-4047-9f3a-055920f9095e.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1656711132603-ebac427ee014?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMjcyODV8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Gavin Phillips","https:\u002F\u002Funsplash.com\u002F@gavinspavin","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-close-up-of-a-yellow-device-2PzwrWNcCB4","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=S_J9iJfd2RY",{"youtube_id":17,"duration_seconds":18,"published_at":19},"S_J9iJfd2RY",104,"2026-10-03T10:15:19+00:00","intermediate",35,"3.x","published","2026-08-07T04:30:59+00:00","Scorciatoie tastiera in Flutter con Shortcuts e Actions","Guida pratica per gestire combinazioni di tasti in Flutter con Shortcuts, Actions e Intent: crea app desktop e web con supporto completo alla tastiera.",{"id":28,"name":29,"slug":30},1,"Guide","guide",{"id":28,"name":32},"Flutter Bot",[34,42,49,56,63,70,77],{"id":35,"position":28,"title":36,"body":37,"code_snippet":38,"code_language":39,"expected_result":40,"demo_url":41,"video_url":41},376,"Comprendere il modello Intent \u002F Shortcuts \u002F Actions","Prima di scrivere codice è importante capire i tre pezzi del puzzle:\n\n- **Intent**: una classe che rappresenta *cosa* vuoi fare (es. `IncrementIntent`). Non contiene logica, è solo un'etichetta.\n- **Shortcuts**: un widget che associa una combinazione di tasti (`LogicalKeySet` o `SingleActivator`) a un `Intent`.\n- **Actions**: un widget che associa un `Intent` a un `Action`, ovvero la logica che viene realmente eseguita.\n\nQuesta separazione permette di riutilizzare gli stessi `Intent` con scorciatoie diverse su piattaforme diverse (ad esempio `Cmd` su macOS e `Ctrl` altrove).\n\nDefiniamo per prima cosa i nostri Intent.","import 'package:flutter\u002Fmaterial.dart';\n\n\u002F\u002F Ogni Intent rappresenta un'intenzione astratta.\nclass IncrementIntent extends Intent {\n  const IncrementIntent();\n}\n\nclass DecrementIntent extends Intent {\n  const DecrementIntent();\n}\n\nclass ResetIntent extends Intent {\n  const ResetIntent();\n}","dart","Hai tre classi Intent pronte a essere collegate a scorciatoie e azioni.",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},377,2,"Creare lo scaffold dell'app con lo stato del contatore","Costruiamo un semplice widget `StatefulWidget` che mantiene un contatore. In seguito collegheremo le scorciatoie ai metodi che modificano questo stato.\n\nPer ora l'interfaccia mostra solo il valore corrente e alcuni pulsanti tradizionali, così potrai confrontare i due approcci.","class ShortcutDemo extends StatefulWidget {\n  const ShortcutDemo({super.key});\n\n  @override\n  State\u003CShortcutDemo> createState() => _ShortcutDemoState();\n}\n\nclass _ShortcutDemoState extends State\u003CShortcutDemo> {\n  int _counter = 0;\n\n  void _increment() => setState(() => _counter++);\n  void _decrement() => setState(() => _counter--);\n  void _reset() => setState(() => _counter = 0);\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Shortcuts & Actions')),\n      body: Center(\n        child: Text(\n          'Contatore: $_counter',\n          style: Theme.of(context).textTheme.headlineMedium,\n        ),\n      ),\n    );\n  }\n}","Un'app con un contatore visualizzato al centro, per ora modificabile solo via codice.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},378,3,"Definire le scorciatoie con il widget Shortcuts","Avvolgiamo il contenuto con il widget `Shortcuts`, passando una mappa che associa i tasti agli `Intent`.\n\nUsiamo `SingleActivator`, il modo moderno e consigliato per descrivere una combinazione: accetta il tasto principale più i modificatori (`control`, `shift`, `meta`, `alt`).\n\nNota: il widget `Shortcuts` deve avere un discendente con il focus per ricevere gli eventi. Useremo `Focus` con `autofocus: true` nel prossimo passo, oppure puoi usare `FocusableActionDetector` per semplificare.","Shortcuts(\n  shortcuts: \u003CShortcutActivator, Intent>{\n    \u002F\u002F freccia su \u002F giù\n    const SingleActivator(LogicalKeyboardKey.arrowUp): const IncrementIntent(),\n    const SingleActivator(LogicalKeyboardKey.arrowDown): const DecrementIntent(),\n    \u002F\u002F Ctrl + R per il reset\n    const SingleActivator(LogicalKeyboardKey.keyR, control: true):\n        const ResetIntent(),\n  },\n  child: \u002F* Actions + Focus verranno aggiunti dopo *\u002F const SizedBox(),\n)","Le combinazioni di tasti sono mappate agli Intent, ma non producono ancora effetti perché mancano le Actions.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},379,4,"Collegare gli Intent alla logica con il widget Actions","Ora avvolgiamo il tutto con `Actions`, che associa ogni tipo di `Intent` a un `Action`.\n\nUsiamo `CallbackAction\u003CT>`, la forma più semplice di `Action`: il suo `onInvoke` viene chiamato quando l'`Intent` corrispondente viene invocato.\n\nÈ fondamentale che il tipo generico dell'`Action` corrisponda al tipo dell'`Intent` nella mappa, altrimenti l'azione non verrà trovata.","Actions(\n  actions: \u003CType, Action\u003CIntent>>{\n    IncrementIntent: CallbackAction\u003CIncrementIntent>(\n      onInvoke: (intent) {\n        _increment();\n        return null;\n      },\n    ),\n    DecrementIntent: CallbackAction\u003CDecrementIntent>(\n      onInvoke: (intent) {\n        _decrement();\n        return null;\n      },\n    ),\n    ResetIntent: CallbackAction\u003CResetIntent>(\n      onInvoke: (intent) {\n        _reset();\n        return null;\n      },\n    ),\n  },\n  child: \u002F* Focus *\u002F const SizedBox(),\n)","Gli Intent sono ora collegati alla logica, ma serve ancora un widget con il focus per ricevere gli eventi da tastiera.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},380,5,"Dare il focus con Focus e assemblare l'albero","Il sistema di scorciatoie funziona solo se un widget nell'albero ha il **focus**. Aggiungiamo un widget `Focus` con `autofocus: true` come figlio di `Actions`.\n\nMettiamo insieme tutti i pezzi nel metodo `build`. L'ordine dall'esterno all'interno è: `Shortcuts` → `Actions` → `Focus` → UI.\n\nRicorda di importare `package:flutter\u002Fservices.dart` per accedere a `LogicalKeyboardKey`.","import 'package:flutter\u002Fservices.dart';\n\n@override\nWidget build(BuildContext context) {\n  return Shortcuts(\n    shortcuts: \u003CShortcutActivator, Intent>{\n      const SingleActivator(LogicalKeyboardKey.arrowUp): const IncrementIntent(),\n      const SingleActivator(LogicalKeyboardKey.arrowDown): const DecrementIntent(),\n      const SingleActivator(LogicalKeyboardKey.keyR, control: true):\n          const ResetIntent(),\n    },\n    child: Actions(\n      actions: \u003CType, Action\u003CIntent>>{\n        IncrementIntent: CallbackAction\u003CIncrementIntent>(\n            onInvoke: (i) { _increment(); return null; }),\n        DecrementIntent: CallbackAction\u003CDecrementIntent>(\n            onInvoke: (i) { _decrement(); return null; }),\n        ResetIntent: CallbackAction\u003CResetIntent>(\n            onInvoke: (i) { _reset(); return null; }),\n      },\n      child: Focus(\n        autofocus: true,\n        child: Scaffold(\n          appBar: AppBar(title: const Text('Shortcuts & Actions')),\n          body: Center(\n            child: Column(\n              mainAxisAlignment: MainAxisAlignment.center,\n              children: [\n                Text('Contatore: $_counter',\n                    style: Theme.of(context).textTheme.headlineMedium),\n                const SizedBox(height: 12),\n                const Text('↑ incrementa  ·  ↓ decrementa  ·  Ctrl+R reset'),\n              ],\n            ),\n          ),\n        ),\n      ),\n    ),\n  );\n}","Avviando l'app su desktop o web, premendo le frecce e Ctrl+R il contatore reagisce alle scorciatoie da tastiera.",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},381,6,"Gestire i modificatori per piattaforma (Cmd vs Ctrl)","Su macOS gli utenti usano il tasto `Cmd` (meta) al posto di `Ctrl`. Per offrire un'esperienza nativa possiamo aggiungere entrambe le scorciatoie oppure rilevare la piattaforma.\n\nUn approccio pulito è definire la mappa in modo condizionale usando `defaultTargetPlatform`. Ricorda di importare `package:flutter\u002Ffoundation.dart`.\n\nÈ una buona pratica offrire scorciatoie coerenti con le convenzioni del sistema operativo su cui gira l'app.","import 'package:flutter\u002Ffoundation.dart';\n\nMap\u003CShortcutActivator, Intent> _buildShortcuts() {\n  final bool isApple = defaultTargetPlatform == TargetPlatform.macOS;\n  return \u003CShortcutActivator, Intent>{\n    const SingleActivator(LogicalKeyboardKey.arrowUp): const IncrementIntent(),\n    const SingleActivator(LogicalKeyboardKey.arrowDown): const DecrementIntent(),\n    \u002F\u002F Cmd+R su macOS, Ctrl+R altrove\n    SingleActivator(\n      LogicalKeyboardKey.keyR,\n      control: !isApple,\n      meta: isApple,\n    ): const ResetIntent(),\n  };\n}","Le scorciatoie usano automaticamente Cmd su macOS e Ctrl sulle altre piattaforme.",{"id":78,"position":79,"title":80,"body":81,"code_snippet":82,"code_language":39,"expected_result":83,"demo_url":41,"video_url":41},382,7,"Usare Action.overridable e invocare le azioni via codice","A volte vuoi eseguire un'azione dal codice (ad esempio da un pulsante) riutilizzando la stessa logica delle scorciatoie. Puoi farlo con `Actions.invoke` o `Actions.maybeInvoke`, che cercano l'`Action` associata all'`Intent` nel contesto.\n\nQuesto è utile per garantire che pulsanti e scorciatoie condividano esattamente lo stesso comportamento, evitando duplicazione di logica.\n\nCollega ad esempio un `FloatingActionButton` all'`IncrementIntent`.","FloatingActionButton(\n  onPressed: () {\n    \u002F\u002F Invoca l'Action registrata per IncrementIntent nel contesto corrente.\n    Actions.maybeInvoke\u003CIncrementIntent>(\n      context,\n      const IncrementIntent(),\n    );\n  },\n  child: const Icon(Icons.add),\n)\n\n\u002F\u002F In alternativa, per bottoni con il focus puoi usare:\n\u002F\u002F Actions.invoke(context, const IncrementIntent());","Il pulsante e la scorciatoia da tastiera eseguono la stessa identica logica di incremento, mantenendo il codice DRY.",[],1793098149003]