Adroit development

2013-02-26

Something that about android app development that caused me some trouble recently.

That being, that the canvas in a view doesn't have a identity matrix as default!

Before I explain let me say that I was developing against API 8, you know the one that supports gramophones - but I can't think that this particular feature has changed.

It goes like this. You have a View on which to display some things. Inside of that view, or rather attached to it you have a Canvas.  All good so far. You can draw on the canvas. However, have you ever tried to centre something in the canvas using canvas.getHeight() / 2 and have it not be in the middle? But then use this.getHeight() / 2 and have it work? (or mostly work, until your rotate the display)

You then realise that the canvas is not the same size as the view, and go off and read the Google API docs And various stack overflow thingies about your observation?

Here is what is going on with this (and more importantly high light a gotcha)

The View-canvas contains a matrix (as all canvases do.) But you will notice that your view also has a status bar at the top (usually) and if you do mathematics on this.getHeight() - canvas.getHeight() it will come out as the hight of the status bar?

However, the canvas actually starts underneath the status bar. But when you put a pixel at 0,0 on the canvas it comes under the status bar i.e. in the right place? What's going on?

Well turns out there is a default matrix on the canvas that translates all your draws by the size of the status bar!

This is all well and good, except that if you ever try and do any matrix operations on the canvas they won't work out properly. It has a non identify matrix you will likely learn your transformations don't quite work.

For example:

Translate to origin, followed by rotate, then translate to centre, won't work as you intend.

There are a few workarounds for this. One being a copy of the default matrix, and add it to the end of your matrix stack. Or - use a new canvas and blit that to the canvas you are given with the view.

This discovery, needles to say wasn't my favourite since it isn't documented anywhere in the android docs, and isn't obvious. Unless I missed it. I'd rather you not see this magic matrix transform in your view.

It Would have been better (in my opinion) if it were accounted for in a parent view. I guess the way they designed the thing their isn't one to hand, hence this magic - it is just that the way the status bar is handled - i.e. it's kind of part of your view now affects all your canvas draws, and they shouldn't be related really, should they?

The other thing I found is that the ScaleGestureDetector implementation in android 2.3 is broken. Essentially you end up with crazy horse scaling if you take your fingers off at the edge of the screen and stuff. A web search will yield you some fixes - one which I found not to work. The other is to wrap the call to the scale listener in and if statement to convince it to only do stuff when two pointers are down.

The fix that worked for me is here: http://code.google.com/p/android/issues/detail?id=12976