Flex Mobile Development: skinning the ActionBar component

One of the important pieces of the Flex framework for mobile development is the ActionBar class. This class lets you create an application title bar with three distinct areas. Starting from left to right you can have a navigation area, a title area, and an actions area. Of course you are not forced to use all these areas.

Why would you want to use this component? There are many reasons for using it but probably the most important is that it is an established user interaction design pattern on Android and iOS. This is how you usually add things like an application or section title, navigation buttons, search input texts, or action buttons.

Here are two screen shots of the Facebook application running on Android and iOS that illustrates this idea:

Using the ActionBar component

Using the ActionBar component is quite easy. First, you’ll need to create a Flex mobile project that uses either ViewNavigatorApplication or TabbedViewNavigatorApplication. Then, you can define the components you want to add to the ActionBar either globally (for all the Views) or individually (for each View).

If you want to add them inside of a View here is how you do it:

<?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">

 <s:navigationContent>
     <s:Button label="Back"/>
 </s:navigationContent>
 <s:titleContent>
     <s:TextInput prompt="type to search" width="100%"/>
 </s:titleContent>
 <s:actionContent>
     <s:Button label="+"/>
 </s:actionContent>

</s:View>

This code produces the following ActionBar:

If you want to set it globally then the method differe slightly depending on what Application root tag you chose when creating the project (ViewNavigatorApplication or TabbedViewNavigatorApplication). For ViewNavigatorApplication you’d do something like this:

<?xml version="1.0" encoding="utf-8"?>
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
       xmlns:s="library://ns.adobe.com/flex/spark"
       firstView="views.ActionBarHomeView">

 <s:navigationContent>
      <s:Button label="Back"/>
 </s:navigationContent>
 <s:titleContent>
      <s:TextInput prompt="type to search" width="100%"/>
 </s:titleContent>
 <s:actionContent>
      <s:Button label="+"/>
 </s:actionContent>

</s:ViewNavigatorApplication>

And for TabbedViewNavigatorApplication you’d add one for each tab you have in your application. For example:

<?xml version="1.0" encoding="utf-8"?>
<s:TabbedViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
       xmlns:s="library://ns.adobe.com/flex/spark">

 <s:ViewNavigator label="Tab 1" width="100%" height="100%" firstView="views.Tab1View">
      <s:navigationContent>
           <s:Button label="Back"/>
      </s:navigationContent>
      <s:titleContent>
           <s:TextInput prompt="type to search" width="100%"/>
      </s:titleContent>
      <s:actionContent>
           <s:Button label="+"/>
      </s:actionContent>
 </s:ViewNavigator>
 <s:ViewNavigator label="Tab 2" width="100%" height="100%" firstView="views.Tab2View">

 </s:ViewNavigator>

</s:TabbedViewNavigatorApplication>

And here is how this code looks like at runtime:

If you set the ActionBar globally and you want to override a section or all sections in a particular View all you have to do is to set that section/sections in the View.

You should also know that you can control the ActionBar visibility and rendering mode. For example in any View in which you want to hide the ActionBar you can set actionBarVisible = false. By default the ActionBar is rendered so it takes up some of the available space for the rest of your application. If you want to have the bar hovering on top of the rest of your application you can set the overlayControls to true in your View.

Understanding how the ActionBar component works

Let’s see how everything is put together. Each part of the ActionBar (navigation, title, and action content) is actually a Spark Group object. If you open the ActionBar class file you’ll find three properties named: actionGroup, navigationGroup, and titleGroup. These Groups are populated by calling the setter methods: actionContent(), titleContent(), and navigationContent(). These methods are called by the View class to push the components you set for each section and the argument is an Array (you can set a Button or a number of UI components for each section).

How about the layout used by these three groups? Well you can set a different layout than the default by calling the actionLayout, navigationLayout, and titleLayout setters.

The ActionBar component is a Spark component and this means that the task for laying out the various UI components that you can add to each section is actually implemented by a skin class. And of course the look and feel is implemented by the same skin class. The default skin is spark.skins.mobile.ActionBarSkin. This class extends the MobileSkin class, which in turns extends the UIComponent.

The interesting parts in the skin class are these three methods: createChildren(), measure(), and layoutContents().

If you take a look at the createChildren() method you will see that here is where the three groups (navigationGroup, actionGroup, and titleGroup) used for each section are actually created and the default layout is set. Also you can see that the default layout used by each group is HorizontalLayout. Here is a snippet of the code that creates the navigationGroup:

navigationGroup = new Group();
var hLayout:HorizontalLayout = new HorizontalLayout();
hLayout.horizontalAlign = HorizontalAlign.LEFT;
hLayout.verticalAlign = VerticalAlign.MIDDLE;
hLayout.gap = 0;
hLayout.paddingLeft = hLayout.paddingTop = hLayout.paddingRight
        = hLayout.paddingBottom = 0;
navigationGroup.layout = hLayout;
navigationGroup.id = "navigationGroup";

