Skinning the SpinnerList for Flex 4.6 Mobile

The SpinnerList component for Flex 4.6 mobile comes with a standard look and feel. When I incorporated this into one of our mobile views, we wanted to personalize it. I started by referencing the following post on the adobe site, Use of a SpinnerList in a mobile application.

Hilite the Currently Selected Item in SpinnerList
One of the desires was to make the currently selected item to appear to be selected. The default behavior of the spinner list is to have the selected item in the center of the component and, by default, is displayed under the selection indicator. We wanted to skin the box around the selected item a light blue to enhance the impression/appearance that it was the selected value. The initially referenced adobe article said to create a custom skin for the SpinnerListContainer.

SpinnerListContainer
The SpinnerListContainer class provides most of the chrome for the SpinnerList and defines the layout. The chrome includes the borders, shadows, and the appearance of the selection indicator.
The default selection indicator box provides a rectangular black line around the middle item in the SpinnerList with a two tone gradient grey shadow within this box. This is illustrated in the image below:

Default Selection Indicator Box

To achieve the blue hilite in the selection indicator box, I overrode the SpinnerListContainerSkin and created a new set of selectionIndicatorClass components to be used. Here is my new class:

package mobile.dri.skins
{
	import mobile.dri.skins.i160.SpinnerListContainerSelectionIndicator;
	import mobile.dri.skins.i240.SpinnerListContainerSelectionIndicator;
	import mobile.dri.skins.i320.SpinnerListContainerSelectionIndicator;
	
	import mx.core.DPIClassification;
	
	import spark.core.SpriteVisualElement;
	import spark.skins.mobile.SpinnerListContainerSkin;
	import spark.skins.mobile.supportClasses.MobileSkin;
	import spark.skins.mobile160.assets.SpinnerListContainerBackground;
	import spark.skins.mobile160.assets.SpinnerListContainerSelectionIndicator;
	import spark.skins.mobile160.assets.SpinnerListContainerShadow;
	import spark.skins.mobile240.assets.SpinnerListContainerBackground;
	import spark.skins.mobile240.assets.SpinnerListContainerSelectionIndicator;
	import spark.skins.mobile240.assets.SpinnerListContainerShadow;
	import spark.skins.mobile320.assets.SpinnerListContainerBackground;
	import spark.skins.mobile320.assets.SpinnerListContainerSelectionIndicator;
	import spark.skins.mobile320.assets.SpinnerListContainerShadow;
	
	public class SpinnerIndicatorContainerSkin extends SpinnerListContainerSkin
	{
		public function SpinnerIndicatorContainerSkin()
		{
			super();
			switch (applicationDPI)
			{
				case DPIClassification.DPI_320:
				{
					borderClass = spark.skins.mobile320.assets.SpinnerListContainerBackground;
					//is of type class,create 320 class SpriteVisualElement	
					selectionIndicatorClass = mobile.dri.skins.i320.SpinnerListContainerSelectionIndicator;
					shadowClass = spark.skins.mobile320.assets.SpinnerListContainerShadow;
					
					cornerRadius = 10;
					borderThickness = 2;
					selectionIndicatorHeight = 80; // was 120
					//selectionIndicatorHeight = 120;
					break;
				}
				case DPIClassification.DPI_240:
				{
					borderClass = spark.skins.mobile240.assets.SpinnerListContainerBackground;
					selectionIndicatorClass = mobile.dri.skins.i240.SpinnerListContainerSelectionIndicator;
					//selectionIndicatorClass = spark.skins.mobile240.assets.SpinnerListContainerSelectionIndicator;
					shadowClass = spark.skins.mobile240.assets.SpinnerListContainerShadow;
					
					cornerRadius = 8;
					borderThickness = 1;
					selectionIndicatorHeight = 60; // was 90
					//selectionIndicatorHeight = 90;
					break;
				}
				default: // default DPI_160
				{
					borderClass = spark.skins.mobile160.assets.SpinnerListContainerBackground;
					selectionIndicatorClass = mobile.dri.skins.i160.SpinnerListContainerSelectionIndicator;
					//selectionIndicatorClass = spark.skins.mobile160.assets.SpinnerListContainerSelectionIndicator;
					shadowClass = spark.skins.mobile160.assets.SpinnerListContainerShadow;
					
					cornerRadius = 5;
					borderThickness = 1;
					selectionIndicatorHeight = 40; // was 60
					//selectionIndicatorHeight = 60;
					break;
				}
			}
		}
	}
}

Each selectionIndicatorClass variable for each application DPI in the class constructor above, was replaced with my own version. The original selectionIndicatorClass variable was assigned to an fxg file. Flash treats *.fxg files as classes. So I copied the fxg files to my own local directory and then made sure to include the corresponding import statement to include these local fxg files into my code.

