How to add Signature in flutter?

Signature Class need to be modified to respond to VerticalDrag , I renamed it to Signature1

now signature area pad should not scroll , you can check the complete code below as it behaves. you will find out that Signature area is no more scrolling with the SingleChildScrollView.

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var color = Colors.black;
  var strokeWidth = 3.0;
  final _sign = GlobalKey<Signature1State>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body:
           SingleChildScrollView(
             child: Column(
               children: <Widget>[
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 SizedBox(height: 15),
                 _showCategory(),
                 _showSignaturePad()
               ],
             ),
           )

      ,
    );
  }

  Widget _showCategory() {
    return TextField(
        onTap: () {
          FocusScope.of(context).requestFocus(FocusNode());
        },
        style: TextStyle(fontSize: 12.0, height: 1.0),
        decoration: InputDecoration(hintText: "TextView"));
  }

  Widget _showSignaturePad() {
    return Container(
      width: double.infinity,
      height: 200,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Container(
          height: 200,
          //color: Colors.red,
          child: Signature1(
            color: color,
            key: _sign,
            strokeWidth: strokeWidth,
          ),
        ),
      ),
      color: Colors.grey.shade300,
    );
  }
}
class Signature1 extends StatefulWidget {
  final Color color;
  final double strokeWidth;
  final CustomPainter backgroundPainter;
  final Function onSign;

  Signature1({
    this.color = Colors.black,
    this.strokeWidth = 5.0,
    this.backgroundPainter,
    this.onSign,
    Key key,
  }) : super(key: key);

  Signature1State createState() => Signature1State();

  static Signature1State of(BuildContext context) {
    return context.findAncestorStateOfType<Signature1State>();
  }
}

class _SignaturePainter extends CustomPainter {
  Size _lastSize;
  final double strokeWidth;
  final List<Offset> points;
  final Color strokeColor;
  Paint _linePaint;

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

  @override
  void paint(Canvas canvas, Size size) {
    _lastSize = size;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], _linePaint);
    }
  }

  @override
  bool shouldRepaint(_SignaturePainter other) => other.points != points;
}

class Signature1State extends State<Signature1> {
  List<Offset> _points = <Offset>[];
  _SignaturePainter _painter;
  Size _lastSize;

  Signature1State();

  void _onDragStart(DragStartDetails details){
    RenderBox referenceBox = context.findRenderObject();
    Offset localPostion = referenceBox.globalToLocal(details.globalPosition);
    setState(() {
      _points = List.from(_points)
        ..add(localPostion)
        ..add(localPostion);
    });
  }
  void _onDragUpdate (DragUpdateDetails details) {
    RenderBox referenceBox = context.findRenderObject();
    Offset localPosition = referenceBox.globalToLocal(details.globalPosition);

    setState(() {
      _points = List.from(_points)..add(localPosition);
      if (widget.onSign != null) {
        widget.onSign();
      }
    });
  }
  void _onDragEnd (DragEndDetails details) => _points.add(null);

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => afterFirstLayout(context));
    _painter = _SignaturePainter(points: _points, strokeColor: widget.color, strokeWidth: widget.strokeWidth);
    return ClipRect(
      child: CustomPaint(
        painter: widget.backgroundPainter,
        foregroundPainter: _painter,
        child: GestureDetector(

          onVerticalDragStart: _onDragStart,
          onVerticalDragUpdate: _onDragUpdate,
          onVerticalDragEnd: _onDragEnd,

          onPanStart: _onDragStart,
          onPanUpdate: _onDragUpdate,
          onPanEnd: _onDragEnd
        ),
      ),
    );
  }

  Future<ui.Image> getData() {
    var recorder = ui.PictureRecorder();
    var origin = Offset(0.0, 0.0);
    var paintBounds = Rect.fromPoints(_lastSize.topLeft(origin), _lastSize.bottomRight(origin));
    var canvas = Canvas(recorder, paintBounds);
    if(widget.backgroundPainter != null) {
      widget.backgroundPainter.paint(canvas, _lastSize);
    }
    _painter.paint(canvas, _lastSize);
    var picture = recorder.endRecording();
    return picture.toImage(_lastSize.width.round(), _lastSize.height.round());
  }

  void clear() {
    setState(() {
      _points = [];
    });
  }

  bool get hasPoints => _points.length > 0;

  List<Offset> get points => _points;

  afterFirstLayout(BuildContext context) {
    _lastSize = context.size;
  }
}

you need to create a CustomGestureDetector.