The measure() method is executed executed after the createChildren() method or when the invalidateSize() method is executed. As the name implies, this method measures the width and the height of the ActionBar taking into consideration the padding (left, right, top, and bottom) and each group width and height (navigationGroup, titleGroup, and actionGroup). It is important to note that the height of the ActionBar is determined by the highest element you have, if this height is bigger than the default height. In other words if you add a Button and set the height of the button to 200, then the ActionBar height will be 200 pixels.

And finally the layoutContent() method is responsible for positioning and setting the size of each Group. This method positions each group starting from left to right with the navigationGroup, titleGroup, and actionGroup. If the total width of the navigationGroup and actionGroup is equal to or more than the total available width then the titleGroup is not displayed (visible = false).

Here is a graphical representation of how these different parts are related:

Skinning the ActionBar component

Let’s talk about styling the ActionBar component. Out of the box you can quite easily change the appearance using the CSS properties. For example you can use CSS to transform the default look and feel  of the ActionBar presented in a screen shot at the beginning of this article to something like this with a little bit of CSS:

And the CSS code that does this is this (please note that you have to include the CSS code or file in the main application file and not in the Views):

<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
s|ActionBar {
      chromeColor: #484877;
      defaultButtonAppearance: beveled;
}
</fx:Style>

The second CSS property in the code above (defaultButtonAppearance) is responsible for rendering the buttons in iOS style. If you inspect the ActionBar class you will find all the important CSS properties that you can play with.

If you want to go beyond background and font colors, sizes, and so forth you have to go down the road of creating a custom skin. And the easiest way to do this is to extend the ActionBarSkin class.

Here are the important parts of the ActionBarSkin class from a skinning perspective:

  • border and borderClass properties. Border represents the first child of the ActionBarSkin (take a look at the createChildren() method).BorderClass is a reference to an FXG file that is set in the class constructor depending on the applicationDPI value. The borderClass is used for creating the borders of the ActionBar and the shadow at the bottom. Here is a screen shot of what this looks like (I placed a pinkish background behind the graphics to provide some contrast for the highlights you can notice on the left/right side):

  • drawBackground() method. This method is responsible for drawing the background. In the previous screen shots this is where the black or dark blue color is coming from. If you take a look at this method you will notice that it uses CSS properties (default or set by the programmer) to draw a gradient.

I think that in the most of cases, when you want to skin an ActionBar component you’ll likely modify the drawBackground() method and create a different borderClass FXG file.

Let me show you an example of a custom skinning. There are three steps involved in this.

First, I created a new FXG file to be used for the background. To do this I opened up the FXG file used by default by the ActionBarSkin in Adobe Illustrator (you can find the path for this FXG in the ActionBarSkin constructor). And I added three green rectangles (some with a solid color, others with a transparent gradient). Here is the result in Illustrator:

Then, I exported the FXG file as an FXG. And I opened this file in a text editor to remove some of the tags and attributes introduced by Illustrator. Finally, I add the FXG file to the Flex mobile project.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008"
  3.     scaleGridLeft="2" scaleGridTop="3" scaleGridRight="88" scaleGridBottom="61">
  4.    
  5.     <Rect y="2" width="89.8335" height="60">
  6.         <fill>
  7.           <SolidColor color="#16938D"/>
  8.         </fill>
  9.       </Rect>
  10.       <Rect x="0.0830078" y="35" width="89.8335" height="26.6665" >
  11.         <fill>
  12.           <LinearGradient x="44.9165" y="0" scaleX="26.6665" rotation="90">
  13.             <GradientEntry ratio="0" color="#FFFFFF" alpha="0"/>
  14.             <GradientEntry ratio="0.160132" color="#E2E2E2" alpha="0.126504"/>
  15.             <GradientEntry ratio="0.400165" color="#A9A9A9" alpha="0.31613"/>
  16.             <GradientEntry ratio="1" alpha="0.79"/>
  17.           </LinearGradient>
  18.         </fill>
  19.       </Rect>
  20.       <Rect y="1.89014" width="89.8335" height="21.3774" >
  21.         <fill>
  22.           <LinearGradient x="44.9165" y="21.3774" scaleX="21.3774" rotation="270">
  23.             <GradientEntry ratio="0" color="#FFFFFF" alpha="0"/>
  24.             <GradientEntry ratio="1" color="#FFFFFF"/>
  25.           </LinearGradient>
  26.         </fill>
  27.       </Rect>
  28.       <Rect width="90" height="2">
  29.         <fill>
  30.           <SolidColor alpha="0.75"/>
  31.         </fill>
  32.       </Rect>
  33.       <Rect y="62" width="90" height="2">
  34.         <fill>
  35.           <SolidColor alpha="0.85"/>
  36.         </fill>
  37.       </Rect>
  38.       <Rect y="64" width="90" height="1">
  39.         <fill>
  40.           <SolidColor alpha="0.35"/>
  41.         </fill>
  42.       </Rect>
  43.       <Rect y="65" width="90" height="1">
  44.         <fill>
  45.           <SolidColor alpha="0.25"/>
  46.         </fill>
  47.       </Rect>
  48.       <Rect y="66" width="90" height="1">
  49.         <fill>
  50.           <SolidColor alpha="0.2"/>
  51.         </fill>
  52.       </Rect>
  53.       <Rect y="67" width="90" height="1">
  54.         <fill>
  55.           <SolidColor alpha="0.15"/>
  56.         </fill>
  57.       </Rect>
  58.       <Rect y="68" width="90" height="1">
  59.         <fill>
  60.           <SolidColor alpha="0.1"/>
  61.         </fill>
  62.       </Rect>
  63.       <Rect y="69" width="90" height="1">
  64.         <fill>
  65.           <SolidColor alpha="0.05"/>
  66.         </fill>
  67.       </Rect>
  68.       <Rect x="1" y="2" alpha="0.25" width="88" height="1" >
  69.         <fill>
  70.           <SolidColor color="#FFFFFF"/>
  71.         </fill>
  72.       </Rect>
  73.       <Rect x="1" y="61" alpha="0.1" width="88" height="1">
  74.         <fill>
  75.           <SolidColor color="#FFFFFF"/>
  76.         </fill>
  77.       </Rect>
  78.       <Rect y="2" width="1" height="60">
  79.         <fill>
  80.           <LinearGradient x="0.5" y="0" scaleX="60" rotation="90">
  81.             <GradientEntry ratio="0" color="#FFFFFF" alpha="0.25"/>
  82.             <GradientEntry ratio="1" color="#FFFFFF" alpha="0.1"/>
  83.           </LinearGradient>
  84.         </fill>
  85.       </Rect>
  86.       <Rect x="89" y="2" width="1" height="60">
  87.         <fill>
  88.           <LinearGradient x="0.5" y="0" scaleX="60" rotation="90">
  89.             <GradientEntry ratio="0" color="#FFFFFF" alpha="0.25"/>
  90.             <GradientEntry ratio="1" color="#FFFFFF" alpha="0.1"/>
  91.           </LinearGradient>
  92.         </fill>
  93.       </Rect>
  94.       <Rect width="90" height="70">
  95.         <fill>
  96.           <SolidColor color="#FFFFFF" alpha="0"/>
  97.         </fill>
  98.       </Rect>
  99. </Graphic>