I replaced the SpinnerListContainerSelectionIndicator.fxg file for each dpi with my own version based on the original class. Note the previous class references via the import statements remain in the code for future reference and comparison. The original SpinnerListContainerSelectionIndicator class for a DPI of 320 was the following(and can be referenced within the sdk at spark.skins.mobile320.assets.SpinnerListContainerSelectionIndicator) :

?xml version="1.0" encoding="utf-8" ?>
<!--

	ADOBE SYSTEMS INCORPORATED
	Copyright 2011 Adobe Systems Incorporated
	All Rights Reserved.

	NOTICE: Adobe permits you to use, modify, and distribute this file
	in accordance with the terms of the license agreement accompanying it.

-->
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008" viewHeight="96" viewWidth="100"
	scaleGridLeft="5" scaleGridTop="12" scaleGridRight="95" scaleGridBottom="83">
	<!-- Transparent rect used to center the bar -->
	<Rect x="0" y="0" width="100" height="8">
		<fill>
			<SolidColor color="#000000" alpha="0"/>
		</fill>
	</Rect>
	<!-- Border --> 
	<Path y="8" winding="nonZero" data="M100 80 0 80 0 0 100 0 100 80ZM2 78 98 78 98 2 2 2 2 78Z">
        <fill>
           	<SolidColor color="#666666"/>
    	</fill>
   	</Path>
   	<!-- Two tone gradient background -->
   	<Rect x="2" y="10" width="96" height="76">
       	<fill>
   			<LinearGradient rotation="90">
               	<GradientEntry ratio="0.0" color="#E6E6E6" alpha="0.2"/>
   				<GradientEntry ratio="0.2" color="#E6E6E6" alpha="0.4"/>
   				<GradientEntry ratio="0.5" color="#E6E6E6" alpha="0.3"/>
   				<GradientEntry ratio="0.5" color="#000000" alpha="0.12"/>
   				<GradientEntry ratio="1.0" color="#000000" alpha="0.2"/>
   			</LinearGradient>
   		</fill>
   	</Rect>
   	<!-- Top highlight -->
   	<Rect x="4" y="10" width="92" height="2">
       	<fill>
           	<SolidColor color="#FFFFFF"/>
        </fill>
   	</Rect>
   	<!-- Bottom highlight -->
   	<Rect x="4" y="84" width="92" height="2">
       	<fill>
           	<SolidColor color="#FFFFFF"/>
		</fill>
   	</Rect>
   	<!-- Shadow -->
    <Rect x="4" y="88" width="92" height="8">
  		<fill>
   			<LinearGradient rotation="90">
   				<GradientEntry ratio="0" color="#000000" alpha="0.15"/>
   				<GradientEntry ratio="1" color="#000000" alpha="0"/>
   			</LinearGradient>
        </fill>
   	</Rect>
</Graphic>

I modified it and placed/referenced it in a new directory location within my project – mobile.dri.skins.i320.The modified code is listed below:

<?xml version="1.0" encoding="utf-8" ?>
<!--

	ADOBE SYSTEMS INCORPORATED
	Copyright 2011 Adobe Systems Incorporated
	All Rights Reserved.

	NOTICE: Adobe permits you to use, modify, and distribute this file
	in accordance with the terms of the license agreement accompanying it.

-->
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008" viewHeight="96" viewWidth="100"
	scaleGridLeft="5" scaleGridTop="12" scaleGridRight="95" scaleGridBottom="83">
	<!-- Transparent rect used to center the bar -->
	<Rect x="0" y="0" width="100" height="8">
		<fill>
			<!-- was black color="#000000" -->
			 <SolidColor color="#000000" alpha="0"/>
		</fill>
	</Rect>
	<!-- Border --> 
	<Path y="8" winding="nonZero" data="M100 80 0 80 0 0 100 0 100 80ZM2 78 98 78 98 2 2 2 2 78Z">
        <fill>
           <!-- was grey color="#666666" made dark blue -->
        	<SolidColor color="#0756a3" />
    	</fill>
   	</Path>
   	<!-- Two tone gradient background -->
   	<Rect x="2" y="10" width="96" height="76">
       	<fill>
   			<LinearGradient rotation="90">
               	<GradientEntry ratio="0.0" color="#7cb5fa" alpha="0.2"/>
				<GradientEntry ratio="0.2" color="#7cb5fa" alpha="0.4"/>
				<GradientEntry ratio="0.5" color="#7cb5fa" alpha="0.3"/>
				<GradientEntry ratio="0.5" color="#3f94f7" alpha="0.12"/>
				<GradientEntry ratio="1.0" color="#3f94f7" alpha="0.2"/>
   			</LinearGradient>
   		</fill>
   	</Rect>
   	<!-- Top highlight -->
   	<Rect x="4" y="10" width="92" height="2">
       	<fill>
           	<SolidColor color="#FFFFFF"/>
        </fill>
   	</Rect>
   	<!-- Bottom highlight -->
   	<Rect x="4" y="84" width="92" height="2">
       	<fill>
           	<SolidColor color="#FFFFFF"/>
		</fill>
   	</Rect>
   	<!-- Shadow -->
    <Rect x="4" y="88" width="92" height="8">
  		<fill>
   			<LinearGradient rotation="90">
   				<GradientEntry ratio="0" color="#000000" alpha="0.15"/>
   				<GradientEntry ratio="1" color="#000000" alpha="0"/>
   			</LinearGradient>
        </fill>
   	</Rect>
