[{"data":1,"prerenderedAt":86},["ShallowReactive",2],{"tutorial-costruire-schede-eleganti-in-flutter-con-card-listtile-e-inkwell":3,"comments-tutorial-costruire-schede-eleganti-in-flutter-con-card-listtile-e-inkwell":85},{"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},88,"Costruire schede eleganti in Flutter con Card, ListTile e InkWell","costruire-schede-eleganti-in-flutter-con-card-listtile-e-inkwell","Impara a creare schede (card) professionali in Flutter: ListTile per il contenuto, InkWell per il tocco con effetto ripple, CardTheme per uno stile coerente in tutta l'app.","Le **card** sono uno dei mattoni più usati nelle interfacce mobile: le trovi negli elenchi di prodotti, nei feed di notizie, nelle dashboard e nei profili utente.\n\nIn Flutter costruire una card è semplicissimo grazie al widget `Card`, che si occupa di ombra, angoli arrotondati e margini. Combinandolo con `ListTile` (per il classico layout icona + titolo + sottotitolo) e con `InkWell` (per il tocco con effetto *ripple*) ottieni componenti curati con poche righe di codice.\n\nIn questo tutorial partiremo da una card minimale e arriveremo a una **lista di schede cliccabili** con immagine, testo e pulsanti di azione, definendo infine uno stile centralizzato con `CardTheme`. Non serve nessun pacchetto esterno: tutto è incluso in Material.\n\n**Cosa ti serve:**\n\n- Flutter installato e funzionante (`flutter doctor` senza errori)\n- Un editor come VS Code o Android Studio\n- Conoscenza di base di widget, `Column` e `ListView.builder`","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F23203c4d-33bb-4a07-8d18-d2501aab1055.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1593451102159-7bbefab0c1d6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODg4NDE4ODR8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Angella Meanix","https:\u002F\u002Funsplash.com\u002F@ang411","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fgrayscale-photo-of-books-on-table-hdr5GP4UJ-g","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=lnbJsFZzLJk",{"youtube_id":17,"duration_seconds":18,"published_at":19},"lnbJsFZzLJk",115,"2026-09-08T09:11:56+00:00","beginner",35,"3.x","published","2026-09-08T04:31:25+00:00","Card, ListTile e InkWell in Flutter: schede eleganti","Guida per principianti: come creare card in Flutter con Card, ListTile, InkWell e CardTheme. Esempi pratici di schede cliccabili e liste di card.",{"id":28,"name":29,"slug":30},1,"Guide","guide",{"id":28,"name":32},"Flutter Bot",[34,42,49,57,64,71,78],{"id":35,"position":28,"title":36,"body":37,"code_snippet":38,"code_language":39,"expected_result":40,"demo_url":41,"video_url":41},585,"Preparare il progetto e la schermata di base","Crea un nuovo progetto e sostituisci il contenuto di `lib\u002Fmain.dart` con una struttura minima: `MaterialApp` con Material 3 attivo e uno `Scaffold` con un `ListView` in cui inseriremo le nostre schede.\n\n```bash\nflutter create card_demo\ncd card_demo\n```\n\nUsiamo un `ListView` come contenitore (invece di una `Column`) perché ci permette di scorrere quando le card diventano tante, senza rischiare l'errore di *overflow*.","import 'package:flutter\u002Fmaterial.dart';\n\nvoid main() => runApp(const CardDemoApp());\n\nclass CardDemoApp extends StatelessWidget {\n  const CardDemoApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Card Demo',\n      theme: ThemeData(\n        useMaterial3: true,\n        colorSchemeSeed: Colors.indigo,\n      ),\n      home: const HomePage(),\n    );\n  }\n}\n\nclass HomePage extends StatelessWidget {\n  const HomePage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Le mie schede')),\n      body: ListView(\n        padding: const EdgeInsets.all(16),\n        children: const [\n          \u002F\u002F qui inseriremo le card\n        ],\n      ),\n    );\n  }\n}","dart","L'app si avvia e mostra una AppBar con il titolo \"Le mie schede\" e un'area di contenuto vuota.",null,{"id":43,"position":44,"title":45,"body":46,"code_snippet":47,"code_language":39,"expected_result":48,"demo_url":41,"video_url":41},586,2,"La prima card con ListTile","`Card` è un contenitore con **un solo figlio**: gli passiamo un `ListTile`, il widget pensato per righe di contenuto composte da:\n\n- `leading`: widget a sinistra (icona, avatar, immagine)\n- `title`: testo principale\n- `subtitle`: testo secondario\n- `trailing`: widget a destra (freccia, switch, icona)\n\nInserisci il codice seguente tra i `children` del `ListView` (ricordati di togliere il `const` dalla lista se aggiungi widget non costanti).","Card(\n  child: ListTile(\n    leading: const Icon(Icons.wb_sunny_outlined),\n    title: const Text('Meteo di oggi'),\n    subtitle: const Text('Sereno, 24°C'),\n    trailing: const Icon(Icons.chevron_right),\n  ),\n),\nconst SizedBox(height: 12),\nCard(\n  child: ListTile(\n    leading: CircleAvatar(\n      backgroundColor: Colors.indigo.shade100,\n      child: const Text('MR'),\n    ),\n    title: const Text('Mario Rossi'),\n    subtitle: const Text('mario.rossi@example.com'),\n    trailing: IconButton(\n      icon: const Icon(Icons.more_vert),\n      onPressed: () {},\n    ),\n  ),\n),","Due schede con ombra leggera e angoli arrotondati: la prima con icona e freccia, la seconda con avatar e menu.",{"id":50,"position":51,"title":52,"body":53,"code_snippet":54,"code_language":55,"expected_result":56,"demo_url":41,"video_url":41},587,3,"Personalizzare aspetto e margini della card","`Card` espone alcune proprietà chiave:\n\n- `elevation`: intensità dell'ombra (0 = piatta)\n- `color` \u002F `surfaceTintColor`: colore di sfondo e tinta Material 3\n- `shape`: forma e raggio degli angoli\n- `margin`: spazio **esterno** alla card (di default 4 px su tutti i lati)\n- `clipBehavior`: taglia il figlio seguendo la forma della card, indispensabile quando dentro c'è un'immagine\n\nUn errore comune è cercare il *padding interno*: la `Card` non ne ha. Lo spazio interno lo aggiungi tu con un `Padding` intorno al contenuto.","Card(\n  elevation: 4,\n  margin: EdgeInsets.zero,\n  clipBehavior: Clip.antiAlias,\n  shape: RoundedRectangleBorder(\n    borderRadius: BorderRadius.circular(20),\n  ),\n  child: const Padding(\n    padding: EdgeInsets.all(20),\n    child: Column(\n      crossAxisAlignment: CrossAxisAlignment.start,\n      children: [\n        Text(\n          'Abbonamento Pro',\n          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),\n        ),\n        SizedBox(height: 8),\n        Text('Accesso illimitato a tutti i corsi, senza pubblicità.'),\n      ],\n    ),\n  ),\n),","alt","Una scheda con angoli molto arrotondati, ombra più marcata e testo distanziato di 20 px dai bordi.",{"id":58,"position":59,"title":60,"body":61,"code_snippet":62,"code_language":39,"expected_result":63,"demo_url":41,"video_url":41},588,4,"Rendere la card cliccabile con InkWell","Per far reagire la card al tocco hai due strade:\n\n1. usare `ListTile(onTap: ...)` quando il contenuto è un `ListTile`;\n2. avvolgere il contenuto in un `InkWell` quando la card è composita.\n\nL'`InkWell` va **dentro** la `Card`, non fuori: così l'effetto ripple resta confinato nell'area della scheda. Per farlo tagliare correttamente agli angoli imposta `clipBehavior: Clip.antiAlias` sulla `Card` e lo stesso `borderRadius` sull'`InkWell`.\n\n> Attenzione: se usi `GestureDetector` invece di `InkWell` il tocco funziona ma non vedrai nessuna animazione di feedback.","Card(\n  clipBehavior: Clip.antiAlias,\n  shape: RoundedRectangleBorder(\n    borderRadius: BorderRadius.circular(16),\n  ),\n  child: InkWell(\n    borderRadius: BorderRadius.circular(16),\n    onTap: () {\n      ScaffoldMessenger.of(context).showSnackBar(\n        const SnackBar(content: Text('Scheda selezionata!')),\n      );\n    },\n    child: const Padding(\n      padding: EdgeInsets.all(16),\n      child: Row(\n        children: [\n          Icon(Icons.download_outlined, size: 32),\n          SizedBox(width: 16),\n          Expanded(\n            child: Text('Scarica il report mensile in formato PDF'),\n          ),\n          Icon(Icons.chevron_right),\n        ],\n      ),\n    ),\n  ),\n),","Toccando la scheda compare l'effetto ripple che si ferma ai bordi arrotondati e in basso appare una SnackBar.",{"id":65,"position":66,"title":67,"body":68,"code_snippet":69,"code_language":39,"expected_result":70,"demo_url":41,"video_url":41},589,5,"Una card composita con immagine, testo e azioni","Il pattern più diffuso è la card \"verticale\": immagine in alto, testo al centro, pulsanti in basso. Si costruisce con una `Column` dentro la `Card`.\n\nPunti da ricordare:\n\n- l'immagine deve stare **come primo figlio** della `Column`, senza `Padding`, per arrivare a filo dei bordi;\n- `AspectRatio` (o `SizedBox(height: ...)`) evita che l'immagine cambi altezza durante il caricamento;\n- `BoxFit.cover` riempie lo spazio senza deformare la foto;\n- per allineare i bottoni a destra usa `Align` oppure `Row(mainAxisAlignment: MainAxisAlignment.end)`.\n\nQui usiamo un'immagine di rete: ricorda che su Android serve il permesso INTERNET (già presente di default nei progetti Flutter).","class ArticoloCard extends StatelessWidget {\n  const ArticoloCard({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      clipBehavior: Clip.antiAlias,\n      child: Column(\n        crossAxisAlignment: CrossAxisAlignment.start,\n        children: [\n          AspectRatio(\n            aspectRatio: 16 \u002F 9,\n            child: Image.network(\n              'https:\u002F\u002Fpicsum.photos\u002F800\u002F450',\n              fit: BoxFit.cover,\n              loadingBuilder: (context, child, progress) {\n                if (progress == null) return child;\n                return const Center(child: CircularProgressIndicator());\n              },\n            ),\n          ),\n          Padding(\n            padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),\n            child: Column(\n              crossAxisAlignment: CrossAxisAlignment.start,\n              children: [\n                Text(\n                  'Weekend in montagna',\n                  style: Theme.of(context).textTheme.titleLarge,\n                ),\n                const SizedBox(height: 8),\n                Text(\n                  'Tre itinerari facili da percorrere in giornata, '\n                  'con partenza a meno di un\\'ora dalla città.',\n                  style: Theme.of(context).textTheme.bodyMedium,\n                ),\n              ],\n            ),\n          ),\n          Padding(\n            padding: const EdgeInsets.symmetric(horizontal: 8),\n            child: Row(\n              mainAxisAlignment: MainAxisAlignment.end,\n              children: [\n                TextButton(onPressed: () {}, child: const Text('Condividi')),\n                const SizedBox(width: 8),\n                FilledButton(onPressed: () {}, child: const Text('Leggi')),\n              ],\n            ),\n          ),\n          const SizedBox(height: 8),\n        ],\n      ),\n    );\n  }\n}","Una scheda con immagine a filo dei bordi arrotondati, titolo, descrizione e due pulsanti allineati a destra.",{"id":72,"position":73,"title":74,"body":75,"code_snippet":76,"code_language":39,"expected_result":77,"demo_url":41,"video_url":41},590,6,"Generare una lista di card da un modello dati","Nelle app reali le card non si scrivono a mano una per una: si generano da una lista di dati con `ListView.builder`.\n\nCreiamo una piccola classe `Corso`, una lista di esempio e un widget `CorsoCard` riutilizzabile che riceve il modello come parametro. Estrarre la card in un widget separato (invece di usare un metodo `_buildCard()`) migliora la leggibilità e le performance, perché Flutter può ricostruire solo il widget che cambia.","class Corso {\n  final String titolo;\n  final String docente;\n  final int lezioni;\n  final IconData icona;\n\n  const Corso({\n    required this.titolo,\n    required this.docente,\n    required this.lezioni,\n    required this.icona,\n  });\n}\n\nconst corsi = \u003CCorso>[\n  Corso(titolo: 'Flutter base', docente: 'A. Bianchi', lezioni: 12, icona: Icons.phone_android),\n  Corso(titolo: 'Dart avanzato', docente: 'L. Verdi', lezioni: 8, icona: Icons.code),\n  Corso(titolo: 'UI e animazioni', docente: 'S. Neri', lezioni: 15, icona: Icons.animation),\n];\n\nclass CorsiPage extends StatelessWidget {\n  const CorsiPage({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Corsi')),\n      body: ListView.builder(\n        padding: const EdgeInsets.all(16),\n        itemCount: corsi.length,\n        itemBuilder: (context, index) => CorsoCard(corso: corsi[index]),\n      ),\n    );\n  }\n}\n\nclass CorsoCard extends StatelessWidget {\n  const CorsoCard({super.key, required this.corso});\n\n  final Corso corso;\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      margin: const EdgeInsets.only(bottom: 12),\n      clipBehavior: Clip.antiAlias,\n      child: ListTile(\n        contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),\n        leading: Icon(corso.icona, size: 32),\n        title: Text(corso.titolo),\n        subtitle: Text('${corso.docente} · ${corso.lezioni} lezioni'),\n        trailing: const Icon(Icons.chevron_right),\n        onTap: () {\n          ScaffoldMessenger.of(context).showSnackBar(\n            SnackBar(content: Text('Apro \"${corso.titolo}\"')),\n          );\n        },\n      ),\n    );\n  }\n}","Una lista scorrevole di tre schede identiche nello stile, ognuna cliccabile e con i dati del corso corrispondente.",{"id":79,"position":80,"title":81,"body":82,"code_snippet":83,"code_language":39,"expected_result":84,"demo_url":41,"video_url":41},591,7,"Centralizzare lo stile con CardTheme","Ripetere `elevation`, `shape` e `margin` su ogni `Card` porta a incoerenze. La soluzione è definire un `CardTheme` (in Flutter recenti `CardThemeData`) nel `ThemeData`: tutte le `Card` dell'app lo erediteranno automaticamente.\n\nDopo questa modifica puoi rimuovere le proprietà di stile dalle singole card e lasciare solo il contenuto. Se una scheda specifica ha bisogno di un aspetto diverso, basta impostare la proprietà direttamente su quella `Card`: il valore locale vince sempre sul tema.\n\n**Bonus:** con `Card.outlined` (o `Card.filled`, disponibili in Material 3) ottieni le varianti bordata e piena senza scrivere uno `shape` personalizzato.","MaterialApp(\n  theme: ThemeData(\n    useMaterial3: true,\n    colorSchemeSeed: Colors.indigo,\n    cardTheme: CardThemeData(\n      elevation: 2,\n      margin: const EdgeInsets.only(bottom: 12),\n      clipBehavior: Clip.antiAlias,\n      shape: RoundedRectangleBorder(\n        borderRadius: BorderRadius.circular(16),\n      ),\n    ),\n  ),\n  home: const CorsiPage(),\n);\n\n\u002F\u002F Ora nel widget basta scrivere:\n\u002F\u002F Card(child: ListTile(...));\n\u002F\u002F\n\u002F\u002F Varianti Material 3 pronte all'uso:\n\u002F\u002F Card.outlined(child: ...)\n\u002F\u002F Card.filled(child: ...)","Tutte le card dell'app hanno lo stesso raggio, la stessa ombra e la stessa spaziatura, definiti in un solo punto del codice.",[],1789205507080]