一、思路
签名就是手在画布上面经过的点,连起来就是。难点就是两个,一个是找出绘制的点,还一个就是把所有的点连接起来。
二、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;
}
}
网友评论