如何为颤振绘图应用程序实现橡皮擦功能



有一个关于通过颤振(YouTube(创建绘图应用程序的视频,该应用程序支持在用户点击屏幕时绘制线条/点,但我找不到功能/方法来擦除用户绘制的内容,如Android本机(如此处(,尤其是线条。我不能只是在它们上面叠加一些颜色,比如白色,因为我在手势检测器下有背景图像。

谁能给我一些建议?

我的示例项目:

import 'dart:ui';
import 'package:flutter/material.dart';
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> {
List<Offset> points = <Offset>[];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
if (details.localPosition.dx < 0) {
return;
}
RenderBox oRenderBox = context.findRenderObject();
Offset oLocationPoints =
oRenderBox.localToGlobal(details.localPosition);
setState(() {
this.points = new List.from(this.points..add(oLocationPoints));
});
},
onPanEnd: (DragEndDetails details) => this.points.add(null),
child: CustomPaint(
painter: SignaturePainter(
points: this.points),
size: Size.infinite)),
),
);
}
}
class SignaturePainter extends CustomPainter {
List<Offset> points = <Offset>[];
SignaturePainter({this.points});
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.red
..strokeCap = StrokeCap.round
..strokeWidth = 3.0;
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(SignaturePainter oldDelegate) {
return oldDelegate.points != points;
}
}

谢谢。

