Canvas橡皮擦正在画一条黑线

Canvas Eraser is drawing a black line

我用谷歌搜索了这个问题并提出了各种解决方案。
但是,none 对我有用。

我在应用程序中有一个绘图 Canvas。
canvas 的背景设置为 Activity 中使用自定义视图 (drawView) 的 png 图像;

Bundle extras = intent.getExtras();
    if (extras != null) {
        if (extras.containsKey("background")) {

            //set the background to the resource in the extras
            int imageResource = intent.getIntExtra("background",-1);
            Drawable image = getResources().getDrawable(imageResource);
            drawView.setBackground(image);
        }
    }

在DrawingViewclass(drawview是实例)中,我将绘制的路径存储在PathPaints的collection中,它有3个属性(路径,使用的油漆,如果它是橡皮擦);

private ArrayList<PathPaint> paths = new ArrayList<PathPaint>();

然后我尝试在 OnDraw 中循环遍历这些路径,并每次使用绘制它们时使用的颜料(多种颜色)重新绘制它们;

protected void onDraw(Canvas canvas) {

    //if the drawing is new - dont draw any paths
    if (isNew != true) {

        //go through every previous path and draw them
        for (PathPaint p : paths) {

            if (!p.isErase)
            {
                canvas.drawPath(p.myPath, p.myPaint);
            }
            else
            {
                //Paint eraserPaint = setDefaultPaint();
                //eraserPaint.setAlpha(0xFF);
                //eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                //eraserPaint.setColor(Color.TRANSPARENT);
                //canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
                canvas.drawPath(p.myPath, p.myPaint);
            }
            canvas.drawBitmap(canvasBitmap, 0, 0, null);

        }
    }

我在网上尝试了很多建议的选项,但都无济于事。

我已经尝试在绘图路径上设置绘画以设置所有各种注释掉的属性。

我尝试在位图上绘图,然后将该位图加载到 canvas (canvas.drawBitmap(canvasBitmap, 0, 0, null))

我已经关闭了这个 class' 构造函数中的硬件加速

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

但是要么没有画线,要么当collection重画路径时,橡皮擦画了一条黑线;

有趣的是,如果我使用没有循环方面的位图执行擦除 - 橡皮擦会按预期工作;

//If we are making a new drawing we don't want to go through all the paths
    if (isNew != true && erase ==false) {

        //go through every previous path and draw them


        for (PathPaint p : paths) {

            if (!p.isErase)
            {
                canvas.drawPath(p.myPath, p.myPaint);
            }
            //this section now takes place in the elseIF
            else
            {
                Paint eraserPaint = setDefaultPaint();
                eraserPaint.setAlpha(0xFF);
                eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                eraserPaint.setColor(Color.TRANSPARENT);


                canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
                canvas.drawPath(p.myPath, p.myPaint);
            }
        }
    }
    else if (isNew != true && erase ==true)
    {   
        //This works correctly for Erasing but I dont have the ability to Undo/Redo with this approach! 
        canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
        canvas.drawPath(drawPath, drawPaint);

    }

然而,这是一个问题,因为我希望能够 Undo/Redo 擦除(因此 collection 的重点)

谁能帮帮我?

看起来你只使用了一个视图(图层),你先放了一张背景图片,然后绘制了替换背景的路径。如果是这样,当您擦除时,您只是从那个 view/layer 中删除,其中包括路径和背景。如果您使用两层(Framelayout 中的两个视图),一层在后面加载背景,另一层在前面放​​置所有路径,然后擦除顶层只会删除路径和背景通过。

分层有不同的方法。例如,这个FrameLayout替换了当前持有背景和绘制路径的视图(在代码中指XXXView。)

<FrameLayout
   android:layout_width= ...copy from the existing XXXView ...
   android:layout_height= ...copy from the existing XXXView ... >

   <ImageView 
      android:id = "@+id/background"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      ...
      ... the background is loaded here />


   <XXXView (this the existing view where the paths are drawn onto)
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      ...
      ... no background here />

</FrameLayout>

检查你的canvas位图,是Config.ARGB8888吗?

同时检查这个答案Android canvas: draw transparent circle on image

我看你的drawView应该是ImageView吧?

此代码是否可以帮助您...

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});
}