signature.dart 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import 'dart:async';
  2. import 'dart:typed_data';
  3. import 'dart:ui' as ui;
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/widgets.dart';
  6. /// Signature canvas. Controller is required, other parameters are optional. It expands by default.
  7. /// This behaviour can be overridden using width and/or height parameters.
  8. class Signature extends StatefulWidget {
  9. Signature({
  10. Key key,
  11. @required this.controller,
  12. this.backgroundColor = Colors.grey,
  13. this.width,
  14. this.height,
  15. }) : assert(controller != null),
  16. super(key: key);
  17. final SignatureController controller;
  18. final double width;
  19. final double height;
  20. final Color backgroundColor;
  21. @override
  22. State createState() => SignatureState();
  23. }
  24. class SignatureState extends State<Signature> {
  25. /// Helper variable indicating that user has left the canvas so we can prevent linking next point
  26. /// with straight line.
  27. bool _isOutsideDrawField = false;
  28. @override
  29. Widget build(BuildContext context) {
  30. var maxWidth = widget.width ?? double.infinity;
  31. var maxHeight = widget.height ?? double.infinity;
  32. var signatureCanvas = GestureDetector(
  33. onVerticalDragUpdate: (DragUpdateDetails details) {
  34. //NO-OP
  35. },
  36. child: Container(
  37. decoration: BoxDecoration(color: widget.backgroundColor),
  38. child: Listener(
  39. onPointerDown: (event) => _addPoint(event, PointType.tap),
  40. onPointerUp: (event) => _addPoint(event, PointType.tap),
  41. onPointerMove: (event) => _addPoint(event, PointType.move),
  42. child: RepaintBoundary(
  43. child: CustomPaint(
  44. painter: _SignaturePainter(widget.controller.points, widget.controller.penColor,
  45. widget.controller.penStrokeWidth),
  46. child: ConstrainedBox(
  47. constraints: BoxConstraints(
  48. minWidth: maxWidth,
  49. minHeight: maxHeight,
  50. maxWidth: maxWidth,
  51. maxHeight: maxHeight),
  52. ),
  53. ),
  54. ),
  55. ),
  56. ),
  57. );
  58. if (widget.width != null || widget.height != null) {
  59. //IF DOUNDARIES ARE DEFINED, USE LIMITED BOX
  60. return Center(
  61. child: LimitedBox(maxWidth: maxWidth, maxHeight: maxHeight, child: signatureCanvas));
  62. } else {
  63. //IF NO BOUNDARIES ARE DEFINED, USE EXPANDED
  64. return Expanded(child: signatureCanvas);
  65. }
  66. }
  67. void _addPoint(PointerEvent event, PointType type) {
  68. Offset o = event.localPosition;
  69. //SAVE POINT ONLY IF IT IS IN THE SPECIFIED BOUNDARIES
  70. if ((widget.width == null || o.dx > 0 && o.dx < widget.width) &&
  71. (widget.height == null || o.dy > 0 && o.dy < widget.height)) {
  72. // IF USER LEFT THE BOUNDARY AND AND ALSO RETURNED BACK
  73. // IN ONE MOVE, RETYPE IT AS TAP, AS WE DO NOT WANT TO
  74. // LINK IT WITH PREVIOUS POINT
  75. if (_isOutsideDrawField) {
  76. type = PointType.tap;
  77. }
  78. setState(() {
  79. //IF USER WAS OUTSIDE OF CANVAS WE WILL RESET THE HELPER VARIABLE AS HE HAS RETURNED
  80. _isOutsideDrawField = false;
  81. widget.controller.addPoint(Point(o, type));
  82. });
  83. } else {
  84. //NOTE: USER LEFT THE CANVAS!!! WE WILL SET HELPER VARIABLE
  85. //WE ARE NOT UPDATING IN setState METHOD BECAUSE WE DO NOT NEED TO RUN BUILD METHOD
  86. _isOutsideDrawField = true;
  87. }
  88. }
  89. }
  90. enum PointType { tap, move }
  91. class Point {
  92. Offset offset;
  93. PointType type;
  94. Point(this.offset, this.type);
  95. }
  96. class _SignaturePainter extends CustomPainter {
  97. List<Point> _points;
  98. Paint _penStyle;
  99. _SignaturePainter(this._points, Color penColor, double penStrokeWidth) {
  100. this._penStyle = Paint()
  101. ..color = penColor
  102. ..strokeWidth = penStrokeWidth;
  103. }
  104. @override
  105. void paint(Canvas canvas, Size size) {
  106. if (_points == null || _points.isEmpty) return;
  107. for (int i = 0; i < (_points.length - 1); i++) {
  108. if (_points[i + 1].type == PointType.move) {
  109. canvas.drawLine(
  110. _points[i].offset,
  111. _points[i + 1].offset,
  112. _penStyle,
  113. );
  114. } else {
  115. canvas.drawCircle(
  116. _points[i].offset,
  117. 2.0,
  118. _penStyle,
  119. );
  120. }
  121. }
  122. }
  123. @override
  124. bool shouldRepaint(CustomPainter other) => true;
  125. }
  126. class SignatureController extends ValueNotifier<List<Point>> {
  127. final Color penColor;
  128. final double penStrokeWidth;
  129. SignatureController({List<Point> points, this.penColor = Colors.black, this.penStrokeWidth = 3.0})
  130. : super(points ?? List<Point>());
  131. List<Point> get points => value;
  132. set points(List<Point> value) {
  133. value = value.toList();
  134. }
  135. addPoint(Point point) {
  136. value.add(point);
  137. this.notifyListeners();
  138. }
  139. bool get isEmpty {
  140. return value.length == 0;
  141. }
  142. bool get isNotEmpty {
  143. return value.length > 0;
  144. }
  145. clear() {
  146. value = List<Point>();
  147. }
  148. Future<ui.Image> toImage() async {
  149. if (isEmpty) return null;
  150. double minX = double.infinity, minY = double.infinity;
  151. double maxX = 0, maxY = 0;
  152. points.forEach((point) {
  153. if (point.offset.dx < minX) minX = point.offset.dx;
  154. if (point.offset.dy < minY) minY = point.offset.dy;
  155. if (point.offset.dx > maxX) maxX = point.offset.dx;
  156. if (point.offset.dy > maxY) maxY = point.offset.dy;
  157. });
  158. var recorder = ui.PictureRecorder();
  159. var canvas = Canvas(recorder);
  160. canvas.drawColor(Colors.white, BlendMode.color);
  161. print(111);
  162. canvas.translate(-(minX - penStrokeWidth), -(minY - penStrokeWidth));
  163. _SignaturePainter(points, penColor, penStrokeWidth).paint(canvas, null);
  164. var picture = recorder.endRecording();
  165. return picture.toImage(
  166. (maxX - minX + penStrokeWidth * 2).toInt(), (maxY - minY + penStrokeWidth * 2).toInt());
  167. }
  168. Future<Uint8List> toPngBytes() async {
  169. var image = await toImage();
  170. var bytes = await image.toByteData(format: ui.ImageByteFormat.png);
  171. return bytes.buffer.asUint8List();
  172. }
  173. }