by Christian Cantrell
 
Christian Cantrell
Created
27 January 2010
 

Requirements

 
Prerequisite knowledge

This article assumes a familiarity with ActionScript 3.
 

 
User level

All
 
As the Flash Platform continues to proliferate and reach more devices, developers need to adopt techniques for authoring with multiple screen sizes and resolutions in mind. This article discusses several techniques to help Flash developers author content that will render properly on any device, regardless of its screen resolution and pixel density.
 
The techniques explored in this article are somewhat "low-level" in that they show the programmatic creation of vectors and the use of algorithms (albeit simple ones) to dynamically size and position assets. There will always be a need for this level of authoring control for some applications, but there will also be "higher-level" and simpler alternatives in the future.
 
Adobe is currently working on a mobile Flex framework (codenamed "Slider"), which will automatically apply some of what's discussed here, and will make it much easier for you to write applications that adapt to different screens. Until Slider is available, however—and for those whose applications might not fit into the framework model—the tips and tricks discussed in this article will help to jumpstart your multi-screen development.
 

 
Terminology

Before exploring specific techniques for authoring SWF-based applications for multiple screen sizes, it's worth covering some relevant terminology. Although you are probably familiar with the general meaning of these terms, a thorough understanding is necessary to actually put them to use:
 
  • Screen size: Screen size simply refers to the diagonal size of a screen, usually in inches. Although screen size is related to resolution and PPI, you cannot determine a screen's resolution or pixels per inch just from its size.
  • Resolution: Screen or display resolution refers to the number of pixels a screen contains in each dimension. The Motorola Droid has a screen resolution of 480 × 854. The 27" external display I'm using right now has a resolution of 1920 × 1200. Multiplying the number of pixels in both dimensions gives you the total number of pixels a screen is capable of displaying.
  • PPI, DPI, and pixel density: PPI (pixels per inch), DPI (dots per inch), and pixel density all refer to the same concept: the number of pixels (in either dimension) per unit of physical distance. It may be hard to imagine that this is an important property for authoring for multiple screen sizes but, as I explain below, PPI is every bit as important as resolution—and in some cases more important.

 
Application architecture

The goal of a multi-screen application is not necessarily to look identical on every device; rather, it should adapt to any device it's installed on. In other words, multi-screen applications should dynamically adjust to the resolution and the PPI of their host devices, displaying more information on larger screens, removing or shrinking elements on smaller screens, ensuring buttons are physically large enough to tap on, and so on. In order for applications to work across different screens, they must be architected in such a way that they draw and redraw themselves at the proper times and using the proper constraints.
 
 
Stage scale mode and alignment
Before laying out your application, it's important that you set the Stage's scale mode and alignment. This should be done in your Sprite's constructor, just before or after registering for the Stage resize event (more on this below):
 
this.stage.scaleMode = StageScaleMode.NO_SCALE; this.stage.align = StageAlign.TOP_LEFT;
Setting the Stage's scale mode to NO_SCALE indicates that you don't want any kind of automatic scaling or layout of your content to occur, and that you will handle all the layout and scaling yourself. This is what enables applications to dynamically adapt themselves to different screen sizes.
 
Setting the Stage's align property to TOP_LEFT indicates that you want to lay content out relative to the top left-hand corner with the coordinates of 0,0.
 
 
Listen for Stage resize events
The best place to do rendering in a multi-screen application is in a Stage resize event handler. The Stage will dispatch a resize event when the application is initialized and the size of the Stage (the area your application has to work with) is set. In a pure ActionScript application, you will want to listen for a Stage resize event in your main Sprite's constructor, like this:
 
this.stage.addEventListener(Event.RESIZE, doLayout);
After registering for resize events on the Stage, doLayout will get called whenever the Stage is resized. For example:
 
  • When your application first initializes.
  • When the window is resized (when running on the desktop).
  • When a device changes orientation.
By performing your layout in the Stage resize event handler, your application will automatically lay itself out whenever the size of the Stage changes, regardless of why it changes.
 
Note:To determine the size of the Stage, use the stage.stageWidth and stage.stageHeight properties.
 
