美文网首页Flutter学习
Flutter 手绘电子签名

Flutter 手绘电子签名

作者: 旺仔_100 | 来源:发表于2021-01-12 20:47 被阅读0次
签名.png

一、思路
签名就是手在画布上面经过的点,连起来就是。难点就是两个,一个是找出绘制的点,还一个就是把所有的点连接起来。

二、flutter中有专门手势识别的widget,其中GestureDetector的onPanUpdate就可以识别正在绘制的点,并把所有的点添加到_points。
当手指抬起来的时候会回调onPanEnd。这个时候添加一个空的点,可以知道什么时候这一笔画结束了。

GestureDetector(
              onPanUpdate: (DragUpdateDetails details) {
                RenderBox renderBox = context.findRenderObject();
                Offset localPosition =
                    renderBox.globalToLocal(details.globalPosition);
                RenderBox referenceBox =
                    _globalKey.currentContext.findRenderObject();

                //校验范围,防止超过外面
                if (localPosition.dx <= 0 || localPosition.dy <= 0) return;
                if (localPosition.dx > referenceBox.size.width ||
                    localPosition.dy > referenceBox.size.height) return;

                setState(() {
                  _points = List.from(_points)..add(localPosition);
                });
              },
              onPanEnd: (DragEndDetails details) {
                _points.add(null);
              },
            ),

三、拿到所有的点之后,把所有的点连起来就可以了,其实就是划线。以null为结束点。

性能优化:上面有一个组件是RepaintBoundary 。这个组件包裹频繁绘制的控件,它不会影响到它之外的控件的重绘,是个性能优化的点。

 Paint paint = Paint()
      ..strokeCap = StrokeCap.round
      ..color = Colors.black
      ..isAntiAlias = true
      ..strokeWidth = mStrokeWidth;

    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i], points[i + 1], paint);
      }
    }
  }

哈哈哈,就是这么简单。然后把当前绘制区域截屏并保存成图片。使用
RepaintBoundary,他需要一个_globalKey来生成Image。

 Widget signWidget() {
    return RepaintBoundary(
      key: _globalKey,
      child: Container(
        child: Stack(
          children: <Widget>[
            GestureDetector(
              onPanUpdate: (DragUpdateDetails details) {
                RenderBox renderBox = context.findRenderObject();
                Offset localPosition =
                    renderBox.globalToLocal(details.globalPosition);
                RenderBox referenceBox =
                    _globalKey.currentContext.findRenderObject();

                //校验范围,防止超过外面
                if (localPosition.dx <= 0 || localPosition.dy <= 0) return;
                if (localPosition.dx > referenceBox.size.width ||
                    localPosition.dy > referenceBox.size.height) return;

                setState(() {
                  _points = List.from(_points)..add(localPosition);
                });
              },
              onPanEnd: (DragEndDetails details) {
                _points.add(null);
              },
            ),
            Container(
              child:  CustomPaint(
                painter: SignatuePainter(_points,mStrokeWidth),
              ),
              color: Colors.red,
            )
           ,
            // Image.file(
            //   File(_imageLocalPath ?? ""),
            //   height: 100,
            //   width: 100,
            // )
          ],
        ),
      ),
    );
  }

  Future<String> _capturePng(File file) async {
    //1.获取RenderRepaintBoundary
    RenderRepaintBoundary boundary =
        _globalKey.currentContext.findRenderObject();
    //2.生成Image
    ui.Image image = await boundary.toImage();
    //3.生成 Unit8List
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData.buffer.asUint8List();
    //4.本地存储Image
    file.writeAsBytes(pngBytes);
    return file.path;
  }

还有一些细节就没有说,附上完整代码。

import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui show ImageByteFormat, Image;

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

/// 一张画布,可以跟随手指画线  例如用来做电子签名
 final  double STROKEWIDTH = 8;
class Signature extends StatefulWidget {
  Function noSign;
  double strokeWith;



  Signature({this.noSign,this.strokeWith});

  @override
  State<StatefulWidget> createState() {
    return SignatureState(noSign ,strokeWith);
  }
}

class SignatureState extends State<Signature> {
  List<Offset> _points = <Offset>[];
  GlobalKey _globalKey;

  String _imageLocalPath;
  Function noSign;
  double mStrokeWidth;


