# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://flutteruniversity.gitbook.io/docs/learn-flutter/professional/custom-painters.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
