|
Guidelines for supporting multiple screen sizes and DPI valuesTo develop an application that is platform independent,
be aware of different output devices. Devices can have different
screen sizes or resolutions and different DPI values, or densities.
Flex engineer Jason SJ describes two approaches to creating resolution-independent
mobile applications on his blog.
Terminology
Resolution is the number of pixels high by the number
of pixels wide: that is, the total number of pixels that a device
supports.
DPI is the number of dots per square inch: that is, the
density of pixels on a device’s screen. The term DPI is used interchangeably
with PPI (pixels per inch).
Flex support for DPIs
The following flex features simplify the process of producing
resolution- and DPI-independent applications:
- Skins
- DPI-aware skins for mobile components. The default mobile
skins do not need additional coding to scale well for most devices’
resolutions.
- applicationDPI
- A property that defines the size for which your custom skins
are designed. Suppose that you set this property at some DPI value,
and a user runs the application on a device with a different DPI
value. Flex scales everything in the application to the DPI of the
device in use.
The default mobile skins are DPI-independent, both with and without
DPI scaling. As a result, if you do not use components with static
sizes or custom skins, you typically do not need to set the applicationDPI property.
Dynamic layouts
Dynamic layouts help you overcome differences in resolution.
For example, setting a control’s width to 100% always fills the
width of the screen, whether the resolution is 480x854 or 480x800.
Set applicationDPI property
When you create density-independent applications, you can set
the target DPI on the root application tag. (For mobile applications,
the root tag is <s:ViewNavigatorApplication>, <s:TabbedViewNavigatorApplication>,
or <s:Application>.)
You set the value of the applicationDPI property
to 160, 240, or 320, depending on the approximate resolution of
your target device. For example: <s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
firstView="views.DensityView1"
applicationDPI="320">
When you set the applicationDPI property, you
effectively define a scale for the application when it is compared
to the target device’s actual resolution (the runtimeDPI)
at runtime. For example, if you set the applicationDPI property
to 160 and the target device has a runtimeDPI of
160, the scale factor is 1 (no scaling). If you set the applicationDPI property
to 240, the scale factor is 1.5 (Flex magnifies everything by 150%).
At 320, the scale factor is 2, so Flex magnifies everything by 200%.
In some cases, non-integer scaling can result in undesirable
artifacts due to interpolation, such as blurred lines.
Disable DPI scaling
To disable DPI scaling for the application, do not set the value
of the applicationDPI property.
Understand applicationDPI and runtimeDPIThe
following table describes two properties of the Application class
that are integral to working with applications at different resolutions:
Property
|
Description
|
applicationDPI
|
The target density or DPI of the application.
When
you specify a value for this property, Flex applies a scale factor
to the root application. The result is an application designed for
one DPI value scales to look good on another device with a different
DPI value.
The scale factor is calculated by comparing the
value of this property with the runtimeDPI property.
This scale factor is applied to the entire application, including
the preloader, pop-ups, and all components on the stage.
When
not specified, this property returns the same value as the runtimeDPI property.
This
property cannot be set in ActionScript; it can only be set in MXML.
You cannot change the value of this property at runtime.
|
runtimeDPI
|
The density or DPI value of the device that
the application is currently running on.
Returns the value
of the Capabilities.screenDPI property, rounded
to one of the constants defined by the DPIClassification class.
This
property is read-only.
|
Create resolution- and DPI-independent applicationsResolution-
and DPI-independent applications have the following characteristics: - Images
- Vector images scale smoothly to match the target device’s
actual resolution. Bitmaps, on the other hand, do not always scale
as well. In these cases, you can load bitmaps at different resolutions,
depending on the device resolution by using the MultiDPIBitmapSource class.
- Text
- The font size of text (not the text itself) is scaled to
match the resolution.
- Layouts
- Use dynamic layouts to ensure that the application looks
good when scaled. In general, avoid using constraint-based layouts
where you specify pixel boundaries with absolute values. If you
do use constraints, use the value of the applicationDPI property
to account for scaling.
- Scaling
- Do not use the scaleX and scaleY properties
on the Application object. When you set the applicationDPI property,
Flex does the scaling for you.
- Styles
- You can use stylesheets to customize style properties for
the target device’s OS and the application DPI settings.
- Skins
- The Flex skins in the mobile theme use the application DPI
value to determine which assets to use at runtime. All visual skin
assets defined by FXG files are suited to the target device.
- Application size
- Do not explicitly set the height and width of the application. Also,
when calculating sizes of custom components or popups, do not use
the stageWidth and stageHeight properties.
Instead, use the SystemManager.screen property.
Determine runtime DPIWhen your application
starts, your application gets the value of the runtimeDPI property
from the Capabilities.screenDPI Flash Player property.
This property is mapped to one of the constants defined by the DPIClassification
class. For example, a Droid running at 232 DPI is mapped to the 240
runtime DPI value. Device DPI values do not always exactly match
the DPIClassification constants (160, 240, or 320). Instead, they
are mapped to those classifications, based on a range of target
values.
The mappings are as follows:
DPIClassification constant
|
160 DPI
|
240 DPI
|
320 DPI
|
Actual device DPI
|
<200
|
>=200 and <280
|
>=280
|
You can customize these mappings to
override the default behavior or to adjust devices that report their
own DPI value incorrectly. For more information, see Override the default DPI.
Choose autoscaling or non-autoscalingChoosing
to use autoscaling (by setting the value of the applicationDPI property)
is a tradeoff between convenience and pixel-accurate visual fidelity.
If you set the applicationDPI property to scale
your application automatically, Flex uses skins targeted at the applicationDPI.
Flex scales the skins up or down to fit the device’s actual density.
Other assets in your application and layout positions are scaled
as well.
If you want to use autoscaling, and you are creating
your own skins or assets targeted at a single DPI value, you typically
do the following: Create a single set of skins and view/component
layouts that are targeted at the applicationDPI you
specify.
Create multiple versions of any bitmap asset used in your
skins or elsewhere in your application, and specify them using the
MultiDPIBitmapSource class. Vector assets and text in your skins
do not need to be density-aware if you are autoscaling.
Don’t use the @media rule in your stylesheets,
because your application only considers a single target DPI value.
Test your application on devices of different densities to
ensure that the appearance of the scaled application is acceptable
on each device. In particular, check devices that cause scaling
by a non-integer factor. For example, if applicationDPI is
160, test your application on 240-DPI devices.
If
you choose not to use autoscaling (by leaving the applicationDPI property
unset), get the applicationDPI value. Use this
property to determine the actual DPI classification of the device,
and adapt your application at runtime by doing the following: Make multiple sets of skins and layouts targeted at each
runtime DPI classification, or make a single set of skins and layouts
that dynamically adapts to different densities. (The built-in Flex
skins take the latter approach—each skin class checks the applicationDPI property
and sets itself up appropriately.)
Use @media rules in your stylesheets to
filter CSS rules based on the device’s DPI classification. Typically,
you customize font sizes and padding values for each DPI value.
Test your application on devices of different densities to
ensure that your skins and layouts are properly adapting.
Select styles based on DPIFlex includes support for applying styles based on the
target OS and application DPI value in CSS. You apply styles with
the @media rule in your stylesheet. The @media rule
is part of the CSS specification; Flex extends this rule to include additional
properties: application-dpi and os-platform.
You use these properties to apply styles selectively based on the
application DPI and the platform on which the application is running.
The following example sets the Spark Button control’s
default fontSize style property to 12. If the device
uses 240 DPI and is running on the Android operating system, the fontSize property
is 10. <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/MediaQueryValuesMain.mxml -->
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="320">
<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";
s|Button {
fontSize: 12;
}
@media (os-platform: "Android") and (application-dpi: 240) {
s|Button {
fontSize: 10;
}
}
</fx:Style>
</s:ViewNavigatorApplication>
Values for application-dpi property
The application-dpi CSS property is compared
against the value of the applicationDPI style property
that is set on the root application. The following are valid values
for the application-dpi CSS property:
Each of the supported values for application-dpi has
a corresponding constant in the DPIClassification class.
Values for the os-platform property
The os-platform CSS property is matched to the
value of the flash.system.Capabilities.version property
of Flash Player. The following are valid values for the os-platform CSS
property: Android
iOS
Macintosh
Linux
QNX
Windows
The matching is not case sensitive.
If none of the entries match, then Flex seeks a secondary match
by comparing the first three characters to the list of supported
platforms.
Defaults for application-dpi and os-platform properties
If you do not explicitly define an expression containing the application-dpi or os-platform properties,
then all expressions are assumed to match.
Operators in the @media rule
The @media rule supports the common operators
“and” and “not”. It also supports comma-separated lists. Separating
expressions by a comma implies an “or” condition.
When you use the “not” operator, the “not” must be the first
keyword in the expression. This operator negates the entire expression,
not just the property that follows the “not”. Because of bug SDK-29191, the “not” operator
must be followed by a media type, such as “all”, before one or more
expressions.
The following example shows how to use some of these common operators: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/MediaQueryValuesMain.mxml -->
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="320">
<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";
/* Every os-platform @ 160dpi */
@media (application-dpi: 160) {
s|Button {
fontSize: 10;
}
}
/* IOS only @ 240dpi */
@media (application-dpi: 240) and (os-platform: "IOS") {
s|Button {
fontSize: 11;
}
}
/* IOS at 160dpi or Android @ 160dpi */
@media (os-platform: "IOS") and (application-dpi:160), (os-platform: "ANDROID") and (application-dpi: 160) {
s|Button {
fontSize: 13;
}
}
/* Every os-platform except Android @ 240dpi */
@media not all and (application-dpi: 240) and (os-platform: "Android") {
s|Button {
fontSize: 12;
}
}
/* Every os-platform except IOS @ any DPI */
@media not all and (os-platform: "IOS") {
s|Button {
fontSize: 14;
}
}
</fx:Style>
</s:ViewNavigatorApplication>
Select bitmap assets based on DPIBitmap image assets typically only render optimally at
the resolution for which they are designed. This limitation can
present challenges when you design applications for multiple resolutions.
The solution is to create multiple bitmaps, each at a different
resolution, and load the appropriate one depending on the value
of the application’s runtimeDPI property.
The Spark BitmapImage and Image components
have a source property of type Object.
Because of this property, you can pass a class that defines which assets
to use. In this case, you pass the MultiDPIBitmapSource class
to map different sources, depending on the value of the runtimeDPI property.
The following example loads a different image, depending on the
DPI: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/views/MultiSourceView3.mxml -->
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="Image with MultiDPIBitmapSource">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
private function doSomething():void {
/* The MultiDPIBitmapSource's source data. */
myTA.text = myImage.source.getSource(FlexGlobals.topLevelApplication.applicationDPI).toString();
}
]]>
</fx:Script>
<s:Image id="myImage">
<s:source>
<s:MultiDPIBitmapSource
source160dpi="assets/low-res/bulldog.jpg"
source240dpi="assets/med-res/bulldog.jpg"
source320dpi="assets/high-res/bulldog.jpg"/>
</s:source>
</s:Image>
<s:Button id="myButton" label="Click Me" click="doSomething()"/>
<s:TextArea id="myTA" width="100%"/>
</s:View>
When you use the BitmapImage and Image classes
with MultiDPIBitmapSource in a desktop application, the source160dpi property
is used for the source.
The Button control’s icon property also takes
a class as an argument. As a result, you can also use a MultiDPIBitmapSource
object as the source for the Button’s icon. You can define the source
of the icon inline, as the following example shows: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/views/MultiSourceView2.mxml -->
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="Icons Inline">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
private function doSomething():void {
/* The MultiDPIBitmapSource's source data. */
myTA.text = dogButton.getStyle("icon").getSource(FlexGlobals.topLevelApplication.applicationDPI).toString();
}
]]>
</fx:Script>
<s:Button id="dogButton" click="doSomething()">
<s:icon>
<s:MultiDPIBitmapSource id="dogIcons"
source160dpi="@Embed('../../assets/low-res/bulldog.jpg')"
source240dpi="@Embed('../../assets/med-res/bulldog.jpg')"
source320dpi="@Embed('../../assets/high-res/bulldog.jpg')"/>
</s:icon>
</s:Button>
<s:TextArea id="myTA" width="100%"/>
</s:View>
You can also define icons by declaring them in a <fx:Declarations> block and
assigning the source with data binding, as the following example
shows: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/views/MultiSourceView1.mxml -->
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
title="Icons in Declarations">
<fx:Declarations>
<s:MultiDPIBitmapSource id="dogIcons"
source160dpi="@Embed('../../assets/low-res/bulldog.jpg')"
source240dpi="@Embed('../../assets/med-res/bulldog.jpg')"
source320dpi="@Embed('../../assets/high-res/bulldog.jpg')"/>
</fx:Declarations>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
private function doSomething():void {
/* The MultiDPIBitmapSource's source data. */
myTA.text = dogIcons.getSource(FlexGlobals.topLevelApplication.applicationDPI).toString();
}
]]>
</fx:Script>
<s:Button id="dogButton" icon="{dogIcons}" click="doSomething()"/>
<s:TextArea id="myTA" width="100%"/>
</s:View>
If the runtimeDPI property maps to a sourceXXXdpi property
that is null or an empty string (""), Flash Player
uses the next higher density property as the source. If that value
is also null or empty, the next lower density is
used. If that value is alsonull or empty,
Flex assigns null as the source, and no image is displayed.
In other words, you cannot explicitly specify that no image should
be displayed for a particular DPI.
Select skin assets based on DPILogic in the default mobile skins’ constructors chooses
assets based on the value of the applicationDPI property.
These classes select assets that most closely match the target DPI
value. When you design custom skins that work both with and without
DPI scaling, use the applicationDPI property and
not the runtimeDPI property.
For example, the spark.skins.mobile.ButtonSkin class
uses a switch/case statement that selects FXG assets that are designed
for particular DPI values, similar to the following: switch (applicationDPI) {
case DPIClassification.DPI_320: {
upBorderSkin = spark.skins.mobile320.assets.Button_up;
downBorderSkin = spark.skins.mobile320.assets.Button_down;
...
break;
}
case DPIClassification.DPI_240: {
upBorderSkin = spark.skins.mobile240.assets.Button_up;
downBorderSkin = spark.skins.mobile240.assets.Button_down;
...
break;
}
}
In addition to conditionally selecting FXG assets, the mobile
skin classes also set the values of other style properties such
as layout gap and layout padding. These settings are based on the
DPI of the target device.
Not setting applicationDPI
If you do not set the applicationDPI property,
then skins default to using the runtimeDPI property.
This mechanism guarantees that a skin that bases its values on the applicationDPI property
rather than on the runtimeDPI property uses the
appropriate resource both with and without DPI scaling.
When creating custom skins, you can choose to ignore the applicationDPI setting.
The result is a skin that is still scaled to match the DPI of the
target device, but it might not appear optimally if its assets are
not specifically designed for that DPI value.
Use applicationDPI in CSS
Use the value of the applicationDPI property
in the CSS @media selector to customize the styles
used by your mobile or tablet application without creating custom
skins. For more information, see Select styles based on DPI.
Manually determine scale factor and current DPITo manually instruct a mobile or tablet application to
select assets based on the target device’s DPI value, you can calculate
the scaling factor at runtime. You do this by dividing the value
of the runtimeDPI property by the applicationDPI style
property:
import mx.core.FlexGlobals;
var curDensity:Number = FlexGlobals.topLevelApplication.runtimeDPI;
var curAppDPI:Number = FlexGlobals.topLevelApplication.applicationDPI;
var currentScalingFactor:Number = curDensity / curAppDPI;
You can use the calculated scaling factor to manually select
assets. The following example defines custom locations of bitmap
assets for each DPI value. It then loads an image from that custom
location: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/DensityMain.mxml -->
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
firstView="views.DensityView1"
applicationDPI="240" initialize="initApp()">
<fx:Script>
<![CDATA[
[Bindable]
public var densityDependentDir:String;
[Bindable]
public var curDensity:Number;
[Bindable]
public var appDPI:Number;
[Bindable]
public var curScaleFactor:Number;
public function initApp():void {
curDensity = runtimeDPI;
appDPI = applicationDPI;
curScaleFactor = appDPI / curDensity;
switch (curScaleFactor) {
case 1: {
densityDependentDir = "../../assets/low-res/";
break;
}
case 1.5: {
densityDependentDir = "../../assets/med-res/";
break;
}
case 2: {
densityDependentDir = "../../assets/high-res/";
break;
}
}
}
]]>
</fx:Script>
</s:ViewNavigatorApplication>
The view that uses the scaling factor is as follows: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/views/DensityView1.mxml -->
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="Home"
creationComplete="initView()">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
[Bindable]
public var imagePath:String;
private function initView():void {
label0.text = "App DPI:" + FlexGlobals.topLevelApplication.appDPI;
label1.text = "Cur Density:" + FlexGlobals.topLevelApplication.curDensity;
label2.text = "Scale Factor:" + FlexGlobals.topLevelApplication.curScaleFactor;
imagePath = FlexGlobals.topLevelApplication.densityDependentDir + "bulldog.jpg";
ta1.text = myImage.source.toString();
}
]]>
</fx:Script>
<s:Image id="myImage" source="{imagePath}"/>
<s:Label id="label0"/>
<s:Label id="label1"/>
<s:Label id="label2"/>
<s:TextArea id="ta1" width="100%"/>
</s:View>
Override the default DPIAfter setting the application DPI value, your application is
scaled based on the DPI value reported by the device on which it
is running. In some cases, devices report incorrect DPI values,
or you want to override the default DPI selection method in favor
of a custom scaling method.
You can override the default scaling behavior of an application
by overriding the default DPI mappings. For example, if a device
incorrectly reports that it is 240 DPI instead of 160 DPI, you can
create a custom mapping that looks for this device and classifies
it as 160 DPI.
To override a particular device’s DPI value, you point the Application
class’s runtimeDPIProvider property to a subclass
of the RuntimeDPIProvider class.
In your subclass, you override the runtimeDPI getter
and add logic that provides a custom DPI mapping. Do not add dependencies
to other classes in the framework such as UIComponent.
This subclass can only call into Player APIs.
The following example sets a custom DPI mapping for a device
whose Capabilities.os property matches “Mac 10.6.5”: package {
import flash.system.Capabilities;
import mx.core.DPIClassification;
import mx.core.RuntimeDPIProvider;
public class DPITestClass extends RuntimeDPIProvider {
public function DPITestClass() {
}
override public function get runtimeDPI():Number {
// Arbitrary mapping for Mac OS.
if (Capabilities.os == "Mac OS 10.6.5")
return DPIClassification.DPI_320;
if (Capabilities.screenDPI < 200)
return DPIClassification.DPI_160;
if (Capabilities.screenDPI <= 280)
return DPIClassification.DPI_240;
return DPIClassification.DPI_320;
}
}
}
The following application uses the DPITestClass to determine
a runtime DPI value to use for scaling. It points to the ViewNavigatorApplication class’s runtimeDPIProvider property: <?xml version="1.0" encoding="utf-8"?>
<!-- mobile_skins/DPIMappingOverrideMain.mxml -->
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
firstView="views.DPIMappingView"
applicationDPI="160"
runtimeDPIProvider="DPITestClass">
</s:ViewNavigatorApplication>
The following is another example of a subclass of the RuntimeDPIProvider
class. In this case, the custom class checks the device’s x and
y resolution to determine if the device is incorrectly reporting
its DPI value: package
{
import flash.system.Capabilities;
import mx.core.DPIClassification;
import mx.core.RuntimeDPIProvider;
public class SpecialCaseMapping extends RuntimeDPIProvider {
public function SpecialCaseMapping() {
}
override public function get runtimeDPI():Number {
/* A tablet reporting an incorrect DPI of 240. We could use
Capabilities.manufacturer to check the tablet's OS as well. */
if (Capabilities.screenDPI == 240 &&
Capabilities.screenResolutionY == 1024 &&
Capabilities.screenResolutionX == 600) {
return DPIClassification.DPI_160;
}
if (Capabilities.screenDPI < 200)
return DPIClassification.DPI_160;
if (Capabilities.screenDPI <= 280)
return DPIClassification.DPI_240;
return DPIClassification.DPI_320;
}
}
}
|
|
|