111

In order to make a simple game, I used a template that draws a canvas with bitmaps like this:

private void doDraw(Canvas canvas) {
    for (int i=0;i<8;i++)
        for (int j=0;j<9;j++)
            for (int k=0;k<7;k++)   {
    canvas.drawBitmap(mBits[allBits[i][j][k]], i*50 -k*7, j*50 -k*7, null); } }

(The canvas is defined in "run()" / the SurfaceView lives in a GameThread.)

My first question is how do I clear (or redraw) the whole canvas for a new layout?
Second, how can I update just a part of the screen?

// This is the routine that calls "doDraw":
public void run() {
    while (mRun) {
        Canvas c = null;
        try {
            c = mSurfaceHolder.lockCanvas(null);
            synchronized (mSurfaceHolder) {
                if (mMode == STATE_RUNNING) 
                    updateGame();
                doDraw(c);          }
        } finally {
            if (c != null) {
                mSurfaceHolder.unlockCanvasAndPost(c);  }   }   }       }
Paul Wintz
  • 2,542
  • 1
  • 19
  • 33
samClem
  • 1,117
  • 2
  • 8
  • 6

21 Answers21

297

Draw transparent color with PorterDuff clear mode does the trick for what I wanted.

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
Sileria
  • 15,223
  • 4
  • 49
  • 28
  • 2
    This should work, but appears to be bugged in 4.4 (at least on the N7.2) Use `Bitmap#eraseColor(Color.TRANSPARENT)`, as in HeMac's answer below. – nmr Mar 18 '14 at 19:32
  • 5
    In fact, `Color.TRANSPARENT` is unnecessary. `PorterDuff.Mode.CLEAR` is totally enough for a `ARGB_8888` bitmap which means setting the alpha and color to [0, 0]. Another way is to use `Color.TRANSPARENT` with `PorterDuff.Mode.SRC`. – leoly May 30 '14 at 12:02
  • 6
    This is not working for me leaving a blank surface instead of Transparent – pallav bohara Mar 20 '18 at 12:36
84

How do I clear (or redraw) the WHOLE canvas for a new layout (= try at the game) ?

Just call Canvas.drawColor(Color.BLACK), or whatever color you want to clear your Canvas with.

And: how can I update just a part of the screen ?

There is no such method that just update a "part of the screen" since Android OS is redrawing every pixel when updating the screen. But, when you're not clearing old drawings on your Canvas, the old drawings are still on the surface and that is probably one way to "update just a part" of the screen.

So, if you want to "update a part of the screen", just avoid calling Canvas.drawColor() method.

