May 5th, 10:56pm 3 comments

Reskinning the Android contextual menu (ViewMenu and ViewMenuItems) in Flex / AIR mobile to look like Gingerbread black

In this example we're going to tweak the built in ViewMenu and ViewMenuItem skins coming with Flex Mobile, which were designed with Froyo in mind. Google in the meanwhile however decided to switch the lights off and changed most of the theme to black.

Gingerbread context menu reference screenshots (well it's Cyanogenmod actually)

(download)

For busy people or if you prefer to follow along more closely, here's the FXP with the final outcome:

http://dl.dropbox.com/u/100064/ViewMenuTest.fxp

 

Adding some ViewMenuItems

Create a home view and throw in some items to test with:

<s:viewMenuItems>
     <s:ViewMenuItem label="Copy" click="doNothing(event)"/>
     <s:ViewMenuItem label="Cut" click="doNothing(event)"/>
     <s:ViewMenuItem label="Paste" click="doNothing(event)"/>
     <s:ViewMenuItem label="Undo" click="doNothing(event)"/>
     <s:ViewMenuItem label="Options" click="doNothing(event)"/>
</s:viewMenuItems>

 

Opening the ViewMenu programatically

First things first, in order to be able test in the emulator easily (or trigger it yourself for any reason) here's how you can toggle the ViewMenu from a View:

<s:Button label="Open ViewMenu" click="openViewMenu(event)"/>

protected function openViewMenu(event:MouseEvent):void
{
     this.parentApplication.viewMenuOpen = true;
}

Otherwise you can open it with ⌘N in OS X (not sure about Windows).

 

Finding, copying and using the built in skin files

Making use of Adobe's generous open sourcing of the Flex SDK, let's hunt down the skin files we need to modify for this example, here's where they are in my case:

/Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/projects/mobiletheme/src/spark/skins/mobile/ViewMenuItemSkin.as
/Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/projects/mobiletheme/src/spark/skins/mobile/ViewMenuSkin.mxml

Copy them into `skins` folder in your own project and rename them to CustomViewMenuItemSkin.as and CustomViewMenuSkin.mxml respectively, updating the packages inside too.

We also need to grab the FXG files for the ViewMenuItem, for those you'll need to copy 2 sets for the different device DPIs. Copy these 3 files:

ViewMenuItem_down.fxg
ViewMenuItem_showsCaret.fxg
ViewMenuItem_up.fxg

...from these 2 folders:

/Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/projects/mobiletheme/src/spark/skins/mobile/assets
/Applications/Adobe Flash Builder 4.5/sdks/4.5.0/frameworks/projects/mobiletheme/src/spark/skins/mobile320/assets

...to `assets` and `assets320` folders in your project respectively.

You'll need to go through the references in the CustomViewMenuItemSkin.as constructor and update the references of the component states to these local FXGs:

upBorderSkin = assets320.ViewMenuItem_up;
downBorderSkin = assets320.ViewMenuItem_down;
showsCaretBorderSkin = assets320.ViewMenuItem_showsCaret;

and

upBorderSkin = assets.ViewMenuItem_up;
downBorderSkin = assets.ViewMenuItem_down;
showsCaretBorderSkin = assets.ViewMenuItem_showsCaret;

 

Applying the custom skins in CSS and setting the text to white

Create a CSS file and set up the custom skins:

s|ViewMenuItem
{
     color:#ffffff;
     skinClass: ClassReference("skins.CustomViewMenuItemSkin");
}

s|ViewMenu
{
    skinClass: ClassReference("skins.CustomViewMenuSkin");
}

Apply the CSS in the main application MXML:

<fx:Style source="css/global.css"/>

 

Modifying the MXML based ViewMenu skin

This is pretty much standard Flex 4 style Spark theming, but what we can do here is to remove the bottom stroke to make it look more like the Android native menu, in:

<s:Group id="contentGroup" left="0" right="0" top="3" bottom="2" minWidth="0" minHeight="0">

change `bottom` to 0 and in `updateDisplayList` override delete the line:

contentGroup.bottom = separatorWeight;

As far as I could test, you can also safely get rid of the background completely, we can set up the right colour purely in the ViewMenuItems later:

<!-- Background -->
        <s:Rect left="0" right="0" top="1" bottom="0" id="background">
            <s:fill>
                <s:SolidColor color="#000000" alpha=".5"/>
            </s:fill>
        </s:Rect>

 

Modifying ActionScript and FXG based ViewMenuItem skins

First let's tweak the FXGs and try to put as much graphics in them as possible to make use of the fact that they are precompiled! In `ViewMenuItem_up.fxg` let's set up the only very slightly transparent black background:

<SolidColor color="#000000" alpha=".9"/>

In `ViewMenuItem_down.fxg` we can remove the transparent rectangle, it's not needed as the `overlay` is the same size:

<!-- Transparent Rect to ensure proper scaling -->
     <Rect width="158" height="100" x="0" y="0">
          <fill>
               <SolidColor color="#000000" alpha="0"/>
          </fill>
     </Rect>

change the `overlay` gradient colours to the Gingerbread orange:

<LinearGradient rotation = "90">
                    <GradientEntry color="#ff9000" ratio="0" alpha="1"/>
                    <GradientEntry color="#ff6600" ratio="1" alpha="1"/>
               </LinearGradient>

Lastly, in `ViewMenuItem_showsCaret.fxg` replace the transparent rectangle with the slightly darker orange:

<!-- overlay -->
     <Rect width="159" height="100" x="0" y="0">
          <fill>
               <LinearGradient rotation = "90">
                    <GradientEntry color="#ffc700" ratio="0" alpha="1"/>
                    <GradientEntry color="#ffaa00" ratio="1" alpha="1"/>
               </LinearGradient>
          </fill>
     </Rect>

Now in `CustomViewMenuItemSkin` we can remove the dynamically rendered background since we are taking care of it in the FXG files:

override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
        // omit background rendering in code and use FXG for background instead
}

Also, let's hide text shadow as the Android context menu is not doing any of that and it's a performance sink having to render complex font vectors anyway:

override protected function createChildren():void
{
          super.createChildren();
 
         // hide the labelDisplayShadow text
          labelDisplayShadow.visible = false;
}

The best would be to remove it entirely, but `ButtonSkin` which it extends is referencing it. You can customise it as an exercise if you feel pedantic :)

Now to make the text visible for `down` and `showsCaret` states, change the colour on state changes instead:

override public function set currentState(value:String):void
{
          super.currentState = value;
 
         switch(value)
          {
               case "up":
                    labelDisplay.textColor = 0xffffff;
                    break;
               case "down":
               case "showsCaret":
                    labelDisplay.textColor = 0x000000;
                    break;
          }
}

If you're still reading, pat yourself on the back, it's been an epic post and a lot to take in :)

As a little bonus I've added some icons to the ViewMenuItems to make them look nicer, take a look if you haven't downloaded the FXP yet!

Posted

Comments (3)

May 05, 2011
jasonsj_adobe said...
Great post. Comments: Typos FXP->FXG, refs to 2.7 in post and FXP (not public for mobile), alternative for lazy people with CSS styles only?
May 05, 2011
Daniel Demmel said...
Thanks a lot for the comments Jason, haven`t realised I shouldn`t be using AIR 2.7 for this, oops :) I`m working on an other post at the moment, but will see how different would it be to do this using CSS (I`m guessing way simpler, but this was more about doing things the hard way and taking a look at the guts of the skins!)
Jun 03, 2011
Marco Pallotta said...
Excellent work, would be interested to see the CSS version. Definately good to have an in depth look at the code for the skins though.

Leave a comment...