Bring your CustomPainter paintings to life in Flutter using Touchable

Natesh Bhat
Flutter Community
Published in
5 min readAug 25, 2020

--

Despite the humongous number of builtin widgets provided by the flutter library, we often find ourselves delving into :

  • Creating complex widgets filled with domain-specific user interface elements.
  • Applying and orchestrating animations to multiple widgets.
  • Customising the look and feel of widgets which expands our creativity to a new level.

To create such art of beauty and wonder, flutter offers us the CustomPainter Widget that gives us the raw power of “painting directly onto the canvas”.
With Flutter’s CustomPainter widget, you can draw pretty much anything on the canvas that you can imagine.

> But that’s NOT what we are here to talk about today… Nope.

Today, we’re here to learn how to make your paintings come alive ! ✨🍾

What we truly want, is not just the ability to create eye catching paintings, we want to transform the static paintings into user interface !

Though “CustomPainter" provides limitless drawing possibilities, it comes with an expensive limitation.

That is, Interaction !

Well, let me unbox the above words in simple terms, by stating out the
limitations of CustomPainter:

  • The Custom Painter lets you only draw shapes on the canvas but many would want to let the user interact with the drawings.
  • You might wanna add touch and drag gestures to each shape or object you draw.
  • You may even wish to animate a specific shape when the user clicks on it.

With CustomPainter all this was extremely difficult to manage……
Until now.

Presenting Touchable :

To solve the above limitations of CustomPainter, I createdTouchable, a flutter library that allows you to add various types of gestures to every shape you draw on the canvas, thus making them touchable 😉 !

Let’s start by looking at an example of a widget that uses CustomPainter .

For simplicity, here’s a minimal page with 2 circles drawn on the canvas (blue and green) with a white background.

Custom Painter Example

Those poor circles are just staying there, unmoving :(
So let’s add some interactivity to the shapes :)

Adding Interactivity :

  • Add touchable as a dependency in your pubspec.yaml file.
  • Wrap your CustomPaint widget inside a CanvasTouchDetector .
CanvasTouchDetector(
builder: (context) => CustomPaint(
painter: MyPainter(context),
),
)
  • Use the TouchyCanvas to draw your shapes and get access to gesture callbacks.
void paint(Canvas canvas, Size size) {
TouchyCanvas touchyCanvas = TouchyCanvas(context, canvas);

touchyCanvas.drawCircle( ... , onTapDown: (_) {
print('You clicked BLUE circle');
});

touchyCanvas.drawCircle( ... , onLongPressStart: (_) {
print('long pressed on GREEN circle');
});
}

Here’s the full code snippet given below :

As you might have guessed, pressing on the blue circle will print You clicked BLUE circle and long pressing on the green circle will run its corresponding callback function.

Alright, we just handled the gestures for the shapes but that’s not particularly exciting, is it? Time to add some excitement!

Animation Time!

Now, we add some magic to the painter : )

Setting the objectives :

We start by setting an objective to create something like this below where we draw two circles :

  • One circle can be dragged around. Let’s call it the earth!
  • The other circle toggles the background colour. This is our Sun!
The Sun and the Earth!

Since we now have a relatively complex set of changing values, we use a state management solution like the provider to handle it in a simple way.

Create the ChangeNotifier (PaintingController) :

First, we create a ChangeNotifier that contains all the painter related properties like the position of the circle and the background colour and methods that change these state properties.

class PaintingController extends ChangeNotifier {
Offset circlePosition;
Color backgroundColor = Colors.white;

void moveCircle(Offset newPosition) {
circlePosition = newPosition;
notifyListeners();
}

void toggleBackGroundColor() {
backgroundColor = backgroundColor == Colors.white ? Colors.blueGrey : Colors.white;
notifyListeners();
}
}

Create the Painter :

We create the painter class keeping the following points in mind :

  • Return true from the shouldRepaint method to enable repaints and animations.
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
  • Get ahold of the PaintingController inside the painter by using the context we send to the painter constructor.
class MyPainter extends CustomPainter {
final BuildContext context;
final PaintingController controller;

MyPainter(this.context) : controller = Provider.of<PaintingController>(context, listen: false);
...
  • In the paint method we draw the earth and the sun :D
@override
void paint(Canvas canvas, Size size) {
TouchyCanvas touchyCanvas = TouchyCanvas(context, canvas);
drawEarth(touchyCanvas, size);
drawSun(touchyCanvas, size);
}
  • We make the earth draggle by adding a onPanUpdate callback and call the moveCircle method of the controller passing the new position.
canvas.drawCircle(..., onPanUpdate: (details) {
controller.moveCircle(Offset(blueCircleCenter.dx + details.delta.dx * 2, blueCircleCenter.dy + details.delta.dy * 2));
});
  • Similarly, we make the sun toggle the background colour by adding onTapDown callback.
canvas.drawCircle(... , onTapDown: (_) {
controller.toggleBackGroundColor();
});
  • To make it look cooler 😎 , we draw some more colourful circles to put some life into it.

Here’s the full code snippet for the Sun and the Earth , below :

The Sun and the Earth in code

Touchable also handles :

  • The painting style (filled ▮ , stroke ▯) of the shape
  • Painting stroke width. So if your shapes are painted thick, it still got it covered.
  • clipping and different clipping modes.
  • HitTestBehavior for each shape, particularly useful when you want gestures on overlapping shapes.

Links :

Have you learnt the art of SMASHing ? Time to test it on the Clap button below 👏🏻 !!!

Clapping is Caring :)

--

--

Natesh Bhat
Flutter Community

An engineer by passion , developer by profession, singer by heart. You can follow me on https://in.linkedin.com/in/nateshmbhat