Flutter - How to create Digital Signature pad (iOS and Android)

Hello guys, in this post we are going to learn how to make digital signature in flutter

Flutter provides a CustomPainter widget to create canvas and draw the paths

Let's start

Create a class CustomePainter which is extends CustomPainter

class CustomePainter extends CustomPainter {
@override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    throw UnimplementedError();
  }
}

 

The CustomPainter has two methods paint() which is create the canvas and shouldRepaint() used to handle the repaint status on canvas.
The Canvas class contains methods to draw different types of shapes

canvas.drawLine();
canvas.drawCircle()
canvas.drawArc()

To draw something on the canvas we need to pass Paint object to the canvas

Paint _linePaint = Paint()
      ..color = strokeColor//Pass color
      ..strokeWidth = strokeWidth //Pass width of Paint
      ..strokeCap = StrokeCap.round // Pass shape of the pen line

 

Here we are using drawLines() method to draw signature on the canvas.
To this method we need to pass point for each coordinate of the user touch

How we will get the user touch points?

By using the GestureDetector() widget we are going to collect all user touch points and pass this points to the canvas

GestureDetector(
          onPanStart: (details) {
            // before painting, set color & strokeWidth.
            isClear = false;
            points[curFrame].color = selectedColor;
            points[curFrame].strokeWidth = strokeWidth;
          },
          onPanUpdate: (details) {
            RenderBox referenceBox = context.findRenderObject();
            Offset localPosition =
            referenceBox.globalToLocal(details.globalPosition);
            state(() {
              points[curFrame].points.add(localPosition);
            });
          },
          onPanEnd: (details) {
            // preparing for next line painting.
            points.add(Point(selectedColor, strokeWidth, []));
            curFrame++;
          },
        )

Finally add CustomPainter widget to our main widget and run the app

Flutter - Digital Signature pad (iOS and Android)

 

Complete code

main.dart 

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

import 'custome_painter.dart';

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

class MyApp extends StatefulWidget {
  @override
  State createState() => _MyAppState();
}

class _MyAppState extends State {
  static final List colors = [
    Colors.black,
    Colors.purple,
    Colors.green,
  ];
  static final List lineWidths = [3.0, 5.0, 8.0];
  // File imageFile;
  int selectedLine = 0;
  Color selectedColor = colors[0];
  List points = [Point(colors[0], lineWidths[0], [])];
  int curFrame = 0;
  bool isClear = false;

  final GlobalKey _repaintKey = new GlobalKey();