Wroclai
  • 26,835
  • 7
  • 76
  • 67
  • 4
    No, if you do not draw every pixel in the surface you will get very strange results (because double buffering is achieved by swapping pointers, so in the parts where you are not drawing you will not see what was there just before). You *have* to redraw every pixel of the surface at each iteration. – Guillaume Brunerie Apr 20 '11 at 12:15
  • @Guillaume Brunerie: What you just described has absolutely nothing to do with what I wrote in my answer. The `Canvas` mechanism keeps old drawings if we're not clearing it with a different bitmap or color. That is theoreticlly correct and it is practically true. – Wroclai Apr 20 '11 at 12:19
  • @Viktor Lannér: I tried your solution and the background is drawn anew. But all bitmaps called by "canvas. drawBitmap" from my array mBits[..] are STILL there. How can I get rid of these? – samClem Apr 20 '11 at 12:38
  • 2
    @Viktor Lannér: If you call `mSurfaceHolder.unlockCanvasAndPost(c)` and then `c = mSurfaceHolder.lockCanvas(null)`, then the new `c` does not contain the same thing as the previous `c`. You can’t update just a part of a SurfaceView, which is what the OP was asking I guess. – Guillaume Brunerie Apr 20 '11 at 12:43
  • @samClem: That depends in which situation you're drawing your bitmaps. If you want to get rid of them, just clear the `Canvas` once more. – Wroclai Apr 20 '11 at 12:43
  • 1
    @Guillaume Brunerie: True, but not all. You can't update a part of the screen, as I wrote. But you can keep old drawings on the screen, which will have the effect of just "update a part of the screen". Try it yourself in a sample application. `Canvas` keeps old drawings. – Wroclai Apr 20 '11 at 12:44
  • 2
    SHAME ON ME !!! I did initialize my array only ONCE at game start NOT at the consecutive re-starts - so ALL OLD pieces remained "onscreen" - they were just drawn anew !!! I AM VERY SORRY for my "dumbness"! Thanks to both of you !!! – samClem Apr 20 '11 at 13:01
  • @Viktor Lannér: I just wrote a little activity which draw a new bitmap in the SurfaceView every second, and the behaviour is the one I’m describing. I can clearly see that there are two buffers, and that you are not always writing to the same buffer. I can post the code as a new answer if you want. – Guillaume Brunerie Apr 20 '11 at 13:08
  • @Guillaume Brunerie: Nope, it's fine since I have my own Live Wallpaper based on a `Canvas`, which is telling me the opposite behaviour. Of course you can post it for other visitors here, it is good to have multiple answers as we can inspect each others solutions. – Wroclai Apr 20 '11 at 13:11
  • 1
    Then this is probably because Live wallpapers do not work the same way as a regular SurfaceView. Anyway, @samClem: always redraw every pixel of the Canvas at each frame (as stated in the doc) or you will have strange flickering. – Guillaume Brunerie Apr 20 '11 at 13:16
  • @Guillaume Brunerie: Well, that's not correct either. Live Wallpapers has the same `SurfaceView` as a regular application. Also, I have seen the same thing on one of my regular applications which have a `SurfaceView`. But as I said, post your answer so other can see what you're meaning. – Wroclai Apr 20 '11 at 13:20
  • Let's not forget the possibility of the dirty rectangle that can be passed to lockCanvas. – snapfractalpop Mar 14 '12 at 22:56
  • Seems to work but does this mean all of those bitmaps I've drawn previously remain on the canvas and are just covered up by a black layer? Is that bad for memory??? – MobileMon Oct 16 '13 at 16:03
  • Crap, what's with translucent Canvas'? – paulgavrikov Dec 23 '13 at 03:44
  • I believe this is subtly wrong. It clears the color channels of all of the pixels, but it doesn't clear their alpha channels because the default drawing behavior for Canvas seems to be some kind of blending. This can cause problems if you use the drawn on canvas's bitmap in another drawing operation later, and depend on blending there. – nmr Jan 05 '14 at 21:45
37

Found this in google groups and this worked for me..

Paint clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(0, 0, width, height, clearPaint); 

This removes drawings rectangles etc. while keeping set bitmap..

Rukmal Dias
  • 3,242
  • 1
  • 34
  • 28
  • 5
    This gives me a black area - not the bitmap I have behind it :( – slott Oct 13 '14 at 07:40
  • Its clears fine, but the bitmap is also remove unlike you said. – Omar Rehman May 03 '15 at 11:47
  • in https://stackoverflow.com/questions/9691985/using-method-canvas-drawbitmapbitmap-src-dst-paint is explained how to draw a rectangle of a bitmap on some rectangle of the canvas. Change an image in a rectangle thus works:lear the previous contents, draw the new image – Martin Mar 13 '18 at 17:43
23

use the reset method of Path class

Path.reset();
Rakesh
  • 2,732
  • 29
  • 28
  • this really is the best answer if your using a path. the other ones can leave the user with a black screen. thanks. – j2emanue Jan 16 '19 at 12:38
  • super duper awesome. thanks. my situation was drawing a triangle with path by get touch events from user. so I wanted to clear previous drawn triangle while drawing new one to simulate moving of it. many thanks. – Reza Jan 30 '21 at 18:09
  • Sorry, I do not understand how to use this. If I draw the path on a canvas, this does not erase it. How to erase then? – Luis A. Florit Jun 18 '22 at 00:44
19

I tried the answer of @mobistry:

canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

But it doesn't worked for me.

The solution, for me, was:

canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);

Maybe some one has the same problem.

Derzu
  • 7,011
  • 3
  • 57
  • 60
12
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
duggu
  • 37,851
  • 12
  • 116
  • 113
