Using Flex UIComponent width and height
AS you might know, the only way to add Sprite based classes into a Flex project is by adding them to a UIComponent or a subclass of UIComponent. This adds some extra complications. One of these is that UIComponent does not report width and height correctly. If provided with an explicit width and height, the UIComponent’s size will be set to whatever it was when initialised and will not change if, for example, the contents of the component are scaled. This means that whatever contains the component will not respond appropriately. Dynamic sizing doesn’t seem to work at all. (As an aside, I do think that this is rather a poor implementation, since it breaks the existing functionality of DisplayObject)
In order to correctly set the size we need to recreate the DisplayObject functionality in order that from outside the component we can see the changes in size. In order to do that, it’s necessary to understand how UIComponent (and by extension, most of the other Flex components) is set up and handles sizing.
First of all, lets take a quick look at how a UIComponent is set up. The methods we need to know about are createChildren, measure and updateDisplayList. createChildren is run once when the component is set up and should contain the code for instantiating the component’s children. measure is used to set the width and height of the component if no explicit size has been set. updateDisplayList is the function that will handle the changes to the display. The properties we are going to use are width, explicitWidth, unscaledWidth and measuredWidth and their corresponding height values.
OK. Time for some code. The example I’m going to use simply draws a gradient box and scales it using a slider. The container is a fixed size, so it should clip the content. Normally it doesn’t
This is the mxml:
and this is the ZoomTestComp class:
package
{
import flash.display.GradientType;
import flash.display.Shape;
import flash.geom.Matrix;
import mx.core.UIComponent;
public class ZoomTestComp extends UIComponent
{
private var _box:Shape;
private var _zoom:Number;
private var _newZoom:Number;
public function ZoomTestComp()
{
super();
_zoom = 1;
_newZoom = 1;
}
[Bindable]
public function get zoom():Number { return _zoom * 100; }
public function set zoom(value:Number):void
{
if (value / 100 == _zoom) return;
_newZoom = value / 100;
if (!isNaN(explicitWidth)) width = _box.width / _zoom * _newZoom;
if (!isNaN(explicitHeight)) height = _box.height / _zoom * _newZoom;
invalidateSize();
}
public function addBox(w:Number, h:Number):void
{
_box = new Shape();
_drawBox(w * _zoom, h * _zoom);
addChild(_box);
if (!isNaN(explicitWidth)) width = _box.width / _zoom * _newZoom;
if (!isNaN(explicitHeight)) height = _box.height / _zoom * _newZoom;
invalidateSize();
invalidateDisplayList();
}
override protected function createChildren():void
{
super.createChildren();
}
override protected function measure():void
{
super.measure();
if (_box == null) return;
measuredWidth = _box.width / _zoom * _newZoom;
measuredHeight = _box.height / _zoom * _newZoom;
_zoom = _newZoom;
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
if (_box == null) return;
_drawBox(unscaledWidth, unscaledHeight);
_zoom = _newZoom;
}
private function _drawBox(width:Number, height:Number):void
{
var matrix:Matrix = new Matrix();
matrix.createGradientBox(width, height, 45 * Math.PI / 180);
_box.graphics.clear();
_box.graphics.beginGradientFill(GradientType.LINEAR, [0xFF0000, 0xFFFF00], [1, 1], [0, 255], matrix);
_box.graphics.drawRect(0, 0, width, height);
_box.graphics.endFill();
}
}
}
Right, so the focus is our ZoomTestComp. In the mxml I’ve set the width and height to 300 (it works without explicit values too) and bound the value of the slider to the zoom property, which you can see is set up as a Bindable getter/setter in the ZoomTestComp class. Under that is an addBox method to add the box dynamically to the container (again it works if you just add it direct). Under that are the 3 methods I mentioned above and a private drawBox method, that actually handles the drawing.
Let’s look at the methods in the order they are called.
First up is createChildren. If you were going to add children directly, you should do it here. Note that drawing/layout methods are best kept in the updateDisplayList method.
The next function is updateDisplayList. This is called every time invalidateDisplayList or invalidateSize is called. InvalidateDisplayList is called when the width or height are set directly. InvalidateSize is called when they are changed indirectly, ie when the percentageHeight is changed. This method draws the box. If you had a more complicated component all of the layout and sizing changes should happen here.
The measure method is called if there is not an absolute or percentage size. It gets the new scaled width and height of the box by dividing by the old zoom and multiplying by the new. It stores these in measuredWidth and measuredHeight. Again, if you had a more complicated component, this method would be used to calculate the size of the component based on its children.
The last function that will be called is set zoom. This is called when the slider is changed and calculates the zoom factor. If there is a set size, the width and height are set here, which in turn calls updateDisplayList, if not, we let invalidateSize handle the resizing.
I’m not 100% happy with this approach as it just seems too fragile. I spent a considerable amount of time looking at how the UIComponent system worked and it seems overly convoluted to me. It may be that I’m missing something key here, but there is scant information out there on how this works. It doesn’t handle percentage sizes at the moment either.
I’m going to have another look at this when I have a bit more time but in the meantime if you know of a better solution I’d love to hear it.
This entry was posted
on Monday, November 24th, 2008 at 6:18 pm and is filed under AS3, Coding, Flex, Flex Builder 3, UIComponent, actionscript, height, sizing, width.
You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.