Multiscreen Support in Mobile AIR Applications

This post is also available in: Russian

As AIR applications can run on multiple platforms and devices, AIR based developers have to take into account a vast diversity of screens, their resolutions and DPIs. Despite a broad diversity of supported platforms, in this post we will focus on multiscreen development for mobile AIR applications based on the Flex framework.

Let’s now agree upon the basic terminology that we are going to use here. For convenience, in parentheses we will indicate the property containing a numerical value of a given parameter.

  1. Screen resolution means the number of pixels on the screen in horizontal and vertical dimensions (Capabilities.screenResolutionX and Capabilities.screenResolutionY)
  2. Display space resolution means both horizontal and vertical pixels of the display space. It may differ much from screen resolution as it does not include the status bar at the top, the button bar at the bottom, and also due to auto-scaling (systemManager.screen.width and systemManager.screen.height).
  3. Screen DPI means the physical number of dots per inch on the device screen running the application (Capabilities.screenDPI).
  4. Runtime DPI returns Screen DPI rounded up to the value of one of the constants defined in the class called DPIClassification (runtimeDPI property) in the main class of the Flex application. The value of this property can be retrieved using FlexGlobals.topLevelApplication.runtimeDPI). This property can only take three values: 160, 240, 320. These are the basic values to which DPI screen is rounded. You have to use fixed values to have a limited set of graphics for three different DPIs. Using the runtimeDPIProvider property, you can specify, for the main application, a class inherited from RuntimeDPIProvider. In this class, you can override the runtimeDPI method and set your own logic to round up Screen DPI to the desired value of DPI runtime. This might be useful, for example, in the event in the addition to screen DPI you need to define its resolution or any other options.
  5. Application DPI means a numeric DPI value for which the application is developed (property – applicationDPI) in the main class of the Flex application. The value of this property can be accessed via FlexGlobals.topLevelApplication.applicationDPI). This is the key property used during automatic scaling performed by the Flex framework. You can set this value can using MXML in the main application class, and can change it at runtime. If applicationDPI is not specified, automatic scaling will not be performed by the Flex framework. This property can only take three values: 160, 240, 320.

Automatic Scaling

Auto-scaling using the Flex framework can be enabled by setting Application DPI. If the developer has specified applicationDPI, then Flex begins to apply a scaling factor to the main application by changing scaleX and scaleY for systemManager. This facilitates development of multi-platform and multi-screen applications.

When developing applications with auto-scaling enabled, it is essential to define the size of display space. To do this, use systemManager.screen.width and systemManager.screen.height only, rather than stage.width and stage.height.

For instance, let’s take a device and an application run by it with the following parameters:

  • runtimeDPI of the device is set to 320;
  • applicationDPI is set to 240;
  • screen resolution is 1280 x 720;

Having launched the application on a device, we can see the following values of properties:

stage.stageWidth: 720
stage.stageHeight: 1230
stage.fullScreenWidth: 720
stage.fullScreenHeight: 1280
Capabilities.screenDPI: 320
Capabilities.screenResolutionX: 720
Capabilities.screenResolutionY: 1280
systemManager.screen.width: 540
systemManager.screen.height: 922.5
systemManager.scaleX: 1.333333333333333
systemManager.scaleY: 1.333333333333333

You can see that the display space size is smaller than the stage size, because automatic scaling results in change of scaleX and scaleY in the application’s systemManager. So, for instance, when centering pop-ups and other functional elements that have to know exact display space sizes, you can find the sizes in systemManager.screen.width and systemManager.screen.height.

Automatic scaling can result in content distortion. To avoid this, please adhere to the following rules:

  1. Use vector images. In case of automatic scaling, Flex will display them similarly on all types of screens. Fonts also present no problem, as they are also scalable without distortion.
  2. For bitmaps, icons or skins you need to have images for three different DPIs. Flex has a number of in-built tools to use a needed image for a particular Runtime DPI.

Customization of Bitmap Resources for Different DPIs

To ensure that bitmaps have no distortions and artifacts associated with automatic scaling of the application, you should define in Flex, which image should be used for a given DPI. To do this, when setting an image source, use an instance of MultiDPIBitmapSource, whose properties contain image resources for three different DPI.

