Author Archive: Jason San Jose

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

Using Explicit Height Items with IconItemRenderer

I came across SDK-31840 when building a Facebook app that shows status updates in a list.

By default, IconItemRenderer sizes to fit it’s contents. If you have a very long message (like a Facebook status update), the item renderer measures to fit the height required to display the full message. What this item renderer doesn’t handle quite right is when the a fixed (explicit) height is specified.

In the end, really needed 2 things. I wanted to cap the item renderer height and let the message truncate. I hit 2 problems (1) truncating and adding an ellipsis doesn’t work for multi-line text and (2) the message would overflow out of the item renderer when I specified an explicit height.

How did I fix it? I monkey patched my IconItemRenderer. I could have also just subclassed and replaced measure() and layoutContents(). I could have even just gone with an MXML version that I wrote that had pretty darn good performance using opaqueBackground and cacheAsBitmap set on the item renderer.

Anyhow, if you’re in the same boat or just curious, check out the patch attached to the bug.

Share on Facebook

Adding Rounded Corners and Other Styles to Mobile Lists

Flash Builder FXP (for SDK 4.5.1): StyledIconItemRendererDemo.fxp

Creating mobile-optimized item renderers based on LabelItemRenderer or IconItemRenderer is pretty straightforward. If all you want to change is the appearance and layout, override drawBackground() and layoutContents() respectively.

drawBackground() implements the backgroundColor of each item and separators between items. layoutContents() positions and sizes each part of the skin (labelDisplay, messageDisplay, and iconDisplay).

To achieve the rounded corner look in the screenshot above, I’ve done the following:

  1. Created a custom List skin that adds padding to the VerticalLayout
  2. Created a subclass of IconItemRenderer that adds styles for cornerRadius and a handful of border-related styles. The styles are implemented in drawBackground().
One thing to point out about the custom list skin. I’ve used padding on the layout here instead of constraints to achieve the same look. This lets me do 2 things (1) use 100% width and height on the List in my View and (2) justifies the VScrollBar to the right side of the View.
Share on Facebook

Adapting Flex Mobile Projects Based on Form Factor

A quick follow up on my last post. Here’s two video demos of the app (FormFactorStyles.fxp) showing how to write a single app that targets phones and tablets using the Flex SDK and Flash Builder 4.5.1.

The first video shows how I apply separate phone and tablet skins using the CSS technique seen here. The Application changes behavior based on the existence of each skin part (e.g. the two navigators). To adapt for ViewNavigator navigation (i.e. popView() and pushView()), views instead dispatch NavigateEvents that the application listens to and determines which ViewNavigator to act on. More details on that in a future post.

This second video shows how the application adds in form-factor specific behavior, again based on the presence of skin parts as well as the utility functions in my FormFactorUtil class. In portrait mode, the primary ViewNavigator is hidden automatically. This feature will be coming up in Flex 4.6 with the new SplitViewNavigator component. I’ve added event handlers for swipe gestures to hide and show the ViewNavigator. This video also shows the exact same project but running on an Android phone and tablet.

Keep in mind that I’ve written this single app that can runs on Android phones and tablets, Apple’s iPad, iPhone and iPod as well as the BlackBerry Playbook. For more information on mobile development with the Flex SDK, go to Adobe’s Flex Developer Center.

Share on Facebook

Using CSS for Phone and Tablet UI

In the brief time I spent pitching in on the MAX Companion app this year I learned a neat trick from the Adobe XD team. As you can see from the screenshots above, the launcher view looks mostly similar, but the layout and button skinning is noticeably different. This is an example of adapting an application based on the form-factor. One thing that isn’t obvious right away is that the MAX Companion is a single binary that adapts to phone and tablet form factors at runtime.

One way to do this is to use MXML states. You might have went to Glenn’s MAX session or read the posts from evangelists Michaël Chaize or Andrew Trice. All 3 talk about how to determine form factor based on resolution, then define states and state groups.

Watch: Best Practices for Building Flex Tablet Applications Glenn Ruehle
Read: Adaptive UI: Phone vs. Tablet Michaël Chaize
Read: Flex for Mobile – Device Form Factor Detection Andrew Trice

With the MXML states strategy, you could accomplish the same result by setting skin classes separately for skin and tablet states.

What I learned from the MAX Companion app was that this can also be done in CSS. Remember that Flex 4.5 introduces media queries that let you define styles (and of course skin classes) based on os-platform and application-dpi. However, CSS media queries won’t tell you anything about screen resolution. Therefore, with CSS, there’s no out-of-the box way to detect a phone vs. a tablet. Basically, what the XD team’s solution boiled down to was CSS code that looks like this:

s|Application views|LauncherView s|Button { skinClass: ClassReference("PhoneButtonSkin"); }
s|Application.tablet views|LauncherView s|Button {skinClass: ClassReference("TabletButtonSkin"); }

 

The first rule sets a default Button skin. The second rule sets a skin only when the Application has the “tablet” style name. In the MAX Companion app, on initialize, they compute the form factor based on the screen (Capabilities.screenResolutionX and Capabilities.screenResolutionY) and the DPI (Capabilities.screenDPI). Based on that information, they find the diagonal size of the screen and set application.styleName = “tablet”. It’s a super simple technique that gives you a lot more flexibility with skinning.

I personally find this approach easier to manage than the MXML states approach, especially when the skinning and layout between form factors are different enough that they become hard to manage in a single MXML file. Also, with the MXML technique, each View would require their own state declarations. With this CSS technique, the form factor detection can be centralized at the Application’s styleName.

