ChatGPT解决这个技术问题 Extra ChatGPT

Android, canvas: How do I clear (delete contents of) a canvas (= bitmaps), living in a surfaceView?

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

S
Sileria

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

Canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

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.
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.
This is not working for me leaving a blank surface instead of Transparent
W
Wroclai

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.


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.
@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: 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.
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 !!!
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.
R
Rukmal Dias

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..


This gives me a black area - not the bitmap I have behind it :(
Its clears fine, but the bitmap is also remove unlike you said.
in stackoverflow.com/questions/9691985/… 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
R
Rakesh

use the reset method of Path class

Path.reset();

this really is the best answer if your using a path. the other ones can leave the user with a black screen. thanks.
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.
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?
D
Derzu

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.


Multiplying with transparency is the answer. Otherwise it can end up black colored on some devices.
d
duggu
mBitmap.eraseColor(Color.TRANSPARENT);

canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

how is this the most correct? why use a bitmap at all when you can just drawColor?
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
B
Buddy
canvas.drawColor(Color.TRANSPARENT, Mode.MULTIPLY);

Please add an explanation of how your code solves the issue. This is flagged as low quality post - From Review
H
Hemant Chand Dungriyal

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...


holder.setFormat(PixelFormat.TRANSPARENT); It works for me.
G
Guillaume Brunerie

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

Well, it seems you're wrong, look at my screen capture here: 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.
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.
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.
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.
@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. developer.android.com/reference/android/view/… and developer.android.com/reference/android/view/…
J
Jason Crosby

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

p
phnghue

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.


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
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 Have you tried with SRC_OVER? Because SRC_OVER is default normal. SRC logic is overwrite old pixel data, it replaces alpha!
u
ucMedia

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

it's work, but after that my draws not visible more
@DynoCris Perhaps you are using the same Paint object!? Try with a new one, and don't forget to upvote please.
it's my code pastebin.com/GWBdtUP5 when i try to draw again i don't see lines more unfourtunatly. but cansvas is clear.
@DynoCris Please change LAYER_TYPE_HARDWARE to LAYER_TYPE_SOFTWARE
oh, really, it's my mistake. But it didn't help me anyway unfourtunatly.
D
Dharman

Don't forget to call invalidate();

canvas.drawColor(backgroundColor);
invalidate();
path.reset();

Why would you call invalidate after drawing something?
C
Chandu

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.


R
Rishabh Jain

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.


C
Carlos Gómez

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

n
n-y

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

J
John Doherty

The following worked for me:

canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SCREEN);

E
Elikill58

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

F
Farid Z

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

r
reznic

Just call

canvas.drawColor(Color.TRANSPARENT)


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).