将 saveLayer(( 和 restore(( 与 BlendMode.clear 一起使用。

从 BlendMode.clear:

使用 Canvas.saveLayer

和 Canvas.restore 时,调用 Canvas.restore 时将应用提供给 Canvas.saveLayer 的 Paint 的混合模式。每次调用 Canvas.saveLayer 都会引入一个新图层,在其上绘制形状和图像;调用 Canvas.restore 时,该图层将合成到父图层上,源是最近绘制的形状和图像,目标为父图层

例:

@override
void paint(Canvas canvas, Size size) {
// default blendMode=BlendMode.srcOver
final _blendMode = BlendMode.clear;
// let's pretend this rectangle is an image.
// in this case, we don't want anything erased from the image, 
// and we also want the image to be drawn under the eraser.
canvas.drawRect(Rect.fromLTWH(100, 100, 100, 100), Paint()..color=Colors.white);
// after having drawn our image, we start a new layer using saveLayer().
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
// drawing the line that should be erased partially.
canvas.drawLine(
Offset(100, 100), Offset(200, 200), Paint()..color = Colors.black..strokeWidth = 5.0);
// erasing parts of the first line where intersected with this line.
canvas.drawLine(
Offset(100, 200), Offset(200, 100), Paint()..color=Colors.red..strokeWidth = 5.0..blendMode=_blendMode);
// first composite this layer and then draw it over the previously drawn layers.
canvas.restore();
// move on with other draw commands..
}

愿这段代码对你有所帮助...

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:animated_floatactionbuttons/animated_floatactionbuttons.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';


var appbarcolor = Colors.blue;
class CanvasPainting_test extends StatefulWidget {

@override
_CanvasPainting_testState createState() => _CanvasPainting_testState();
}

class _CanvasPainting_testState extends State<CanvasPainting_test> {
GlobalKey globalKey = GlobalKey();

List<TouchPoints> points = List();
double opacity = 1.0;
StrokeCap strokeType = StrokeCap.round;
double strokeWidth = 3.0;
double strokeWidthforEraser = 3.0;
Color selectedColor;

Future<void> _pickStroke() async {
//Shows AlertDialog
return showDialog<void>(
context: context,

//Dismiss alert dialog when set true
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick stroke value.
actions: <Widget>[
//Resetting to default stroke value
FlatButton(
child: Icon(
Icons.clear,
),
onPressed: () {
strokeWidth = 3.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 24,
),
onPressed: () {
strokeWidth = 10.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 40,
),
onPressed: () {
strokeWidth = 30.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 60,
),
onPressed: () {
strokeWidth = 50.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}

Future<void> _opacity() async {
//Shows AlertDialog
return showDialog<void>(
context: context,

//Dismiss alert dialog when set true
barrierDismissible: true,

builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick opacity value.
actions: <Widget>[
FlatButton(
child: Icon(
Icons.opacity,
size: 24,
),
onPressed: () {
//most transparent
opacity = 0.1;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.opacity,
size: 40,
),
onPressed: () {
opacity = 0.5;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.opacity,
size: 60,
),
onPressed: () {
//not transparent at all.
opacity = 1.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}

Future<void> _pickStrokeforEraser() async {
//Shows AlertDialog
return showDialog<void>(
context: context,

//Dismiss alert dialog when set true
barrierDismissible: true, // user must tap button!
builder: (BuildContext context) {
//Clips its child in a oval shape
return ClipOval(
child: AlertDialog(
//Creates three buttons to pick stroke value.
actions: <Widget>[
//Resetting to default stroke value
FlatButton(
child: Icon(
Icons.clear,
),
onPressed: () {
strokeWidthforEraser = 3.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 24,
),
onPressed: () {
strokeWidthforEraser = 10.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 40,
),
onPressed: () {
strokeWidthforEraser = 30.0;
Navigator.of(context).pop();
},
),
FlatButton(
child: Icon(
Icons.brush,
size: 60,
),
onPressed: () {
strokeWidthforEraser = 50.0;
Navigator.of(context).pop();
},
),
],
),
);
},
);
}

Future<void> _save() async {
RenderRepaintBoundary boundary =
globalKey.currentContext.findRenderObject();
ui.Image image = await boundary.toImage();
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();

//Request permissions if not already granted
if (!(await Permission.storage.status.isGranted))
await Permission.storage.request();

final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(pngBytes),
quality: 60,
name: "canvas_image");
print(result);
}
String erase = 'yes';
List<Widget> fabOption() {
return <Widget>[
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "camera",
child: Icon(Icons.camera),
tooltip: 'camera',
onPressed: () {
//min: 0, max: 50
setState(() {
erase = 'yes';
this._showDialog();
// _save();
});
},
),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "paint_save",
child: Icon(Icons.file_download),
tooltip: 'Save',
onPressed: () {
//min: 0, max: 50
setState(() {
erase = 'yes';
_save();
});
},
),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "paint_stroke",
child: Icon(Icons.brush),
tooltip: 'Stroke',
onPressed: () {
//min: 0, max: 50
setState(() {
erase = 'yes';
_pickStroke();
});
},
),
// FloatingActionButton(
//   heroTag: "paint_opacity",
//   child: Icon(Icons.opacity),
//   tooltip: 'Opacity',
//   onPressed: () {
//     //min:0, max:1
//     setState(() {
//       _opacity();
//     });
//   },
// ),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "Erase",
child: Icon(Icons.ac_unit),
tooltip: 'Erase',
onPressed: () {
//min: 0, max: 50
setState(() {
// _save();
// selectedColor = Colors.transparent;
// print(Platform.isAndroid);
erase = 'no';
_pickStrokeforEraser();
});
},
),
FloatingActionButton(
backgroundColor: appbarcolor,
heroTag: "Clear All",
child: Icon(Icons.clear),
tooltip: "Clear All",
onPressed: () {
setState(() {
erase = 'yes';
points.clear();
});
}),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_red",
child: colorMenuItem(Colors.red),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.red;
});
},
),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_green",
child: colorMenuItem(Colors.green),
tooltip: 'Color',
onPressed: () {
setState(() {
erase = 'yes';
selectedColor = Colors.green;
});
},
),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_pink",
child: colorMenuItem(Colors.pink),
tooltip: 'Color',
onPressed: () {
setState(() {
selectedColor = Colors.pink;
});
},
),
FloatingActionButton(
backgroundColor: Colors.white,
heroTag: "color_blue",
child: colorMenuItem(Colors.blue),
tooltip: 'Color',
onPressed: () {
setState(() {
erase = 'yes';
selectedColor = Colors.blue;
});
},
),
];
}


void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
//          title: new Text("Alert Dialog title"),
content: Row(
// mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
onPressed: getImageCamera,
child: Text('From Camera'),
),
SizedBox(
width: 5,
),
RaisedButton(
onPressed: getImageGallery,
child: Text('From Gallery'),
)
],
),
);
},
);
}
File _image;
Future getImageCamera() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);