Check this updated version of Signature that I just changed to you:


    import 'dart:async';
    import 'dart:ui' as ui;

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

    class Signature extends StatefulWidget {
      final Color color;
      final double strokeWidth;
      final CustomPainter backgroundPainter;
      final Function onSign;

      Signature({
        this.color = Colors.black,
        this.strokeWidth = 5.0,
        this.backgroundPainter,
        this.onSign,
        Key key,
      }) : super(key: key);

      SignatureState createState() => SignatureState();

      static SignatureState of(BuildContext context) {
        return context.findAncestorStateOfType<SignatureState>();
      }
    }

    class CustomPanGestureRecognizer extends OneSequenceGestureRecognizer {
      final Function onPanStart;
      final Function onPanUpdate;
      final Function onPanEnd;

      CustomPanGestureRecognizer({@required this.onPanStart, @required this.onPanUpdate, @required this.onPanEnd});

      @override
      void addPointer(PointerEvent event) {
        onPanStart(event.position);
        startTrackingPointer(event.pointer);
        resolve(GestureDisposition.accepted);
      }

      @override
      void handleEvent(PointerEvent event) {
        if (event is PointerMoveEvent) {
          onPanUpdate(event.position);
        }
        if (event is PointerUpEvent) {
          onPanEnd(event.position);
          stopTrackingPointer(event.pointer);
        }
      }

      @override
      String get debugDescription => 'customPan';

      @override
      void didStopTrackingLastPointer(int pointer) {}
    }

    class _SignaturePainter extends CustomPainter {
      Size _lastSize;
      final double strokeWidth;
      final List<Offset> points;
      final Color strokeColor;
      Paint _linePaint;

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

      @override
      void paint(Canvas canvas, Size size) {
        _lastSize = size;
        for (int i = 0; i < points.length - 1; i++) {
          if (points[i] != null && points[i + 1] != null) canvas.drawLine(points[i], points[i + 1], _linePaint);
        }
      }

      @override
      bool shouldRepaint(_SignaturePainter other) => other.points != points;
    }

    class SignatureState extends State<Signature> {
      List<Offset> _points = <Offset>[];
      _SignaturePainter _painter;
      Size _lastSize;

      SignatureState();

      @override
      Widget build(BuildContext context) {
        WidgetsBinding.instance.addPostFrameCallback((_) => afterFirstLayout(context));
        _painter = _SignaturePainter(points: _points, strokeColor: widget.color, strokeWidth: widget.strokeWidth);
        return ClipRect(
          child: CustomPaint(
            painter: widget.backgroundPainter,
            foregroundPainter: _painter,
            child: RawGestureDetector(
              gestures: {
                CustomPanGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomPanGestureRecognizer>(
                  () => CustomPanGestureRecognizer(
                    onPanStart: (position) {
                      RenderBox referenceBox = context.findRenderObject();
                      Offset localPostion = referenceBox.globalToLocal(position);
                      setState(() {
                        _points = List.from(_points)..add(localPostion)..add(localPostion);
                      });
                      return true;
                    },
                    onPanUpdate: (position) {
                      RenderBox referenceBox = context.findRenderObject();
                      Offset localPosition = referenceBox.globalToLocal(position);

                      setState(() {
                        _points = List.from(_points)..add(localPosition);
                        if (widget.onSign != null) {
                          widget.onSign();
                        }
                      });
                    },
                    onPanEnd: (position) {
                      _points.add(null);
                    },
                  ),
                  (CustomPanGestureRecognizer instance) {},
                ),
              },
            ),
          ),
        );
      }

      Future<ui.Image> getData() {
        var recorder = ui.PictureRecorder();
        var origin = Offset(0.0, 0.0);
        var paintBounds = Rect.fromPoints(_lastSize.topLeft(origin), _lastSize.bottomRight(origin));
        var canvas = Canvas(recorder, paintBounds);
        if (widget.backgroundPainter != null) {
          widget.backgroundPainter.paint(canvas, _lastSize);
        }
        _painter.paint(canvas, _lastSize);
        var picture = recorder.endRecording();
        return picture.toImage(_lastSize.width.round(), _lastSize.height.round());
      }

      void clear() {
        setState(() {
          _points = [];
        });
      }

      bool get hasPoints => _points.length > 0;

      List<Offset> get points => _points;

      afterFirstLayout(BuildContext context) {
        _lastSize = context.size;
      }
    }


Special attention to CustomPanGestureRecognizer

You can read more in:

Gesture Disambiguation


This is happening because the gesture from SingleChildScrollView overrides your Signature widget’s gesture as the SingleChildScrollView is the parent. There are few ways to solve it as in the other responses in this thread. But the easiest one is using the existing package. You can simply use the below Syncfusion's Flutter SignaturePad widget which I am using now for my application. This widget will work on Android, iOS, and web platforms.

Package - https://pub.dev/packages/syncfusion_flutter_signaturepad

Features - https://www.syncfusion.com/flutter-widgets/flutter-signaturepad

Documentation - https://help.syncfusion.com/flutter/signaturepad/getting-started