It is not necessary to set the dimensions of your SWF file using SWF metadata. In fact, doing so may prevent your resize event handler from being called when the application initializes. It's best to set the width and height of your application in the initial window section of your application descriptor file like this:
 
<initialWindow> <width>320</width> <height>480</height> <!-- several other properties... --> </initialWindow>

 
Determining asset sizes

Applications designed to run on devices with different screen sizes and resolutions will often need to determine the size of assets dynamically. In other words, a button that looks and works perfectly on one device might be far too small to read or tap on devices with higher resolutions. Consequently, it's important that developers know how to think in terms of both pixels and inches.
 
 
Thinking in pixels
In order to add a solid-colored background to an application, you need only think in terms of pixels. For example, it doesn't matter how big or small the screen is—the background will always need to match the screen's dimensions in pixels. The code below shows adding a solid-colored background to an application of any size:
 
var bg:Sprite = new Sprite(); bg.x = 0; bg.y = 0; bg.graphics.beginFill(0x006E59); bg.graphics.drawRect(0, 0, this.stage.stageWidth, this.stage.stageHeight); this.addChild(bg);
The stageWidth and stageHeight properties on the Stage object indicate the dimensions in pixels of the content's Stage. This information is all you need to create a background that works with any size application on any size device.
 
 
Thinking in physical units
Sizing assets in pixels works in cases where the assets can be sized relatively (as in the case of a background), but not when assets need to be sized absolutely. In other words, it doesn't matter how big or small a background is as long as it's the size of the entire Stage; however, the size of things like fonts and buttons needs to be controlled more precisely. That's when you have to think in terms of physical units, or PPI.
 
 
Inches
Using PPI to determine an asset's dimensions allows you to control the exact size of an asset regardless of what kind of screen it's being rendered on. For example, to make a button that is always ¾" × ¼" whether it's being rendered on a huge desktop monitor or a small mobile screen, you must use the screen's PPI.
 
Note: Research has shown that a hit target should be no smaller than ¼", or 7mm, in order to be hit consistently and reliably. The only way to make sure your buttons are usable across devices is to think in terms of physical units.
 
The PPI of the current screen can be determined by the Capabilities.screenDPI property. Of course, assets are always ultimately sized in pixels rather than inches, so it's necessary to convert PPI into pixels. I use a simple utility function like this:
 
/** * Convert inches to pixels. */ private function inchesToPixels(inches:Number):uint { return Math.round(Capabilities.screenDPI * inches); }
The code below demonstrates how to create a sprite that will appear as ¾" × ¼" on any device:
 
var button:Sprite = new Sprite(); button.x = 20; button.y = 20; button.graphics.beginFill(0x 003037); button.graphics.drawRect(0, 0, this.inchesToPixels(.75), this.inchesToPixels(.25)); button.graphics.endFill(); this.addChild(button);
 
Metric
So as not to be too American-centric, and because the metric system is superior at smaller scales, here's a version for converting millimeters to pixels:
 
/** * Convert millimeters to pixels. */ private function mmToPixels(mm:Number):uint { return Math.round(Capabilities.screenDPI * (mm / 25.4)); }

 
Dynamic layout

Now that your application is architected in such a way that it can be authored to adapt to multiple screen sizes, and now that you have techniques for determining asset sizes, it's time to start laying out assets.
 
The key to laying out assets capable of adapting to different screen sizes is to know what to hard-code and what to calculate based on properties of the current screen. For example, to create a title bar at the top of your application, you know that you want the x and y coordinates to be 0,0 which means those properties can be hard-coded. In other words, regardless of what kind of device your application is running on, you will always want your title bar positioned in the top-left corner. The following code shows creating a new Sprite to be used as a title bar, and hard-coding its position:
 
var titleBar:Sprite = new Sprite(); titleBar.x = 0; titleBar.y = 0;
Although the position of the title bar won't change from one device to another, its width will. On high-resolution devices, the width needs to be greater.
 
Determining the width of the title bar is as easy as using the stage.stageWidth property, but what about the height? You could hard-code the height in pixels, but the size of it will change pretty dramatically from device to device depending on resolution. In this case, a better approach is to think in terms of physical units which will give your title bar a consistent look across all devices.
 