Second, I created a new ActionScript class that extends the ActionBarkSkin. In the constructor I make sure that the borderClass uses the FXG file I created in the previous step. And I overrode the drawBackground() method because the FXG file does everything I need. Here is the complete listing:

  1. package org.corlan.skins {
  2.         import spark.skins.mobile.ActionBarSkin;
  3.        
  4.         public class CustomActionBarSkin extends ActionBarSkin {
  5.                
  6.                 public function CustomActionBarSkin() {
  7.                         super();
  8.                         //backgroundActionBar is the name of the FXG file
  9.                         borderClass = backgroundActionBar;
  10.                 }
  11.                
  12.                 override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void {
  13.  
  14.                 }
  15.         }
  16. }

Third, I changed the CSS from the main application file in order to set the new skin class as the ActionBar skin class, and the chromeColor used for the buttons. Here is the code:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
  3.    xmlns:s="library://ns.adobe.com/flex/spark"
  4.    firstView="views.ActionBarHomeView">
  5.  
  6. <fx:Style>
  7.         @namespace s "library://ns.adobe.com/flex/spark";
  8.                
  9.         s|ActionBar {
  10.                 chromeColor: #16938D;
  11.                 defaultButtonAppearance: beveled;
  12.                 skinClass: ClassReference("org.corlan.skins.CustomActionBarSkin");
  13.         }
  14.                
  15. </fx:Style>    
  16. </s:ViewNavigatorApplication>

Finally, here is how the skinned ActionBar at runtime:

In conclusion, if you couple these techniques with skinning of the other components you plan to use inside the ActionBar then you can really get something that is quite unique.

You can download the Flex project source code used for this example from here.

11 thoughts on “Flex Mobile Development: skinning the ActionBar component

  1. @Moonkin

    If you look at the FXG file you will see that on the tag (which is the root tag) these attributes are set:

    scaleGridLeft=”2″ scaleGridTop=”3″ scaleGridRight=”88″ scaleGridBottom=”61″

    This is how the FXG is scaled up or down.

    cheers,
    Mihai

  2. Thank you Mihai, great explanation…
    Wish on more that kind tutorials.

    Cheers

  3. Pingback: Flex Mobile Development: skinning the ActionBar component | Solu-pedia.com: a place where problems get solutioned

  4. Great article…

    Is it possible to specify different CSS values for particular views – so one view could show an icon in the navigationGroup area while another shows the beveled (ios) style?

  5. I have imported your fxp and it works nicely. However, if I copy it to my project, the compiler says it can not find the borderClass variable?? Any Ideas?

  6. Thanks for a great tutorial I learned a lot, but I would like to take it a step further. I have completely re-skinned the actionbar to look like the android 4.0 actionbar, but I would like to change the dividers. Do you know how I can do this?

Leave a Reply

Your email address will not be published. Required fields are marked *