Let’s consider several examples of bitmap customization.
Here is the class of component displaying a bell icon in a normal and selected state, respectively:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package ru.denivip.europa.views.schedule
{
import spark.components.Group;
import spark.primitives.BitmapImage;
import spark.utils.MultiDPIBitmapSource;

public class BellSwitcher extends Group
{
    private var bellIcon:BitmapImage = new BitmapImage();

    // Source for the icon in a normal state
    private var multiSource:MultiDPIBitmapSource;

    // Source for the icon in a selected state
    private var multiSourceSelected:MultiDPIBitmapSource;

    public function BellSwitcher()
    {
        super();

        // For each of sources, set bitmap images
        // for three different DPIs
        multiSource = new MultiDPIBitmapSource();
        multiSource.source160dpi = "/assets/icon_bell@160.png";
        multiSource.source240dpi = "/assets/icon_bell@240.png";
        multiSource.source320dpi = "/assets/icon_bell@320.png";

        multiSourceSelected = new MultiDPIBitmapSource();
        multiSourceSelected.source160dpi = "/assets/icon_bell_selected@160.png";
        multiSourceSelected.source240dpi = "/assets/icon_bell_selected@240.png";
        multiSourceSelected.source320dpi = "/assets/icon_bell_selected@320.png";

        addElement(bellIcon);
        commitSelected();
    }
    private var _selected:Boolean = false;
    public function set selected(value:Boolean):void
    {
        _selected = value;
        commitSelected();
    }
    protected function commitSelected():void
    {
        // The icon source is determined based on the element state
        bellIcon.source = _selected ? multiSourceSelected : multiSource;
    }
}
}

An example of using MultiDPIBitmapSource for bitmaps embedded in the application at compile time. Skin class for a CheckBox component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!--?xml version="1.0" encoding="utf-8"?-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fb="http://ns.adobe.com/flashbuilder/2009" alpha.disabledStates="0.5">

    <fx:Metadata>
        /** * @copy spark.skins.spark.ApplicationSkin#hostComponent */ [HostComponent("spark.components.CheckBox")]
    </fx:Metadata>
   
    <s:states>
        <s:State name="up" />
        <s:State name="over" stateGroups="overStates" />
        <s:State name="down" stateGroups="downStates" />
        <s:State name="disabled" stateGroups="disabledStates" />
        <s:State name="upAndSelected" stateGroups="selectedStates" />
        <s:State name="overAndSelected" stateGroups="overStates, selectedStates" />
        <s:State name="downAndSelected" stateGroups="downStates, selectedStates" />
        <s:State name="disabledAndSelected" stateGroups="disabledStates, selectedStates" />
    </s:states>

    <s:BitmapImage excludeFrom="selectedStates">
        <s:source>
            <s:MultiDPIBitmapSource
                source160dpi="@Embed(source='assets/images/ui/check_box@160.png')"
                source240dpi="@Embed(source='assets/images/ui/check_box@240.png')"
                source320dpi="@Embed(source='assets/images/ui/check_box@320.png')"/>
        </s:source>
    </s:BitmapImage>
    <s:BitmapImage includeIn="selectedStates">
        <s:source>
            <s:MultiDPIBitmapSource
                source160dpi="@Embed(source='assets/images/ui/check_box_checked@160.png')"
                source240dpi="@Embed(source='assets/images/ui/check_box_checked@240.png')"
                source320dpi="@Embed(source='assets/images/ui/check_box_checked@320.png')"/>
        </s:source>
    </s:BitmapImage>
</s:SparkSkin>

Use of Style Sheets Linked to DPI

Flex also lets you customize style sheets depending on the runtime DPI and operating system type. If you need, for instance, to set font size for a particular DPI or operating system, you can explicitly specify it in the style sheet, using the @media rule. This rule can have two properties: os-platform specifying the type of the Operating System and application-dpi allowing you to specify styles for the needed DPI.

The os-platform property can have the following values:

  • Android
  • iOS
  • Macintosh
  • Linux
  • QNX
  • Windows

The syntax of @media can be quite complex and list different features and their combinations separated by commas.

Here are several examples of @media rules and its properties os-platform and application-dpi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* Any operating system and 160 DPI */
@media (application-dpi: 160) {
    s|Button {
        fontSize: 10;
    }
}

/* Only iOS and 240 DPI */
@media (application-dpi: 240) and (os-platform: "IOS") {
    s|Button {
        fontSize: 11;
    }
}

/* iOS at 160 DPI or Android at 160 DPI */
@media (os-platform: "IOS") and (application-dpi: 160), (os-platform: "ANDROID") and (application-dpi: 160) {
    s | Button {
        fontSize: 13;
    }
}

/ * Any OS except Android at 240 DPI */
@media not all and (application-dpi: 240) and (os-platform: "Android") {
    s|Button {
        fontSize: 12;
    }
}

/* Any OS except iOS at any DPI */
@media not all and (os-platform: "IOS") {
    s|Button {
        fontSize: 14;
    }
}

Automatic Scaling

To disable auto-scaling (or rather not to enable it), please do not specify Application DPI in property applicationDPI in the main class.

When Automatic Scaling is disabled in Flex, you have to re-size interface and design elements yourself. For images, you can use customization of bitmap resources that we have already mentioned above.

Using style sheets for each DPI value, you can customize font size, icon types and other parameters depending on the device screen. If auto-scaling is disabled, applicationDPI will be set to runtimeDPI. Therefore, when using styles with auto-scaling disabled, Runtime DPI will be linked.

Flex has a fairly extensive toolkit to develop multi-platform and multi-screen applications, almost eliminating development routine. Auto-scaling offers great benefits and allows you to develop applications faster and be sure that they will look perfect on various devices.

Leave a Reply