title: Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroup
body: <p>I've seen a few people ask how to zoom an entire ViewGroup (such as a RelativeLayout) in one go. At the moment this is something I need to achieve. The most obvious approach, to me, would be to hold the zoom scale factor as a single variable somewhere; and in each of the child Views' onDraw() methods, that scale factor would be applied to the Canvas prior to graphics being drawn. </p>
  
  <p>However, before doing that, I have tried to be clever (ha - usually a bad idea) and extend RelativeLayout, into a class called ZoomableRelativeLayout. My idea is that any scale transformation could be applied just once to the Canvas in the overridden dispatchDraw() function, so that there would be absolutely no need to separately apply the zoom in any of the child views. </p>
  
  <p>Here's what I did in my ZoomableRelativeLayout. It's just a simple extension of RelativeLayout, with dispatchDraw() being overridden:</p>
  
  <pre><code>protected void dispatchDraw(Canvas canvas){         
      canvas.save(Canvas.MATRIX_SAVE_FLAG);       
      canvas.scale(mScaleFactor, mScaleFactor);
      super.dispatchDraw(canvas);     
      canvas.restore();       
  }
  </code></pre>
  
  <p>The mScaleFactor is manipulated by a ScaleListener in the same class. </p>
  
  <p>It does actually work. I can pinch to zoom the ZoomableRelativeLayout, and all of the views held within properly rescale together. </p>
  
  <p>Except there's a problem. Some of those child views are animated, and hence I periodically call invalidate() on them. When the scale is 1, those child views are seen to redraw periodically perfectly fine. When the scale is other than 1, those animated child views are only seen to update in a portion of their viewing area - or not at all - depending on the zoom scale. </p>
  
  <p>My initial thinking was that when an individual child view's invalidate() is being called, then it's possibly being redrawn individually by the system, rather than being passed a Canvas from the parent RelativeLayout's dispatchDraw(), meaning that the child view ends up refreshing itself without the zoom scale applied. But oddly, the elements of the child views that are redrawn on the screen are to the correct zoom scale. It's almost as if the area that the system decides to actually update in the backing bitmap remains unscaled - if that makes sense. To put it another way, if I have a single animated child View and I gradually zoom in further and further from an initial scale of 1, and if we place an imaginary box on the area where that child view is when the zoom scale is 1, then the calls to invalidate() only cause a refresh of the graphics in that imaginary box. But the graphics that are seen to update <em>are</em> being done to the right scale. If you zoom in so far that the child view has now moved completely away from where it was with a scale of 1, then no part of it at all is seen to refresh. I'll give another example: imagine my child view is a ball that animates by switching between yellow and red. If I zoom in a little bit such that the ball moves to the right and down, at a certain point you'll just see the top-left quarter of the ball animate colours. </p>
  
  <p>If I continuously zoom in and out, I see the child views animate properly and entirely. This is because the entire ViewGroup is being redrawn. </p>
  
  <p>I hope this makes sense; I've tried to explain as best as I can. Am I on a bit of a loser with my zoomable ViewGroup strategy? Is there another way?</p>
  
  <p>Thanks,</p>
  
  <p>Trev</p>