Archive for November, 2011

Skinning Callout and CalloutButton

Skinning Callout and CalloutButton introduce a few challenges. Let’s start with Callout first.

The default Callout skin is a little more complex than the typical mobile skin. A few issues to highlight:

  • The arrow skin part is implemented as a subclass named CalloutArrow
  • The backgroundColor gradient fill also fills the arrow for all callout and arrow position combinations
  • The arrow is not given a position in the skin. Instead, Callout manages the arrow position based on the Callout’s position relative to the owner.
  • The skin implements open/close state transition effects in ActionScript
To make Callout skinning easier, spark.skins.mobile.CalloutSkin exposes protected properties that subclasses can modify in order to tweak the existing visuals. For example, the screenshot and code below show how I’ve squared up the Callout’s corners, removed the background gradient, and specified new default sizes for the arrow.
package skins
{
import mx.core.DPIClassification;

import spark.skins.mobile.CalloutSkin;

public class BoxyCalloutSkin extends CalloutSkin
{
    public function BoxyCalloutSkin()
    {
        super();
        
        // this skin does not implement contentBackgroundAppearance=inset
        contentBackgroundInsetClass = null;
        
        // disable drop shadow
        dropShadowVisible = false;
        
        // disable backgroundColor gradient fill
        useBackgroundGradient = false; 
        
        // square up the corners
        contentCornerRadius = 0; 
        backgroundCornerRadius = 0; 
        
        // backgroundColor frame is not visible
        frameThickness = 0; 
        
        // add border properties (by default this is NaN, no border)
        borderThickness = 1; 
        borderColor = 0x333333; 
        
        // new arrow proportions
        switch (applicationDPI)
        {
            case DPIClassification.DPI_320:
            {
                arrowWidth = 60; 
                arrowHeight = 30; 
                break;
            }
            case DPIClassification.DPI_240:
            {
                arrowWidth = 45; 
                arrowHeight = 15; 
                break;
            }
            default:
            {
                arrowWidth = 30; 
                arrowHeight = 10; 
                break;
            }
        }
    }
    
    override protected function createChildren():void
    {
        // BoxyCalloutArrow subclasses CalloutArrow
        
        // create arrow first, super will skip default arrow creation 
        arrow = new BoxyCalloutArrow();
        arrow.id = "arrow";
        arrow.styleName = this;
        
        // call super
        super.createChildren();
        
        // add arrow above all other children
        addChild(arrow);
    }
}
}
package skins
{
import spark.skins.mobile.supportClasses.CalloutArrow;

public class BoxyCalloutArrow extends CalloutArrow
{
    public function BoxyCalloutArrow()
    {
        super();
        
        borderThickness = 1; 
        borderColor = 0x333333; 
        gap = 0;
        useBackgroundGradient = false;
    }
}
}

CalloutButton is simply a subclass of Button. Because of this, wherever a CalloutButton is used, it typically takes on the skinClass value inherited from Button style rules.

To create a Callout from it’s skin, CalloutButton looks for a dropDown property in the skin. If it’s not present, it creates a default Callout. In order to change any properties of CalloutButton’s generated Callout, you have to change CalloutButton’s skin. This isn’t quite intuitive at first.

To use the same BoxyCalloutSkin from the previous example in a CalloutButton, I’ve defined a subclass of the ActionBar’s BeveledActionButtonSkin.

<?xml version="1.0" encoding="utf-8"?>
<!-- 
CalloutButton is a Button and uses the standard Button skins by default. 
For this example, I explicitly use the beveled ActionBar button skin.
-->
<mobile:BeveledActionButtonSkin xmlns:fx="http://ns.adobe.com/mxml/2009" 
                                xmlns:s="library://ns.adobe.com/flex/spark" 
                                xmlns:mobile="spark.skins.mobile.*">
    <fx:Declarations>
        <!-- 
        An optional dropDown skin part defines the Callout generated after 
        (1) pressing a CalloutButton or (2) calling calloutButton.openDropDown().
        Button skins don't contain a dropDown skin part by default. In this case,
        CalloutButton creates a default Callout.
        -->
        <fx:Component id="dropDown">
            <s:Callout skinClass="skins.BoxyCalloutSkin"
                       backgroundColor="0x999999"
                       contentBackgroundAlpha="0"/>
        </fx:Component>
    </fx:Declarations>
</mobile:BeveledActionButtonSkin>

Finally, here’s the code for the View shown in the screen shot above. It shows usages of both an independent Callout and a CalloutButton, both using the same custom BoxyCalloutSkin. The full project can also be downloaded here.

<?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" title="HomeView">
    <fx:Declarations>
        <!-- boxyCallout uses BoxyCalloutSkin -->
        <s:Callout id="boxyCallout" 
                   width="{Math.floor(this.width * .3)}"
                   height="{Math.floor(this.height * .3)}"
                   skinClass="skins.BoxyCalloutSkin"
                   mouseDownOutside="boxyCallout.close()"
                   backgroundColor="0x999999"
                   contentBackgroundAlpha="0"/>
    </fx:Declarations>
    <s:actionContent>
        <!-- use a standard button to open boxyCallout -->
        <s:Button id="button1" 
                  label="BoxyCalloutSkin" 
                  click="boxyCallout.open(button1)"/>
        <!--
        Use a CalloutButton with a custom CalloutButtonSkin.
        The new skin uses BoxyCalloutSkin on the dynamically created Callout.
        -->
        <s:CalloutButton id="calloutButton"
                         label="CalloutButton" 
                         skinClass="skins.CalloutButtonSkin">
            <!-- 
            Filler to give the Callout some size.
            The callout instanace can be accessed via calloutButton.callout
            once the open event has fired.
            -->
            <s:Group width="{Math.floor(this.width * .3)}"
                     height="{Math.floor(this.height * .3)}"/>
        </s:CalloutButton>
    </s:actionContent>
    <s:actionLayout>
        <s:HorizontalLayout verticalAlign="middle" 
                            gap="5"/>
    </s:actionLayout>
