> For the complete documentation index, see [llms.txt](https://flutteruniversity.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://flutteruniversity.gitbook.io/docs/learn-flutter/professional/custom-painters.md).

# Custom Painters

Creating custom graphics in Flutter can be done with the `CustomPainter` class, which allows you to draw shapes, text, and images directly onto the screen. The `Canvas` object provides a wide array of drawing functions to help you create your custom visuals.

***

## Custom Painting in Flutter 🎨

### 1. Introduction to CustomPainter 🖌️

`CustomPainter` allows you to draw directly to the screen. It’s used in conjunction with a `CustomPaint` widget.

```dart
class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Your painting code here...
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}
```

### 2. Drawing Shapes 🟦

Use the `Canvas` object to draw shapes like circles, rectangles, arcs, and paths.

```dart
void paint(Canvas canvas, Size size) {
  final paint = Paint()
    ..color = Colors.blue
    ..style = PaintingStyle.fill;

  final rect = Rect.fromLTWH(0, 0, 100, 100);
  canvas.drawRect(rect, paint);
}
```

### 3. Drawing Text 🅰️

Draw text by first creating a `TextPainter`, setting the text and style, then painting it onto the canvas.

```dart
void paint(Canvas canvas, Size size) {
  final textPainter = TextPainter(
    text: TextSpan(
      text: 'Hello, world!',
      style: TextStyle(color: Colors.black, fontSize: 30),
    ),
    textDirection: TextDirection.ltr,
  );

  textPainter.layout();
  textPainter.paint(canvas, Offset(50, 50));
}
```

### 4. Drawing Images 🖼️

Load images and draw them onto the canvas.

```dart
ui.Image image;

void loadImage() async {
  final ByteData data = await rootBundle.load('assets/image.png');
  final List<int> bytes = data.buffer.asUint8List();
  final ui.Codec codec = await ui.instantiateImageCodec(bytes);
  final ui.FrameInfo frameInfo = await codec.getNextFrame();
  image = frameInfo.image;
}

void paint(Canvas canvas, Size size) {
  if (image != null) {
    canvas.drawImage(image, Offset.zero, Paint());
  }
}
```

## Custom Dial Example:

{% tabs %}
{% tab title="Code" %}

```dart
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DialScreen(),
    );
  }
}

class DialScreen extends StatefulWidget {
  @override
  _DialScreenState createState() => _DialScreenState();
}

class _DialScreenState extends State<DialScreen> {
  double dialValue = 0.0;

  void _updateDialValue(double value) {
    setState(() {
      dialValue = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Dial Example'),
      ),
      body: Center(
        child: CustomDial(
          value: dialValue,
          onChanged: _updateDialValue,
        ),
      ),
    );
  }
}

class CustomDial extends StatelessWidget {
  final double value;
  final ValueChanged<double> onChanged;

  CustomDial({required this.value, required this.onChanged});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        RenderBox box = context.findRenderObject() as RenderBox;
        Offset position = box.globalToLocal(details.globalPosition);
        final double angle = atan2(position.dy - box.size.height / 2, position.dx - box.size.width / 2);
        final double newValue = angle / (2 * pi) + 0.5;
        onChanged(newValue.clamp(0.0, 1.0));
      },
      child: CustomPaint(
        painter: DialPainter(value),
        size: Size(200, 200),
      ),
    );
  }
}

class DialPainter extends CustomPainter {
  final double value;

  DialPainter(this.value);

  @override
  void paint(Canvas canvas, Size size) {
    double angle = 2 * pi * (value - 0.25);
    Offset center = size.center(Offset.zero);
    double radius = size.width / 2;

    Paint paintLine = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 8.0;

    Paint paintOutline = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke;

    Paint paintKnob = Paint()..color = Colors.black;

    canvas.drawCircle(center, radius, paintOutline);
    canvas.drawLine(center, Offset(center.dx + radius * cos(angle), center.dy + radius * sin(angle)), paintLine);
    canvas.drawCircle(Offset(center.dx + radius * cos(angle), center.dy + radius * sin(angle)), 25, paintKnob);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}
```

{% endtab %}

{% tab title="Result" %}

<figure><img src="/files/eG9K73VjMOeHQ8p6uZdx" alt="" width="450"><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

In this example:

* We create a `DialScreen` widget to hold the current value of the dial and to create a `CustomDial` widget.
* The `CustomDial` widget takes a value and a callback for when the value changes. It creates a `GestureDetector` to handle drag updates, and a `CustomPaint` widget to actually draw the dial.
* Inside the `onPanUpdate` callback, we calculate the new value of the dial based on the angle of the drag and call `onChanged` to update the value.
* We create a `DialPainter` class that extends `CustomPainter`, which draws the dial using the current value.
* The `paint` method of `DialPainter`, we draw an outline circle, a line from the centre of the circle to the current position of the dial, and a knob at the end of the line.

***

## Assignments 📝

* [ ] Create a custom painter to draw a simple shape.
* [ ] Extend your custom painter to include text.
* [ ] Load an image and display it within your custom painter.
