How to draw onto a view canvas without upscaling artifacts when the view parent has a scale factor?
android, android-canvas, ondraw, zooming
Solution
This issue takes it roots from Composition pattern. Android View Framework (that's not an official name, but you got the idea) also follows this pattern - `ViewGroup` (base class for all layouts) is essentially a `View`. So, when your layout draws itself it just takes content of all children views. Sounds easy.
Since API Level 11 `View` transformation matrix can be modified via `setScaleX`, `setTranslationX`, etc. methods (well, in fact it was possible before API Level 11, but it was a bit tricky). This transformation is then used to transform drawings of this particular `View`. Therefore, when you're applying transformation to `ViewGroup`, you're affecting only `ViewGroup` drawing itself, which in return consists of drawing its children.
Moreover, those transformations are affecting only drawing and dispatch of motion events. So, when you're applying `setScale*` you're just modifying the visual representation of the `View`, while logical coordinates and size of the `View` stays the same (`getLeft`, `getTop`, `getRight`, `getBottom`, `getWidth`, `getHeight`).
Now closer to your question. In short - you're doing it wrong. More detailed explanation follows from my previous statements. By affecting transformation of `View` you're not changing it's logical position and size. Therefore child `View` won't detect anything abnormal while drawing - it draws as if there was no scale at all.
What to do then? I would propose three different solutions.
- My favorite solution that I'm using pretty oftenDetach your `View` from layout being scaled and add it on top of it. Then you'll need to scale this `View` manually, taking into account data behind it.
- Override `setScale` method of layout being scaled to propagate it to children. You can create some interface and check if any of the children implements it.
- Vice versa. In your `View` iterate through the all parents until you'll find the root and then calculate scale accordingly. This shouldn't be to much work if your hierarchy is not too deep.
Problem
When drawing onto a canvas in a `View`'s `onDraw` method you don't know about any scale factor of its parent. This leads to all views and their drawings being up/down scaled whereby one can explore "pixelized" drawings. Example: So let's say we've got a child view with a custom drawing ``` class MyView extends View { @Override public onDraw(Canvas canvas) { // Draw something fancy here that looks ugly when scaled up } } ``` And we add this this view into a parent view: ``` MyView view = new MyView(this); parent.addChild(view); parent.setScaleX(10); parent.setScaleY(10); ``` The canvas drawing in the subview will thus being upscaled by factor 10. This is due to `onDraw()` providing a canvas to draw to that does not reflect this scale factor. That said the canvas always keeps the same dimensions regardless of its final destination. Thus child views do not have the chance to provide higher resolution drawings when their parents are being scaled up. This obviously leads to scaling artifacts. You can observe the same behavior when you scale a button's parent view: Is there any chance to respect all parents transformations when drawing onto child views? Or is this a general limitation when drawing onto canvases in a view hierarchy where view parents can have a scale factor not equal to 1. In my use case it is not an option to forward the scale change to all children instead of applying it to the parent. This is due to the fact that there some of those children have animations that modify the scale factor. Hence these animations would override any scale change.