[{"data":1,"prerenderedAt":85},["ShallowReactive",2],{"tutorial-controlli-di-selezione-in-flutter-checkbox-radio-switch-e-slider":3,"comments-tutorial-controlli-di-selezione-in-flutter-checkbox-radio-switch-e-slider":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},97,"Controlli di selezione in Flutter: Checkbox, Radio, Switch e Slider","controlli-di-selezione-in-flutter-checkbox-radio-switch-e-slider","Impara a usare i widget di selezione di Flutter: Checkbox, Switch, Radio e Slider. Costruisci passo dopo passo una schermata di impostazioni completa, con stato, riepilogo dei valori e personalizzazione del tema.","Quasi ogni app ha una schermata di **preferenze**: attivare le notifiche, accettare i termini, scegliere un livello di qualità, regolare il volume. Flutter mette a disposizione una famiglia di widget pensati esattamente per questo: `Checkbox`, `Switch`, `Radio` e `Slider`, insieme alle loro varianti \"pronte all'uso\" `CheckboxListTile`, `SwitchListTile` e `RadioListTile`.\n\nTutti questi widget hanno una caratteristica in comune: sono **controllati**. Non memorizzano internamente il valore selezionato, ma lo ricevono dall'esterno (`value`) e notificano i cambiamenti con una callback (`onChanged`). Sta a te salvare il nuovo valore nello stato e ricostruire la UI: è il classico pattern `setState` dei `StatefulWidget`.\n\nIn questo tutorial costruiremo una schermata **Impostazioni** completa:\n\n- una checkbox per accettare i termini;\n- alcuni switch per le notifiche;\n- un gruppo di radio button per la qualità di download;\n- uno slider per il volume e un `RangeSlider` per una fascia di prezzo;\n- un riepilogo finale dei valori scelti.\n\nAl termine avrai chiaro il funzionamento del ciclo *valore → callback → setState → rebuild*, che è il cuore di qualsiasi form in Flutter.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Fb7fec773-dd3c-4e6f-9e31-4cdbbb2bdfa8.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1784124243546-4fc4e54f7e18?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODk2MTk0ODR8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Aaron Mrvelj","https:\u002F\u002Funsplash.com\u002F@aaronmrvelj","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fglowing-control-panel-with-switches-and-digital-displays-jLc7ThomVlg","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=QnKIGt6Pln0",{"youtube_id":17,"duration_seconds":18,"published_at":19},"QnKIGt6Pln0",96,"2026-09-17T09:44:10+00:00","beginner",35,"3.x","published","2026-09-17T04:31:24+00:00","Checkbox, Radio, Switch e Slider in Flutter","Guida beginner ai controlli di selezione in Flutter: Checkbox, Switch, Radio e Slider con setState, ListTile pronti all'uso e personalizzazione del tema.",{"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},647,"Creare la schermata Impostazioni con uno StatefulWidget","Partiamo da un progetto nuovo (`flutter create controlli_demo`) e sostituiamo il contenuto di `lib\u002Fmain.dart`.\n\nTutti i controlli di selezione sono **stateless dal punto di vista del dato**: mostrano ciò che gli passi e ti avvisano quando l'utente interagisce. Per questo la schermata che li contiene deve essere uno `StatefulWidget`, dove dichiariamo le variabili che conservano i valori.\n\nIniziamo con lo scheletro: `MaterialApp`, `Scaffold`, una `AppBar` e una `ListView` (che ci permette di scorrere quando gli elementi cresceranno, evitando il classico errore di overflow).","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 MaterialApp(\n      title: 'Controlli di selezione',\n      theme: ThemeData(\n        colorSchemeSeed: Colors.indigo,\n        useMaterial3: true,\n      ),\n      home: const ImpostazioniPage(),\n    );\n  }\n}\n\nclass ImpostazioniPage extends StatefulWidget {\n  const ImpostazioniPage({super.key});\n\n  @override\n  State\u003CImpostazioniPage> createState() => _ImpostazioniPageState();\n}\n\nclass _ImpostazioniPageState extends State\u003CImpostazioniPage> {\n  \u002F\u002F Qui aggiungeremo le variabili di stato nei prossimi passi.\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Impostazioni')),\n      body: ListView(\n        padding: const EdgeInsets.symmetric(vertical: 8),\n        children: const [\n          ListTile(title: Text('Contenuto in arrivo...')),\n        ],\n      ),\n    );\n  }\n}","dart","L'app si avvia mostrando una schermata con AppBar 'Impostazioni' e una lista vuota pronta ad accogliere i controlli.",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},648,2,"Checkbox e CheckboxListTile: le scelte booleane","La `Checkbox` è il controllo più semplice: ha un `value` di tipo `bool?` e una callback `onChanged`.\n\nRegole da ricordare:\n\n- se passi `onChanged: null` la checkbox appare **disabilitata**;\n- senza `setState` dentro `onChanged` la spunta **non cambia** visivamente, perché il widget disegna sempre il valore che gli passi;\n- `CheckboxListTile` unisce checkbox, titolo e sottotitolo in una riga cliccabile per intero: è la scelta migliore in una lista di impostazioni.\n\nAggiungiamo una checkbox \"nuda\" affiancata da un testo e una `CheckboxListTile` per l'accettazione dei termini.","\u002F\u002F Dentro _ImpostazioniPageState\nbool _accettaTermini = false;\nbool _ricordami = true;\n\n\u002F\u002F ...dentro il build, come figli della ListView:\nRow(\n  children: [\n    Checkbox(\n      value: _ricordami,\n      onChanged: (bool? nuovo) {\n        setState(() => _ricordami = nuovo ?? false);\n      },\n    ),\n    const Text('Ricordami su questo dispositivo'),\n  ],\n),\nCheckboxListTile(\n  value: _accettaTermini,\n  onChanged: (bool? nuovo) {\n    setState(() => _accettaTermini = nuovo ?? false);\n  },\n  title: const Text('Accetto i termini di servizio'),\n  subtitle: const Text('Obbligatorio per continuare'),\n  controlAffinity: ListTileControlAffinity.leading, \u002F\u002F spunta a sinistra\n  secondary: const Icon(Icons.description_outlined),\n),","Toccando la riga dei termini o la checkbox la spunta si attiva e disattiva correttamente.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":39,"expected_result":55,"demo_url":41,"video_url":41},649,3,"Switch e SwitchListTile: attivare e disattivare funzioni","Lo `Switch` esprime lo stesso concetto della checkbox (vero\u002Ffalso) ma con una semantica diversa: indica un'azione che ha **effetto immediato**, come accendere le notifiche. La checkbox, invece, si usa di solito dentro un form che verrà confermato con un pulsante.\n\nA differenza di `Checkbox`, il `value` di `Switch` è un `bool` non nullable.\n\nUn dettaglio utile: possiamo **disabilitare** uno switch dipendente passando `onChanged: null`. Nell'esempio, gli switch di email e push restano spenti e non toccabili finché le notifiche generali sono disattivate.","bool _notifiche = true;\nbool _notificheEmail = false;\nbool _notifichePush = true;\n\n\u002F\u002F ...nella ListView:\nSwitchListTile(\n  value: _notifiche,\n  onChanged: (bool nuovo) => setState(() => _notifiche = nuovo),\n  title: const Text('Notifiche'),\n  subtitle: const Text('Attiva o disattiva tutte le notifiche'),\n  secondary: const Icon(Icons.notifications_active_outlined),\n),\nSwitchListTile(\n  value: _notificheEmail,\n  \u002F\u002F null => controllo disabilitato\n  onChanged: _notifiche\n      ? (bool nuovo) => setState(() => _notificheEmail = nuovo)\n      : null,\n  title: const Text('Email'),\n),\nSwitchListTile(\n  value: _notifichePush,\n  onChanged: _notifiche\n      ? (bool nuovo) => setState(() => _notifichePush = nuovo)\n      : null,\n  title: const Text('Push'),\n),","Spegnendo lo switch 'Notifiche' i due switch sottostanti diventano grigi e non rispondono al tocco.",{"id":57,"position":58,"title":59,"body":60,"code_snippet":61,"code_language":39,"expected_result":62,"demo_url":41,"video_url":41},650,4,"Radio e RadioListTile: una sola scelta tra tante","I radio button servono quando le opzioni sono **mutuamente esclusive**. Il meccanismo è leggermente diverso: ogni `Radio` ha un proprio `value` (l'opzione che rappresenta) e tutti condividono lo stesso `groupValue` (l'opzione attualmente selezionata). Quando `value == groupValue` il pallino appare pieno.\n\nPer rendere il codice leggibile e sicuro usiamo un `enum` invece di stringhe: l'analizzatore ci avviserà se dimentichiamo un caso.\n\n> Nota: dalle versioni più recenti di Flutter esiste anche il widget `RadioGroup`, che raggruppa i radio e gestisce la selezione al posto tuo. L'API con `groupValue` mostrata qui resta la più diffusa e funziona su tutte le versioni 3.x.","enum Qualita { bassa, media, alta }\n\nextension QualitaLabel on Qualita {\n  String get etichetta => switch (this) {\n        Qualita.bassa => 'Bassa (risparmio dati)',\n        Qualita.media => 'Media (consigliata)',\n        Qualita.alta => 'Alta (file più pesanti)',\n      };\n}\n\n\u002F\u002F Dentro lo State:\nQualita _qualita = Qualita.media;\n\n\u002F\u002F ...nella ListView:\nconst Padding(\n  padding: EdgeInsets.fromLTRB(16, 16, 16, 4),\n  child: Text('Qualità download',\n      style: TextStyle(fontWeight: FontWeight.bold)),\n),\n...Qualita.values.map(\n  (opzione) => RadioListTile\u003CQualita>(\n    value: opzione,\n    groupValue: _qualita,\n    onChanged: (Qualita? nuovo) {\n      if (nuovo != null) setState(() => _qualita = nuovo);\n    },\n    title: Text(opzione.etichetta),\n    dense: true,\n  ),\n),","Selezionando una qualità il pallino si sposta: solo un'opzione per volta risulta attiva.",{"id":64,"position":65,"title":66,"body":67,"code_snippet":68,"code_language":39,"expected_result":69,"demo_url":41,"video_url":41},651,5,"Slider e RangeSlider: valori numerici continui o a scatti","Lo `Slider` gestisce un `double` compreso tra `min` e `max`. Proprietà chiave:\n\n- `divisions`: divide la barra in scatti discreti (senza, il movimento è continuo);\n- `label`: mostra un fumetto con il valore durante il trascinamento (visibile solo se `divisions` è impostato);\n- `onChangeEnd`: utile per salvare il valore **una sola volta**, quando l'utente rilascia il dito, evitando decine di scritture su disco o chiamate di rete.\n\nIl `RangeSlider` funziona allo stesso modo ma lavora con un oggetto `RangeValues` (con `start` e `end`) per selezionare un intervallo.","double _volume = 60;\nRangeValues _prezzo = const RangeValues(20, 80);\n\n\u002F\u002F ...nella ListView:\nListTile(\n  leading: const Icon(Icons.volume_up_outlined),\n  title: Text('Volume: ${_volume.round()}%'),\n  subtitle: Slider(\n    value: _volume,\n    min: 0,\n    max: 100,\n    divisions: 20, \u002F\u002F scatti da 5 in 5\n    label: '${_volume.round()}%',\n    onChanged: (double nuovo) => setState(() => _volume = nuovo),\n    onChangeEnd: (double finale) {\n      debugPrint('Salvo il volume: $finale');\n    },\n  ),\n),\nListTile(\n  leading: const Icon(Icons.euro),\n  title: Text(\n    'Prezzo: ${_prezzo.start.round()}€ - ${_prezzo.end.round()}€',\n  ),\n  subtitle: RangeSlider(\n    values: _prezzo,\n    min: 0,\n    max: 200,\n    divisions: 40,\n    labels: RangeLabels(\n      '${_prezzo.start.round()}€',\n      '${_prezzo.end.round()}€',\n    ),\n    onChanged: (RangeValues nuovi) => setState(() => _prezzo = nuovi),\n  ),\n),","Trascinando gli slider il titolo si aggiorna in tempo reale e nella console appare il log al rilascio.",{"id":71,"position":72,"title":73,"body":74,"code_snippet":75,"code_language":39,"expected_result":76,"demo_url":41,"video_url":41},652,6,"Raccogliere i valori e mostrare un riepilogo","Ora che tutti i controlli scrivono nello stesso `State`, leggere i valori è immediato: sono semplici variabili Dart.\n\nAggiungiamo in fondo alla lista un pulsante \"Salva\" che mostra un `SnackBar` con il riepilogo. Il pulsante è attivo **solo** se i termini sono stati accettati: passando `onPressed: null` Flutter disabilita automaticamente il bottone e ne applica lo stile grigio.\n\nQuesto è anche il punto in cui, in un'app reale, salveresti i dati (per esempio con `shared_preferences`) o li invieresti al backend.","String _riepilogo() {\n  final buffer = StringBuffer()\n    ..writeln('Ricordami: ${_ricordami ? 'sì' : 'no'}')\n    ..writeln('Notifiche: ${_notifiche ? 'attive' : 'spente'}')\n    ..writeln('Qualità: ${_qualita.etichetta}')\n    ..writeln('Volume: ${_volume.round()}%')\n    ..write('Prezzo: ${_prezzo.start.round()}-${_prezzo.end.round()}€');\n  return buffer.toString();\n}\n\n\u002F\u002F ...in fondo alla ListView:\nconst Divider(),\nPadding(\n  padding: const EdgeInsets.all(16),\n  child: FilledButton.icon(\n    onPressed: _accettaTermini\n        ? () {\n            ScaffoldMessenger.of(context).showSnackBar(\n              SnackBar(content: Text(_riepilogo())),\n            );\n          }\n        : null,\n    icon: const Icon(Icons.save_outlined),\n    label: const Text('Salva impostazioni'),\n  ),\n),","Il pulsante 'Salva impostazioni' è grigio finché non si accettano i termini; premendolo compare uno SnackBar con tutti i valori scelti.",{"id":78,"position":79,"title":80,"body":81,"code_snippet":82,"code_language":39,"expected_result":83,"demo_url":41,"video_url":41},653,7,"Personalizzare stile e accessibilità dal tema","Invece di ripetere `activeColor` su ogni widget, conviene definire lo stile **una volta sola** nel `ThemeData`. Flutter offre `CheckboxThemeData`, `SwitchThemeData`, `RadioThemeData` e `SliderThemeData`; le proprietà accettano `WidgetStateProperty` (chiamata `MaterialStateProperty` nelle versioni Flutter precedenti alla 3.22) per differenziare lo stato selezionato da quello normale.\n\nDue accortezze finali:\n\n- usa `ListTile`\u002F`*ListTile` quando puoi: l'area cliccabile più grande migliora l'usabilità sui dispositivi touch;\n- aggiungi un `semanticLabel` alla `Checkbox` \"nuda\" (senza testo associato) per gli screen reader, e ricorda che `Slider` annuncia automaticamente il valore se imposti `label`.","ThemeData(\n  useMaterial3: true,\n  colorSchemeSeed: Colors.indigo,\n  checkboxTheme: CheckboxThemeData(\n    fillColor: WidgetStateProperty.resolveWith((states) {\n      if (states.contains(WidgetState.selected)) return Colors.indigo;\n      return null; \u002F\u002F usa il default del tema\n    }),\n    shape: RoundedRectangleBorder(\n      borderRadius: BorderRadius.circular(4),\n    ),\n  ),\n  switchTheme: SwitchThemeData(\n    thumbColor: WidgetStateProperty.resolveWith((states) =>\n        states.contains(WidgetState.selected) ? Colors.white : null),\n  ),\n  sliderTheme: const SliderThemeData(\n    showValueIndicator: ShowValueIndicator.always,\n  ),\n);\n\n\u002F\u002F Checkbox senza etichetta: aggiungi sempre una label semantica\nCheckbox(\n  value: _ricordami,\n  semanticLabel: 'Ricordami su questo dispositivo',\n  onChanged: (v) => setState(() => _ricordami = v ?? false),\n);","Tutti i controlli condividono lo stesso stile coerente e restano accessibili ai lettori di schermo.",[],1792316930657]