Flex 4 Skin States

While there’s plenty of information about skinning your Flex 4 component, I haven’t found a whole bunch of information on how to get them to talk together. In most situations I would consider this bad practice, however there is often a need for identifying properties set on your component from your skin in order to display certain aspects properly.

NOTE: I am in no way declaring this as the proper way to do things, this is just the best thing I’ve come up with. If you know/have a better way, please let me know!

Quick Overview

This part will be long for something called “quick,” but it’s a good refresher or an introduction to Flex 4 skinning if you’ve never done/seen it.  If you’re already familiar with it, feel free to skip to the Communicating section.

Skin Parts

With components, you define your skin parts. These are nothing more than public references with special metadata above them:

//ButtonBase.as - Spark ButtonBase
[SkinPart(required="false")]
public var labelDisplay:IDisplayText;

Note the ability to make it required or not. If it is required, of course you MUST implement it in the skin.
The skin implementation is nothing more than also creating a public reference.

  • If you’re working in MXML, just define a component of type IDisplayText (Label, Text, etc) with id="labelDisplay".
  • If defining your skin in ActionScript, simply create a public variable of the same type, with the variable name being labelDisplay.

Skin States

Skin states are metadata defined above the class definition. They of course identify which states this component (class) will potentially change to. If you do not define these (can also be defined in the constructor) then UIComponent will throw an error stating the state does not exist, which can be a pain.

Component implementation:

//ButtonBase.as - Spark ButtonBase
[SkinState("up")]
[SkinState("down")]
[SkinState("over")]
[SkinState("disabled")]

//Alternatively, we can define in our constructor
//  (this is an example, NOT in the actual ButtonBase class)
public function ButtonBase():void
{
  super();
  this.states = this.states.concat(
    new State({name: "up"}),
    new State({name: "down"}),
    //etc., etc.
  );
}

And the skin implementation:

<!-- MXML: Within the <s:SparkSkin /> declaration -->
<s:States>
  <s:State name="up">
  <s:State name="down">
</s:States>
//Again, we can alternatively define states in our AS3 constructor
//Extending SparkSkin or MobileSkin
public function ButtonSkin():void
{
  super();
  this.states = this.states.concat(
    new State({name: "up"}),
    new State({name: "down"}),
    //etc., etc.
  );
}

From as best as I can tell, the declarations for skins are merely for code completion, and do  not actually need to outright state them (no pun intended). I’ve often seen (particularly with the Flex 4.5 Hero) the states not defined at all in the skin. This is an assumption, as I’ve yet to find any documentation stating either way.

State Validation

In both your component and your skin implementation, you will need to make decisions and validate your skin state. This really isn’t that difficult, nor is it as hard as  you’d expect.

In the component, you merely override the function which determines your skin state:

//Snippet from ButtonBase.as - Spark ButtonSkin
override protected function getCurrentSkinState():String
{
  if (!enabled)
  return "disabled";

  if (isDown())
  return "down";

  if (hovered || mouseCaptured)
  return "over";

  return "up";
}

In the skin, and particularly MXML, this is handled for you based on the markup notation of your sub-components.

//Snippet from ButtonSkin.mxml - Spark ButtonSkin
<s:GradientEntry color="0x000000"
 color.down="0xFFFFFF"
 alpha="0.01"
 alpha.down="0" />

Note how the dot-notation identifies which state to apply the property to.

In ActionScript, we must work a little harder:

NOTE: This part is theory, I’ve yet to actually implement a skin in AS3 (aside from Mobile, but that’s a different story).

//Theoretical ButtonSkin.as - Spark ButtonSkin
//Since UIComponent::_currentState is private, we must host our own.
protected var _currentState:String = 'normal';

override public function setCurrentState(stateName:String, playTransition:Boolean = true):void
{
  this._currentState = stateName;
  //Do some logic based on our state, set some properties, etc.
  this.invalidateDisplayList();
}

override protected function updateDisplayList(w:Number, h:Number):void
{
 super.updateDisplayList(w, h);

 if (this._currentState === 'over') {
 //Change the display somehow based on properties and styles
 }
}

State Inheritence

Quick note on inheriting states. Yes, states are inherited. No, there’s (currently) no way to exclude them. It’s really not that big of a deal though, just implement two states (or have a ‘default’ that states fall back to) the exact same.

If it irks you that bad, go vote here: https://bugs.adobe.com/jira/browse/SDK-24739

Communicating

So I bored you to death about all that just to get to the point of communicating from SKIN to COMPONENT. It is my belief that there is no good excuse for the other way around (COMPONENT to SKIN). A skin may change on the fly, or per utilization of the component implementation. This means that we can not guarantee that the skin will be of any interface type except one of base assumptions:

  • SparkSkin
  • Skin
  • UIComponent

That doesn’t give us a whole lot to work with in all reality. We can’t assume there’s a property or method designated to flipping a component upside down, or changing layout.

