2

I have the following code to activate/deactivate the eraser:

public PorterDuffXfermode clear = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);    
eraseB.setOnClickListener(new OnClickListener() {

                    @Override
                    public void onClick(View v) {
                        if (!eraser) {
                            eraser = true;
                            eraseB.setImageResource(R.drawable.erase_on);
                            paint = new Paint(Paint.DITHER_FLAG);
                            paint.setColor(0x00000000);
                            paint.setAlpha(0x00);
                            paint.setXfermode(clear);
                            paint.setStyle(Paint.Style.STROKE);
                            paint.setStrokeJoin(Paint.Join.ROUND);
                            paint.setStrokeCap(Paint.Cap.ROUND);
                            paint.setStrokeWidth(stroke);
                            paintv.setPaint(paint);
                        } else {
                            eraser = false;
                            eraseB.setImageResource(R.drawable.erase);
                            paint = new Paint(Paint.DITHER_FLAG);
                            paint.setDither(true);
                            paint.setXfermode(null);
                            paint.setColor(Color.RED);
                            paint.setStyle(Paint.Style.STROKE);
                            paint.setStrokeJoin(Paint.Join.ROUND);
                            paint.setStrokeCap(Paint.Cap.ROUND);
                            paint.setStrokeWidth(stroke);
                            paintv.setPaint(paint);
                        }
                    }
                });

setPaint is from my customView:

public void setPaint(Paint paint) {
    this.paint = paint;
    LogService.log("in setPaint", "paint = " + paint);
}

and onDraw I use:

canvas.drawPath(mPath, paint);

If I deactivate the eraser, it will draw with a red line, but instead, if I activate the eraser, instead of erasing, it will draw a black line. How can I fix this

rosu alin
  • 5,674
  • 11
  • 69
  • 150
  • This guy has a solution which also worked for me: http://stackoverflow.com/questions/25094845/implementing-an-eraser-in-an-android-drawing-app-black-trail-and-then-transpar – peresisUser Dec 21 '16 at 08:04

2 Answers2

5

Canvas does not support eraser while Bitmap does.

Basic workaround flow:

  1. Create another canvas

  2. Create a bitmap

  3. Set that bitmap to that canvas

    public void init(int width, int height) {
        Log.i(TAG,"init with "+width+"x"+height);
        foreground = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        cacheCanvas = new Canvas();
        cacheCanvas.setBitmap(foreground);
    }
    
  4. Record the touches on that bitmap, including paint and eraser

    public boolean onTouchEvent(MotionEvent event) {
        float eventX = event.getX();
        float eventY = event.getY();
    
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            currentStroke = new Stroke();
            currentStroke.color = paint;
            currentStroke.path.moveTo(eventX, eventY);
            currentStroke.path.lineTo(eventX, eventY);
    
            synchronized (strokes) {
                strokes.add(currentStroke);
            }
            lastTouchX = eventX;
            lastTouchY = eventY;
            // There is no end point yet, so don't waste cycles invalidating.
            return true;
    
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_UP:
            // Start tracking the dirty region.
            resetDirtyRect(eventX, eventY);
    
            // When the hardware tracks events faster than they are delivered,
            // the
            // event will contain a history of those skipped points.
            int historySize = event.getHistorySize();
            for (int i = 0; i < historySize; i++) {
                float historicalX = event.getHistoricalX(i);
                float historicalY = event.getHistoricalY(i);
            expandDirtyRect(historicalX, historicalY);
                if (i == 0) {
                    lastX = historicalX;
                    lastY = historicalY;
                    currentStroke.path.lineTo(historicalX, historicalY);
                } else {
                    currentStroke.path.quadTo(lastX, lastY,
                        (historicalX + lastX) / 2,
                        (historicalY + lastY) / 2);
                }
            }
    
        // After replaying history, connect the line to the touch point.
            if(historySize==0){
                long duration=event.getEventTime()-event.getDownTime();
                float offset=0.1f;
                if(duration<300){
                    offset=50.0f/duration;
                }
                currentStroke.path.lineTo(eventX+offset, eventY+offset);
            }else{
                currentStroke.path.lineTo(eventX, eventY);
            }
            synchronized (strokes) {
                strokes.add(currentStroke);
            }
    
            break;
    
        default:
        return false;
        }
    
    // Include half the stroke width to avoid clipping.
        float width = paint.getStrokeWidth() / 2;
        invalidate((int) (dirtyRect.left - width),
            (int) (dirtyRect.top - width), (int) (dirtyRect.right + width),
            (int) (dirtyRect.bottom + width));
    
        lastTouchX = eventX;
        lastTouchY = eventY;
    
        return true;
        }
    
  5. Draw that bitmap on the canvas of your View

    protected void onDraw(Canvas canvas) {
        synchronized (strokes) {
            if (strokes.size() > 0) {
                for (Stroke s : strokes) {
                    cacheCanvas.drawPath(s.path, s.color);
                }
                canvas.drawBitmap(foreground, 0, 0, null);
                strokes.clear();
            }
        }
    }
    
Patrick Chan
  • 1,019
  • 10
  • 14
  • Thanks works pretty good for me, but my paths get some kind of pixelated when I use the canvas.drawBitmap(foreground, 0, 0, null) vs directly drawing on the canvas. Any idea on this? Thanks. – Chris Nov 23 '16 at 11:08
0

If you are drawing on solid color background, use the paint of same color in brush to that of background and the brush will behave like an eraser.

CodenameLambda1
  • 1,299
  • 7
  • 17
  • That's what I did now, But i wanted to know, why that logic does not work, because, I have made a similar about about 1-2 years ago, and it worked there, I took the code, line by line, copied in my app and it doesn't work – rosu alin Sep 20 '13 at 11:03
  • Unfortunately this is no good for me, because I have some popups in the back, that I need to be able to write on, but when I erase the draed lines, If I draw over with the background color, then I will also erase the popups, and that is not acceptable – rosu alin Sep 20 '13 at 12:07
  • In that case you can use a transparent canvas over another canvas to draw the stuffs. Then you can use a clear color paint to fill in transparent pixels. This will work like an eraser and it will not erase anything on the lower canvas. – CodenameLambda1 Sep 20 '13 at 12:20
  • The popups I have are not in the canvas, they are custom textviews, that appear, similar to a chat application. But I need to be able to draw over them, this is why I have a canvas with bringtoFront() true, so I can draw over the textviews. Now if I draw something over a textview, and then want to delete, normally porter duff's clear filter would delete the lines I drawn from the canvas, and it wil remain clear so that the textviews are visible. Now If I draw with the background color over, it will draw over those textviews. – rosu alin Sep 20 '13 at 12:32