</s:View>

Download: CalloutSkinning.fxp

Share on Facebook

Callout Content Background Styles

In addition to the Spark styles for contentBackgroundColor and contentBackgroundAlpha styles, Callout adds a 3rd style called contentBackgroundAppearance. By default, the value is “inset” and the Callout appears with a slight border around the contentBackgroundColor as well as a drop shadow from the top edge to give an inset appearance. Other values for the style include “flat” and “none”. See the example code and screen shots below.

contentBackgroundAppearance=”inset” (default)

contentBackgroundAppearance=”flat”

contentBackgroundAppearance=”none”

<?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" 
        title="Callout contentBackgroundAppearance">
    <fx:Script>
        <![CDATA[
            import mx.core.UIComponent;
            
            import spark.components.ContentBackgroundAppearance;
            
            protected function openCallout(event:MouseEvent, 
                                           owner:UIComponent, 
                                           value:String=spark.components.ContentBackgroundAppearance.INSET):void
            {
                callout.setStyle('contentBackgroundAppearance', value);
                callout.open(owner);
            }
            
        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:Callout id="callout"
                   width="{Math.floor(this.width * .3)}"
                   height="{Math.floor(this.height * .3)}"
                   mouseDownOutside="callout.close()"/>
    </fx:Declarations>
    <s:actionContent>
        <s:Button id="button1" label="Inset (default)" 
                  click="openCallout(event, button1)"/>
        <s:Button id="button2" label="Flat"
                  click="openCallout(event, button2, spark.components.ContentBackgroundAppearance.FLAT)"/>
        <s:Button id="button3" label="None"
                  click="openCallout(event, button3, spark.components.ContentBackgroundAppearance.NONE)"/>
    </s:actionContent>
    <s:actionLayout>
        <s:HorizontalLayout verticalAlign="middle" gap="5"/>
    </s:actionLayout>
</s:View>
Share on Facebook

Mobile List Paging with Page Indicator Skin

Flex 4.6 introduced major enhancements to List and Scroller including paging and item snapping. For more details, read the spec or documentation.

With list paging, you can now push/pull, swipe or throw horizontally and vertically to scroll through items in a list. The code below shows how to configure a List for horizontal paging and force all items in the List to match the List’s size. When matching the List’s size, this essentially forces only one item to be viewed at a time.

<s:List id="pagedList"
        width="100%" height="100%"
        verticalScrollPolicy="off" horizontalScrollPolicy="on"
        pageScrollingEnabled="true"
        itemRenderer="renderers.BackgroundColorRenderer"
        skinClass="skins.PagedListSkin">
    <s:layout>
        <s:TileLayout orientation="rows" requestedRowCount="1" 
                      columnWidth="{pagedList.width}" rowHeight="{pagedList.height}" 
                      verticalGap="0" horizontalGap="0"/>
    </s:layout>
    <s:ArrayCollection id="colorData">
        <fx:Number>0xFF0000</fx:Number>
        <fx:Number>0xFF9900</fx:Number>
        <fx:Number>0xFFFF00</fx:Number>
        <fx:Number>0x00FF00</fx:Number>
        <fx:Number>0x0000FF</fx:Number>
        <fx:Number>0x9900FF</fx:Number>
    </s:ArrayCollection>
</s:List>

I’ve put together an example PageIndicator.fxp project that shows horizontal, vertical and grid paging. Please note that grid paging isn’t supported by default. The example project uses nested Lists to accomplish the same effect.

In addition to paging, the example project uses a custom skin with page indicators that replace the traditional mobile scrollbars. The PagedListSkin.as skin shows dots for each item in the list and highlights the dot for the currently visible item. For a demo, watch the video above.

Share on Facebook

iOS TitleWindow Skin

Download: mobiletheme_ios_usage.fxp

I’ve made a minor update to my¬†iOS theme to include a TitleWindow skin that’s styled after the iOS alert dialog (UIAlertView). The updated project also includes a Button skin specifically for use with TitleWindow.

The skin itself implements a closeButton skin part. If controlBarContent is specified for the TitleWindow, the closeButton is removed with the expectation that closing the TitleWindow is handled by some other content/action.

<!-- Use built-in close button -->
<s:TitleWindow id="titleWindow1" title="TitleWindow" close="titleWindow1_closeHandler(event)">
    <s:Label text="Hello World" horizontalCenter="0"/>
</s:TitleWindow>
<!-- User-provided OK/Cancel button -->
<s:TitleWindow id="titleWindow2" title="TitleWindow OK/Cancel">
    <s:Label text="Hello World" horizontalCenter="0"/>
    <s:controlBarContent>
        <s:Button label="Cancel" width="50%" click="PopUpManager.removePopUp(titleWindow2)"/>
        <s:Button label="OK" emphasized="true" width="50%" click="PopUpManager.removePopUp(titleWindow2)"/>
    </s:controlBarContent>
</s:TitleWindow>

The mobiletheme_ios_usage.fxp project includes the example shown above as well as the library project “mobiletheme_ios” that you can reuse in your own project. Please note that I’ve made the theme a little more flexible by changing the media queries so that the skins are applied by default regardless of os-platform.

Share on Facebook