  SignatureState(this.noSign,this.mStrokeWidth);

  @override
  void initState() {
    super.initState();
    //横屏
    SystemChrome.setPreferredOrientations(
        [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
    _globalKey = GlobalKey();
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        Expanded(
          child: signWidget(),
        ),
        Padding(
          child: bottom(noSign),
          padding: EdgeInsets.only(right: 32),
        ),
      ],
    );
  }

  Widget bottom(Function noSign) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        RaisedButton(
          color: Theme.of(context).primaryColor,
          onPressed: () async {
            setState(() {
              _points?.clear();
              _points = [];
              _imageLocalPath = null;
            });
          },
          child: Text(
            '重写',
            style: TextStyle(color: Colors.white),
          ),
        ),
        RaisedButton(
          color: Theme.of(context).primaryColor,
          onPressed: () async {
            if(_points != null && _points.length > 0){
              File file = await _saveImageToFile();
              String toPath = await _capturePng(file);
              print("path:${file.path}");
              setState(() {
                _imageLocalPath = toPath;
              });
            }else{
              noSign();
            }

          },
          child: Text(
            '保存',
            style: TextStyle(color: Colors.white),
          ),
        ),
      ],
    );
  }

  Widget signWidget() {
    return RepaintBoundary(
      key: _globalKey,
      child: Container(
        child: Stack(
          children: <Widget>[
            GestureDetector(
              onPanUpdate: (DragUpdateDetails details) {
                RenderBox renderBox = context.findRenderObject();
                Offset localPosition =
                    renderBox.globalToLocal(details.globalPosition);
                RenderBox referenceBox =
                    _globalKey.currentContext.findRenderObject();

                //校验范围,防止超过外面
                if (localPosition.dx <= 0 || localPosition.dy <= 0) return;
                if (localPosition.dx > referenceBox.size.width ||
                    localPosition.dy > referenceBox.size.height) return;

                setState(() {
                  _points = List.from(_points)..add(localPosition);
                });
              },
              onPanEnd: (DragEndDetails details) {
                _points.add(null);
              },
            ),
            Container(
              child:  CustomPaint(
                painter: SignatuePainter(_points,mStrokeWidth),
              ),
              color: Colors.red,
            )
           ,
            // Image.file(
            //   File(_imageLocalPath ?? ""),
            //   height: 100,
            //   width: 100,
            // )
          ],
        ),
      ),
    );
  }

  Future<File> _saveImageToFile() async {
    Directory tempDir = await getTemporaryDirectory();
    // Directory tempDir = await getExternalStorageDirectory();
    int curT = DateTime.now().millisecondsSinceEpoch;
    String toFilePath = "${tempDir.path}/$curT.png";
    File file = File(toFilePath);
    bool exists = await file.exists();
    if (!exists) {
      await file.create(recursive: true);
    }
    return file;
  }

  Future<String> _capturePng(File file) async {
    //1.获取RenderRepaintBoundary
    RenderRepaintBoundary boundary =
        _globalKey.currentContext.findRenderObject();
    //2.生成Image  pixelRatio 不加上去,会很糊
    ui.Image image = await boundary.toImage(pixelRatio: ui.window.devicePixelRatio);
    //3.生成 Unit8List
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    Uint8List pngBytes = byteData.buffer.asUint8List();
    //4.本地存储Image
    file.writeAsBytes(pngBytes);
    return file.path;
  }
}

class SignatuePainter extends CustomPainter {
  final List<Offset> points;
  double mStrokeWidth ;

  SignatuePainter(this.points,mStrokeWidth);

  @override
  void paint(Canvas canvas, Size size) {
    if(mStrokeWidth == null || mStrokeWidth == 0){
      mStrokeWidth = STROKEWIDTH;
    }
    Paint paint = Paint()
      ..strokeCap = StrokeCap.round
      ..color = Colors.black
      ..isAntiAlias = true
      ..strokeWidth = mStrokeWidth;

    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i], points[i + 1], paint);
      }
    }
  }

  @override
  bool shouldRepaint(SignatuePainter oldDelegate) {
    return points != oldDelegate.points;
  }
}

相关文章

网友评论

    本文标题:Flutter 手绘电子签名

    本文链接:https://www.haomeiwen.com/subject/akgeaktx.html