Search code examples
flutterdartsignatureflutter-widgetsinglechildscrollview

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.


Solution

  • 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;
      }
    }