How to find the element offset in a group? - apache-flex

I have a visual element that is nested in two groups. How would I find the X and Y offset of that element from a parent group?
Here is the code:
<group id="rootGroup">
<group id="parentGroup" x="30" y="50">
<button id="myButton" x="40" y="20" />
</group>
</group>
The button position can change over time as well as the parent groups position. So I was trying to use something like localToGlobal.

Below is an example application that shows how to do this. The basic idea is convert the target object's (the button) coordinates to global coordinates with localToGlobal(). Then use globalToLocal() to convert the global coordinates into the desired coordinate space.
The most important step is the first part. To convert the button's coordinates into global coordinates, we actually use the parent of the button -- because the button "exists" in it's parent's coordinate space. This is always a little confusing when I do it :)
Run this app and play with it. To really test it, add one more BorderContainer around the "rootGroup" and offset "rootGroup" by a few pixels so that the root's coordinate space is not the same as the global coordinate space.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
private function onCreationComplete():void
{
var p:Point = new Point(childButton.x, childButton.y);
trace(p); // (x=50, y=50)
// convert the button's coordinates to Global coordinate space
// note since the button exists in the parent object's coordinate plane
// you do this conversion using the parent
var global:Point = parentGroup.localToGlobal(p);
trace(global); // (x=151, y=151) <-- 1 extra pixel from border of the BorderContainer
// now that we have global coordinates, use globalToLocal() to convert
// these coordinates into the desired coordinate plane
var rootLocal:Point = rootGroup.globalToLocal(global);
trace(rootLocal); // (x=151, y=151)
var parentLocal:Point = parentGroup.globalToLocal(global);
trace(parentLocal); // (x=50, y=50)
}
]]>
</fx:Script>
<s:BorderContainer id="rootGroup" borderColor="#FF0000">
<s:BorderContainer id="parentGroup" x="100" y="100" borderColor="#00FF00">
<s:Button id="childButton" x="50" y="50" label="Click Me"/>
</s:BorderContainer>
</s:BorderContainer>
</s:Application>

Related

Rotating a Label 90 degree in an Item Renderer

I'm trying to rotate a label on my item renderer.
When I rotate it 45 degrees, it's working just fine but when I rotate it 90 degrees,
which is what I wanna do, label is rotating but after list created,
rotated labels are stepping up each other.
I can select the 45 degree ones but it seems like 90 degree ones has no
width at all. When I declare width and height and padding but that did not solve it too.
How can I make my labels 90 degree without making them step up each other?
My item renderer:
<?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" >
<fx:Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
dateLabel.text = data.date;
}
]]>
</fx:Script>
/* When rotation is 90, my labels are just stepping up eachother */
<s:Label id="dateLabel" rotation="45"/>
</s:ItemRenderer>
The BasicLayout that your renderer is using (by default) doesn't respect the transformations that occur in the object's width/height/position/etc when you rotate it. It still tries to layout the objects as if they were not rotated.
However, if you use any other layout, like VerticalLayout or HorizontalLayout, the objects new dimensions (after rotation) are used.
I may not be explaining the above properly, but a simple solution to this problem is just to add a layout declaration to your renderer:
<s:layout>
<s:VerticalLayout/>
</s:layout>

Problem with scrolling a sparks list with TileLayout in Flex 4.5

I'm experiencing a very strange behavior with a spark list with TileLayout placed inside a scroller. Basically, I want to have a title area above my list that scrolls away when the user scrolls down the list. To do this I put the title and the list inside a Group and wrapped the group inside a scroller with width and height = 100%. I also set the verticalScrollPolicy to off on the list to make sure everything scrolls together.
The problem is that if the list has the default VerticalLayout everything works fine but if you assign a TileLayout to the same list it only partially renders the items (about 30 items when testing on iPhone 4).
Here's the fully functioning code. Try it like this first, then try removing the <s:layout> part to confirm that it works well with VerticalLayout:
<?xml version="1.0" encoding="utf-8"?>
<s:View
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var myAC:ArrayCollection = new ArrayCollection([
"01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100"
]);
]]>
</fx:Script>
<s:Scroller width="100%" height="100%">
<s:VGroup>
<s:Label text="TITLE" width="100%" height="200" backgroundColor="#333333" color="#EEEEEE"/>
<s:List
id="list"
width="100%"
verticalScrollPolicy="off"
dataProvider="{myAC}" >
<s:layout>
<s:TileLayout
columnWidth="200"
rowHeight="200"
useVirtualLayout="true" />
</s:layout>
</s:List>
</s:VGroup>
</s:Scroller>
</s:View>
What am I doing wrong? Or is this a bug?
You need to calculate and set the height of the list. You can easily calculate it by figuring out the number of rows times the height of a row as below:
private function listHeight():Number {
// In this example you had 3 items to a row on the iPhone4 emulator
var numRows:Number = Math.ceil(myAC.length / 3);
// The height of the row (200) plus the gap between rows (6)
var rowHeight:Number = 200 + 6;
return numRows * rowHeight;
}
This is not a perfect solution, especially if you are targeting multiple screen densities (as the number of items per row will differ from device to device), but it might get you on the right track.
A better solution would be to extend the list component in an ActionScript class and add a header which you can set.

