I’ve finally had a chance to dive in to Flex 4. One trick I learned is incredibly powerful. CSS is a powerful way to specify settings to something from the outside, but it quickly becomes a pain if you need to push pixels around. I’m going to show you how you can create a skin that reads its properties from a stylesheet. This allows css to do what it does best: set simple properties, and lets you do the pixel pushing in the Skin.

Let’s start with some Flex 3 code we’d like to duplicate. This will create a “modal” overlay that covers the app with a translucent screen. You might use this for a LightBox.

<!-- MyOverlay.mxml -->
<mx:Canvas 
    width="100%" height="100%"
    styleName="overlay"
    >

    <!-- Put a panel or something here -->

</mx:Canvas>

/** styles.css **/
.overlay {
    background-color: #000000;
    background-alpha: 0.7;
}

Our First Skin

We want to create this in Flex 4. The (rough) equivalent to canvas is a Group, but it isn’t skinnable, so we can use a SkinnableContainer instead.

<!-- MyOverlay.mxml -->
<s:SkinnableContainer
    width="100%" height="100%"
    skinName="overlay"
    >

    <!-- Stuff -->

</s:SkinnableContainer>

SkinnableContainer doesn’t have a way to set backgroundColor or backgroundAlpha! The css will not work.

Let’s create the same functionality in a skin instead.

<!-- skins/OverlaySkin.mxml -->
<s:SparkSkin ... >

    <!-- The highest-level base class that can use this-->
    <fx:Metadata>
        [HostComponent("spark.components.SkinnableContainer")]
    </fx:Metadata>
    
    <!-- specify all the states the host component uses -->
    <s:states>
        <s:State name="normal"/>
        <s:State name="disabled"/>
    </s:states>
    
    <!-- Here's our overlay! -->
    <s:Rect left="0" right="0" top="0" bottom="0">
        <s:fill>
            <s:SolidColor color="#000000" alpha="0.7"/>
        </s:fill>
    </s:Rect>
    
    <!-- Put the panel, or whatever is in the component here --> 
    <s:Group id="contentGroup" verticalCenter="0" horizontalCenter="0">
        <s:layout><s:BasicLayout/></s:layout>
    </s:Group>
    
</s:SparkSkin>

That’s not too bad. We have a skin, and now we can apply it to our component by setting theskinClass property. For example:

<!-- MyOverlay.mxml -->
<s:SkinnableContainer
    width="100%" height="100%"
    skinClass="skins.OverlaySkin"
    >

    <!-- content -->

</s:SkinnableContainer>

Or we can set the skinClass in the style.

<!-- MyOverlay.mxml -->
<s:SkinnableContainer
    width="100%" height="100%"
    styleName="overlay" 
    >

    <!-- content -->

</s:SkinnableContainer>

/** styles.css **/
.overlay {
    skinClass: ClassReference("skins.OverlaySkin");
}

Using CSS on the Skin

Now we have a styleName we can apply to any SkinnableContainer and it will make it look like our black, translucent overlay. This works great, so we start using it, but pretty soon we realize that we need to be able to add a background to another container, but we don’t want to create a new skin just to tweak the color and alpha. Here is where it gets fun. We change the Rect tag in our skin to look like this:

<s:Rect left="0" right="0" top="0" bottom="0">
        <s:fill>
                <s:SolidColor color="{getStyle('backgroundColor')}" alpha="{getStyle('backgroundAlpha')}"/>
        </s:fill>
</s:Rect>

Now it will get it’s color and alpha from the style. We can change our overlay style to look like this

/** styles.css **/
.overlay {
    background-color: #000000;
    background-alpha: 0.7;
    skinClass: ClassReference("skins.OverlaySkin");
}

That looks a lot more like the Flex 3 version! We can then add more styles for other kinds of containers that need backgrounds, and our skin is now much more useful than before.

The Final Version

<!-- skins/BackgroundSkin.mxml -->
<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" 
             xmlns:s="library://ns.adobe.com/flex/spark" 
             xmlns:mx="library://ns.adobe.com/flex/halo">
    
    <fx:Metadata>
        [HostComponent("spark.components.SkinnableContainer")]
    </fx:Metadata>
    
    
    <s:states>
        <s:State name="normal"/>
        <s:State name="disabled"/>
    </s:states>
    
    <s:Rect left="0" right="0" top="0" bottom="0">
        <s:fill>
            <s:SolidColor color="{getStyle('backgroundColor')}" alpha="{getStyle('backgroundAlpha')}"/>
        </s:fill>
    </s:Rect>
    
    <!-- Tells the skin where to put the children you have specified -->
    <s:Group id="contentGroup" verticalCenter="0" horizontalCenter="0">
        <s:layout><s:BasicLayout/></s:layout>
    </s:Group>
    
</s:SparkSkin>

/** styles.css **/
.overlay {
    background-color: #000000;
    background-alpha: 0.7;
    skinClass: ClassReference("skins.OverlaySkin");
}

.error {
    background-color: #FF0000;
    background-alpha: 1.0;
}

.success {
    background-color: #00FF00;
    background-alpha: 1.0;
}

Using this technique will allow you to play to the strengths of both CSS and Spark Skins.


Great example but do when I do this in the Eclipse designer, my page is blank. Any idea? If I use the skinClass, all is well.

Thanks for the help, this stopped my frustrations

Luan - you caught me. I never use the designer because, well, too many things make it break :)

Try this instead: color="{getStyle('backgroundColor') || 0x000000}" which should use black if the style is null. 

I haven't tried it, but it might work.


Hi Sean, Great example, helped me understanding the basic concepts. 

One question though, can I define the layout type in host component instead of the skin file? I want to use the background rect in many containers but the layout type differs... any suggestions?


I think, I can define the layout in the host component as well, so that might just work.

Can we define the style names in the component to class selector, ie:

DataGrid 
{
backgroundAlpha: 0.2;
headerStyleName: "GridHeader";
}
.GridHeader
{
color: #594733;
}
So for SkinnableContainer, something like:

SkinnableContainer
{
styleName:rectStyle;
}
.rectStyle
{
background-color:Black;
}


Any layout you specify in the host component overrides the the one you specify for the contentGroup.

Hi Sean, Regarding your above default styling code:

color="{getStyle('backgroundColor') || 0x000000}"


I tried this with backgroundAlpha

color="{getStyle('backgroundAlpha') || 0.5}"

But I was wishing to set it to 0 in my style:

<customContainer backgroundAlpha="0" />

but 0 == false, so the OR fails and the backgroundAlpha is set to 0.5

Workaround is to use a ternary operator in the skin, testing explicitly for undefined instead of anything that's equal to false (0, undefined, null, false, etc):

<s:Group alpha="{getStyle('backgroundAlpha') != undefined ? getStyle('backgroundAlpha') : 0.2}" />

:)


the combination of color="{getStyle('backgroundColor') and CSS "skinClass: ClassReference..." works very well. 
However, there is a bug (maybe). By doing so, if you have a transition for a state that to change a property of hostComponent, it won't work. Such as: Resize heightTo="5" target="{hostComponent}". Only way works is to assign skinClass directly in MXML while skinClass ref is still in CSS.

Anybody knows a work around?