The following code creates a title bar that demonstrates all of the following concepts:
 
  • Hard-coding an absolute position when appropriate
  • Using stage.stageWidth to dynamically determine the width of the title bar in pixels
  • Setting the height of the title bar in inches in order to ensure a consistent look across devices
var titleBar:Sprite = new Sprite(); titleBar.x = 0; titleBar.y = 0; titleBar.graphics.beginFill(0x003037); titleBar.graphics.drawRect(0, 0, this.stage.stageWidth, this.inchesToPixels(.3)); titleBar.graphics.endFill(); this.addChild(titleBar);
The examples above demonstrate how to dynamically size assets, but what about dynamically positioning them? For example, the position of the title bar is obvious since it always originates from the top-leftcorner, but what about positioning a footer whose x coordinate is always 0 but whose y coordinate is determined by the height of the Stage?
 
The code below shows how to create a footer that will always span the entire width of the application, and always be positioned at the bottom, regardless of the height of the screen:
 
var footer:Sprite = new Sprite(); footer.graphics.beginFill(0x003037); footer.graphics.drawRect(0, 0, this.stage.stageWidth, this.inchesToPixels(.3)); footer.graphics.endFill(); footer.x = 0; footer.y = this.stage.stageHeight - footer.height; this.addChild(footer);

 
Relative positioning