print(image);

if (image != null) {
setState(() {
_image = image;
});
Navigator.of(context, rootNavigator: true).pop('dialog');
}
}

Future getImageGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);

print(image);

if (image != null) {
setState(() {
_image = image;
print(_image);
});
Navigator.of(context, rootNavigator: true).pop('dialog');
}
}
/*-------------------------------------*/



@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('paint on image and erase'),backgroundColor: Colors.blueGrey
// leading: IconButton(
//   icon: Icon(Icons.arrow_back_ios),onPressed: (){
//   Navigator.pop(context);
// },),
),
body: GestureDetector(
onPanUpdate: (details) {
setState(() {
RenderBox renderBox = context.findRenderObject();
erase!='no'?   points.add(TouchPoints(
points: renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..strokeCap = strokeType
..isAntiAlias = true
..color = selectedColor.withOpacity(opacity)
..strokeWidth = strokeWidth))

: points.add(TouchPoints(
points:  renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..color = Colors.transparent
..blendMode = BlendMode.clear
..strokeWidth = strokeWidthforEraser
..style = PaintingStyle.stroke
..isAntiAlias = true
));
});
},
onPanStart: (details) {
setState(() {
RenderBox renderBox = context.findRenderObject();
erase!='no'?   points.add(TouchPoints(
points: renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..strokeCap = strokeType
..isAntiAlias = true
..color = selectedColor.withOpacity(opacity)
..strokeWidth = strokeWidth))

: points.add(TouchPoints(
points:  renderBox.globalToLocal(details.globalPosition),
paint: Paint()
..color = Colors.transparent
..blendMode = BlendMode.clear
..strokeWidth = strokeWidthforEraser
..style = PaintingStyle.stroke
..isAntiAlias = true
));


});
},
onPanEnd: (details) {
setState(() {
points.add(null);
});
},
child: RepaintBoundary(
key: globalKey,
child: Stack(
children: <Widget>[
Center(
child: _image == null
? Image.asset(
"assets/images/helo.jfif",
)
: Image.file(_image),
),
CustomPaint(
size: Size.infinite,
painter: MyPainter(
pointsList: points,
),
),
],
),
),
),
floatingActionButton: AnimatedFloatingActionButton(
fabButtons: fabOption(),
colorStartAnimation: appbarcolor,
colorEndAnimation: Colors.red[300],
animatedIconData: AnimatedIcons.menu_close),
);
}

Widget colorMenuItem(Color color) {
return GestureDetector(
onTap: () {
setState(() {
selectedColor = color;
});
},
child: ClipOval(
child: Container(
padding: const EdgeInsets.only(bottom: 8.0),
height: 36,
width: 36,
color: color,
),
),
);
}








}

class MyPainter extends CustomPainter {
MyPainter({this.pointsList});

//Keep track of the points tapped on the screen
List<TouchPoints> pointsList;
List<Offset> offsetPoints = List();

//This is where we can draw on canvas.
@override
void paint(Canvas canvas, Size size) {
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points, pointsList[i].paint);
canvas.drawCircle(pointsList[i].points, pointsList[i].paint.strokeWidth/2, pointsList[i].paint);
}
}
canvas.restore();
}

//Called when CustomPainter is rebuilt.
//Returning true because we want canvas to be rebuilt to reflect new changes.
@override
bool shouldRepaint(MyPainter oldDelegate) => true;
}

//Class to define a point touched at canvas
class TouchPoints {
Paint paint;
Offset points;
TouchPoints({this.points, this.paint});
}

最新更新