Alex Rivera | Logout

Extending RelativeLayout, and overriding dispatchDraw() to create a zoomable ViewGroup

Asked 2011-06-16T21:32:47.387
24

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.

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.

Here's what I did in my ZoomableRelativeLayout. It's just a simple extension of RelativeLayout, with dispatchDraw() being overridden:

protected void dispatchDraw(Canvas canvas){         
    canvas.save(Canvas.MATRIX_SAVE_FLAG);       
    canvas.scale(mScaleFactor, mScaleFactor);
    super.dispatchDraw(canvas);     
    canvas.restore();       
}

The mScaleFactor is manipulated by a ScaleListener in the same class.

It does actually work. I can pinch to zoom the ZoomableRelativeLayout, and all of the views held within properly rescale together.

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.

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(), meani

Edit
Report

1 Answer

13

If you are applying a scale factor to the drawing of your children, you also need to apply the appropriate scale factor to all of the other interactions with them -- dispatching touch events, invalidates, etc.

So in addition to dispatchDraw(), you will need to override and appropriate adjust the behavior of at least these other methods. To take care of invalidates, you will need to override this method to adjust the child coordinates appropriately:

http://developer.android.com/reference/android/view/ViewGroup.html#invalidateChildInParent(int[], android.graphics.Rect)

If you want the user to be able to interact with the child views you will also need to override this to adjust touch coordinates appropriately before they are dispatched to the children:

http://developer.android.com/reference/android/view/ViewGroup.html#dispatchTouchEvent(android.view.MotionEvent)

Also I would strongly recommend you implement this all inside of a simple ViewGroup subclass that has a single child view it manages. This will get rid of any complexity of behavior that RelativeLayout is introducing in its own ViewGroup, simplifying what you need to deal with and debug in your own code. Put the RelativeLayout as a child of your special zooming ViewGroup.

Finally, one improvement to your code -- in dispatchDraw() you want to save the canvas state after applying the scaling factor. This ensures that the child can't modify the transformation you have set.

answered 2011-06-27T08:53:04.110

Your Answer