I’ve attached FormFactorStyles.fxp as a demo of this CSS trick. In my Application’s initialize event handler, I call a utility method FormFactorUtil.initStyleName(this). This function determines the screen’s diagonal size. Anything less than 7 inches gets the “phone” style name, all others get “tablet”. The app also shows some other useful debug information like OS, screenDPI, and the difference between stage dimensions vs. screenResolutionX and screenResolutionY.

The snippet below shows how FormFactorUtil computes the screen diagonal and other debug info.

/**
 *  Updates all screen measurements when the stage is resized.
 */
private function updateSize(event:Event=null):void
{
    var stage:Stage = FlexGlobals.topLevelApplication.stage;

    _diagonal = Math.sqrt(Math.pow(Capabilities.screenResolutionX, 2) + Math.pow(Capabilities.screenResolutionY, 2));
    _diagonal = _diagonal / Capabilities.screenDPI;

    _width = Capabilities.screenResolutionX / Capabilities.screenDPI;
    _height = Capabilities.screenResolutionY / Capabilities.screenDPI;

    _formFactor = (_diagonal <= 7) ? PHONE : TABLET;

    _stageFullScreenResolution = new Rectangle(0, 0, stage.fullScreenWidth, stage.fullScreenHeight);
    _stageResolution = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
    _screenResolution = new Rectangle(0, 0, Capabilities.screenResolutionX, Capabilities.screenResolutionY);
    _screenSize = new Rectangle(0, 0, _width, _height);

    instance.debugData = new ArrayCollection([
        {label: "OS", value: FormFactorUtil.os},
        {label: "screenDPI", value: Capabilities.screenDPI},
        {label: "applicationDPI", value: FlexGlobals.topLevelApplication.applicationDPI},
        {label: "stageResolution (stageWidth, stageHeight)", value: _stageResolution},
        {label: "stageFullScreenResolution (fullScreenWidth, fullScreenHeight)", value: _stageFullScreenResolution},
        {label: "screenResolution (screenResolutionX, screenResolutionY) in pixels", value: _screenResolution},
        {label: "screenSize (screenResoltionX, screenResolutionY) in inches", value: _screenSize},
        {label: "diagonal in inches", value: diagonal.toFixed(1)}
    ]);
}

 

There’s a lot more in this app to talk about. Way too much for one blog post. Check out the screen shots below and stay tuned for more tips developing phone and tablet apps simultaneously as the Flex 4.6 release date gets closer.

Share on Facebook

MAX 2011: Creating Skins and Packaging into Themes with Flex

As promised, here are the files you need to complete my lab at home. The FXP files will work for Flash Builder 4.5 or higher.

Creating Skins and Packaging into Themes with Flex – Lab workbook
Flash Builder projects (FXP) and solution files

 

 

Share on Facebook

Embedding Fonts in Flex Mobile Projects

 

Embedded fonts can be tricky. Embedding fonts in Flex mobile projects is slightly trickier. Another developer on the SDK team forwarded me this stackoverflow question to take a look at. There aren’t a ton of font embedding examples out there, so let’s take a quick look.

There are just a few things you need to know about fonts in mobile:

  • All mobile skins, LabelItemRenderer and IconItemRenderer use StyleableTextField (based on TextField) instead of the Flash Text Engine (FTE)
  • The Spark Label component uses FTE
  • StyleableTextField only supports embedded fonts with embedAsCFF=false
  • You have to embed fonts twice, once with embedAsCFF=false and again as embedAsCFF=true, to use fonts in both types of text components
  • Avoid using embedded fonts with editable text (TextInput and TextArea)
The screenshot above shows usage of embedded fonts both in StyleableTextField (ActionBar and the List LabelItemRenderer) as well as FTE (Label).
Here’s the example code below. Notice that I’ve used a different name when using embedAsCFF=true.

Application.mxml

<?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.HomeView">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
	<fx:Style>
		@namespace s "library://ns.adobe.com/flex/spark";

		/* StyleableTextField, regular */
		@font-face {
			src: url("assets/COMIC.TTF");
			fontFamily: "comic";
			embedAsCFF: false;
		}

		/* StyleableTextField, bold */
		@font-face {
			src: url("assets/COMICBD.TTF");
			fontFamily: "comic";
			fontWeight: bold;
			embedAsCFF: false;
		}

		/* Label, regular */
		@font-face {
			src: url("assets/COMIC.TTF");
			fontFamily: "comicCFF";
			embedAsCFF: true;
		}

		/* Label, bold */
		@font-face {
			src: url("assets/COMICBD.TTF");
			fontFamily: "comicCFF";
			fontWeight: bold;
			embedAsCFF: true;
		}

		s|Label
		{
			fontFamily: "comicCFF";
		}

		s|ActionBar
		{
			fontFamily: "comic";
		}

		s|LabelItemRenderer
		{
			fontFamily: "comic";
		}
	</fx:Style>
	<s:actionContent>
		<s:Button label="Button"/>
	</s:actionContent>
</s:ViewNavigatorApplication>

HomeView.mxml

<?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="StylableTextField embedAsCFF=false">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
	<s:VGroup width="100%" height="100%">
		<s:Label text="Label embedAsCFF=true, Regular"/>
		<s:Label text="Label embedAsCFF=true, Bold" fontWeight="bold"/>
		<s:List width="100%" height="100%">
			<s:ArrayCollection>
				<fx:String>StylableTextField embedAsCFF=false</fx:String>
				<fx:String>Comic Sans Regular</fx:String>
				<!-- ... -->
		</s:List>
	</s:VGroup>
</s:View>

 

 

Share on Facebook