Using Menus in your Flex 4.5 Mobile Application

There is a new Spark ViewMenu component to use for mobile application development. Here’s a list of important facts to know about the ViewMenu:

  • ViewMenu is automatically created when the device Menu button is pressed, but can be programmatically opened as well.
  • VewMenu will close when you click outside of it, but can also be programmatically closed.
  • ViewMenu extends SkinnablePopUpContainer (another new Flex 4.5 component), and is therefore skinnable.
  • ViewMenu has it’s own custom layout class (ViewMenuLayout) that can be modified, or replaced by any custom layout extending LayoutBase.
  • Do not create a ViewMenu component in your code, simply define the ViewMenuItem’s in a tag within your view and they will be used when a ViewMenu instance created (via the Menu key or programmatically). See below for sample.
  • ViewMenuItem(s) are Buttons and dispatch click events, they are also skinnable.
  • Here’s an example of a ViewMenu with icons and labels (project source in fxp here):

    The code snippet from my View class for the above screen shot is shown below:

    ...
    <s:viewMenuItems>
    		<s:ViewMenuItem label="Add" click="itemClickInfo(event)" icon="{addIcon}"  iconPlacement="left" />
    		<s:ViewMenuItem label="Remove" click="itemClickInfo(event)" icon="{delIcon}" iconPlacement="left" />
    		<s:ViewMenuItem label="Find" click="itemClickInfo(event)" icon="{findIcon}" iconPlacement="left"/>
    		<s:ViewMenuItem label="Next" click="itemClickInfo(event)" icon="{nextIcon}" iconPlacement="left"/>
    		<s:ViewMenuItem label="Prev" click="itemClickInfo(event)" icon="{prevIcon}" iconPlacement="left"/>
    </s:viewMenuItems>
    ...
    

    Custom Skinning
    The ViewMenu is skinnable. Actually, the ViewMenu and the ViewMenuItem objects are both skinnable, and if you are doing a custom skin you will most likely need to skin both. I created a sample to try out some custom skinning with it and will include the source code here. To create a custom skin, the best thing to do is create a new skin as a copy of the base skin. For the ViewMenu you can find the default ViewMenuSkin under /Applications/Adobe Flash Builder 4.5/sdks/4.5.1/frameworks/projects/mobiletheme/src/spark (on Mac). They make it very easy in Flash Builder to create a copy of a default MXML skin, you just go to File | New -> MXML Skin, then enter the component to base your skin class on and it will filter the list for the skin class to create a copy of and fill it in with a match if found. Here’s a screenshot of that dialog:

    Interestingly, I noticed this particular default mobile skin class is actually written in MXML, whereas the rest of the mobile skins are written in ActionScript, and the Adobe documentation recommends using ActionScript skins for mobile (for performance). I would be interested to hear why that one in particular is MXML…

    Anyway, for my sample I decided I wanted to change the way the menu used the rectangles and make ellipse buttons. Since the background had the divider lines and I don’t want that for my sample, I just set the background color to white and commented out the divider line so nothing would show up for it, only my ellipse buttons from my custom ViewMenuItem skin shown later.

    ...
     <!-- Divider line 
            <s:Rect left="0" right="0" top="0" height="0">
                <s:stroke>
                    <s:SolidColorStroke id="dividerStroke" weight="1" color="#000000" alpha=".9"/>   
                </s:stroke>
            </s:Rect> -->
    
    <!-- Background -->
    <s:Rect left="0" right="0" top="1" bottom="0" id="background" >
    	<s:fill> 
    		<s:SolidColor color="0xFFFFFF"/>
    	</s:fill> 
     </s:Rect>
    ...
    

    For the ViewMenuItem object you have to create an ActionScript class for the custom skin. For my example I simply pasted the code from the default one into my new file. I found it in the sdk under /Applications/Adobe Flash Builder 4.5/sdks/4.5.1/frameworks/projects/mobiletheme/src/spark/skins/mobile/ViewMenuItemSkin.as. Without creating the custom skin and setting that background to white I would get this:

    Now in my custom ViewMenuItemSkin, I modified the following function to draw an ellipse rather than a rectangle:

    override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
    {
    	if (currentState == "showsCaret" || currentState == "down")
    	{
    	        graphics.beginFill(getStyle("chromeColor"));
    	}
    	else
    	{
    	       colorMatrix.createGradientBox(unscaledWidth, 
                        unscaledHeight, Math.PI / 2, 0, 0);
    		var chromeColor:uint = getStyle("chromeColor");				
    		graphics.beginGradientFill(GradientType.LINEAR,
    					[chromeColor, chromeColor],
    					[0.0, 0.98],
    					[0, 255],
    					colorMatrix);
    	}
    	graphics.drawEllipse(0,0,unscaledWidth,unscaledHeight);  // Changed this from drawRect()
    	graphics.endFill();	
    }
    

    But even though the ellipses showed up when I first ran it, I noticed when I pressed down on the button a rectangle was getting created for that state, so I realized I also needed to modify some fxg assets that I could see were referred to within the skin to create the rectangles for the different states.

     public function ViewMenuItemSkin()
        {
            super();
            
            switch (applicationDPI)
            {
                case DPIClassification.DPI_320:
                {
                    upBorderSkin = spark.skins.mobile320.assets.ViewMenuItem_up;
                    downBorderSkin = spark.skins.mobile320.assets.ViewMenuItem_down;
                    showsCaretBorderSkin = spark.skins.mobile320.assets.ViewMenuItem_showsCaret;
                    
                    layoutGap = 12;
                    layoutPaddingLeft = 12;
                    layoutPaddingRight = 12;
                    layoutPaddingTop = 12;
                    layoutPaddingBottom = 12;
                    layoutBorderSize = 2;   
                    
                    
                    break;
                }
                case DPIClassification.DPI_240:
                {   
                    upBorderSkin = spark.skins.mobile.assets.ViewMenuItem_up;
                    downBorderSkin = spark.skins.mobile.assets.ViewMenuItem_down;
                    showsCaretBorderSkin = spark.skins.mobile.assets.ViewMenuItem_showsCaret;
                    
                    layoutGap = 8;
                    layoutPaddingLeft = 8;
                    layoutPaddingRight = 8;
                    layoutPaddingTop = 8;
                    layoutPaddingBottom = 8;
                    layoutBorderSize = 1;
                    
                    break;
                    
                }
                default:
                {
                    upBorderSkin = spark.skins.mobile.assets.ViewMenuItem_up;
                    downBorderSkin = spark.skins.mobile.assets.ViewMenuItem_down;
                    showsCaretBorderSkin = spark.skins.mobile.assets.ViewMenuItem_showsCaret; 
                    
                    layoutGap = 6;
                    layoutPaddingLeft = 6;
                    layoutPaddingRight = 6;
                    layoutPaddingTop = 6;
                    layoutPaddingBottom = 6;
                    layoutBorderSize = 1;
                }
            }
      

    I located the assets within the SDK under their corresponding DPI folder (/Applications/Adobe Flash Builder 4.5/sdks/4.5.1/frameworks/projects/mobiletheme/src/spark/skins/mobile320/assets for the 320 DPI for instance). I copied the fxg for each state into new fxg files and changed all the Rect tags to Ellipse and then referred to my custom fxg in that above function instead. Here’s an example of the ViewMenuItem_down fxg that I modified:

    Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008"
    	scaleGridLeft="6" scaleGridRight="152" scaleGridTop="6" scaleGridBottom="94">
    	
    	<!-- Transparent Rect to ensure proper scaling -->
    	<Ellipse width="158" height="100" x="0" y="0">
    		<fill>
    			<SolidColor color="#000000" alpha="0"/>
    		</fill>
    	</Ellipse>	
    	
    	<!-- overlay -->	
    	<Ellipse width="158" height="100" x="0" y="0">
    		<fill>
    			<LinearGradient rotation = "90">
    				<GradientEntry color="#000000" ratio="0" alpha="0.25"/>
    				<GradientEntry color="#000000" ratio="1" alpha="0.05"/>
    			</LinearGradient>
    		</fill>
    	</Ellipse>
    
    	<!-- Outermost border -->
    	<Ellipse width="156" height="98" x="1" y="1">
    		<stroke>
    			<SolidColorStroke color="#000000" weight="2" alpha="0.2" joints="miter"/>
    		</stroke>
    	</Ellipse>
    	
    	<!-- Middle Outer Border -->
    	<Ellipse width="152" height="94" x="3" y="3">
    		<stroke>
    			<SolidColorStroke color="#000000" weight="2" alpha="0.1" joints="miter"/>
    		</stroke>
    	</Ellipse>
    </Graphic>
    

    I then specified those fxg files in that above code such as:

    case DPIClassification.DPI_320:
    {
         ...					
         upBorderSkin = ViewMenuItem_upHolly;
         downBorderSkin = ViewMenuItem_downHolly;
         showsCaretBorderSkin = ViewMenuItem_showsCaretHolly;
         ...
    }
    

    once I modified those I no longer got any rectangles when interacting with my menu.

    Applying a Skin Class
    To apply the skin class for the ViewMenu, you need to specify it in CSS since the ViewMenu object is implicit. You could set the skinClass inline on your ViewMenuItem object in your view, or set that via CSS as well. In this case I used CSS rather than having to specify it for each inline. My CSS looks like:

            <fx:Style>
    		@namespace s "library://ns.adobe.com/flex/spark";
    		 s|ViewMenu {
    			skinClass: ClassReference("skins.MyViewMenuSkin");
    		} 
    		s|ViewMenuItem {
    			skinClass: ClassReference("skins.MyVMItemSkin");
    		 	chromeColor: #994499;
    		}  
    	</fx:Style>
    

    With my custom skins applied, here’s my new menu (yes, it is purple ;))

    It’s important to also note the layout used within the custom skin. First I kept the default ViewMenuLayout, which had the following settings in my ViewMenuSkin class:

     <!--- @copy spark.components.SkinnableContainer#contentGroup -->
            <s:Group id="contentGroup" left="0" right="0" top="0" bottom="0" minWidth="400" minHeight="50">
    		    <s:layout>
    				<s:ViewMenuLayout horizontalGap="2" verticalGap="2" id="contentGroupLayout"
    								  requestedMaxColumnCount="3" requestedMaxColumnCount.landscapeGroup="6"/>
    			</s:layout>
    	</s:Group>   
    

    With the above layout, if you rotate the device to have a landscape aspect ratio it will put the icons into columns, such as:

    Then I decided to try out another layout just for kicks. I tried out a SnakeLayout from http://flexlayouts.org and it was so simple to switch out. I added the flexlayouts.swc to my libs folder and changed my code in the custom ViewMenuSkin class to use it:

     <!--- @copy spark.components.SkinnableContainer#contentGroup -->
            <s:Group id="contentGroup" left="0" right="0" top="0" bottom="0" minWidth="400" minHeight="50">
    		    <s:layout>
    				<layouts:SnakeLayout/>
    		    </s:layout>
    	</s:Group>   
    

    It now looks like this:

    Piece of cake!!

    If you want to find out more about the ViewMenu, see the Adobe specification on it here.

    DOWNLOAD the Flash Builder project (fxp) and start working with it, view the source files quickly or grab the Android (.apk) and iPhone/iPad (.ipa) HERE and try them out on your own device!

    Filed in: Flash BuilderFlash Builder 4.5Flex 4.5Flex 4.5/MobileFlex AndroidFlex iOSFlex Mobile Tags:

About the Author ()

Comments (13)

Trackback URL | Comments RSS Feed

  1. Greg says:

    Just wondering how these looks on iPhone or iPad. There is no menu button on iPhone. Also is there a way to do popup menu when one item is selected in the center of device?

  2. Joseph says:

    If I am not mistaken these Menus are used for the Current View… Think of the Skype App on iPhone 4: A Menu popups up from the bottom to ask you if you want to use front facing or back facing camera. I imagine it’s used for cases such as these.

  3. Bob says:

    Is there any way to create a set of ViewMenuItems that would be used for all views of the application, or do we need to repeat all the ViewMenuItems in each view?

  4. pol2095 says:

    Hello, how to know if the viewMenu is open, I want to run a function when the viewMenu is open, thanks.

    • Hi there, you can check the viewMenuOpen property from your main MXML application to see if the menu is currently open or closed, it returns a boolean. If you’re checking it from your View class, you can use mx.core.FlexGlobals.topLevelApplication.viewMenuOpen. Hope that helps! Holly

  5. This appears to be out of context of the menu item here but I was wondering if you figured out how to change the background of the ListView to be transparent. Actually I can get the ListView clear using contentBackgroundAlpha set to 0.1 but what I can’t figure out is how to allow the IconItemRenderer (IIR) to be transparent or at least low enough to show the background image

    Using Alpha on the IIR causes the contents of the IIR to also be transparent.

    Even if it was possible to put an image in the background of the IIR then a semi-transparent one would resolve the issue.

  6. Jeff Boothe says:

    @Gerard:
    Use autoDrawBackground=”false” in your custom ItemRenderer to remove the up, down, and selected background colors from being drawn. Another one of those tricky ones that you’ll scour css styles and never find it. :)

  7. c says:

    what if you’re using the blank application template, just a spark application. how can ViewMenu still be implemented?

  8. Ivan says:

    I’m running into a problem where the ViewMenu won’t appear with a device menu key press while a TextInput has focus. The ‘menuKeyPressed’ event on my View doesn’t get fired at all until I change focus away from the TextInput. What can I do about this?

  9. Sam says:

    hi Holly,
    I am using viewMenuItem in tabbedView.But its not working with tabbedView. Is there any way to display this in any view???
    Please help me regarding this…

  10. Starlover says:

    How about having a lot of menu items?
    Can it be made like the windows start menu?

Leave a Reply