In flex, how to get coordinates when using VBox for stacking up components?

In flex, I am using VBox & HBox to stack components. When I try to get x,y coordinate of a component, I always get 0. If I specify coordinate like mx:VBox x="120", then I get the value.
How can I get the coordinate without explicitly stating it.
You can translate the coordinates relatively to the stage. Check out the code below:
var box:VBox = new VBox;
var child:DisplayObject = new DisplayObject;
box.addChild(child);
child.addEventListener(FlexEvent.UPDATE_COMPLETE, updateCompleteHandler);
...
private function updateCompleteHandler(fe:FlexEvent):void{
// find global coordinates
var globalCoords:Point = child.localToGlobal(new Point(0,0));
// coordsInBox is what you are looking for
var coordsInBox:Point = box.globalToLocal(globalCoords);
}
The point is to use localToGlobal for the child components and then globalToLocal to translate the global coordinates so that they were expressed relatively to the box container.
Please notice, that the coordinates won't be processed until UPDATE_COMPLETE is called by the child object.
The X and Y values of a component are always relative to the component's parent. directionsHelp.x and directionsHelp.y will both return the position relative to the VBox containing them which, unless you explicitly set the values or insert other components around it, will be 0 by default.
The thing to remember about localToGlobal() is that you must call it from the child. If you have an Application and you just call localToGlobal( new Point(child.x, child.y) ), it will try to return the given point within the Application relative to the stage (because you haven't specified what "local" is), which will therefore conduct no transformations, and it will therefore stay equal to (0, 0).
If however you call child.localToGlobal( new Point(child.x, child.y) ), it will return the value of the child's position relative to the stage, transforming the given point by however much the child is offset on the stage.
Here's a quick app to demonstrate:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundColor="#FFFFFF">
<mx:Script>
<![CDATA[
private function updateCoords():void
{
var point:Point = new Point(directionsHelp.x, directionsHelp.y);
point = directionsHelp.localToGlobal(point);
directionsHelp.text = "My stage coordinates are ("+point.x+", "+point.y+")";
}
]]>
</mx:Script>
<mx:VBox>
<mx:Box height="100" width="100" borderStyle="solid" borderColor="#000000"
horizontalAlign="center" verticalAlign="middle">
<mx:Label text="100 x 100" />
</mx:Box>
<mx:VBox>
<mx:Text id="directionsHelp" color="#4FA4CD" fontSize="8" fontWeight="bold"
text="Click the button to display my position on the stage." />
<mx:Button label="Get Position" click="updateCoords()" />
</mx:VBox>
</mx:VBox>
</mx:Application>

sequencing through several images

I'm using flex and have a few images that I need to sequence through. I may add some text under each image. What I'm trying to do is automatically sequence through these images such that a black fading effect appears briefly between each image and the next - sure you've seen that sort of thing before.
My questions are this:
should these images be considered as states of a component or what? I was going to go that way, but corrections are welcome
how to get that black fading effect between the images and the next. Any clues please
Update: found an example of it. This example has more elements, but the idea is the same, an images fades and another images loads.
http://www.lifeblue.com/flash/lb_banner.swf
You can try using a ViewStack and a Timer to iterate through its children. Just add a fade in/fade out effect on them and you'll get what you're looking for.
Something like that :
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundColor="black" creationComplete="creationCompleteHandler">
<mx:Script>
<![CDATA[
private var timer:Timer;
private function creationCompleteHandler():void
{
timer = new Timer( 2000 );
timer.addEventListener( TimerEvent.TIMER, timerHandler );
timer.start();
}
private function timerHandler( e:TimerEvent ):void
{
if( vs.selectedIndex == vs.getChildren().length - 1 )
vs.selectedIndex = 0;
else
vs.selectedIndex++;
}
]]>
</mx:Script>
<mx:Fade id="fadeIn" alphaFrom="0" alphaTo="1" duration="500" effectEnd="timer.start();"/>
<mx:Fade id="fadeOut" alphaFrom="1" alphaTo="0" duration="500" effectEnd="timer.stop();"/>
<mx:ViewStack id="vs">
<mx:Canvas showEffect="fadeIn" hideEffect="fadeOut">
<mx:Image source="picture1.jpg"/>
</mx:Canvas>
<mx:Canvas showEffect="fadeIn" hideEffect="fadeOut">
<mx:Image source="picture2.jpg"/>
</mx:Canvas>
<mx:Canvas showEffect="fadeIn" hideEffect="fadeOut">
<mx:Image source="picture3.jpg"/>
</mx:Canvas>
</mx:ViewStack>
</mx:Application>
Note that this code isn't the most optimized one. The best would be to create a custom component with a black background and 2 Image components.
Set the alpha property of both of
them to 0
Load a picture in the first one, then
play your fade in effect
Begin to load the following picture
in the second Image component
Fade out the first one, fade in the
second, and load the following
picture in the first Image, etc.
This way you only got one Container (instead of one per picture plus the ViewStack) and 2 Images.
It will also be easier to clean them from memory if you need to.

Flex: Moving canvases between several ui elements

I need to animate a lable movement between 2 canvases...
The mxml example of the code is:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="main()" frameRate="1">
<mx:Script>
<![CDATA[
import mx.controls.Label;
public function main():void
{
onEnd();
}
private function onEnd():void
{
(canv1.getChildAt(0) as Label).move(canv2.x, canv2.y);
}
]]>
</mx:Script>
<mx:Panel x="208" y="0" width="190" height="200" layout="absolute" title="Panel2" id="d">
</mx:Panel>
<mx:Panel width="200" height="200" id="c" title="Panel 1">
<mx:Canvas width="135" height="56" id="canv1" label="c1" themeColor="#79B4DA" backgroundColor="#65D565">
<mx:Label text="Move me after event" y="10"/>
</mx:Canvas>
<mx:Canvas width="135" height="79" id="canv2" label="C2" backgroundColor="#E4CACA">
</mx:Canvas>
</mx:Panel>
</mx:Application>
Currently the problem is that the label actually do not leave borders of the first canvas (I see scrollbars instead of it).
I think this is related to globalToLocal conversion problems, but do not understand how to use it.
Also another question is how to animate the movement corretly, because move function performs movement without any visible action.
(The movement happens seamlessly).
I don't think the move function will solve this for you since it will only move the label within it's parent, just as Robusto has commented above.
The way to go about this would perhaps be to see to it that you first detach the label from its parent. Then move it, and add it as a child to the other canvas. Manipulating x,y coordinates will not implicitly change the parent for you. That will always have to be done explicitly, which is good...
So for example, to actually switch parent you would need to do something like this (pseudo code):
/**
* This function only switches the parent.
*/
private function moveLabel(label:Label) {
label.parent.removeChild(label);
canv2.addChild(label);
}
If you want this action to be animated you would first have to detach the label from the canvas and see to it that it is added to the parent of the canvas, in your case, the panel with id "c". Then you can tween it into position and add it to the correct canvas.
TweenLite is a great library for tweening. http://www.greensock.com/tweenlite/
But I guess the main lesson here is that manipulating coordinates will never result in a new parent for the component you are moving.
Update: Here's a complete example of how you could solve this, take into account that the code is not very nice looking, but it contains a simple example of animation.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="main()">
<mx:Script>
<![CDATA[
import mx.controls.Label;
import flash.geom.Point;
import gs.TweenLite;
import gs.easing.Expo;
public function main():void
{
onEnd();
}
private function onEnd():void
{
var label:Label = canv1.getChildAt(0) as Label;
var originX:int = label.localToGlobal(new Point(label.x, label.y)).x;
var originY:int = label.localToGlobal(new Point(label.x, label.y)).y;
label.parent.removeChild(label);
stage.addChild(label);
label.x = originX;
label.y = originY;
TweenLite.to(label, 1.5, {y: "80", ease:Expo.easeOut, onComplete:onFinishTween, onCompleteParams:[label]});
}
private function onFinishTween(label:Label):void {
stage.removeChild(label);
label.x = canv2.globalToLocal(new Point(label.x, label.y)).x;
label.y = canv2.globalToLocal(new Point(label.x, label.y)).y;
canv2.addChild(label);
}
]]>
</mx:Script>
<mx:Canvas width="135" height="56" id="canv1" label="c1" themeColor="#79B4DA" backgroundColor="#65D565" y="-1" x="9">
<mx:Label text="Move me after event" y="10"/>
</mx:Canvas>
<mx:Canvas width="135" height="79" id="canv2" label="C2" backgroundColor="#E4CACA" y="90" x="8">
</mx:Canvas>
</mx:Application>

Resources