  double get strokeWidth => lineWidths[selectedLine];

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

  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(title: Text("Digital Signature"),backgroundColor: Colors.pink,centerTitle: true,),
          body: Container(
            color: Colors.grey,
            padding: EdgeInsets.all(8),
            child: Column(
              children: [
                Expanded(
                  child: Container(
                    color: Colors.white,
                    margin: EdgeInsets.all(8.0),
                    child: Stack(
                      alignment: Alignment.center,
                      children: [
                        Positioned(
                          child: _buildCanvas(),
                          top: 0.0,
                          bottom: 0.0,
                          left: 0.0,
                          right: 0.0,
                        ),
                      ],
                    ),
                  ),
                ),
                _buildBottom(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildCanvas() {
    return StatefulBuilder(builder: (context, state) {
      return CustomPaint(
        painter: CustomePainter(
          points: points,
          strokeColor: selectedColor,
          strokeWidth: strokeWidth,
          isClear: isClear,
        ),
        child: GestureDetector(
          onPanStart: (details) {
            // before painting, set color & strokeWidth.
            isClear = false;
            points[curFrame].color = selectedColor;
            points[curFrame].strokeWidth = strokeWidth;
          },
          onPanUpdate: (details) {
            RenderBox referenceBox = context.findRenderObject();
            Offset localPosition =
            referenceBox.globalToLocal(details.globalPosition);
            state(() {
              points[curFrame].points.add(localPosition);
            });
          },
          onPanEnd: (details) {
            // preparing for next line painting.
            points.add(Point(selectedColor, strokeWidth, []));
            curFrame++;
          },
        ),
      );
    });
  }

  Widget _buildBottom() {
    return Container(
      color: Colors.pink,
      padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
      child: StatefulBuilder(builder: (context, state) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            GestureDetector(
              child: Icon(
                Icons.brightness_1,
                size: 10.0,
                color: selectedLine == 0
                    ? Colors.white
                    : Colors.white.withOpacity(0.5),
              ),
              onTap: () {
                state(() {
                  selectedLine = 0;
                });
              },
            ),
            GestureDetector(
              child: Icon(
                Icons.brightness_1,
                size: 15.0,
                color: selectedLine == 1
                    ? Colors.white
                    : Colors.white.withOpacity(0.5),
              ),
              onTap: () {
                state(() {
                  selectedLine = 1;
                });
              },
            ),
            GestureDetector(
              child: Icon(
                Icons.brightness_1,
                size: 20.0,
                color: selectedLine == 2
                    ? Colors.white
                    : Colors.white.withOpacity(0.5),
              ),
              onTap: () {
                state(() {
                  selectedLine = 2;
                });
              },
            ),
            GestureDetector(
              child: Container(
                color: selectedColor == colors[0]
                    ? Colors.white
                    : Colors.white.withOpacity(0.2),
                child: Icon(
                  Icons.create,
                  color: colors[0],
                ),
              ),
              onTap: () {
                state(() {
                  selectedColor = colors[0];
                });
              },
            ),
            GestureDetector(
              child: Container(
                color: selectedColor == colors[1]
                    ? Colors.white
                    : Colors.white.withOpacity(0.2),
                child: Icon(
                  Icons.create,
                  color: colors[1],
                ),
              ),
              onTap: () {
                state(() {
                  selectedColor = colors[1];
                });
              },
            ),
            GestureDetector(
              child: Container(
                color: selectedColor == colors[2]
                    ?  Colors.white
                    : Colors.white.withOpacity(0.2),
                child: Icon(
                  Icons.create,
                  color: colors[2],
                ),
              ),
              onTap: () {
                state(() {
                  selectedColor = colors[2];
                });
              },
            ),
            GestureDetector(
              child: Text('clear',style: TextStyle(color: Colors.white),),
              onTap: () {
                setState(() {
                  reset();
                });
              },
            ),

          ],
        );
      }),
    );
  }

  void reset() {
    isClear = true;
    curFrame = 0;
    points.clear();
    points.add(Point(selectedColor, strokeWidth, []));
  }
}

 

CustomePainter file
class Point {
  Color color;
  List points;
  double strokeWidth = 5.0;

  Point(this.color, this.strokeWidth, this.points);
}

class CustomePainter extends CustomPainter {

  final double strokeWidth;
  final Color strokeColor;
  Paint _linePaint;
  final bool isClear;
  final List points;

  CustomePainter({
    @required this.points,
    @required this.strokeColor,
    @required this.strokeWidth,
    this.isClear = true,
  }) {
    _linePaint = Paint()
      ..color = strokeColor
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;
  }

  void paint(Canvas canvas, Size size) {
    if (isClear || points == null || points.length == 0) {
      return;
    }
    for (int i = 0; i < points.length; i++) {
      _linePaint..color = points[i].color;
      _linePaint..strokeWidth = points[i].strokeWidth;
      List curPoints = points[i].points;
      if (curPoints == null || curPoints.length == 0) {
        break;
      }
      for (int i = 0; i < curPoints.length - 1; i++) {
        if (curPoints[i] != null && curPoints[i + 1] != null)
          canvas.drawLine(curPoints[i], curPoints[i + 1], _linePaint);
//      canvas.drawPoints(PointMode.polygon, curPoints, _linePaint);
      }
    }
  }

  bool shouldRepaint(CustomePainter other) => true;
}