Download Flight Effect

Imagine an airplane that takes off the ground, flies across a distance and lands at an arbitrary destination. This post will explain how to do the same in Flex, using the Flight effect, which you can download here.
Before I explain any further, take a look at a running example. I’ve taken the Cairngormstore and added the Flight effect to the point when the user wants to add a product to his shopping cart. Simply click on the “Add to Cart” button to see the effect playing.
store_with_flighteffect.JPG
The key here is that the product doesn’t immediately appear in the shopping cart DataGrid control as the user presses the “Add to Cart” button. Instead, the Flight effect animates the product into the shopping cart control. Download it here.


The implementation of this effect is surly too exaggerated for any real life application and I strongly recommend consulting your User Experience experts before using an effect such as this. However, if applied responsibly, animating visual state changes of your applications instead of just setting and using movement to manage the user’s attention can help with cognition. Consult people who understand user experience design before using effects like this.
What is it?
The flight effect moves a display object either along a straight line or a quadratic bezier curve. While playing the display object is being scaled up and down to mimic a flight object taking off and landing (with using a good bit of imagination)
I’ll explain the usage of this effect with a simpler example. This flight effect is a standard Flex effect, so you can use it in ActionScript, Behaviours, Transitions etc. The following example shows a login panel moving and rotating to the bottom right corner of the screen. It plays in a Canvas container and all we need to set up the effect is to apply a simple Transition tag to the view state change.

<mx:states>
<mx:State name="stateA">
<mx:SetProperty target="{ login1 }" name="x" value="519"/>
<mx:SetProperty target="{ login1 }" name="y" value="323"/>
</mx:State>
</mx:states>
<mx:transitions>
<mx:Transition effect="{ mover }" />
</mx:transitions>
<mx:Sequence id="mover">
<mxeffects:Flight
target="{ login1 }"
duration="2000"
maximumHeight="2"
rotate="true" />
</mx:Sequence>
<sides:Login
id="login1"
change="currentState = ( currentState == 'stateA' ) ? '' : 'stateA';"/>

BTW: Behind the scences, Flight uses a MoveOnQuadCurve effect that I ported from AnimationPackage. You could also use this effect without Flight if you wish.
Flying across Flex container constraints
Since we use a Canvas container in the example above, the Panel component can be moved to arbitrary positions within that Canvas container. However, in some applications, like in the Cairngormstore sample application, we need to move across different containers. How I did this in Cairngormstore and how you can do the same in your applications will be the topic of the next section.
Flight Effect Applied
As pointed out in the end of the last section, the effect by itself isn’t enough if you want display objects to move across Flex containers. This section will show how I achieved this in the Cairngormstore sample application and how you can apply the same concept to your application. You can by the way also use a simple Move effect using the same concept.
But before I start with any implementation, let me please remind you on using this effect responsibly. As with many effects, applied in the wrong way, effects can dramatically reduce the user experience instead if increasing it. Consult a User Experience expert before you apply it to your application.
10000 feet overview
In order to let the flight object (our airplane) cross Flex container boundaries we need professional assistance. A FlightCaptain object can help us with

  • starting up our airplane.
  • finding our origin and destination coordinates.
  • flying the airplane according to a specified route.

If this sounds all a bit strange to you, follow me with a little example.
Let’s fly one box from the black area to the red area.
With clicking the button, the user tells the FlightCaptain to get going:

var captain : FlightCaptain = new FlightCaptain();
captain.airplaneType = Login;
captain.origin = login;
captain.destination = destinationContainer;
captain.play();

We tell him the type of plane we would like him to use (a custom Panel component of type Login), and the start and destination locations.
In the above example the FlightCaptain creates a new airplane of type Login, flies it to the destination location and once landed, he removes it (he would put it back into the hanger)
But in your specific application, you might want the FlightCaptain to move not just copy the display object that we tell him is the airplane.
We can do that with removing the display object specified with the origin property when FlightCaptain is taking off…

captain.addEventListener( TweenEvent.TWEEN_START, acualTakeOffTime );

…and adding it to the display list once the captain lands.

captain.addEventListener( TweenEvent.TWEEN_START, acualTakeOffTime );

Important to note here is that we use the TweenEvent.TWEEN_START event. This event is triggered when the first tween update is performed, meaning we are sure our airplane is in the air (popup is created) and we can safely remove our old display object.
We also have access to the route our FlightCaptain is taking. His route property is a Flight effect. We can i.e. tell him to take a long way round with modifying the xBetween and yBetween properties of route.

captain.flight.yBetween = 1000;

Here’s the full code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:sides="view.sides.*"
layout="horizontal">
<mx:Script>
<![CDATA[
import mx.events.TweenEvent;
import mx.events.EffectEvent;
import mx.core.Container;
import view.sides.Login;
import com.adobe.ac.mxeffects.FlightCaptain;
private var originContainer : Container;
private var destinationContainer : Container;
private function fly() : void
{
destinationContainer = ( login.parent == blackBox ) ? redBox : blackBox;
originContainer = ( destinationContainer == blackBox ) ? redBox : blackBox;
var captain : FlightCaptain = new FlightCaptain();
captain.addEventListener( TweenEvent.TWEEN_START, acualTakeOffTime );
captain.addEventListener( TweenEvent.TWEEN_END, acualLandingTime );
captain.airplaneType = Login;
captain.origin = login;
captain.destination = destinationContainer;
captain.configureFlight();
captain.play();
}
private function acualTakeOffTime( event : TweenEvent ) : void
{
originContainer.removeChild( login );
}
private function acualLandingTime( event : TweenEvent ) : void
{
destinationContainer.addChild( login );
}
]]>
</mx:Script>
<mx:Canvas
id="blackBox"
backgroundColor="black"
width="400" height="400"
>
<sides:Login
id="login" left="8" top="8"
change="fly()" />
</mx:Canvas>
<mx:Canvas
id="redBox"
backgroundColor="red"
width="400" height="400"/>
</mx:Application>


Behind the captain’s curtain

How does the FlightCaptain do his job? This section explains how you he manages to fly across Flex containers.
Let’s first take a look into FlightCaptain’s play method.
At first we get the airplane of the specified type…

airplane = getPlaneFromHanger();

…which simply creates a popup using PopUpManager.

private function getPlaneFromHanger() : IFlexDisplayObject
{
return PopUpManager.createPopUp( DisplayObject( Application.application ), typeOfPlane );
}

Then he finds out about the actual coordinates where he is expected to start and land.

var originCoords : Point = findOriginCoords( origin );
var destinationCoords : Point = findDestinationCoords( destination );

This translates the local coordinates of our specified origin and destination display objects into a global coordinate system. We need to do this because our origin and destination display objects are potentially deeply nested somewhere between various Flex containers. Our FlightCaptain prefers to fly high up in the sky (in popup coordinate space, which understands global coordinates), therefore we need to translate.

private function findOriginCoords( origin : DisplayObject ) : Point
{
var originTopLeft : Point = new Point( 0, 0 );
originTopLeft = origin.localToGlobal( originTopLeft );
return originTopLeft;
}
private function findDestinationCoords( destination : DisplayObject ) : Point
{
var destinationTopLeft : Point = new Point( 0, 0 );
destinationTopLeft = destination.localToGlobal( destinationTopLeft );
return destinationTopLeft;
}

Have fun with it!