</Graphic>

The first thing I modified was the border outline from grey to dark blue. The original setting:

<!-- Border --> 
	<Path y="8" winding="nonZero" data="M100 80 0 80 0 0 100 0 100 80ZM2 78 98 78 98 2 2 2 2 78Z">
        <fill>
           	<SolidColor color="#666666"/>
    	</fill>
   	</Path>

I changed it to dark blue thusly:

<!-- Border --> 
	<Path y="8" winding="nonZero" data="M100 80 0 80 0 0 100 0 100 80ZM2 78 98 78 98 2 2 2 2 78Z">
        <fill>
           <!-- was grey color="#666666" made dark blue -->
        	<SolidColor color="#0756a3" />
    	</fill>
   	</Path>

Then I modified the two toned grey gradient background. Here is the original

<!-- Two tone gradient background -->
   	<Rect x="2" y="10" width="96" height="76">
       	<fill>
   			<LinearGradient rotation="90">
               	<GradientEntry ratio="0.0" color="#E6E6E6" alpha="0.2"/>
   				<GradientEntry ratio="0.2" color="#E6E6E6" alpha="0.4"/>
   				<GradientEntry ratio="0.5" color="#E6E6E6" alpha="0.3"/>
   				<GradientEntry ratio="0.5" color="#000000" alpha="0.12"/>
   				<GradientEntry ratio="1.0" color="#000000" alpha="0.2"/>
   			</LinearGradient>
   		</fill>
   	</Rect>

Here is the modified version with a blue gradient which I am using:

<!-- Two tone gradient background -->
   	<Rect x="2" y="10" width="96" height="76">
       	<fill>
   			<LinearGradient rotation="90">
               	<GradientEntry ratio="0.0" color="#7cb5fa" alpha="0.2"/>
				<GradientEntry ratio="0.2" color="#7cb5fa" alpha="0.4"/>
				<GradientEntry ratio="0.5" color="#7cb5fa" alpha="0.3"/>
				<GradientEntry ratio="0.5" color="#3f94f7" alpha="0.12"/>
				<GradientEntry ratio="1.0" color="#3f94f7" alpha="0.2"/>
   			</LinearGradient>
   		</fill>
   	</Rect>

Here is the result:

Spinner Default highlight

SpinnerListItemRenderer
The next item I checked was the default SpinnerListItemRenderer. You can override the item renderer to customize the appearance or contents of the list items. To view the default behavior via Flashbuilder, I selected the drop down menu from the top title bar:
Navigate > OpenType

Which popped up the following dialog:
Open Type Popup Dialog

I typed in SpinnerListItemRenderer and was then able to view the default class.
The SpinnerListItemRenderer has a drawBackground method:

 /**
     *  @private
     *
     *  @langversion 3.0
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
    {
        // draw a transparent background for hit testing
        graphics.beginFill(0x000000, 0);
        graphics.lineStyle();
        graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
        graphics.endFill();
    }

So I extended the class and overrode the drawBackground method. I modified the beginFill color from black to white which resulted in framing the indicator box with white hilites instead of blending a grey into the background.

 /**
     *  @private
     *
     *  @langversion 3.0
     *  @playerversion AIR 3
     *  @productversion Flex 4.6
     */
    override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
    {
        // draw a transparent background for hit testing
        graphics.beginFill(0xFFFFFF, 0);
        graphics.lineStyle();
        graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
        graphics.endFill();
    }

Before:
Spinner Default highlight

After overriding the drawBackground with white hilites:
Spinner with white highlight

Modify Number of Rows to be Displayed
The other issue was to set the number of rows to be displayed to the user. The default number of rows in a SpinnerList is 5. One of the lists was only 3 items long, and when it was reused it looked weird having an empty row above and below the 3 item list. So my quick and dirty fix was to change the height of the SpinnerListContainer to a constant value which caused the number of rows to be displayed/visible to decrease to 3 using the default font height.

The adobe article referenced at the beginning, indicated that you should set the row count via the SpinnerListSkin. Following this path, did not seem to make a difference because of the size of the SpinnerListContainer in which the SpinnerList resides.

Advertisements

One Response to Skinning the SpinnerList for Flex 4.6 Mobile

  1. Daniel Rener says:

    Thanks for posting this. It was a little verbose, but it saved me a lot of time, so thanks!

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: