[{"data":1,"prerenderedAt":85},["ShallowReactive",2],{"tutorial-input-di-base-in-flutter-textfield-bottoni-e-gesti-ontap":3,"comments-tutorial-input-di-base-in-flutter-textfield-bottoni-e-gesti-ontap":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},76,"Input di base in Flutter: TextField, bottoni e gesti onTap","input-di-base-in-flutter-textfield-bottoni-e-gesti-ontap","Impara a raccogliere l'input dell'utente in Flutter: TextField con TextEditingController, ElevatedButton e altri bottoni, gestione del tocco con InkWell e GestureDetector.","Ogni app ha bisogno di ricevere input dall'utente: un nome da digitare, un bottone da premere, una card da toccare. In Flutter questi tre elementi si traducono in `TextField`, nei widget della famiglia `Button` e nei rilevatori di gesti come `InkWell` e `GestureDetector`.\n\nIn questo tutorial per principianti costruiremo una piccola schermata \"Saluta l'utente\": l'utente scrive il proprio nome, preme un bottone e vede comparire un messaggio personalizzato. Lungo la strada vedremo come:\n\n- leggere il testo digitato con un `TextEditingController`;\n- personalizzare l'aspetto di un campo con `InputDecoration`;\n- usare `ElevatedButton`, `OutlinedButton`, `TextButton` e `IconButton`;\n- rendere \"toccabile\" qualsiasi widget con `InkWell` e `GestureDetector`;\n- abilitare o disabilitare un bottone in base a ciò che l'utente scrive;\n- liberare le risorse con `dispose()` e chiudere la tastiera.\n\nServe solo un progetto Flutter appena creato (`flutter create input_demo`) e la voglia di sporcarsi le mani con `setState`.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F03a93992-2a32-4d4b-8c24-c14f685a8d10.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1599413720280-1beeb76ff93a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODc4MDUwNzl8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Chase Chappell","https:\u002F\u002Funsplash.com\u002F@chappelldigitalmarketing","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fperson-holding-white-smartphone-on-white-table-QzeXD_pHXMk","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=46M_eJ1-pE4",{"youtube_id":17,"duration_seconds":18,"published_at":19},"46M_eJ1-pE4",123,"2026-08-27T07:22:08+00:00","beginner",35,"3.x","published","2026-08-27T04:31:19+00:00","TextField e bottoni in Flutter: guida per iniziare","Tutorial Flutter per principianti su input di base: TextField, TextEditingController, ElevatedButton, IconButton, InkWell e GestureDetector con esempi.",{"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},507,"Preparare la schermata di partenza","Creiamo un nuovo progetto e sostituiamo il contenuto di `lib\u002Fmain.dart` con una schermata minimale. Usiamo uno **StatefulWidget** perché più avanti dovremo aggiornare l'interfaccia in risposta all'input dell'utente.\n\nAlcune note sul codice:\n\n- `MaterialApp` fornisce tema, routing e stile Material;\n- `Scaffold` dà la struttura base (AppBar + body);\n- `SingleChildScrollView` evita l'errore di overflow quando la tastiera compare e riduce lo spazio disponibile: è uno degli inconvenienti più comuni per chi inizia.","import 'package:flutter\u002Fmaterial.dart';\n\nvoid main() => runApp(const InputDemoApp());\n\nclass InputDemoApp extends StatelessWidget {\n  const InputDemoApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Input di base',\n      theme: ThemeData(\n        colorSchemeSeed: Colors.indigo,\n        useMaterial3: true,\n      ),\n      home: const HomePage(),\n    );\n  }\n}\n\nclass HomePage extends StatefulWidget {\n  const HomePage({super.key});\n\n  @override\n  State\u003CHomePage> createState() => _HomePageState();\n}\n\nclass _HomePageState extends State\u003CHomePage> {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Input di base')),\n      body: const SingleChildScrollView(\n        padding: EdgeInsets.all(24),\n        child: Column(\n          crossAxisAlignment: CrossAxisAlignment.stretch,\n          children: [\n            Text('Come ti chiami?', style: TextStyle(fontSize: 20)),\n          ],\n        ),\n      ),\n    );\n  }\n}","dart","Avviando l'app con `flutter run` vedi una AppBar con il titolo \"Input di base\" e il testo \"Come ti chiami?\" nel corpo della pagina.",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},508,2,"Aggiungere un TextField e leggerne il valore","Il widget `TextField` mostra un campo di testo. Per **leggere** ciò che l'utente digita il modo più comune è collegargli un `TextEditingController`.\n\nRegole d'oro:\n\n1. il controller si crea in `initState()` (o come campo `final`);\n2. va **sempre** distrutto in `dispose()`, altrimenti si crea un memory leak;\n3. il valore corrente si legge con `controller.text`.\n\nCon `InputDecoration` personalizziamo etichetta, bordo e icona. Le proprietà `textInputAction` e `onSubmitted` gestiscono il tasto \"Invio\" della tastiera software.","class _HomePageState extends State\u003CHomePage> {\n  final TextEditingController _nomeController = TextEditingController();\n  String _saluto = '';\n\n  @override\n  void dispose() {\n    _nomeController.dispose(); \u002F\u002F libera le risorse\n    super.dispose();\n  }\n\n  void _saluta() {\n    final nome = _nomeController.text.trim();\n    setState(() {\n      _saluto = nome.isEmpty ? 'Ciao sconosciuto!' : 'Ciao $nome!';\n    });\n    FocusScope.of(context).unfocus(); \u002F\u002F chiude la tastiera\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Input di base')),\n      body: SingleChildScrollView(\n        padding: const EdgeInsets.all(24),\n        child: Column(\n          crossAxisAlignment: CrossAxisAlignment.stretch,\n          children: [\n            const Text('Come ti chiami?', style: TextStyle(fontSize: 20)),\n            const SizedBox(height: 16),\n            TextField(\n              controller: _nomeController,\n              textInputAction: TextInputAction.done,\n              textCapitalization: TextCapitalization.words,\n              onSubmitted: (_) => _saluta(),\n              decoration: const InputDecoration(\n                labelText: 'Nome',\n                hintText: 'Es. Giulia',\n                prefixIcon: Icon(Icons.person_outline),\n                border: OutlineInputBorder(),\n              ),\n            ),\n            const SizedBox(height: 24),\n            Text(_saluto, style: const TextStyle(fontSize: 22)),\n          ],\n        ),\n      ),\n    );\n  }\n}","Digitando un nome e premendo \"Invio\" sulla tastiera compare il messaggio \"Ciao \u003Cnome>!\" e la tastiera si chiude.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},509,3,"Aggiungere i bottoni: ElevatedButton, OutlinedButton e TextButton","In Material 3 i bottoni principali sono:\n\n| Widget | Quando usarlo |\n| --- | --- |\n| `ElevatedButton` | azione primaria, ben visibile |\n| `FilledButton` | alternativa piena all'elevated |\n| `OutlinedButton` | azione secondaria |\n| `TextButton` | azione terziaria \u002F link |\n| `IconButton` | sola icona, es. in AppBar |\n\nTutti accettano il parametro `onPressed`: se gli passi `null` il bottone appare **disabilitato**. È un dettaglio importante e spesso ignorato dai principianti.\n\nAggiungiamo una riga con il bottone \"Saluta\" e uno per pulire il campo. Nota `_nomeController.clear()`, che svuota il testo.","\u002F\u002F dentro la Column, dopo il TextField\nconst SizedBox(height: 16),\nRow(\n  children: [\n    Expanded(\n      child: ElevatedButton.icon(\n        onPressed: _saluta,\n        icon: const Icon(Icons.waving_hand_outlined),\n        label: const Text('Saluta'),\n      ),\n    ),\n    const SizedBox(width: 12),\n    OutlinedButton(\n      onPressed: () {\n        _nomeController.clear();\n        setState(() => _saluto = '');\n      },\n      child: const Text('Pulisci'),\n    ),\n  ],\n),\nTextButton(\n  onPressed: () {\n    ScaffoldMessenger.of(context).showSnackBar(\n      const SnackBar(content: Text('Scrivi il tuo nome e premi Saluta')),\n    );\n  },\n  child: const Text('Come funziona?'),\n),","Sotto al campo compaiono tre bottoni: \"Saluta\" mostra il messaggio, \"Pulisci\" svuota tutto, \"Come funziona?\" apre una SnackBar in basso.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},510,4,"Abilitare il bottone solo quando c'è del testo","Per reagire **mentre** l'utente scrive abbiamo due strade:\n\n- la callback `onChanged` del `TextField`;\n- un listener sul `TextEditingController` (utile se il testo cambia anche da codice).\n\nUsiamo la seconda: aggiungiamo un listener in `initState()` che chiama `setState`, così a ogni carattere digitato la UI si ricostruisce. Il bottone riceve `onPressed: null` quando il campo è vuoto e Flutter lo mostra automaticamente in grigio.\n\n⚠️ Ricordati di rimuovere il listener (o semplicemente di fare `dispose()` del controller, che lo elimina insieme a sé) per evitare chiamate a `setState` su un widget non più montato.","@override\nvoid initState() {\n  super.initState();\n  _nomeController.addListener(_onTestoCambiato);\n}\n\nvoid _onTestoCambiato() {\n  setState(() {}); \u002F\u002F ricostruisce la UI a ogni carattere\n}\n\n@override\nvoid dispose() {\n  _nomeController.removeListener(_onTestoCambiato);\n  _nomeController.dispose();\n  super.dispose();\n}\n\nbool get _puoSalutare => _nomeController.text.trim().isNotEmpty;\n\n\u002F\u002F nel build, sostituisci l'ElevatedButton con:\nElevatedButton.icon(\n  onPressed: _puoSalutare ? _saluta : null, \u002F\u002F null = disabilitato\n  icon: const Icon(Icons.waving_hand_outlined),\n  label: Text(_puoSalutare ? 'Saluta' : 'Scrivi un nome'),\n)","Il bottone \"Saluta\" resta grigio e non cliccabile finché il campo è vuoto; appena digiti un carattere si attiva e cambia etichetta.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},511,5,"Rendere toccabile qualsiasi widget con InkWell e GestureDetector","Non solo i bottoni possono essere premuti: qualsiasi widget può diventare interattivo.\n\n- **`InkWell`**: aggiunge il tocco *e* l'effetto ripple di Material. Va inserito dentro un `Material` (la `Card` lo è già) e conviene dargli lo stesso `borderRadius` del contenitore.\n- **`GestureDetector`**: più generico, riconosce tap, doppio tap, long press, drag… ma **non** disegna nessun feedback visivo.\n\nAggiungiamo una card con dei saluti rapidi: toccandola il nome viene inserito nel campo. E una piccola area con `GestureDetector` che conta i doppi tap.","\u002F\u002F helper dentro lo State\nvoid _usaNome(String nome) {\n  _nomeController.text = nome;\n  _nomeController.selection =\n      TextSelection.collapsed(offset: nome.length); \u002F\u002F cursore a fine testo\n  _saluta();\n}\n\n\u002F\u002F nel build, dopo i bottoni\nconst SizedBox(height: 24),\nconst Text('Nomi rapidi', style: TextStyle(fontWeight: FontWeight.bold)),\nconst SizedBox(height: 8),\nWrap(\n  spacing: 8,\n  children: ['Giulia', 'Marco', 'Sara'].map((nome) {\n    return Card(\n      clipBehavior: Clip.antiAlias,\n      child: InkWell(\n        onTap: () => _usaNome(nome),\n        onLongPress: () => ScaffoldMessenger.of(context).showSnackBar(\n          SnackBar(content: Text('Tocco prolungato su $nome')),\n        ),\n        child: Padding(\n          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),\n          child: Text(nome),\n        ),\n      ),\n    );\n  }).toList(),\n),\nconst SizedBox(height: 24),\nGestureDetector(\n  onDoubleTap: () => setState(() => _saluto = 'Doppio tap ricevuto!'),\n  behavior: HitTestBehavior.opaque, \u002F\u002F rileva il tocco anche sulle aree vuote\n  child: Container(\n    height: 80,\n    alignment: Alignment.center,\n    decoration: BoxDecoration(\n      color: Colors.indigo.withValues(alpha: 0.08),\n      borderRadius: BorderRadius.circular(12),\n    ),\n    child: const Text('Fai doppio tap qui'),\n  ),\n),","Toccando una card il nome finisce nel campo e appare subito il saluto; con il doppio tap sull'area colorata il messaggio cambia in \"Doppio tap ricevuto!\".",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},512,6,"Rifinire: campo password, IconButton e tastiera dedicata","Per completare il quadro degli input di base vediamo tre rifiniture molto usate:\n\n1. **`obscureText`** per le password, con un `IconButton` come `suffixIcon` per mostrare\u002Fnascondere il testo;\n2. **`keyboardType`** per scegliere la tastiera giusta (`TextInputType.emailAddress`, `.number`, `.phone`…);\n3. **`maxLength`** per limitare i caratteri e mostrare il contatore.\n\nQuesti tre parametri fanno una differenza enorme nella qualità percepita dell'app e costano una riga di codice ciascuno.","\u002F\u002F nuovo campo dello State\nbool _nascondiPassword = true;\nfinal TextEditingController _passwordController = TextEditingController();\n\n\u002F\u002F ricordati di fare _passwordController.dispose() in dispose()\n\n\u002F\u002F nel build, dentro la Column\nconst SizedBox(height: 24),\nTextField(\n  controller: _passwordController,\n  obscureText: _nascondiPassword,\n  keyboardType: TextInputType.visiblePassword,\n  maxLength: 20,\n  decoration: InputDecoration(\n    labelText: 'Password',\n    border: const OutlineInputBorder(),\n    prefixIcon: const Icon(Icons.lock_outline),\n    suffixIcon: IconButton(\n      tooltip: _nascondiPassword ? 'Mostra' : 'Nascondi',\n      icon: Icon(\n        _nascondiPassword ? Icons.visibility_off : Icons.visibility,\n      ),\n      onPressed: () =>\n          setState(() => _nascondiPassword = !_nascondiPassword),\n    ),\n  ),\n),","Il campo password mostra i pallini, il contatore \"0\u002F20\" e l'icona a forma di occhio che alterna testo visibile e nascosto.",{"id":78,"position":79,"title":80,"body":81,"code_snippet":82,"code_language":39,"expected_result":83,"demo_url":41,"video_url":41},513,7,"Errori tipici e prossimi passi","Ricapitoliamo gli inciampi più comuni con gli input di base:\n\n- **Dimenticare `dispose()` del controller** → memory leak e warning in console.\n- **Usare `setState` in un `StatelessWidget`** → non esiste: serve uno `StatefulWidget`.\n- **Overflow quando appare la tastiera** → avvolgi il contenuto in `SingleChildScrollView` o usa `resizeToAvoidBottomInset`.\n- **Impostare sia `controller` sia `initialValue`** → Flutter lancia un'asserzione: scegline uno solo (`initialValue` esiste su `TextFormField`, non su `TextField`).\n- **`GestureDetector` che non rileva i tocchi sulle zone trasparenti** → imposta `behavior: HitTestBehavior.opaque`.\n- **Leggere `controller.text` prima che l'utente scriva** → ottieni una stringa vuota, non `null`: usa `trim().isEmpty` per i controlli.\n\nDa qui puoi proseguire con la validazione strutturata (`Form` + `TextFormField`), la gestione del focus tra più campi con `FocusNode`, oppure spostare lo stato fuori dal widget con Provider o Riverpod.","\u002F\u002F Bonus: chiudere la tastiera toccando ovunque sullo sfondo\n@override\nWidget build(BuildContext context) {\n  return GestureDetector(\n    onTap: () => FocusScope.of(context).unfocus(),\n    child: Scaffold(\n      appBar: AppBar(title: const Text('Input di base')),\n      body: \u002F* ... il tuo contenuto ... *\u002F const SizedBox.shrink(),\n    ),\n  );\n}","L'app funziona senza warning: toccando lo sfondo la tastiera si chiude e tutti i controller vengono correttamente rilasciati alla chiusura della schermata.",[],1789120599703]