How to calculate the size of a sprite? - apache-flex

I have a sprite that I do some custom drawing in, but I would like the container to know where to position the sprite properly. To do this, the container needs to know how big the sprite is. UIComponents go through a measure stage, but sprites don't . How do I calculate the size that a sprite will be?
Edit: I'm doing the drawing in Event.ENTER_FRAME, and it's animated, so I can't tell ahead of time how big it's going to be. The UIComponent has a measure function and I'd like to create something similar.

The precise answer, as far as I can gather is, you can't tell ahead of time, you must actually draw into the sprite to determine it's size.

Sprites take the size that you draw in them. It has no size at all until you've drawn something in it. If your application allows you can draw a border (rectangle maybe) first and then measure the sprite. But don't draw outside the borders later.

Also, depending on what you are drawing, then you may be able to use math to pre-calculate the final size.
i.e. if you are drawing a circle, you could use Math to figure out the final height / width.
mike

Have a look here -- I hope this answers your question:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.core.UIComponent;
private var s:Sprite;
private var w:UIComponent;
override protected function createChildren():void
{
super.createChildren();
if (!s)
s = getSprite();
w = new UIComponent();
trace("The sprite measures " + s.width + " by " + s.height + ".");
w.addChild(s);
box.addChild(w);
}
private function getSprite():Sprite
{
var s:Sprite = new Sprite();
s.graphics.lineStyle(1, 0xFFFFFF);
s.graphics.beginFill(0xFFFFFF, 1);
s.graphics.drawRect(0, 0, Math.floor(Math.random() * 1000), Math.floor(Math.random() * 1000));
s.graphics.endFill();
return s;
}
]]>
</mx:Script>
<mx:Box id="box" backgroundColor="#FFFFFF" />
</mx:Application>
If you run this, the trace statement should display the height and width of the drawn Sprite, which is randomly generated. In other words, you can get the height and width of the sprite simply by querying its height and width properties.

If you need to layout some others components based on the Sprite width and height, but before actually drawing it, you can draw on a flash.display.Shape object, use this size as a reference, and then attach the Shape on the Sprite.

Related

Smooth scrolling a Spark List with variable height items using the mouse wheel

We have a list of variable height items that we display in a Spark List control. When the user clicks and drags the vertical scrollbar the list scrolls smoothly. When the up/down arrows are used it moves in small and discrete steps. When the mouse wheel is used the list scrolls in very large discrete steps that is problematic for the user.
We would like to enable smooth scrolling for the mouse wheel. The height of our items vary significantly and it is easy to get lost when you scroll with the moouse due to the discrete scrolling.
Our implementation is fairly simple:
<s:List id="chartList"
dataProvider="{pm.charts}"
itemRenderer="charts.ChartItemRenderer"
horizontalScrollPolicy="off"
verticalScrollPolicy="on"
useVirtualLayout="false"
cachePolicy="auto">
</s:List>
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="false"
xmlns:charts="charts.*"
>
<fx:Script>
<![CDATA[
private var _canvas:BitmapData;
public function set canvas(value:BitmapData):void
{
_canvas = value;
}
[Bindable]
public function get canvas():BitmapData
{
return _canvas;
}
public function render(x:int,y:int, data:int):void
{
_canvas.draw(this);
}
]]>
</fx:Script>
<charts:DefaultChartContainer
chart="{data}"
cachePolicy="on"/>
</s:ItemRenderer>
There does not appear to be an out of the box method for implementing smooth scrolling in a Spark List. How would one go about implementing smooth scrolling in a spark list for variable height items?
Edit:
Here is another way to do this:
http://forums.adobe.com/message/3844410#3844410
Ok, so this wont be easy, but it is doable.
1) Create a custom skin for your list
2) In the custom skin, replace the scroller with a custom scroller (MyScroller)
3) Create a new class that extends Scroller, called MyScroller
4) Adobe in their infinite wisdom made skin_mouseWheelHandler private - which they seem to do all over the place, so you would have to override something higher up, maybe attachSkin and detachSkin. Or you could try adding your own skin_mouseWheelHandler with a higher priority in attachSkin and prevent default on it so the default does not get called.
5) Replicate the code in skin_mouseWheelHandler, and modify it to suit your requirements.
Like #Sunil_D. suggested, listening the mousewheel event and manually adjusting the HorizontalScrollPosition is an easy way to handle this. Add the EventListener for your component
chartList.addEventListener(MouseEvent.MOUSE_WHEEL, chartList_mouseWheelHandler);
and increase/decrease the horizontalScrollPosition with, for example, multiples of event.delta
protected function chartList_mouseWheelHandler(event:MouseEvent):void
{
chartList.verticalScrollPosition -= (event.delta * SOME_CORRECT_VALUE);
}
Finding the proper SOME_CORRECT_VALUE might need some experimenting, but it shouldn't be to hard to find.

BorderContainer as Foreground Object

I am searching for a way to have my spritevisualelement have round corners so that it is displayed in a circular shape.
The SpriteVisualElement contains a Video Stream from FMS to display but I do not want it to be rectangular.
Somebody got a hint for me?
<s:BorderContainer
borderColor="0x000000"
borderAlpha="50"
cornerRadius="150"
borderWeight="3"
x="161"
y="10"
>
<s:SpriteVisualElement id="vid" width="480" height="320"/>
</s:BorderContainer>
<s:SpriteVisualElement id="vid_self_preview" x="710" y="373" width="90" height="60"/>
But the Container keeps being in the background and the whole remote Video which is displayed in the "vid" (=id) is in the foreground.
How can I set the Container to be in Foreground? then just setting whole application background would do the job.
Sounds like a perfect use case for an Alpha Mask.
Try this:
import spark.core.MaskType;
private function addMask():void {
var vidMask:SpriteVisualElement = new SpriteVisualElement();
// first mask the entire thing transparent
vidMask.graphics.beginFill(0x000000, 0);
vidMask.graphics.drawRect(0,0,480,320);
vidMask.graphics.endFill();
// then draw the rounded rectangle (or whatever) that you want to show
vidMask.graphics.beginFill(0x000000, 1); //color doesn't matter
vidMask.graphics.drawRoundRect(0,0,480, 320, 200, 200); // choose
vidMask.graphics.endFill();
addElement(vidMask); //the mask has to be on the Display List
vid.mask = vidMask; // attach the mask to the sve
vid.maskType = MaskType.ALPHA; // choose alpha masking
}
And in your MXML:
<s:SpriteVisualElement id="vid" width="480" height="320" addedToStage="addMask()">
UPDATE
Actually, now that I thought about it some more, my initial answer is misleading. You really only need to have a Clipping Mask (not an Alpha one), since there is no gradation in the opacity. You can update the addMask function as follows:
private function addMask():void {
var vidMask:SpriteVisualElement = new SpriteVisualElement();
// draw the rounded rectangle (or whatever) that you want to show
vidMask.graphics.beginFill(0x000000, 1); //color doesn't matter
vidMask.graphics.drawRoundRect(0,0,480, 320, 200, 200); // the last two effect the roundness
vidMask.graphics.endFill();
addElement(vidMask); //the mask has to be on the Display List
vid.mask = vidMask; // attach the mask to the sve
vid.maskType = MaskType.CLIP; // choose clip masking
}
Unless you do end up using a graded opacity in your mask, this method would be preferable, since it should simplify the compositing the player has to carry out.

Why does Flex's Matrix Transform Snap Child Back to Top Corner of Parent?

