Intro to Flash Camo Part 2
Jun 15th in Workflow
by Jesse Freeman
Welcome to the part two of my introduction to the Flash Camouflage Framework. In part 1 we built the foundation for our site, components, labels, css parser, and set up our PropertySheet. In this part we're going to focus on the design side of our site by cleaning up the layout, skins and adding interaction.
Let's get started.
Hello, my name is Jesse Freeman and I am Brooklyn based Flash Developer/Artist. I have been a Flash Developer for over 7 years now and an interactive artist for about 10 years. Some of my clients include VW, Tommy Hilfiger, Heavy.com, MLB, the New York Jets, HBO and many more. I have a Flash Blog called FlashArtofWar.com as well as my portfolio FlashBum.com. I also run a New York City meetup called Flash Developer Happy Hour that I invite anyone in the area to join.
Follow FlashBum on twitter @theflashbum.
Preview
To refresh our memory, this is the final product we're going to create. It is the Bobble Person Site Launcher I use on http://jessefreeman.com.
Step 15: Decals Sheets
I've talked about creating a Decal Sheet System before, but now we're going to take advantage of Flash Camo's own built in system. Let's talk about how Decals work in Camo.
The main feature of the framework is the DecalSheet system (located in the "camo.core.decal" package); made up of the DecalSheetManager, DecalSheet and Decal classes. The DecalSheet concept was inspired by the decals you would get with model airplanes. Each model kit would contain sheets of graphics and on each sheet you could peel off a decal and place it on the model. Camo's version of the DecalSheet allow you to load in external images, cut out decals and skin your application with them.
For this step you will need Photoshop CS 3/4 to open the psd. You can download it from here.
Once you have downloaded the PSD, open it up and take a look.
As you can see we have our simple design. I've gone ahead and done all of the prep work for you so let's just go to "save as" and create a new PNG. Note: if you go to "Save For Web" you will see the slices we'll be using later in the tutorial.
We'll save this png as "default.png" to our "html-template/images/skins" folder in our BobbleHeadApp project. You can also right-click on the following image and use it instead of the PSD. Make sure you save it as "default.png"
Now that we have our skin, we need to create a Global DecalSheet Manager.
Step 16: Create a Global DecalSheet Manager
Our site is going to make extensive use of the DecalSheet system, but first we're going to need to come up with a way to make it accessible throughout our application. Just like we did with the GlobalStyleSheetManager, we're going to create a Singleton for our DecalSheetManager class.
The DecalSheetManager (located in the "camo.core.managers" package) is the manager for the decal system. It handles the xml that defines a list of DecalSheet images and coordinates for cutting out each Decal. It also stores a collection of DecalSheets to make creating complex Decal lookup tables easier to manage. You can configure DecalSheets and Decals on the DecalSheetManager at run time or with XML. Once this data is set, the DecalSheetManager goes out and gets all the images it needs for the DecalSheet images.
Now we're ready to set up our GlobalDecalSheetManager class. You'll need to create a new class called "GlobalDecalSheetManager" and put it into "com.jessefreeman.managers" package.
Here's the code for the class:
package com.jessefreeman.managers
{
import camo.core.managers.DecalSheetManager;
public class GlobalDecalSheetManager
{
public static const INIT:String = "init";
private static var __instance:DecalSheetManager;
/**
*
* @param enforcer
*
*/
public function GlobalDecalSheetManager(enforcer:SingletonEnforcer)
{
if (enforcer == null)
{
throw new Error("Error: Instantiation failed: Use GlobalDecalSheetManager.instance instead.");
}
}
/**
*
* @return
*
*/
public static function get instance():DecalSheetManager
{
if (GlobalDecalSheetManager.__instance == null)
{
GlobalDecalSheetManager.__instance = new DecalSheetManager();
}
return GlobalDecalSheetManager.__instance;
}
}
}
internal class SingletonEnforcer{}
This should look familiar to you by now, so there isn't much to go over. Let's talk about how to load in DecalSheet XML and parse it.
Step 17: Defining Decals
We're going to modify the DecalSheet xml in our htm-template folder. In order to create our decals we'll need to know the x,y,width and height of each decal we want to use. Here is a quick mockup of our PSD with the coordinates.
Now that we have all the values we need, replace the contents of our decahsheet.xml in the html-template/xml folder with the following:
<?xml version="1.0" encoding="UTF-8"?>
<decalsheet>
<sheets baseURL="images/skins">
<sheet name="default" src="/default.png" preload="true" w="570" h="359"/>
</sheets>
<decals>
<decal name="head" sheet="default" x="433" y="181" w="136" h="138"/>
<decal name="body" sheet="default" x="255" y="139" w="182" h="139"/>
<decal name="leftArm" sheet="default" x="208" y="278" w="194" h="76"/>
<decal name="leftFoot" sheet="default" x="4" y="172" w="125" h="78"/>
<decal name="leftLeg" sheet="default" x="128" y="172" w="122" h="78"/>
<decal name="rightArm" sheet="default" x="251" y="17" w="182" h="122"/>
<decal name="rightLeg" sheet="default" x="3" y="6" w="216" h="168"/>
</decals>
</decalsheet>
This xml should be easy to follow, we're defining our DecalSheet source then defining each decal and the sheet they belong to. Notice how the sheet has an attribute to preload, we're going to use DecalSheetManager's loader to prelaod all of our site's images. This will play a more important role when we preload several DecalSheets later on.
Step 18: Loading DecalSheets
The DecalSheetManager uses xml to create DecalSheets and Decals. We'll need to load in some XML first then we can pass it off to the GlobalDecalSheetManager's DecalSheetManager (try saying that 3 times fast).
Let's start by adding the following methods after our "onPropertySheetLoad" method in the Doc Class:
private function loadDecalSheetData():void
{
var urlLoader:URLLoader = new URLLoader();
urlLoader.addEventListener(Event.COMPLETE, onDecalSheetDataLoad, false, 0, true);
urlLoader.load(new URLRequest("xml/decalsheet.xml"));
}
private function onDecalSheetDataLoad(event:Event):void
{
// Remove Event Listener
URLLoader(event.target).removeEventListener(Event.COMPLETE, onDecalSheetDataLoad);
//
var xml:XML = XML(event.target.data);
var decalSheetManager:DecalSheetManager = GlobalDecalSheetManager.instance;
decalSheetManager.addEventListener(LoaderManagerEvent.PRELOAD_NEXT, onPreloadNext, false, 0, true);
decalSheetManager.addEventListener(LoaderManagerEvent.PRELOAD_DONE, onDecalSheetsLoad, false, 0, true);
decalSheetManager.parseXML(xml);
}
protected function onPreloadNext(event:LoaderManagerEvent):void
{
trace("Loading DecalSheet " + event.data.totalPreloaded + " of " + event.data.totalPreloading);
}
private function onDecalSheetsLoad(event:LoaderManagerEvent):void
{
var target:DecalSheetManager = event.target as DecalSheetManager;
target.removeEventListener(LoaderManagerEvent.PRELOAD_DONE, onDecalSheetsLoad);
init();
}
You will also need to import the following:
import camo.core.managers.DecalSheetManager;
import com.jessefreeman.managers.GlobalDecalSheetManager;
import camo.core.events.LoaderManagerEvent;
We just added a lot of code so let's take a second to go over what's going on. First we define the "loadDecalSheetData" method. All we're doing here is creating a new urlLoader and having it load our decalsheet.xml. When the load is completed we call "onDecalSheetDataLoad". This method handles removing the event listener from our URL loader and passes off the XML to the instance of the DecalSheetManager obtained from our GlobalDecalSheetManager Singleton. We also added two listeners; one for the notification of the "next" preload initiation and the second is when the preload is completed. When we're done loading we call "onDecalSheetLoad", remove our listeners and call init.
Now we have to remove the "init" call from our "onPropertySheetLoad" method. Replace that function with the following:
private function onPropertySheetLoad(event:Event):void
{
var target:URLLoader = event.target as URLLoader;
target.removeEventListener(Event.COMPLETE, onPropertySheetLoad);
propSheet.parseCSS("global.properties", target.data);
loadDecalSheetData();
}
If you compile, everything should look the same but we're now loading in our DecalSheet xml along with the DecalSheet image.
Step 19: Decal Display
We'll need a component to display our Decal in. Normally you can take advantage of the BoxModel's background image property to automatically load in any image you would need, but since our body parts need to be centered we'll have to position them by hand.
Let's start by renaming our "SimpleDisplay" in "com.jessefreeman.components" to "DecalDisplay".
Once you've done that, open the class up and replace its code with the following:
package com.jessefreeman.components
{
import camo.core.decal.Decal;
import com.jessefreeman.managers.GlobalDecalSheetManager;
import com.jessefreeman.components.AbstractComponent;
public class DecalDisplay extends AbstractComponent
{
protected var decalInstance:Decal;
public var pixelSnapping:String = "auto";
public var smoothing:Boolean = true;
public function DecalDisplay(id:String)
{
super(this, id);
}
public function set src(decalName:String):void
{
decalInstance = GlobalDecalSheetManager.instance.getDecal(decalName, pixelSnapping, smoothing);
if (!contains(decalInstance))
addChild(decalInstance);
}
}
}
As you can see, we have a few properties that we can use to style this class from a PropertySheet. Let's take a look at the public variables, pixelSnapping and smoothing should look familiar if you have worked with the Bitmap Class before. These just help with making our Decals look smooth when they bobble around.
The main functionality of the class is the setter for src. As you can see it accepts a decal name, then requests the Decal from the GlobalDecalSHeetManager. If the decal exists we add it to the display. Let's talk about Decals a little.
A Decal is a Bitmap that contains a reference to the DecalSheet it was cut out from. Now that you have seen how we get xml data into the DecalSheetManager, let's talk about how to retrieve Decal instances. When you call "getDecal" on the DecalSheetManager or DecalSheet, you need to supply the name of a Decal. You can check if a Decal exists by looking through a DecalSheet's "decalName" Array. Once a Decal is requested, the Decal's name gets passed to the parent DecalSheet through the "sample" method and the Decal's BitmapData is returned inside of a new Decal Instance.
Since all Decals contain a reference to the DecalSheet it was cut out from, this connection allows the Decal to receive updates from its parent DecalSheet. You can change a DecalSheet's BitmapData at any time just as you would any other Bitmap instance. Decals listen for "Event.CHANGE" events from their parent Sheet and once an event is received, it resamples the DecalSheet and updates its own BitmapData. Later we'll reskin an entire application on the fly by changing the BitmapData of our DecalSheet.
Step 20: Using The Decal Display
Now that we have our DecalDisplay we need to add it to our BobbleContainer. In BobbleContainer, add the following method after "get active":
public function set src(value:String):void
{
decalDisplay = new DecalDisplay(id + "DecalDisplay");
decalDisplay.src = value;
addChildAt(decalDisplay, 0);
}
Also we'll need to create the following property:
protected var decalDisplay:DecalDisplay;
So all we're doing is adding a setter for src that we can pass into a new instance of the DecalDisplay. Notice that we're using "addChildAt"? Since we add our label, we want to make sure that the DecalDisplay is at the lowest end of the display list. Now we have our DecalDisplay in place, we just need to set up some property styles.
Step 21: Styling Decal Displays
We have everything we need in place to start displaying our Decals. All we have to do is add some new properties to our PropertySheet. Take a look at this sample style for the DecalDisplay then we'll update the entire property sheet:
#body
{
x: 355;
y: 229;
width: 181;
height: 144;
src: body;
}
#bodyDecalDisplay
{
x: -84;
y: -65;
}
As you may have noticed earlier when we create our DecalDisplays in the BobbleContainer, we pass in the BobbleContainer's id and the string "DecalDisplay" so each instance has a unique id. In the above example we've added src to refer to the name of the Decal we want to use and then have a corresponding style with the x,y offset for the decal. Replace the contents of your "main.properties.css" with the following:
/* CSS file */
.BobblePerson
{
part-ids: head body leftArm rightArm leftLeg leftFoot rightLeg;
}
.Label
{
font: Arial Black;
size: 20px;
embedFonts: true;
color: #ffffff;
anti-alias-type: advanced;
}
#siteLabel
{
text: ANATOMY OF JESSE FREEMAN;
x: 155;
y: 60;
size: 18px;
rotation: 24px;
letter-spacing: -2;
text-height: 30px;
alpha: .5;
}
.BobbleContainer
{
active: true;
rollOverForce: 1;
roll-over-force: 1;
nodding-force: 0;
nodding-angle: 0;
nodding-range: 10;
nodding-hit: .7;
nodding-damp: .985;
}
/* BobbleContainers */
#body
{
x: 355;
y: 229;
src: body;
}
#head
{
x: 449;
y: 250;
src: head;
}
#leftArm
{
x: 381;
y: 313;
src: leftArm;
}
#leftFoot
{
x: 145;
y: 213;
src: leftFoot;
}
#leftLeg
{
x: 263;
y: 235;
src: leftLeg;
}
#rightArm
{
x: 419;
y: 176;
src: rightArm;
}
#rightLeg
{
x: 275;
y: 176;
src: rightLeg;
}
/* BobbleContainer Labels */
#bodyLabel
{
alpha: 1;
rotation: 20;
x: -35;
y: -30;
text-align: center;
auto-size: center;
}
#leftArmLabel
{
text: bFreeDesign;
x: -90;
y: 25;
rotation: -9;
size: 18;
letter-spacing: -3;
}
#leftFootLabel
{
text: eMail;
x: -78;
y: -13;
rotation: 6;
}
#leftLegLabel
{
text: FlashBum;
x: -95;
y: -40;
rotation: 9;
}
#rightArmLabel
{
text: FlashArtOfWar;
x: -80;
y: -75;
rotation: 32;
font-size: 16;
letter-spacing: -1;
}
#rightLegLabel
{
text: iLikeToDream;
x: -157;
y: -83;
rotation: 27;
}
/* BobbleContainer Displays */
#bodyDecalDisplay
{
x: -84;
y: -65;
}
#headDecalDisplay
{
x: -20;
y: -18;
}
#leftArmDecalDisplay
{
x: -162;
y: -11;
}
#leftFootDecalDisplay
{
x: -104;
y: -20;
}
#leftLegDecalDisplay
{
x: -103;
y: -43;
}
#rightArmDecalDisplay
{
x: -136;
y: -104;
}
#rightLegDecalDisplay
{
x: -192;
y: -142;
}
Now you have a clean PropertySheet with our decals correctly connected. If you do a compile you should see all of the body parts in their correct place along with our labels.
Step 22: Focus Event
As we begin to add interactivity to our app we'll need a custom event to track mouse interaction on the body parts. Let's create a new class called "FocusEvent" in "com.jessefreeman.events"
Here's the code:
package com.jessefreeman.events
{
import flash.events.Event;
public class FocusEvent extends Event
{
public static const IN_FOCUS:String = "inFocus";
public static const LOST_FOCUS:String = "lostFocus";
public var text : *;
public var skinName : String;
public function FocusEvent(type:String, text:String = "" , skinName:String = "default", bubbles:Boolean=false, cancelable:Boolean=false)
{
this.text = text;
this.skinName = skinName;
super(type, bubbles, cancelable);
}
}
}
So in this FocusEvent class we have added two new properties to the event's default constructor, "text" and "skinName". Text will be the label of the body part we roll over and skinName will be used later when we start to dynamically reskin our app. We also have two constants representing our states, "inFocus" and "lostFocus".
Let's dispatch this event by going into the BobbleContainer class and add the following line to the end of the "onRollOver" method:
dispatchEvent( new FocusEvent( FocusEvent.IN_FOCUS, rollOverText, "default", true, true ) );
Then add the following to the "onRollOut" method:
dispatchEvent( new FocusEvent( FocusEvent.LOST_FOCUS, rollOverText, "default", true, true ) );
Also make sure you import the FocusEvent:
import com.jessefreeman.events.FocusEvent;
Now we have two events being dispatched letting us know the state of BobbleContainer. Before we're done we need to add a new public variable to the BobbleContainer:
public var rollOverText : String = "";
We pass this variable into the in focus event that gets dispatched. You will need to modify your css with the new rollOverText. Replace these styles found in the /* BobbleContainers */ comment block with the following:
/* BobbleContainers */
#body
{
x: 355;
y: 229;
rollOverLabel:;
src: body;
}
#head
{
x: 449;
y: 250;
src: head;
rollOverText: RESUME;
}
#leftArm
{
x: 381;
y: 313;
src: leftArm;
rollOverText: HIRE;
}
#leftFoot
{
x: 145;
y: 213;
src: leftFoot;
rollOverText: CONTACT;
}
#leftLeg
{
x: 263;
y: 235;
src: leftLeg;
rollOverText: PORTFOLIO;
}
#rightArm
{
x: 419;
y: 176;
src: rightArm;
rollOverText: BLOG;
}
#rightLeg
{
x: 275;
y: 176;
src: rightLeg;
rollOverText: MY ART;
}
Step 23: Updating Body Label
Now we have all of the roll over text in place it's time to display them. You may have noticed that the "body" part doesn't have any rollOverText. We're going to display the body part's text here. Let's go into the BobblePerson and make a few modifications.
At the end of the "createParts" method add the following two event listeners:
display.addEventListener( FocusEvent.IN_FOCUS, onContainerRollOver );
display.addEventListener( FocusEvent.LOST_FOCUS, onContainerRollOut );
Below the "createParts" method add the following:
public function onContainerRollOver(event:FocusEvent):void
{
if (event.target != partDisplay)
{
partDisplay.label.text = event.text;
}
}
public function onContainerRollOut(event:FocusEvent):void
{
partDisplay.label.text = "";
}
We'll need to add a new getter so put the following under get "partIds".
public function get partDisplay():BobbleContainer
{
var index:int = _partIds.indexOf(partDisplayId);
return (index != -1) ? partInstances[index] : null;
}
We're almost there, now let's make sure you have the following property:
public var partDisplayId : String;
Then import the follow class:
import com.jessefreeman.events.FocusEvent;
Before we test this, we just need to update our ".BobblePerson" css style to include the following property:
part-display-id: body;
Now do a compile and roll over the body parts.
Notice how we can change the body part that will display the text at any time by modifying the part-display-id in our css. Let's add in the final set of logic to our site.
Step 24: Adding Click Logic
Now we're going to add some listeners to track when a BodyPart is clicked so we can open a corresponding url in a new window. Go into the BobbleContainer and replace "addEventListeners" and "removeEventListeenrs" with the following code:
protected function addEventListeners(target : IEventDispatcher) : void
{
target.addEventListener( MouseEvent.ROLL_OVER, onRollOver );
target.addEventListener( MouseEvent.ROLL_OUT, onRollOut );
target.addEventListener( MouseEvent.CLICK, onClick );
}
protected function removeEventListeners(target : IEventDispatcher) : void
{
target.removeEventListener( MouseEvent.ROLL_OVER, onRollOver );
target.removeEventListener( MouseEvent.ROLL_OUT, onRollOut );
target.removeEventListener( MouseEvent.CLICK, onClick );
}
We're simply adding a listener for Mouse Click. Add the following method below "onRollOut":
protected function onClick(event : MouseEvent) : void
{
if(url)
navigateToURL( url, "_self" );
}
Let's add a new public variable for our urls:
public var url : URLRequest;
Import the following classes:
import flash.net.URLRequest;
import flash.net.navigateToURL;
All we need to do is modify our PropertySheet to include urls for each part by replacing the "/* BobbleContainers */" comment block with the following:
/* BobbleContainers */
#body
{
x: 355;
y: 229;
rollOverLabel:;
src: body;
}
#head
{
x: 449;
y: 250;
src: head;
rollOverText: RESUME;
url: url("http://www.linkedin.com/in/jessefreeman");
}
#leftArm
{
x: 381;
y: 313;
src: leftArm;
rollOverText: HIRE;
url: url("http://www.bfreedesign.com");
}
#leftFoot
{
x: 145;
y: 213;
src: leftFoot;
rollOverText: CONTACT;
url: url("mailto:jessefreeman@gamil.com");
}
#leftLeg
{
x: 263;
y: 235;
src: leftLeg;
rollOverText: PORTFOLIO;
url: url("http://www.flashbum.com");
}
#rightArm
{
x: 419;
y: 176;
src: rightArm;
rollOverText: BLOG;
url: url("http://www.flashartofwar.com");
}
#rightLeg
{
x: 275;
y: 176;
src: rightLeg;
rollOverText: MY ART;
url: url("http://www.iliketodream.com");
}
Notice how we have added url properties. This is how we tell Camo's CSS parser to convert urls into URL Requests. Now when a user clicks on a body part we simply pass off the url request set by the PropertyStyleSheet to the "navigateToURL" class.
If you would like the Bobble Parts to act as buttons, simply add the following to the ".BobbleContainer" style:
button-mode: true;
use-hand-cursor: true;
mouse-children: false;
Now all of the BobbleContainer classes will automatically act as buttons. You may want to change one more style, the "#body", so it doesn't have mouse interactivity. Simply add the following to the property selector:
button-mode: false;
use-hand-cursor: false;
mouse-children: false;
Now it overrides the Class's default settings and will not have mouse interactivity.
Let's look into how to do some advanced skinning.
Step 25: Advanced Skinning
When you opened up the PSD you may have noticed a few extra "skins" in the Body Skin folder.
I have prepared the following skins for us to use. You can download the zip here and put the skins in your html-template/images/skins folder.
Now you have each skin let's look at how we can load them all in.
Step 26: Loading Multiple DecalSheets
Loading in multiple DecalSheet images is as easy as modifying out xml. Let's replace our decalsheet.xml with the following:
<?xml version="1.0" encoding="UTF-8"?>
<decalsheet>
<sheets baseURL="images/skins">
<sheet name="default" src="/default.png" preload="true" w="570" h="359"/>
<sheet name="iliketodream" src="/iliketodream_skin.png" preload="true" w="570" h="359"/>
<sheet name="flashbum" src="/flashbum_skin.png" preload="true" w="570" h="359"/>
<sheet name="bfreedesign" src="/bfreedesign.png" preload="true" w="570" h="359"/>
<sheet name="artofwar" src="/artofwar_skin.png" preload="true" w="570" h="359"/>
</sheets>
<decals>
<decal name="head" sheet="default" x="433" y="181" w="136" h="138"/>
<decal name="body" sheet="default" x="255" y="139" w="182" h="139"/>
<decal name="leftArm" sheet="default" x="208" y="278" w="194" h="76"/>
<decal name="leftFoot" sheet="default" x="4" y="172" w="125" h="78"/>
<decal name="leftLeg" sheet="default" x="128" y="172" w="122" h="78"/>
<decal name="rightArm" sheet="default" x="251" y="17" w="182" h="122"/>
<decal name="rightLeg" sheet="default" x="3" y="6" w="216" h="168"/>
</decals>
</decalsheet>
All we've done here is added four new DecalSheets to preload. Compile the site and check your connections to make sure they are being loaded.
Step 27: Adding Reskin Logic
Now that we're loading in our skins, let's add some logic to handle dynamically changing the skin bitmapdata. Let's go into our BobblePerson class and add the following two functions:
protected function saveDefaultBitmapData():void
{
defaultSkin = GlobalDecalSheetManager.instance.getSheet("default");
defaultSkinBMD = defaultSkin.bitmapData.clone();
}
protected function switchSkin(skinName:String = "default"):void
{
var newSkinBitmapData:BitmapData;
if (skinName == "default")
{
newSkinBitmapData = defaultSkinBMD.clone();
}
else
{
newSkinBitmapData = GlobalDecalSheetManager.instance.getSheet(skinName).bitmapData.clone();
}
if (newSkinBitmapData)
defaultSkin.bitmapData = newSkinBitmapData;
}
Let's talk about what's going on here. We're going to take advantage of a neat feature of Camo's DecalSheet system by replacing the bitmap data of our default skin with BitmapData from other skins. In order to do this we need to have a reference to the default DecalSheet as well as its BitmapData. We do this in "saveDefaultBitmapData". Next we need to add logic to switch skins. This happens in the "switchSkin" method. As you can see we set up a variable to hold our new BitmapData.
Next we test to see if the skin is "default" or something else. If it's the default skin we get the BitmapData from "defaultSkinBMD". If it's another skin we'll ask the GlobalDecalSheetManager's instance for that sheet. Lastly, we make sure we have a valid BitmapData in our newSkinBitmapData variable then set the BitmapData of our default skin to the newSkinBitmapData.
We need to add the following variables:
protected var defaultSkin:DecalSheet;
protected var defaultSkinBMD:BitmapData;
and make sure you've imported the BitmapData class:
import flash.display.BitmapData;
import camo.core.decal.DecalSheet;
import com.jessefreeman.managers.GlobalDecalSheetManager;
Finally we'll call "saveDefaultBitmapData" in the setter for "partIds". Make sure your setter looks like this:
public function set partIds(value:Array):void
{
_partIds = value;
saveDefaultBitmapData();
createParts();
}
Now let's go into our BobbleContainer class and add the following property:
public var skin:String = "default";
Next you will need to modify the dispatchEvent in the "onRollOver" method to look like this:
dispatchEvent(new FocusEvent(FocusEvent.IN_FOCUS, rollOverText, skin, true, true));
Notice how we're now passing up the skin value through the event? This will tell the body the correct skin to use when there's a roll over.
Let's go back to our BobblePerson and modify the "onContainerRollOver" and "onContainerRollOut" methods to look like this:
public function onContainerRollOver(event:FocusEvent):void
{
if (event.target != partDisplay)
{
partDisplay.label.text = event.text;
switchSkin(event.skinName);
}
}
public function onContainerRollOut(event:FocusEvent):void
{
partDisplay.label.text = "";
switchSkin("default");
}
Now we just have to modify our PropertySheet to add a skin name to our body parts. Replace the /* BobbleContainers */ comment block with the following:
/* BobbleContainers */
#body
{
x: 355;
y: 229;
rollOverLabel:;
src: body;
button-mode: false;
use-hand-cursor: false;
mouse-children: false;
}
#head
{
x: 449;
y: 250;
src: head;
rollOverText: RESUME;
url: url("http://www.linkedin.com/in/jessefreeman");
skin: default;
}
#leftArm
{
x: 381;
y: 313;
src: leftArm;
rollOverText: HIRE;
url: url("http://www.bfreedesign.com");
skin: bfreedesign;
}
#leftFoot
{
x: 145;
y: 213;
src: leftFoot;
rollOverText: CONTACT;
url: url("mailto:jessefreeman@gamil.com");
skin: default;
}
#leftLeg
{
x: 263;
y: 235;
src: leftLeg;
rollOverText: PORTFOLIO;
url: url("http://www.flashbum.com");
skin: flashbum;
}
#rightArm
{
x: 419;
y: 176;
src: rightArm;
rollOverText: BLOG;
url: url("http://www.flashartofwar.com");
skin: artofwar;
}
#rightLeg
{
x: 275;
y: 176;
src: rightLeg;
rollOverText: MY ART;
url: url("http://www.iliketodream.com");
skin: iliketodream;
}
Now if you compile, you will see the skin of our bobble guy change depending on which body part you roll over.
Notice how quickly everything is reskined. This is why Decals are so powerful. You can change the look and feel of your entire application on the fly by changing the BitmapData of your DecalSheets at runtime.
Step 28: Deploy
Now our site is done and we're ready to deploy it to a server. This is fairly easy to do in Flex builder and a step most people over look. Simply go to the Project menu and select Export Release Build.
Once this runs you will have a new folder called bin-release along with your bin-debug. Everything looks the same, but if you check out the side of our BobbleHeadApp.swf you will notice that the release build is about 24k and the debug version is 36k. That is barely 66% of the original size. I know when you are only talking about a few k it's not a major deal, but every k counts. Also, in larger projects this can be a significant saving.
Now you are ready to copy this bin-release folder over to your FTP server and make it live.
Conclusion
As you can see, we have created a simple Flash Site using Flash Camouflage in less then 30 steps. By now you should have a better understanding about the power of incorporating Decals and CSS in your next project. You have also seen how easy it is to build components that work with Flash Camo's three main features. Like all frameworks there is a learning curve and this is only a small sample of what is possible. Also, Flash Camo is still in Beta so there may be small bugs or code cleanup as the project matures.
If you encounter any issues, please leave a comment or submit an issue to the issue list. Remember Flash Camo is completely open source, you should check out the code and see what is going on under the hood. A lot of advanced techniques are being used so it's a great place to learn how a framework is put together. If you use Camo in one of your projects let please me know..
Thanks for following along!
User Comments
( ADD YOURS )samBrown June 15th
good stuff! looking forward to digging into this after work; I can see this being a huge time saver for projects that require numerous graphic updates, etc. -
( )Dario Gutierrez June 15th
I’m currently using this framework, thanks for these two parts and very detailed tut.
( )Jesse Freeman June 15th
Glad to hear you are using it. I have finally frozen the API so the only changes will be bug fixes and no more re-factoring of the API. If you have any questions please feel free to ask them here and I can see what I can do to help you on your own projects.
( )Diego SA June 16th
Looks so simple, but it’s so advanced and professional. I need to try it. Thanks Jesse!
( )Glenn June 27th
One problem with CSS-based text though, is that you can’t use colons or semi-colons in your body-copy, else they’d ruin your entire stylesheet.
( )Jesse Freeman June 28th
You are correct. I usually use this framework with XML for section data. Since this is a simple example I was able to put the copy in the CSS. Another thing I had done in the past was to escape the text. Realistically you shouldn’t put label text or urls in CSS but it’s good to know you can with Flash Camo.
( )