15

I have a custom view that fills my entire screen. (A piano keyboard) When a user touches the key, it causes invalidate() to be called and the whole keyboard gets redrawn to show the new state with a touched key.

Currently the view is very simple, but I plan to add a bit more nice graphics. Since the whole keyboard is dynamically rendered this would make redrawing the entire keyboard more expensive.

So I thought, let's look into partial redrawing. Now I call invalidate(Rect dirty) with the correct dirty region. I set my onDraw(Canvas canvas) method to only draw the keys in the dirty region if I do indeed want a partial redraw. This results in those keys being drawn, but the rest of the keyboard is totally black/not drawn at all.

Am I wrong in expecting that calling invalidate(Rect dirty) would "cache" the current canvas, and only "allows" drawing in the dirty region?

Is there any way I can achieve what I want? (A way to "cache" the canvas and only redraw the dirty area?"

Peterdk
  • 15,625
  • 20
  • 101
  • 140

2 Answers2

19

Current nice workaround is to manually cache the full canvas to a bitmap:

 private void onDraw(Canvas canvas)
 {
     if (!initialDrawingIsPerformed)
     {
          this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
            Config.ARGB_8888); //Change to lower bitmap config if possible.
          Canvas cacheCanvas = new Canvas(this.cachedBitmap);
          doInitialDrawing(cacheCanvas);
          canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
          initialDrawingIsPerformed = true;
     }
     else
     {
          canvas.drawBitmap(this.cachedBitmap, 0, 0, new Paint());
          doPartialRedraws(canvas);
     }
 }

Ofcourse, you need to store the info about what to redraw yourself and preferably not use a new Paint everytime, but that are details.

Also note: Bitmaps are quite heavy on the memory usage of your app. I had crashes when I cached a View that was used with a scroller and that was like 5 times the height of the device, since it used > 10MB memory!

Peterdk
  • 15,625
  • 20
  • 101
  • 140
  • 1
    Hmm, I don't like this solutnio because the reason his keys are partially drawn is because he should first have a full draw of hiscanvas anyway. The question is thus why his canvas didn't draw correctly in the first place. –  Nov 08 '12 at 18:55
  • 1
    I need to note that I now understand that when you call `invalidate` with a dirty `rect`, you can just use your normal drawing code, and it should only actually perform the drawing operations that are in the dirty `rect`. `Canvas` discards the operations that are outside the `rect`. Still, this means that a lot of your own code still renders and in my tests with HW acceleration on it was slower than just rendering the view fully. – Peterdk Jan 07 '14 at 01:45
  • 1
    Note that for drawing bitmaps to canvas the paint param can be null, e.g. `canvas.drawBitmap(bitmap, 0, 0, null)`. – greg7gkb Mar 19 '16 at 06:01
5

To complement Peterdk's answer, you could save your operations in a Picture instead of a Bitmap.

  • A Bitmap will save all pixels, like he said it could take a lot of memory.
  • A Picture will save the calls, like drawRect, drawLine, etc.

It depends of what is really heavy in your application : a lot of draw operations, a few draw operations but controlled by heavy calculations, a lot of blank/unused space (prefer Picture) etc...

MappaM
  • 809
  • 8
  • 15
  • Excuse me. Will the `Picture` record `drawBitmap(bitmap)` calls? Probably no, right? – Yeung Feb 01 '13 at 02:57
  • Note that Picture playback is only supported on software canvases, so you can't use Hardware layers for views that use this. – greg7gkb Mar 19 '16 at 03:23