I'm having trouble with Matrix Transforms in Flex.
I have a Flex Canvas with nothing but an Image in it, like so.
<mx:Canvas>
<mx:Image id="img" source="/some/Url" />
</mx:Canvas>
To this image I am applying zoom/rotation transforms using code like this:
private function _rotateAndZoomImage(zoom:Number, angle:Number):void{
var radians:Number = angle * (Math.PI / 180.0);
var offsetWidth:Number = ( img.contentWidth/2.0 );
var offsetHeight:Number = ( img.contentHeight/2.0 );
var matrix:Matrix = new Matrix();
matrix.translate(-offsetWidth, -offsetHeight);
matrix.rotate(radians);
//Do the zoom
matrix.scale (zoom/100, zoom/100);
matrix.translate(+offsetWidth, +offsetHeight);
img.transform.matrix = matrix;
}
This all works. BUT. The image is draggable. The user can move it anywhere inside the canvas that is it's parent.
Once the image has been moved and the transform is applied the image gets "snapped" back to the original 0,0 (top left of the canvas) location before being transformed and is left there afterwards - not where the user would expect it to be.
After messing about with this for the best part of a day I still can't work out why on earth it's doing this. Anybody?
Edit: Note that the same happens without the two calls to .translate() in the above code. So the following code also snaps the image back to 0,0 of the canvas after it has been moved:
private function _rotateAndZoomImage(zoom:Number, angle:Number):void{
var radians:Number = angle * (Math.PI / 180.0);
var matrix:Matrix = new Matrix();
matrix.rotate(radians);
matrix.scale (zoom/100, zoom/100);
img.transform.matrix = matrix;
}
Jake
Here is how I fixed it. I overrode the 'move' function in my Image class, called super.move(), and then reset the transform matrix to the value I originally set it to (in this case _affineTransform).
override public function move(x:Number, y:Number):void
{
super.move(x,y);
this.transform.matrix = _affineTransform;
}
Hacky, but watcha gonna do...if someone has a cleaner solution, please let me know!
If you look in the source code for the set x() override in mx.core.UIComponent, you'll see the following line:
_layoutFeatures.layoutX = value
Basically, it ignores the default display list x value and stores the position elsewhere. The real x is probably set later once the full layout has been processed (replacing your translation value). You should probably always use x, y, scaleX, scaleY, rotation and any other relevant properties. It seems that Adobe doesn't intend for Flex to support the regular display object transform matrix.
I think you may have to add the x,y values to your translation:
private function _rotateAndZoomImage(zoom:Number, angle:Number):void{
var radians:Number = angle * (Math.PI / 180.0);
var offsetWidth:Number = ( img.contentWidth/2.0 );
var offsetHeight:Number = ( img.contentHeight/2.0 );
var matrix:Matrix = new Matrix();
matrix.translate(-offsetWidth, -offsetHeight);
matrix.rotate(radians);
//Do the zoom
matrix.scale (zoom/100, zoom/100);
matrix.translate(img.x + offsetWidth, img.y + offsetHeight);
img.transform.matrix = matrix;
}
try setting this.
matrix.tx
matrix.ty
The solution/workaround to this is quite simple. Instead of this:
<mx:Canvas>
<mx:Image id="img" source="/some/Url" mouseDown="img.startDrag()" />
</mx:Canvas>
I now have this:
<mx:Canvas>
<mx:Canvas id="inner" mouseDown="inner.startDrag()" clipContent="false">
<mx:Image id="img" source="/some/Url" />
</mx:Canvas>
</mx:Canvas>
The draggable image no longer lives directly inside the main Canvas. Instead it lives inside a child Canvas. The image is no longer draggable itself. It's the inner Canvas that gets dragged.
Now, when a transformation is applied to the image, it stays put as its parent's 0,0 coords are now where they're supposed to be, as it's where you dragged the canvas to.
Hope this helps somebody and saves them the time I've wasted on this.
Jake

How to create HBox with only the top corners rounded in Flex?