heMac
  • 1,529
  • 14
  • 14
  • 2
    how is this the most correct? why use a bitmap at all when you can just drawColor? – RichieHH Aug 02 '14 at 12:39
  • Although this might not work for the original poster (they don't necessarily have access to the bitmap) this is definitely useful for clearing the contents of a bitmap when that's available. In those cases, only the first line is required. Useful technique, +1 – head in the codes Mar 19 '15 at 17:32
6
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);
Buddy
  • 10,874
  • 5
  • 41
  • 58
aliakbarian
  • 709
  • 1
  • 11
  • 20
  • 2
    Please add an explanation of how your code solves the issue. This is flagged as low quality post - [From Review](http://stackoverflow.com/review/low-quality-posts/15421966) – Suraj Rao Mar 05 '17 at 08:19
4

please paste below code on surfaceview extend class constructor.............

constructor coding

    SurfaceHolder holder = getHolder();
    holder.addCallback(this);

    SurfaceView sur = (SurfaceView)findViewById(R.id.surfaceview);
    sur.setZOrderOnTop(true);    // necessary
    holder = sur.getHolder();
    holder.setFormat(PixelFormat.TRANSPARENT);

xml coding

    <com.welcome.panelview.PanelViewWelcomeScreen
        android:id="@+id/one"
        android:layout_width="600px"
        android:layout_height="312px"
        android:layout_gravity="center"
        android:layout_marginTop="10px"
        android:background="@drawable/welcome" />

try above code...

3

For me calling Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) or something similar would only work after I touch the screen. SO I would call the above line of code but the screen would only clear after I then touched the screen. So what worked for me was to call invalidate() followed by init() which is called at the time of creation to initialize the view.

private void init() {
    setFocusable(true);
    setFocusableInTouchMode(true);
    setOnTouchListener(this);

    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setDither(true);
    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeJoin(Paint.Join.ROUND);
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setStrokeWidth(6);

    mCanvas = new Canvas();
    mPaths = new LinkedList<>();

    addNewPath();
}
Jason Crosby
  • 3,533
  • 4
  • 28
  • 49
3

Here is the code of a minimal example showing that you always have to redraw every pixel of the Canvas at each frame.

This activity draw a new Bitmap every second on the SurfaceView, without clearing the screen before. If you test it, you will see that the bitmap is not always written to the same buffer, and the screen will alternate between the two buffers.

I tested it on my phone (Nexus S, Android 2.3.3), and on the emulator (Android 2.2).

public class TestCanvas extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new TestView(this));
    }
}

class TestView extends SurfaceView implements SurfaceHolder.Callback {

    private TestThread mThread;
    private int mWidth;
    private int mHeight;
    private Bitmap mBitmap;
    private SurfaceHolder mSurfaceHolder;

