[{"data":1,"prerenderedAt":75},["ShallowReactive",2],{"tutorial-disegni-personalizzati-in-flutter-con-custompaint-e-custompainter":3,"comments-tutorial-disegni-personalizzati-in-flutter-con-custompaint-e-custompainter":74},{"id":4,"title":5,"slug":6,"excerpt":7,"intro":8,"cover_image":9,"video_url":10,"difficulty":11,"estimated_minutes":12,"flutter_version":13,"status":14,"published_at":15,"meta_title":16,"meta_description":17,"category":18,"author":22,"steps":24},25,"Disegni personalizzati in Flutter con CustomPaint e CustomPainter","disegni-personalizzati-in-flutter-con-custompaint-e-custompainter","Impara a usare CustomPaint e CustomPainter per disegnare grafica vettoriale personalizzata: forme, linee, gradienti e un indicatore di progresso circolare animato.","## Introduzione\n\nQuando i widget standard di Flutter non bastano, entra in gioco il potente sistema di disegno a basso livello basato su `CustomPaint` e `CustomPainter`. Grazie a questi strumenti puoi disegnare direttamente sul canvas: linee, curve, cerchi, poligoni, gradienti e testo, con un controllo pixel-perfect.\n\nIn questo tutorial costruiremo passo dopo passo alcuni disegni personalizzati, culminando in un **indicatore di progresso circolare animato** completamente realizzato con `CustomPainter`. Al termine avrai le basi per creare qualsiasi grafica vettoriale nella tua app.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Ftutorials\u002Fe741ace2-10c5-44d0-985f-e3f204296ffe.jpg",null,"intermediate",35,"3.x","published","2026-07-01T04:30:52+00:00","CustomPaint e CustomPainter in Flutter: guida pratica","Tutorial su CustomPaint e CustomPainter in Flutter: disegna forme, gradienti e un progress circolare animato sul canvas passo dopo passo.",{"id":19,"name":20,"slug":21},1,"Guide","guide",{"id":19,"name":23},"Flutter Bot",[25,32,39,46,53,60,67],{"id":26,"position":19,"title":27,"body":28,"code_snippet":29,"code_language":30,"expected_result":31,"demo_url":10,"video_url":10},169,"Impostare il widget CustomPaint","Il widget `CustomPaint` fa da ponte tra l'albero dei widget e il canvas. Ha due proprietà chiave:\n\n- `painter`: il `CustomPainter` che disegna dietro i figli.\n- `size`: la dimensione dell'area di disegno (se non ci sono figli).\n\nPartiamo da uno scaffold minimo che riserva uno spazio quadrato al nostro disegno.","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      home: Scaffold(\n        appBar: AppBar(title: const Text('CustomPaint Demo')),\n        body: Center(\n          child: CustomPaint(\n            size: const Size(250, 250),\n            painter: MyPainter(),\n          ),\n        ),\n      ),\n    );\n  }\n}","dart","L'app si avvia con un'area vuota di 250x250 pixel al centro dello schermo (il painter verrà creato al passo successivo).",{"id":33,"position":34,"title":35,"body":36,"code_snippet":37,"code_language":30,"expected_result":38,"demo_url":10,"video_url":10},170,2,"Creare il primo CustomPainter","Un `CustomPainter` richiede l'override di due metodi:\n\n- `paint(Canvas canvas, Size size)`: dove disegni usando l'oggetto `Canvas`.\n- `shouldRepaint(covariant CustomPainter old)`: restituisce `true` se il disegno deve essere aggiornato.\n\nL'oggetto `Paint` definisce colore, stile (`fill`\u002F`stroke`), spessore e altro. Disegniamo un cerchio e una linea di base.","class MyPainter extends CustomPainter {\n  @override\n  void paint(Canvas canvas, Size size) {\n    final center = Offset(size.width \u002F 2, size.height \u002F 2);\n\n    final circlePaint = Paint()\n      ..color = Colors.blue.shade200\n      ..style = PaintingStyle.fill;\n    canvas.drawCircle(center, size.width \u002F 3, circlePaint);\n\n    final linePaint = Paint()\n      ..color = Colors.blueAccent\n      ..strokeWidth = 4\n      ..style = PaintingStyle.stroke;\n    canvas.drawLine(\n      Offset(0, size.height \u002F 2),\n      Offset(size.width, size.height \u002F 2),\n      linePaint,\n    );\n  }\n\n  @override\n  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;\n}","Vedi un cerchio azzurro pieno con una linea orizzontale che lo attraversa al centro dell'area di disegno.",{"id":40,"position":41,"title":42,"body":43,"code_snippet":44,"code_language":30,"expected_result":45,"demo_url":10,"video_url":10},171,3,"Disegnare forme complesse con Path","La classe `Path` permette di comporre forme arbitrarie combinando linee (`lineTo`), curve di Bézier (`quadraticBezierTo`, `cubicTo`) e archi. È lo strumento ideale per icone, grafici e forme organiche.\n\nDisegniamo un triangolo e un'onda usando un `Path`.","@override\nvoid paint(Canvas canvas, Size size) {\n  final trianglePaint = Paint()\n    ..color = Colors.deepOrange\n    ..style = PaintingStyle.fill;\n\n  final triangle = Path()\n    ..moveTo(size.width \u002F 2, 20)\n    ..lineTo(20, size.height - 20)\n    ..lineTo(size.width - 20, size.height - 20)\n    ..close();\n  canvas.drawPath(triangle, trianglePaint);\n\n  final wavePaint = Paint()\n    ..color = Colors.white\n    ..style = PaintingStyle.stroke\n    ..strokeWidth = 3;\n\n  final wave = Path()..moveTo(0, size.height \u002F 2);\n  wave.quadraticBezierTo(\n    size.width * 0.25, size.height * 0.3,\n    size.width * 0.5, size.height \u002F 2,\n  );\n  wave.quadraticBezierTo(\n    size.width * 0.75, size.height * 0.7,\n    size.width, size.height \u002F 2,\n  );\n  canvas.drawPath(wave, wavePaint);\n}","Compare un triangolo arancione con sopra un'onda bianca disegnata tramite curve di Bézier.",{"id":47,"position":48,"title":49,"body":50,"code_snippet":51,"code_language":30,"expected_result":52,"demo_url":10,"video_url":10},172,4,"Aggiungere gradienti con Shader","Per rendere il disegno più accattivante possiamo applicare un gradiente al `Paint` tramite la proprietà `shader`. Usiamo `LinearGradient` (o `RadialGradient`, `SweepGradient`) e generiamo lo shader passando un `Rect` di riferimento.","@override\nvoid paint(Canvas canvas, Size size) {\n  final rect = Offset.zero & size;\n\n  final gradientPaint = Paint()\n    ..shader = const LinearGradient(\n      begin: Alignment.topLeft,\n      end: Alignment.bottomRight,\n      colors: [Colors.purple, Colors.orange],\n    ).createShader(rect);\n\n  final rrect = RRect.fromRectAndRadius(\n    Rect.fromLTWH(20, 20, size.width - 40, size.height - 40),\n    const Radius.circular(24),\n  );\n  canvas.drawRRect(rrect, gradientPaint);\n}","L'area mostra un rettangolo con angoli arrotondati riempito da un gradiente che sfuma dal viola all'arancione.",{"id":54,"position":55,"title":56,"body":57,"code_snippet":58,"code_language":30,"expected_result":59,"demo_url":10,"video_url":10},173,5,"Costruire un progress circolare","Ora mettiamo insieme le tecniche per un componente utile: un indicatore di progresso circolare. Disegniamo un arco di sfondo grigio e un arco colorato che rappresenta la percentuale, usando `drawArc`.\n\nIl parametro `progress` va da 0.0 a 1.0; lo trasformiamo in un angolo in radianti (`2 * pi * progress`). Il `StrokeCap.round` arrotonda le estremità.","import 'dart:math' as math;\n\nclass CircularProgressPainter extends CustomPainter {\n  final double progress; \u002F\u002F 0.0 - 1.0\n  CircularProgressPainter(this.progress);\n\n  @override\n  void paint(Canvas canvas, Size size) {\n    final center = Offset(size.width \u002F 2, size.height \u002F 2);\n    final radius = size.width \u002F 2 - 10;\n    final rect = Rect.fromCircle(center: center, radius: radius);\n\n    final bgPaint = Paint()\n      ..color = Colors.grey.shade300\n      ..style = PaintingStyle.stroke\n      ..strokeWidth = 12;\n    canvas.drawCircle(center, radius, bgPaint);\n\n    final progressPaint = Paint()\n      ..color = Colors.teal\n      ..style = PaintingStyle.stroke\n      ..strokeWidth = 12\n      ..strokeCap = StrokeCap.round;\n\n    canvas.drawArc(\n      rect,\n      -math.pi \u002F 2, \u002F\u002F parte dall'alto\n      2 * math.pi * progress,\n      false,\n      progressPaint,\n    );\n  }\n\n  @override\n  bool shouldRepaint(covariant CircularProgressPainter old) =>\n      old.progress != progress;\n}","Con un valore di progress (es. 0.6) vedi un anello grigio con un arco teal che ne copre il 60% partendo dall'alto.",{"id":61,"position":62,"title":63,"body":64,"code_snippet":65,"code_language":30,"expected_result":66,"demo_url":10,"video_url":10},174,6,"Animare il progress con AnimationController","Per far crescere l'arco in modo fluido combiniamo il painter con un `AnimationController`. Notare come `shouldRepaint` che confronta il valore di `progress` sia fondamentale per far ridisegnare il canvas ad ogni frame senza sprechi.","class AnimatedProgress extends StatefulWidget {\n  const AnimatedProgress({super.key});\n\n  @override\n  State\u003CAnimatedProgress> createState() => _AnimatedProgressState();\n}\n\nclass _AnimatedProgressState extends State\u003CAnimatedProgress>\n    with SingleTickerProviderStateMixin {\n  late final AnimationController _controller;\n\n  @override\n  void initState() {\n    super.initState();\n    _controller = AnimationController(\n      vsync: this,\n      duration: const Duration(seconds: 2),\n    )..forward();\n  }\n\n  @override\n  void dispose() {\n    _controller.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return AnimatedBuilder(\n      animation: _controller,\n      builder: (context, _) {\n        return CustomPaint(\n          size: const Size(200, 200),\n          painter: CircularProgressPainter(_controller.value),\n          child: SizedBox(\n            width: 200,\n            height: 200,\n            child: Center(\n              child: Text(\n                '${(_controller.value * 100).round()}%',\n                style: const TextStyle(fontSize: 28),\n              ),\n            ),\n          ),\n        );\n      },\n    );\n  }\n}","L'anello si riempie progressivamente da 0% a 100% in 2 secondi, con la percentuale aggiornata al centro.",{"id":68,"position":69,"title":70,"body":71,"code_snippet":72,"code_language":30,"expected_result":73,"demo_url":10,"video_url":10},175,7,"Best practice e ottimizzazione","Alcuni accorgimenti per usare `CustomPainter` in modo efficiente:\n\n- **`shouldRepaint`**: restituisci `true` solo quando i dati cambiano davvero, per evitare ridisegni inutili.\n- **`RepaintBoundary`**: avvolgi il `CustomPaint` per isolare i repaint dal resto dell'albero.\n- **Riusa gli oggetti `Paint`** quando possibile invece di crearli ad ogni `paint`.\n- **`clipRect`\u002F`clipPath`**: limita l'area di disegno per performance e per creare effetti di ritaglio.\n- Per grafica statica e riutilizzabile valuta l'export in `Picture` con `PictureRecorder`.\n\nCon queste basi puoi disegnare qualsiasi cosa: badge, grafici custom, firme, mappe di calore e molto altro.","\u002F\u002F Isolare i repaint del disegno dal resto della UI\nRepaintBoundary(\n  child: CustomPaint(\n    size: const Size(200, 200),\n    painter: CircularProgressPainter(0.75),\n  ),\n)","Il disegno viene ridipinto solo quando necessario, migliorando le performance dell'app anche con animazioni complesse.",[],1785219610218]