I'm trying to create an HBox in flex that has only the top corners rounded. HBox (and all the other flex containers) have a cornerRadius style, but it applies to all corners. I'm not seeing a way to specify the style of individual corners.
Is there any built-in way to do this, or will I need to write my own custom drawing code to draw the background with rounded corners?
It depends what border skin you're using. If you're using the built in skins (HaloBorder), there's a style that you can specify "roundedBottomCorners" (which is a boolean), which allows you to control whether the bottom corners are rounded or not. If you're using your own border skin, you can add in whatever styles you want to control the corners.
Alas, the only solution I know is to draw the background yourself, using Programmatic Skinning -- specifically, orderriding RectangularBorder::
package
{
import mx.skins.RectangularBorder;
public class HBoxSkin extends RectangularBorder
{
public function HBoxSkin()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var cr:Number = getStyle("cornerRadius");
var bc:Number = getStyle("backgroundColor");
graphics.clear();
graphics.beginFill(bc, 1);
// Draw the rectangle manually, rounding only the top two corners
graphics.drawRoundRectComplex(0, 0, unscaledWidth, unscaledHeight, cr, cr, 0, 0);
graphics.endFill();
}
}
}
... and then applying the change using the borderSkin property of your HBox:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<!-- Apply the skin using the borderSkin property of your HBox -->
<mx:HBox borderSkin="HBoxSkin" backgroundColor="#FFFFFF" cornerRadius="10" height="100" width="100" top="10" left="10" />
</mx:Application>
The Flex Documentation gives an example of how to implement programmatic skinning. It's beyond beginner level, unfortunately (this stuff gets a lot easier in Flex 4), but if you follow the code in my example and in the documentation's, you should be able to get something together. Good luck!

Is there a multiline text workaround for Flex

Is there a workaround for displaying multiline text in Flex 3? The two controls I have tried so far are mx:Text, and mx:TextArea. Each control has its own bug associated with it. For reference: mx:Text bug - http://bugs.adobe.com/jira/browse/SDK-9819 mx:TextArea bug - http://bugs.adobe.com/jira/browse/SDK-12616. Basically, neither control handles scrolling correctly if you do not specify a height and the text wraps onto the next line (height is determined dynamically by Flex, based on the wrapping). Does anybody have a workaround that might be helpful?
Thanks.
Update: One of the methods I have tried in the past has been to manually calculate the height of a mx:Text element. I can do this by using the following:
var textItem:Text = new Text();
var len:int = value.length;
var lines:int = int(len/115) + 1;
var height:int = lines * 20;
textItem.height = height;
While this seems to get around the problem in mx:Text, there is one big fault. The calculation relies heavily on font-size, letter-spacing, and the width of textItem. I can use this method, and move on with my project. However, maintenance on this is inevitable, and with code like this, it will a gigantic PITA.
I've had to deal with this a few times myself. The best way I've found to get dynamic height sizing of <mx:Text> is to leave the height out of the text and then specify a percent height of 100% on the enclosing VBox, HBox, etc. Something like the following should work for you:
<mx:VBox width="100%" height="100%">
<mx:Text text="Your really long text goes here." width="100%"/>
</mx:VBox>
As this is a bit of a hack itself, your milage may vary.
Edit
If you want to extend your above example so that maintenance on the code is easier, you should look into the TextLineMetrics class. This will allow you to measure the width and height of your text, taking into account font, size, etc. The docs for TextLineMetrics can be found here. To use your above example, you'd want to do something like the following:
var textItem:Text = new Text();
var metrics:TextLineMetrics = textItem.measureText( value );
var len:int = metrics.width;
var lines:int = int(len/textItem.width) + 1;
var height:int = lines * metrics.height;
textItem.height = height;
I use a variable height text area class that works very well for me:
package
{
import mx.controls.TextArea;
/**
* TextArea that xpands to the height of the content contained
* within.
* #author joel
*
*/
public class VariableHeightTextArea extends TextArea
{
public function VariableHeightTextArea()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(this.height != int(this.textField.measuredHeight) + 5 )
{
this.height = this.textField.measuredHeight + 5;
}
}
}
}

Resources