Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add Signature in flutter?

I have implemented signature_pad in my flutter project and it works fine.

Unfortunately when I place it inside SingleChildScrollView, the signature was not drawn. It scrolled instead of signed.

It seems like is the GestureDetector but I have no idea how to fix it.

Can someone give me some clue on this?

Thanks.

like image 441
Hoo Avatar asked Jan 28 '20 17:01

Hoo


People also ask

How do I add a signature to a PDF in flutter?

Sign PDFs in Flutter End users can tap on a signature element, which will open a popover, allowing them to draw or type a signature or select an image to sign the document with. ℹ️ Note: To sign a document, you need to have the Electronic Signatures component enabled in your license.

What is App signature in flutter?

A Flutter plugin providing performance optimized signature canvas with ability to set custom style, boundaries and initial state. This is native flutter implementation, so it supports all platforms.


2 Answers

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;
  }
}
like image 166
Saed Nabil Avatar answered Sep 30 '22 13:09

Saed Nabil


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

like image 39
Bruno Antunes Avatar answered Sep 30 '22 11:09

Bruno Antunes