Drawing Basic Shapes, Text, and Images in Flutter

Flutter, Google’s UI toolkit, enables developers to create beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. A key aspect of building engaging user interfaces involves drawing custom shapes, text, and images directly onto the screen. This post will explore how to draw basic shapes, text, and images in Flutter, providing a foundational understanding for creating custom visualizations and UI elements.

Why Draw Shapes, Text, and Images?

Drawing shapes, text, and images allows developers to create unique UI experiences beyond standard widgets. Here’s why it’s valuable:

  • Customization: Enables the creation of fully customized UI components that are not limited by pre-built widgets.
  • Visualizations: Essential for creating charts, graphs, and other data visualizations.
  • Gaming: Fundamental for building game UIs and rendering game elements.
  • Flexibility: Offers fine-grained control over visual elements.

Drawing Basics in Flutter

In Flutter, drawing is primarily done using the CustomPaint widget along with a CustomPainter class. Here’s the basic structure:

import 'package:flutter/material.dart';

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Drawing logic goes here
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false; // Return true if the painter needs to be redrawn
  }
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyPainter(),
      child: Container(), // You can place child widgets here if needed
    );
  }
}

Let’s break down this code:

  • MyPainter: A class that extends CustomPainter. It contains the paint method, where you’ll write the drawing instructions.
  • paint(Canvas canvas, Size size): This method is called when Flutter needs to draw something. The canvas provides methods for drawing shapes, text, and images, while size provides the dimensions of the drawing area.
  • shouldRepaint: A method that determines whether the painter should be redrawn. Return true if the painting needs to be updated (e.g., due to changes in data), otherwise return false.
  • CustomPaint: A widget that uses a CustomPainter to draw on the screen.

Drawing Basic Shapes

Now let’s explore how to draw common shapes in Flutter.

Drawing a Circle

To draw a circle, use the drawCircle method of the Canvas class:

import 'package:flutter/material.dart';

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 4;

    canvas.drawCircle(center, radius, paint);
  }

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

In this example:

  • Paint is configured to set the color to blue and the style to fill.
  • The center of the circle is calculated based on the size of the canvas.
  • The radius is set to one-quarter of the canvas width.

Drawing a Rectangle

To draw a rectangle, use the drawRect method:

import 'package:flutter/material.dart';

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.green
      ..style = PaintingStyle.stroke
      ..strokeWidth = 4.0;

    final rect = Rect.fromPoints(
      Offset(size.width * 0.1, size.height * 0.1),
      Offset(size.width * 0.9, size.height * 0.9),
    );

    canvas.drawRect(rect, paint);
  }

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

Here:

  • Paint is configured to set the color to green, the style to stroke (outline), and the stroke width to 4.0.
  • A Rect is defined using two Offset points, representing the top-left and bottom-right corners of the rectangle.

Drawing a Line

To draw a line, use the drawLine method:

import 'package:flutter/material.dart';

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.red
      ..strokeWidth = 5.0;

    final startPoint = Offset(size.width * 0.1, size.height / 2);
    final endPoint = Offset(size.width * 0.9, size.height / 2);

    canvas.drawLine(startPoint, endPoint, paint);
  }

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

In this example:

  • Paint is configured to set the color to red and the stroke width to 5.0.
  • The start and end points of the line are defined using Offset.

Drawing Text

To draw text, you’ll need to use the TextPainter class. Here’s how you can draw text on the canvas:

import 'package:flutter/material.dart';

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final textStyle = TextStyle(
      color: Colors.black,
      fontSize: 24,
    );

    final textSpan = TextSpan(
      text: 'Hello, Flutter!',
      style: textStyle,
    );

    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );

    textPainter.layout(
      minWidth: 0,
      maxWidth: size.width,
    );

    final x = size.width / 2 - textPainter.width / 2;
    final y = size.height / 2 - textPainter.height / 2;

    textPainter.paint(canvas, Offset(x, y));
  }

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

Explanation:

  • TextStyle: Defines the style of the text, including color and font size.
  • TextSpan: Contains the text and its style.
  • TextPainter: A class that handles the layout and painting of text.
  • layout: Prepares the text for painting by calculating its size.
  • paint: Draws the text on the canvas at the specified Offset.

Drawing Images

To draw images, you first need to load the image. Flutter provides the Image class for loading and displaying images. Here’s how to draw an image on the canvas:

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class MyPainter extends CustomPainter {
  ui.Image? image;

  MyPainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    if (image != null) {
      final rect = Rect.fromPoints(
        Offset(0, 0),
        Offset(size.width, size.height),
      );
      canvas.drawImageRect(
        image!,
        Rect.fromLTWH(0, 0, image!.width.toDouble(), image!.height.toDouble()),
        rect,
        Paint(),
      );
    }
  }

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

class MyImageWidget extends StatefulWidget {
  @override
  _MyImageWidgetState createState() => _MyImageWidgetState();
}

class _MyImageWidgetState extends State {
  ui.Image? image;

  @override
  void initState() {
    super.initState();
    _loadImage();
  }

  Future _loadImage() async {
    final data = await rootBundle.load('assets/my_image.png');
    final bytes = data.buffer.asUint8List();
    final image = await decodeImageFromList(bytes);
    setState(() {
      this.image = image;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyPainter(image),
      child: Container(),
    );
  }
}

Explanation:

  • Loading the Image: The _loadImage method loads the image from the assets bundle and decodes it into a ui.Image.
  • drawImageRect: Draws the image onto the canvas within the specified rectangle.

Practical Applications and Considerations

  • Performance: Drawing complex shapes and images can be performance-intensive. Optimize your drawing logic and consider caching drawn elements.
  • Animation: Combine drawing with animation techniques to create dynamic visual effects.
  • Responsiveness: Ensure your drawings adapt to different screen sizes and orientations by using the Size parameter provided in the paint method.

Conclusion

Drawing shapes, text, and images in Flutter allows developers to create highly customized and engaging user interfaces. By leveraging the CustomPaint widget and the drawing capabilities of the Canvas class, you can create anything from simple shapes to complex visualizations. Experiment with these techniques to bring your creative ideas to life and enhance the user experience of your Flutter applications. Mastering these fundamental drawing concepts opens up a world of possibilities for custom UI design and creative expression in Flutter development.