Flutter : Signature Pad

I will show you how to build a signature pad with flutter 2.0

Flutter : Signature Pad

Hi! we are going to talk about Flutter 2.0's signature pad creation.
If you have a project that deals with papers and that requires automation and portability, this article is for you. 

Since you are here, it only means that you know the basics in creating a flutter application, so I will not talk about that one here.

LETS GET STARTED

First we will be creating our SignaturePainter, the purpose of this painter is what it says "Painter" meaning this will handle our signature output and will draw our signature based on the screen gesture.

import 'package:flutter/material.dart';

class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  List<Offset?> points;
  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 3.0
      ..strokeCap = StrokeCap.square;
    for (var i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null && ((points[i]!.dy < size.height && points[i+1]!.dy < size.height) && (points[i]!.dy > 0 && points[i+1]!.dy > 0)) && ((points[i]!.dx < size.width && points[i+1]!.dx < size.width) && (points[i]!.dx > 0 && points[i+1]!.dx > 0))) {
        canvas.drawLine(points[i]!, points[i + 1]!, paint);
      }
    }
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;

On this one, we need to import material package for the purpose of using the following:

  • Offset;
  • Canvas;
  • CustomPainter;
  • Size;
  • Paint;
  • Color; and
  • Stroke Cap

Our Class extended CustomPainter to override its functions that we need to achieve our goal

List<Offset?> points; this will become our data container for that accepts offsets and null, you might wonder why we need to accept a null value? Stay tuned and you will learn why.

Next is we overrode paint method, now this is what I was talking about when I said above the reason why we extended CustomPainter to our class, paint method is a property of CustomPainter that accepts Canvas and Size of the parent widget.

var paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 3.0
      ..strokeCap = StrokeCap.square;

On this part we are now building our Pen, yes this will become the output of our signature, and you can edit as you wish.

@override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;

This is the second overriden method from CustomPainter and the purpose of this one is to update our widget, to make it clearer this method listens to each gesture and lets you see the update right away, that is if you will be doing the same as I did and return TRUE, updates will not be available if you return false.

You might wonder, "Why did he jumped to the second overriden method and did not discussed the loop one?", that is because the fun part is there, that is where the magic happens.

for (var i = 0; i < points.length - 1; i++)

 Traversing the points array.

Now the fun part starts here, you should be careful doing this because this might affect the output if you wont follow my steps, so read carefully.

On the conditioning part we have a long line of complex expression first is :

points[i] != null && points[i + 1] != null

Now this is the reason why we will be accepting null values inside our points collection, for the moment you can already have this work, but the thing with this is that when you have a smaller canvas or you put our signature pad inside a small container, I mean if your parent widget does not get the whole device's screen, this can cause problems in the future, and the error for this one is it can overlap your parent widget and its sub widgets, simply your signature will draw even outside your parent widget.

To solve the above mentioned problem, or should I say "Future Problem", we will need to do the following code

(points[i]!.dy < size.height && points[i+1]!.dy < size.height)

now this script checks if your gesture on the screen will not go down further the height of your canvas. This means the bottom side of your canvas

(points[i]!.dy > 0 && points[i+1]!.dy > 0))

and this one will be the one to check if your gesture on screen will not got up further the height of your canvas. This means the top side of your canvas

Now we have our

vertical constraint. And since we are already set on the vertical side, of course we need to do the horizontal side.

(points[i]!.dx < size.width && points[i+1]!.dx < size.width)

on this one, this checks if your on-screen gesture will not exceed the width of your canvas, meaning the right side of your canvas; and finally

(points[i]!.dx > 0 && points[i+1]!.dx > 0)

this checks that your on-screen gesture will not go further left if you reached the very beginning of your canvas.

And we are done with the horizontal constraint.

We are at the final part of our painter, and the only one left is the drawing of the line.

canvas.drawLine(points[i]!, points[i + 1]!, paint);

We will simply call the canvas' property "drawLine" method and it will draw the line from our collection of points/offset.

How to use

To use our painter we only be calling it inside a CustomPaint Widget as its painter and Wrap it with gesture detector ( as how its name suggests) and make sure you have your local points collection inside this class or globally. 

sample code :

child: GestureDetector(
              onPanUpdate: (det){
                setState(() {
                  var renderBox = context.findRenderObject() as RenderBox;
                  var localPosition = renderBox.globalToLocal(det.globalPosition);
                  _signatureViewModel.points.add(localPosition);
                });
              },
              onPanDown: (det){
                setState(() {
                  _signatureViewModel.points.add(null);
                });
              },
              child: CustomPaint(
                painter: SignaturePainter(_signatureViewModel.points),
                child: Container(),
              ),
            ),

And we are done with our discussion, hope you will make a good use of this code and article.