    public TestView(Context context) {
        super(context);
        mThread = new TestThread();
        mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon);
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        mWidth = width;
        mHeight = height;
        mThread.start();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {/* Do nothing */}

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mThread != null && mThread.isAlive())
            mThread.interrupt();
    }

    class TestThread extends Thread {
        @Override
        public void run() {
            while (!isInterrupted()) {
                Canvas c = null;
                try {
                    c = mSurfaceHolder.lockCanvas(null);
                    synchronized (mSurfaceHolder) {
                        c.drawBitmap(mBitmap, (int) (Math.random() * mWidth), (int) (Math.random() * mHeight), null);
                    }
                } finally {
                    if (c != null)
                        mSurfaceHolder.unlockCanvasAndPost(c);
                }

                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    interrupt();
                }
            }
        }   
    }
}
Guillaume Brunerie
  • 4,676
  • 3
  • 24
  • 32
  • Well, it seems you're wrong, look at my screen capture here: http://img12.imageshack.us/i/devicey.png/# When you've added a delay like one second the double buffering is more noticed, but(!) there is still previous drawings on the screen. Also, your code is wrong: it should be `SurfaceHolder.Callback`, not just `Callback`. – Wroclai Apr 20 '11 at 13:31
  • 1
    I think you don’t understand what I mean. What one could expect is that the difference between frame *n* and frame *n+1* is that there is one more Bitmap. But this is completely wrong, there is one more Bitmap between frame *n* and frame *n+2*, but frame *n* and frame *n+1* are completely unrelated even if I just added a Bitmap. – Guillaume Brunerie Apr 20 '11 at 13:39
  • Nope, I fully understand you. But, as you see, your code doesn't work. After a while, the screen is full with icons. Therefore, we need to clear the `Canvas` if we want to fully redraw the whole screen. That makes my statement about "previous drawings" true. The `Canvas` keeps previous drawings. – Wroclai Apr 20 '11 at 13:59
  • 4
    Yes if you want, a `Canvas` keeps previous drawings. But this is completely useless, the problem is that when you use `lockCanvas()` you do not know what are those “previous drawing”, and cannot assume anything about them. Perhaps that if there are two activities with a SurfaceView of the same size, they will share the same Canvas. Perhaps that you get a chunk of uninitialized RAM with random bytes in it. Perhaps that there is always the Google logo written in the Canvas. You cannot know. **Any application which is not drawing every pixel after `lockCanvas(null)` is broken.** – Guillaume Brunerie Apr 20 '11 at 15:00
  • Well, I haven't said you know anything about the previous drawings, but they are still there. The `Canvas` don't know about the objects. What `Canvas` keeps are the previous drawings. This is true, according to me, according to your own application and it is a fact made by many other than me. I don't know what you're arguing about; you've already disproved your case. – Wroclai Apr 20 '11 at 19:50
  • 1
    @GuillaumeBrunerie 2.5 yrs after the fact I've come across your post. Official Android documentation supports your advice concerning "drawing every pixel". There's only one case where a portion of the canvas is guaranteed to still be there with a subsequent call to lockCanvas(). Refer to the Android documentation for SurfaceHolder and its two lockCanvas() methods. http://developer.android.com/reference/android/view/SurfaceHolder.html#lockCanvas(android.graphics.Rect) and http://developer.android.com/reference/android/view/SurfaceHolder.html#lockCanvas() – UpLate Nov 20 '13 at 23:35
2

With the following approach, you can clear the whole canvas or just a part of it.
Please do not forget to disable Hardware acceleration since PorterDuff.Mode.CLEAR doesn’t work with hardware acceleration and finally call setWillNotDraw(false) because we override the onDraw method.

//view's constructor
setWillNotDraw(false);
setLayerType(LAYER_TYPE_SOFTWARE, null);

//view's onDraw
Paint TransparentPaint = new Paint();
TransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(0, 0, width, height, TransparentPaint); 
ucMedia
  • 4,105
  • 4
  • 38
  • 46
2

Erasing on Canvas in java android is similar erasing HTML Canvas via javascript with globalCompositeOperation. The logic was similar.

U will choose DST_OUT (Destination Out) logic.

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

Note: DST_OUT is more useful because it can erase 50% if the paint color have 50% alpha. So, to clear completely to transparent, the alpha of color must be 100%. Apply paint.setColor(Color.WHITE) is recommended. And make sure that the canvas image format was RGBA_8888.

After erased, go back to normal drawing with SRC_OVER (Source Over).

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

Update small area display literally will need to access graphic hardware, and it maybe not supported.

The most close for highest performance is using multi image layer.

phnghue
  • 1,578
  • 2
  • 10
  • 9
  • Can you help me here? Yours is the only solution that has worked out for my case so i up voted, there is just a little problem that needs to be sort out – Hamza Khan Jun 16 '20 at 04:16
  • I am using canvas with surface holder, so i do this to clear canvas(canvas taken from surface holder): paint!!.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_OUT)) canvas!!.drawPaint(paint!!) surfaceHolder!!.unlockCanvasAndPost(canvas) then to be able to redraw it: paint!!.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC)) canvas!!.drawPaint(paint!!) surfaceHolder!!.unlockCanvasAndPost(canvas) now the problem is after clearing the canvas like this, when i draw a bitmap over canvas, it is drawn after a blink of Color, which is annoying, can u show me the way here? @phnghue – Hamza Khan Jun 16 '20 at 04:18
  • @hamza khan Have you tried with SRC_OVER? Because SRC_OVER is default normal. SRC logic is overwrite old pixel data, it replaces alpha! – phnghue Jun 28 '20 at 12:22
1

