19

I have an drawing app for Android and I am currently trying to add a real eraser to it. Before, I had just used white paint for an eraser, but that won't do anymore since now I allow background colors and images. I do this by having an image view underneath my transparent canvas.

The problem that I am facing is that whenever I enable my eraser, it draws a solid black trail while I have my finger down, but once I release it goes to transparent. See the screen shots below:

This is how it looks while my finger is on the screen - a solid black trail How it looks while my finger is still on the screen

This is what it looks like once I remove my finger from the screen How it looks once I let go

So, it seems like I am getting close, but I can't find the right combination of settings to avoid the black trail while my finger is touching while erasing. Here are some relevant code snippets:

onDraw

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawColor(Color.TRANSPARENT);
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
    canvas.drawPath(mPath, mPaint);
    canvas.drawPath(mPreviewPath, mPaint);
}

onTouchEvent

@Override
public boolean onTouchEvent(MotionEvent event) {
    float currentX = event.getX();
    float currentY = event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            touchStart(currentX, currentY);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            touchMove(currentX, currentY);
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            touchUp(currentX, currentY);
            invalidate();
            break;
    }
    return true;
}

Current attempt at eraser settings

public void startEraser() {
    mPaint.setAlpha(0);
    mColor = Color.TRANSPARENT;
    mPaint.setColor(Color.TRANSPARENT);
    mPaint.setStrokeWidth(mBrushSize);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setMaskFilter(null);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    mPaint.setAntiAlias(true);
}

There are several other posts about erasers, but most of them just say to use PorterDuff.Mode.CLEAR, setMakFilter(null) and that that should work. In my case, it doesn't. No matter what I try, I get the black trail first and then the desired result only after I release.

I can provide more code if necessary.

Scott
  • 2,557
  • 5
  • 26
  • 32
  • Are you sure you're masking the `Path` you're using to store the erasure in (I'm guessing `mPreviewPath`)? – nhaarman Aug 04 '14 at 14:44
  • mPreviewPath is only used when I draw shapes - circles, squares, straight lines. It shows a preview of what will be drawn permanently once you lift your finger. – Scott Aug 04 '14 at 14:45
  • So aren't you 'previewing' the stuff that will be erased once you lift your finger? – nhaarman Aug 04 '14 at 14:47
  • No - that isn't how I want it to work. I don't want the black preview at all - I want it to actually erase. `mPreviewPath` isn't touched when you just have the brush selected. – Scott Aug 04 '14 at 14:49
  • Yeah I know what you want, I'm just suggesting things that might be happening without you knowing it :) There is too little code in the question to determine that unfortunately. – nhaarman Aug 04 '14 at 14:51
  • Good call - I will take a look and will let you know. – Scott Aug 04 '14 at 15:53
  • @Scott I think I can explain the black trail. But before that, set the activity window's background to, say `green`. You can do this in your theme: add `@color/some_shade_of_green`.. or any color. The point is: now the trail should be of the color you set as the window's background. By default, its black. With the options you've set, you're punching a hole (or trail?) through your view's background: peeking at the window background. – Vikram Aug 04 '14 at 16:45
  • @Vikram Okay - is there anyway to make it NOT do that, and just show what is directly below it (the ImageView)? Why would it first show the black trail, and then show what is behind it once I lift my finger? – Scott Aug 04 '14 at 16:47
  • 2
    @Scott Were you able to confirm my suspicion about the trail being the window's background? We can work out a solution once we confirm the problem :) – Vikram Aug 04 '14 at 17:00
  • 1
    @Scott If the window background is indeed _the_ problem, the solution would be to detach the bg from the `ImageView` (or whatever view that you are drawing on) - and place it on the layout/container holding the View. We do this because the background is static - it should not be affected by the eraser (am I right?). So, the bg goes to the `Linear/RelativeLayout` holding the `ImageView`. When you have to export the image (to say png/jpeg) -> create a canvas backed by a bitmap -> draw the bg to canvas first -> draw the path(s) to the canvas -> compress the bitmap to whatever format. – Vikram Aug 04 '14 at 17:34

5 Answers5

15

I could suggest you to read the official sample of FingerPaint.java
It exactly matches what you are trying to achieve here.

To not show the trail when you erase content, take a look at the onDraw() method and the eraserMode variable:

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawColor(0xFFAAAAAA);
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
    if (!eraserMode) {
        canvas.drawPath(mPath, mPaint);
    }
}

boolean eraserMode = false;

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    eraserMode = false;
    mPaint.setXfermode(null);
    mPaint.setAlpha(0xFF);
    switch (item.getItemId()) {
        /*...*/
        case ERASE_MENU_ID:
            // Add this line
            eraserMode = true;
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            return true;
        /*...*/
    }
    return super.onOptionsItemSelected(item);
}
Simon Marquis
  • 7,248
  • 1
  • 28
  • 43
  • Thanks, but I DO want to show a trail - just not the solid black trail. I want it to properly erase as I move my finger. – Scott Aug 11 '14 at 11:47
  • 2
    Alright, so you need to do ```mCanvas.drawPath(mPath, mPaint);``` each time your are in eraserMode and a touch event occurs. This will modify the renderer bitmap. And don't forget to call invalidate after calling this method. – Simon Marquis Aug 11 '14 at 11:50
  • 3
    No, you're not. In this example, there are two `Canvas` one used in the onDraw method to render the view, and another to render the drawing outside of the view. – Simon Marquis Aug 11 '14 at 11:54
  • mCanvas.drawPath(mPath, mPaint); in onTouchEvent for erasemode is not working for me. – Rakesh Yadav Jan 20 '18 at 08:15
10

People Still looking for a shorter way to remove that black line while erasing. Just addsetLayerType(View.LAYER_TYPE_SOFTWARE, drawPaint); this line to the constructor and here you go. Cheers!

Fahid Nadeem
  • 412
  • 2
  • 7
  • 19
  • How do I go back to drawing from this mode. Setting setLayerType(View.LAYER_TYPE_NONE, drawPaint) again for going again to painting doesn't work. – Rakesh Yadav Jan 20 '18 at 08:09
  • When using this method, your view will take longer to draw than the default hardware accelerated method. Therefore, be sure to test rendering performance. – amram99 May 13 '19 at 15:17
7

None of the above answers, worked properly for me. After some hit and trial, and some logics as well, solved it, check the ACTION_MOVE

@Override
public boolean onTouchEvent(MotionEvent event) {

    float touchX = event.getX();
    float touchY = event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            isEdited = true;
            drawPath.moveTo(touchX, touchY);
            break;
        case MotionEvent.ACTION_MOVE:
            if(isEraser) {
                drawPath.lineTo(touchX, touchY);
                drawCanvas.drawPath(drawPath, drawPaint);
                drawPath.reset();
                drawPath.moveTo(touchX, touchY);
            } else {
                drawPath.lineTo(touchX, touchY);
            }
            break;
        case MotionEvent.ACTION_UP:
            drawCanvas.drawPath(drawPath, drawPaint);
            drawPath.reset();
            break;
        default:
            return false;
    }

    invalidate();
    return true;
}

and this

public void setEraser(boolean isEraser) {
    this.isEraser = isEraser;
    if (isEraser) {
        drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    } else {
        drawPaint.setXfermode(null);
    }
}
Rakesh Yadav
  • 1,966
  • 2
  • 21
  • 35
1

Can you comment canvas.drawPath(mPreviewPath, mPaint) line and see if it works:

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawColor(Color.TRANSPARENT);
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
    canvas.drawPath(mPath, mPaint);
    //canvas.drawPath(mPreviewPath, mPaint);
}
rupesh jain
  • 3,410
  • 1
  • 14
  • 22
0

when you want make eraser set painting color same as color of canvas not transparent in real life. in android we set set paint color as theme color of canvas mostly either white or black.

mColor = Color.BLACK;
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(mBrushSize);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAntiAlias(true);
Ashish Singh
  • 371
  • 3
  • 16
  • 2
    I can't just use the background color as I stated in my question, because I allow images as the background. – Scott Aug 11 '14 at 11:47