[{"data":1,"prerenderedAt":73},["ShallowReactive",2],{"tutorial-effetti-parallax-nelle-liste-in-flutter-con-flow-e-scrollcontroller":3,"comments-tutorial-effetti-parallax-nelle-liste-in-flutter-con-flow-e-scrollcontroller":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},60,"Effetti parallax nelle liste in Flutter con Flow e ScrollController","effetti-parallax-nelle-liste-in-flutter-con-flow-e-scrollcontroller","Impara a creare un elegante effetto parallax scorrendo una lista di card in Flutter, usando ScrollController e il widget Flow per posizionare dinamicamente le immagini.","L'effetto parallax dona profondità e dinamismo alle interfacce: mentre l'utente scorre, gli elementi in primo piano e sullo sfondo si muovono a velocità diverse.\n\nIn questo tutorial costruiremo una lista di card con immagini che scorrono più lentamente rispetto al contenuto, creando un piacevole effetto parallax. Useremo `ScrollController` per intercettare la posizione dello scroll e `Flow` (oppure una semplice trasformazione) per traslare l'immagine in base alla posizione della card nella viewport.\n\nNon servono pacchetti esterni: tutto è realizzato con widget nativi di Flutter.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002F823abd33-635e-4d94-92eb-360c0784bc82.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1784484293862-817b355f264a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMjcyOTd8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Marek Piwnicki","https:\u002F\u002Funsplash.com\u002F@marekpiwnicki","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fmountain-silhouettes-against-a-vibrant-orange-sunset-sky-b9Qo5xAW988",null,"intermediate",35,"3.x","published","2026-08-11T04:30:56+00:00","Effetto parallax nelle liste Flutter con ScrollController","Tutorial pratico per creare un effetto parallax scorrevole nelle liste Flutter usando ScrollController e trasformazioni, senza pacchetti esterni.",{"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},402,"Preparare il modello dati e la lista di elementi","Iniziamo definendo un semplice modello `ParallaxItem` che contiene il titolo, il sottotitolo e l'URL dell'immagine. Creiamo poi una lista di esempio da mostrare.\n\nUsare immagini di rete (via `Image.network`) è comodo per il prototipo, ma nulla vieta di sostituirle con asset locali.","class ParallaxItem {\n  final String title;\n  final String subtitle;\n  final String imageUrl;\n\n  const ParallaxItem({\n    required this.title,\n    required this.subtitle,\n    required this.imageUrl,\n  });\n}\n\nconst demoItems = \u003CParallaxItem>[\n  ParallaxItem(\n    title: 'Montagne',\n    subtitle: 'Alpi italiane',\n    imageUrl: 'https:\u002F\u002Fpicsum.photos\u002Fid\u002F1018\u002F1200\u002F800',\n  ),\n  ParallaxItem(\n    title: 'Foresta',\n    subtitle: 'Boschi del nord',\n    imageUrl: 'https:\u002F\u002Fpicsum.photos\u002Fid\u002F1015\u002F1200\u002F800',\n  ),\n  ParallaxItem(\n    title: 'Lago',\n    subtitle: 'Riflessi al tramonto',\n    imageUrl: 'https:\u002F\u002Fpicsum.photos\u002Fid\u002F1016\u002F1200\u002F800',\n  ),\n  ParallaxItem(\n    title: 'Deserto',\n    subtitle: 'Dune infinite',\n    imageUrl: 'https:\u002F\u002Fpicsum.photos\u002Fid\u002F1019\u002F1200\u002F800',\n  ),\n];","dart","Abbiamo un modello dati e una lista di elementi di esempio pronti da renderizzare.",{"id":38,"position":39,"title":40,"body":41,"code_snippet":42,"code_language":35,"expected_result":43,"demo_url":15,"video_url":15},403,2,"Creare la struttura base con ListView e ScrollController","Costruiamo uno `StatefulWidget` che ospita un `ScrollController`. È fondamentale creare il controller in `initState` e liberarlo in `dispose` per evitare memory leak.\n\nAssegniamo il controller alla `ListView.builder` in modo da poter leggere la posizione dello scroll nei passi successivi.","class ParallaxListPage extends StatefulWidget {\n  const ParallaxListPage({super.key});\n\n  @override\n  State\u003CParallaxListPage> createState() => _ParallaxListPageState();\n}\n\nclass _ParallaxListPageState extends State\u003CParallaxListPage> {\n  final ScrollController _controller = ScrollController();\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Parallax List')),\n      body: ListView.builder(\n        controller: _controller,\n        padding: const EdgeInsets.symmetric(vertical: 16),\n        itemCount: demoItems.length,\n        itemBuilder: (context, index) {\n          return ParallaxCard(\n            item: demoItems[index],\n            scrollController: _controller,\n          );\n        },\n      ),\n    );\n  }\n}","La schermata mostra una lista scorrevole; le card (ancora da definire) riceveranno il controller.",{"id":45,"position":46,"title":47,"body":48,"code_snippet":49,"code_language":35,"expected_result":50,"demo_url":15,"video_url":15},404,3,"Calcolare la posizione della card nella viewport","Il cuore dell'effetto parallax è capire dove si trova ogni card rispetto allo schermo. Usiamo il `RenderObject` della card e quello della lista per calcolare un valore normalizzato tra 0 e 1.\n\n- 0 significa che la card è appena entrata dal fondo dello schermo.\n- 1 significa che sta per uscire in alto.\n\nQuesto valore ci servirà per traslare l'immagine.","double _calculateParallaxFactor(GlobalKey itemKey, ScrollController controller) {\n  final itemContext = itemKey.currentContext;\n  if (itemContext == null) return 0.5;\n\n  final RenderBox box = itemContext.findRenderObject() as RenderBox;\n  \u002F\u002F posizione globale del top della card\n  final Offset position = box.localToGlobal(Offset.zero);\n  final double screenHeight = WidgetsBinding\n      .instance.platformDispatcher.views.first.physicalSize.height \u002F\n      WidgetsBinding.instance.platformDispatcher.views.first.devicePixelRatio;\n\n  \u002F\u002F normalizziamo tra 0 (in basso) e 1 (in alto)\n  final double fraction =\n      (position.dy \u002F screenHeight).clamp(0.0, 1.0);\n  return fraction;\n}","Abbiamo una funzione che restituisce un valore normalizzato in base alla posizione verticale della card.",{"id":52,"position":53,"title":54,"body":55,"code_snippet":56,"code_language":35,"expected_result":57,"demo_url":15,"video_url":15},405,4,"Costruire la ParallaxCard con l'effetto di traslazione","Ora creiamo il widget `ParallaxCard`. Ascoltiamo lo scroll tramite `AnimatedBuilder` collegato allo `ScrollController`: ogni volta che il controller cambia, ricostruiamo solo la parte necessaria.\n\nUsiamo un `ClipRRect` per arrotondare gli angoli e posizioniamo l'immagine con `OverflowBox`, così da avere spazio extra per traslarla senza mostrare bordi vuoti. La traslazione verticale è calcolata dal fattore ottenuto nel passo precedente.","class ParallaxCard extends StatelessWidget {\n  final ParallaxItem item;\n  final ScrollController scrollController;\n\n  ParallaxCard({\n    super.key,\n    required this.item,\n    required this.scrollController,\n  });\n\n  final GlobalKey _cardKey = GlobalKey();\n\n  @override\n  Widget build(BuildContext context) {\n    return Padding(\n      key: _cardKey,\n      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),\n      child: ClipRRect(\n        borderRadius: BorderRadius.circular(16),\n        child: SizedBox(\n          height: 220,\n          child: Stack(\n            fit: StackFit.expand,\n            children: [\n              AnimatedBuilder(\n                animation: scrollController,\n                builder: (context, _) {\n                  final factor =\n                      _calculateParallaxFactor(_cardKey, scrollController);\n                  \u002F\u002F massimo spostamento in pixel\n                  final double dy = (factor - 0.5) * 80;\n                  return OverflowBox(\n                    maxHeight: 300,\n                    child: Transform.translate(\n                      offset: Offset(0, dy),\n                      child: Image.network(\n                        item.imageUrl,\n                        fit: BoxFit.cover,\n                      ),\n                    ),\n                  );\n                },\n              ),\n              _buildGradientOverlay(),\n              _buildTexts(),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n\n  Widget _buildGradientOverlay() {\n    return DecoratedBox(\n      decoration: const BoxDecoration(\n        gradient: LinearGradient(\n          begin: Alignment.topCenter,\n          end: Alignment.bottomCenter,\n          colors: [Colors.transparent, Colors.black54],\n        ),\n      ),\n    );\n  }\n\n  Widget _buildTexts() {\n    return Positioned(\n      left: 16,\n      bottom: 16,\n      child: Column(\n        crossAxisAlignment: CrossAxisAlignment.start,\n        children: [\n          Text(\n            item.title,\n            style: const TextStyle(\n              color: Colors.white,\n              fontSize: 22,\n              fontWeight: FontWeight.bold,\n            ),\n          ),\n          Text(\n            item.subtitle,\n            style: const TextStyle(color: Colors.white70, fontSize: 14),\n          ),\n        ],\n      ),\n    );\n  }\n}","Scorrendo la lista, le immagini si muovono più lentamente rispetto alle card creando l'effetto parallax.",{"id":59,"position":60,"title":61,"body":62,"code_snippet":63,"code_language":35,"expected_result":64,"demo_url":15,"video_url":15},406,5,"Ottimizzare le prestazioni con RepaintBoundary","L'`AnimatedBuilder` ricostruisce la card ad ogni frame di scroll. Per evitare di ridisegnare parti costose (come i testi statici e il gradiente), avvolgiamo l'immagine parallax in un `RepaintBoundary`.\n\nInoltre spostiamo i widget che non cambiano fuori dal `builder` dell'`AnimatedBuilder` usando il parametro `child`, così Flutter li riusa senza ricostruirli.","AnimatedBuilder(\n  animation: scrollController,\n  child: RepaintBoundary(\n    child: Image.network(item.imageUrl, fit: BoxFit.cover),\n  ),\n  builder: (context, child) {\n    final factor = _calculateParallaxFactor(_cardKey, scrollController);\n    final double dy = (factor - 0.5) * 80;\n    return OverflowBox(\n      maxHeight: 300,\n      child: Transform.translate(\n        offset: Offset(0, dy),\n        child: child, \u002F\u002F riusa il widget immagine senza ricrearlo\n      ),\n    );\n  },\n)","L'effetto rimane fluido anche con molte card, riducendo il carico di rendering.",{"id":66,"position":67,"title":68,"body":69,"code_snippet":70,"code_language":35,"expected_result":71,"demo_url":15,"video_url":15},407,6,"Rifinire l'esperienza con physics ed effetto sui testi","Come tocco finale possiamo aggiungere un `BouncingScrollPhysics` alla lista per uno scroll più elastico e applicare una leggera traslazione anche ai testi, in direzione opposta all'immagine, per accentuare la profondità.\n\nQuesto rende l'insieme più vivo: mentre lo sfondo scende, il testo sale leggermente.","\u002F\u002F Nella ListView\nListView.builder(\n  controller: _controller,\n  physics: const BouncingScrollPhysics(),\n  itemCount: demoItems.length,\n  itemBuilder: (context, index) => ParallaxCard(\n    item: demoItems[index],\n    scrollController: _controller,\n  ),\n);\n\n\u002F\u002F Testi con parallax opposto (dentro un AnimatedBuilder analogo)\n\u002F\u002F dyText = (factor - 0.5) * -20;\n\u002F\u002F Transform.translate(offset: Offset(0, dyText), child: _buildTexts());","Lo scorrimento è elastico e testi e immagini si muovono in direzioni opposte, per un effetto parallax completo e curato.",[],1789120602589]