8

有一个关于通过颤振(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;
  }
}

谢谢。

4

2 回答 2

7

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..
}
于 2020-05-16T22:38:22.523 回答
0

这段代码可以帮助你...

    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});
    }
于 2020-11-20T12:43:03.257 回答