There are three primary ways to lay out assets (two of which I've already covered):
 
  1. Use a hard-coded position as we did with the application's background and the title bar
  2. Calculate a position as we did with the application's footer
  3. Calculate a position based on other assets
Calculating the position of an asset based on another asset is referred to as relative positioning, and it's an extremely important technique for designing multi-screen applications. Going back to the title bar example, we succeeded in creating a title bar that will always be positioned and rendered like you want it to, but what about the title itself?
 
You could always hard-code a y position, which would place it a few pixels down from the top, then calculate an x coordinate based on the width of the Stage and the width of the title, but that won't always yield the best results—for two reasons:
 
  1. Since the height of the title bar will change depending on the screen's PPI, the text won't always be centered vertically.
  2. If you decided to move your title bar for any reason (for example, if you decide to get rid of the footer and put your title bar at the bottom), you would also have to change the code for the title.
Both of these issues can be addressed by positioning your title relative to your title bar. Since this is something I find myself doing often, I have a simple utility function that does it for me:
 
/** * Center one DisplayObject relative to another. */ private function center(foreground:DisplayObject, background:DisplayObject):void { foreground.x = (background.width / 2) - (foreground.width / 2); foreground.y = (background.height / 2) + (foreground.height / 2); }
Using the center() function above, positioning my title is simple:
 
var title:SimpleLabel = new SimpleLabel("My Application", "bold", 0xffffff, "_sans", this.inchesToPixels(.15)); this.center(title, titleBar); this.addChild(title);

 
Adaptive content

Dynamically sizing and laying out things like title bars is one thing, but actual application content can be more challenging. For example, consider a game whose main content is a grid of squares. What's the best technique for making the game playable on multiple devices? Should the squares simply be scaled up or down depending on screen size, or should rows and columns be added or removed?
 
Both are valid approaches, depending on the game. For example, in the case of a chess or checkers game, you can't add or remove rows or columns based on the size of the screen. In this case, it's usually best just to scale your content up or down in order to keep it consistent.
 
Some games can actually adapt their game play based on the size of the screen. For example, a real-time strategy game may be enhanced on a larger screen since higher resolutions can accommodate more tiles, or in the case of smaller screens, it may be best to remove tiles so that the remaining tiles can be larger and render more detail. In this case, you need your content to adapt.
 
There's no single strategy or formula for adapting content to various screen sizes since content is so diverse, but there are some standard techniques that can be used. The following describes the logic of adapting a game board to any size screen:
 
  1. Determine the size of your tiles or blocks. If your tiles contain bitmaps, the size will be predetermined by the pixel dimensions of your bitmaps (since bitmaps don't scale as well as vectors). If your tiles are vectors, you may decide to standardize on an absolute size for your tiles.
  2. Determine how much space you have to lay out your tiles. The amount of space you have to lay out your tiles should be the total size of the screen less things like title bars, navigation, etc.
  3. Calculate the number of rows and columns that will fit into the remaining space. Be sure to take into consideration any gap you may want between your squares or tiles.
  4. Lay out your tiles in the available space. This is a little trickier than it sounds since there will almost always be space remaining both above and below your game board which you will most likely want to split evenly across vertical and horizontal margins.
The code below demonstrates the logic of laying as many ¼" blocks as possible in the allotted space while maintaining an equal margin both above and below the game board:
 
// Display as many blocks on the screen as will fit var BLOCK_SIZE:Number = .25; var BLOCK_BUFFER:uint = 3; var blockSize:uint = this.inchesToPixels(BLOCK_SIZE); var blockTotal:uint = blockSize + BLOCK_BUFFER; var cols:uint = Math.floor(this.stage.stageWidth / blockTotal); var rows:uint = Math.floor((this.stage.stageHeight - titleBar.height) / blockTotal); var blockXStart:uint = (this.stage.stageWidth - ((cols * blockSize) + ((cols - 1) * BLOCK_BUFFER))) / 2; var blockX:uint = blockXStart; var blockY:uint = ((this.stage.stageHeight + titleBar.height) - ((rows * blockSize) + ((rows - 1) * BLOCK_BUFFER))) / 2; for (var colIndex:uint = 0; colIndex < rows; ++colIndex) { for (var rowIndex:uint = 0; rowIndex < cols; ++rowIndex) { // Use a private function to draw the block var block:Sprite = this.getBlock(blockSize); block.x = blockX; block.y = blockY; this.addChild(block); blockX += blockTotal; } blockY += blockTotal; blockX = blockXStart; } }
The code below is the function that generates each block:
 
/** * Get a new block to add to the game board */ private function getBlock(blockSize:uint):Sprite { var block:Sprite = new Sprite(); block.graphics.beginFill(0xAAC228); block.graphics.drawRect(0, 0, blockSize, blockSize); block.graphics.endFill(); block.cacheAsBitmap = true; return block; }
Note: As each block is created, its cacheAsBitmap property is set to true. Although mobile application optimization is beyond the scope of this article, it's always best to set the cacheAsBitmap property to true for DisplayObjects that you don't anticipate will need to be scaled or rotated frequently. Although this improves performance on the desktop, it can have dramatic results on devices with less powerful processors.
 

 
Managing fonts

Fonts are a key element of almost all applications, and must be handled with the same care as other assets when designing for multiple screens. Below are three tips for using fonts in a way that will successfully adapt across devices:
 
  • Use FTE (Flash Text Engine): I highly recommend using FTE, or the new Flash Text Engine introduced in Flash Player 10, Adobe AIR 1.5, and Flash Lite 4. Not only is rendering better than with TextField objects, but FTE gives you the ability to position your text much more precisely. The properties of TextLine like ascent, descent, textWidth, and textHeight make it possible to position text with pixel-perfect accuracy.
  • Consider creating a component: In the title example previously, I use my own very simple component called SimpleLabel which encapsulates my use of FTE and makes creating text far simpler. Not only do I save several lines of code everyplace I want to add some text, but I also have one central location where I can make universal text changes, or fix text-related bugs.
  • Override width and height getters if necessary: In my SimpleText component, I override the DisplayObject object's width and height properties in order to make text fields work better with some of my utilities like the center() function above. Following are the width and height getters that give me the best results:
public override function get width():Number { return this.textLine.textWidth; } public override function get height():Number { return (this.textLine.ascent - 1); }

 
Where to go from here

As the Flash Platform proliferates, so do opportunities for Flash developers. The ability to use the same tools, skills, and code to build applications across an increasing array of diverse devices is hugely powerful, and gives Flash developers the chance to reach an unprecedented number of users. In order to take advantage of the ubiquity of the Flash Platform, however, developers need to build applications with multiple screens in mind. Fortunately, the understanding and mastery of just a few relatively simple techniques give developers the tools they need to make the most of the Flash Platform.