Don't forget to call invalidate();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();
Dharman
  • 30,962
  • 25
  • 85
  • 135
1

I found my solution.

PaintView class:

public void clear() {
    mPath.reset();
    mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    paths.clear();
}

And MainActivity:

  clear_canvas_.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            paintView.clear();
        }
    });
Elikill58
  • 4,050
  • 24
  • 23
  • 45
0

Your first requirement, how to clear or redraw whole canvas - Answer - use canvas.drawColor(color.Black) method for clearing the screen with a color of black or whatever you specify .

Your second requirement, how to update part of the screen - Answer - for example if you want to keep all other things unchanged on the screen but in a small area of screen to show an integer(say counter) which increases after every five seconds. then use canvas.drawrect method to draw that small area by specifying left top right bottom and paint. then compute your counter value(using postdalayed for 5 seconds etc., llike Handler.postDelayed(Runnable_Object, 5000);) , convert it to text string, compute the x and y coordinate in this small rect and use text view to display the changing counter value.

Chandu
  • 11
0

Try to remove the view at onPause() of an activity and add onRestart()

LayoutYouAddedYourView.addView(YourCustomView); LayoutYouAddedYourView.removeView(YourCustomView);

The moment you add your view, onDraw() method would get called.

YourCustomView, is a class which extends the View class.

Rishabh Jain
  • 145
  • 8
0

In my case, I draw my canvas into linearlayout. To clean and redraw again:

    LinearLayout linearLayout = findViewById(R.id.myCanvas);
    linearLayout.removeAllViews();

and then, I call the class with the new values:

    Lienzo fondo = new Lienzo(this,items);
    linearLayout.addView(fondo);

This is the class Lienzo:

class Lienzo extends View {
    Paint paint;
    RectF contenedor;
    Path path;
    ArrayList<Items>elementos;

    public Lienzo(Context context,ArrayList<Items> elementos) {
        super(context);
        this.elementos=elementos;
        init();
    }

    private void init() {
        path=new Path();
        paint = new Paint();
        contenedor = new RectF();
        paint.setStyle(Paint.Style.FILL);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        contenedor.left = oneValue;
        contenedor.top = anotherValue;
        contenedor.right = anotherValue;
        contenedor.bottom = anotherValue;

        float angulo = -90; //starts drawing at 12 o'clock
        //total= sum of all element values
        for (int i=0;i<elementos.size();i++){
            if (elementos.get(i).angulo!=0 && elementos.get(i).visible){
                paint.setColor(elementos.get(i).backColor);
                canvas.drawArc(contenedor,angulo,(float)(elementos.get(i).value*360)/total,true,paint);

                angulo+=(float)(elementos.get(i).value*360)/total;
            }
        } //for example
    }
}
Carlos Gómez
  • 357
  • 4
  • 9
0

In my case, creating canvas every time worked for me, even though it's not memory-friendly

Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.image);
imageBitmap = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());
canvas = new Canvas(imageBitmap);
canvas.drawBitmap(bm, 0, 0, null);
necip
  • 333
  • 4
  • 11
-1

I had to use a separate drawing pass to clear the canvas (lock, draw and unlock):

Canvas canvas = null;
try {
    canvas = holder.lockCanvas();
    if (canvas == null) {
        // exit drawing thread
        break;
    }
    canvas.drawColor(colorToClearFromCanvas, PorterDuff.Mode.CLEAR);
} finally {
    if (canvas != null) {
        holder.unlockCanvasAndPost(canvas);
    }
}
Farid Z
  • 960
  • 1
  • 9
  • 18
-1

The following worked for me:

canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SCREEN);
John Doherty
  • 3,669
  • 36
  • 38
-3

Just call

canvas.drawColor(Color.TRANSPARENT)

reznic
  • 672
  • 6
  • 9
  • 16
    That doesn't work because it'll just draw transparency on top of the current clip...effectively doing nothing. You can, however, change the Porter-Duff transfer mode to achieve the desired effect: `Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)`. If I'm not mistaken, the color can actually be anything (doesn't have to be TRANSPARENT) because `PorterDuff.Mode.CLEAR` will just clear the current clip (like punching a hole in the canvas). – ashughes Jan 05 '12 at 21:44