NOTE: One may argue layout should be handled by the LayoutBase implementation, however the Flex SDK itself breaks this theory all over the place. It is often-enough found that you must implement your layout logic in the skin implementation, purely to overwrite what the SDK team has implemented. This is not desired, nor does it conform to their own stated theory. In my attempts to override the layout in a LayoutBase implementation, the skin would often cause my layout to have nil-effect.

So… then what?

Well, simple… Have our skin assume things about our component!

While I agree this isn’t exactly the most elegant solution, it’s the only thing I could come up with. Please correct me if I’m wrong about this!

I needed to change positions of components based on properties set on the host component.
Sure, I could have implemented a state for this, but I would have increased every state permuation times two per every layout change I wanted to make. It just seemed to make more sense to have a property that would drive why an item would change positions, while the state would define general layout.

Here’s an example in more detail:

A mail client has many components to it: A path tree for your folder organization, a header view, and a body view. Sometimes it even has more things like a contact panel, etc.
A state could define whether it’s a compact, or expanded view. In the compact view, you see just the tree, and headers. In the expanded view, you see every component.
However, a simple change to allow the user to define whether the headers were on top or the bottom, would make me have two extra states:

  • compact
  • compactInverted
  • expanded
  • expandedInverted

I didn’t like this, and technically the state in my mind only defined compact or expanded.. The inversion process really could go into 3 or 4 more permutations definining whether the tree view was on the left, or right, etc., etc. As you can see, your states can grow SUPER large, SUPER quick.
Maybe in an MXML implementation it wouldn’t be so bad, as you can use the dot-notation, however I was working in ActionScript, and was quickly annoyed by all the condition checks of every permutation.

By the way, this same concept goes for grabbing particular data from our component, and it goes like this:

override protected function commitProperites():void
{
 super.commitProperites();

 if (null !== this.treeDisplay) {
   this.treeDisplay.dataProvider = MailClient(this.hostComponent).mailTreeData;
 }

 //...
}

override protected function updateDisplayList(w:Number, h:Number):void
{
  super.updateDisplayList(w, h);
  if (this.currentState == "compact" || this.currentState == "compactInverted") {
    //Hide expanded state items...
  } else {
    //Show expanded items...
  }
  if (MailClient.HEADERS_TOP === MailClient(this.hostComponent).headersPosition) {
   //Put the headers on top, and body content on bottom
  } else {
   //Duh, invert them!
  }
}

Note that the assumption here is that our hostComponent property (given to every skin from the component itself) is a specific class-type. If our skin changes, we may or may not even care about these properties, which is why I feel this is the cleanest/safest way to get data communicated between skin and component.

As a last ditch effort to prove my point, here’s how it would look if it were a state-driven implementation:

override protected function updateDisplayList(w:Number, h:Number):void
{
  super.updateDisplayList(w, h);
  if (this.currentState == "compact" || this.currentState == "compactInverted") {
    //Hide expanded state items...
    // ...
  } else if (this.currentState == "expanded" || this.currentState == "expandedInverted") {
    //Show expanded state items...
    // ...
  }

  if (this.currentState == "compactInverted" || this.currentState == "expandedInverted") {
    //Place body content on top
  } else {
    //Place headers on top
  }
}

As you can see, all we did was introduce inversion of the header/body content. The point should be clear – this gets ugly very quick. In the first example, also note that as a property we can expand upon it and add positions very quickly and easily. If we went with states, our code would get bulkier and uglier at an exponential rate.

See Also

http://www.adobe.com/devnet/flex/articles/flex4_skinning.html

http://saturnboy.com/2009/09/flex4-component-states-skin-states/

Advertisements

About killerspaz

I'm a developer that loves to tinker with cutting edge technology. I have recently been playing with the Flash Platform (AS3/Flex), Android (custom roms, replacement apps, scripting), and looking at opportunities in the mobile markets.

Posted on 11.07.2010, in Flex and tagged , , , , . Bookmark the permalink. 1 Comment.

  1. Regarding – “This part is theory, I’ve yet to actually implement a skin in AS3 (aside from Mobile, but that’s a different story).”

    This is completely acceptable in my opinion. Adobe is trying to force mxml on us as the only effective way to apply skins. I still prefer pure actionscript, because it’s a “landlocked standard for architecting logic.” Meaning, the logic paths that the software architect had in mind in the software is understood and preserved across any number of lines of code because a pure actionscript implementation of desired logic. Mixing mxml and actionscript is architecturally disastrous because it breaks these intentional paths of logic. This is what I have found to be true after having architected as3 projects with millions of lines of actionscript. It will be extremely difficult for Adobe to provide us with any kind of reliable multithreading support with mxml in the picture. (probably why in 2011 it’s still not supported, which is unacceptable because in mobile it’s absolutely necessary for any average data intensive application, can anyone hear “it’s frozen!” spouting from the mouths of our clients??).

    In your example above, you could get away with this:

    override public function set currentState(value:String):void {
    super.currentState = value;

    this.invalidateDisplayList();
    }

    This is because setCurrentState() ends up calling that anyways.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: