支持多个屏幕大小和 DPI 值的指导原则
要部署独立于平台的应用程序,应了解不同的输出设备。设备可以具有不同的屏幕大小或分辨率以及不同的 DPI 值或密度。
Flex 工程师 Jason SJ 在他的博客中介绍了两种创建与分辨率无关的移动设备应用程序的方法。
术语
分辨率是像素高度乘以像素宽度得到的数值:即设备支持的像素总数。
DPI 是每平方英寸的点数:即设备屏幕上的像素密度。术语 DPI 和 PPI(每英寸像素数)可以互换使用。
Flex 对 DPI 的支持
以下 Flex 功能简化了生成与分辨率和 DPI 无关的应用程序的过程。
- 外观
- 移动设备组件与 DPI 有关的外观。默认移动设备外观无需额外编写代码,即可根据大多数设备的分辨率进行正常缩放。
- applicationDPI
- 该属性用于定义自定义外观的设计尺寸。假设将该属性设置为某 DPI 值,当用户在具有不同 DPI 值的设备上运行应用程序时,Flex 会根据所使用设备的 DPI 缩放应用程序中的所有内容。
无论是否具有 DPI 缩放功能,默认移动设备外观都与 DPI 无关。因此,如果不使用具有静态大小或自定义外观的组件,则通常无需设置 applicationDPI 属性。
动态布局
动态布局可以帮助您适应各种不同的分辨率。例如,如果将控件的宽度设置为 100%,将始终填满屏幕的宽度,无论屏幕分辨率是 480x854 还是 480x800。
设置 applicationDPI 属性
设置与密度无关的应用程序时,可以在根应用程序标签上设置目标 DPI。(对于移动设备应用程序,根标签为 <s:ViewNavigatorApplication>、<s:TabbedViewNavigatorApplication> 或 <s:Applica ion>。)
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.DensityView1" applicationDPI="320">
设置 applicationDPI 属性时,可以对照运行时目标设备的实际分辨率 (runtimeDPI) 来有效定义应用程序的缩放比例。例如,如果将 applicationDPI 属性设置为 160,而目标设备的 runtimeDPI 为 160,则缩放比例因子为 1(不缩放)。如果将 applicationDPI 属性设置为 240,则缩放比例因子为 1.5(Flex 将所有内容放大到 150%)。设置为 320 时,缩放比例因子为 2,则 Flex 将所有内容放大到 200%。
在某些情况下,非整数缩放会因为插值处理而造成失真,例如线条模糊。
禁用 DPI 缩放
要对应用程序禁用 DPI 缩放,请不要设置 applicationDPI 属性的值。
了解 applicationDPI 和 runtimeDPI
属性 |
说明 |
---|---|
applicationDPI |
应用程序的目标密度或 DPI。 当您指定该属性的值时,Flex 会对根应用程序应用缩放比例因子。这将使针对某一 DPI 值设计的应用程序进行缩放,以便在具有不同 DPI 值的设备上正常显示。 将此属性的值与 runtimeDPI 属性的值进行比较,可以计算出缩放比例因子。此缩放比例因子应用于整个应用程序,包括预加载器、弹出窗口和所有显示的组件。 如果不指定此属性的值,此属性将返回与 runtimeDPI 属性相同的值。 此属性不能在 ActionScript 中设置,而只能在 MXML 中设置。在运行时,不能更改此属性的值。 |
runtimeDPI |
当前正在运行应用程序的设备的密度或 DPI 值。 返回 Capabilities.screenDPI 属性的值,舍入到 DPIClassification 类所定义的一个常量。 此属性为只读属性。 |
创建与分辨率和 DPI 无关的应用程序
- 图像
- 矢量图可以平滑缩放至与目标设备实际分辨率相符的大小。而位图则无法像矢量图那样缩放。在这些情况下,您可以使用 MultiDPIBitmapSource 类,根据设备分辨率,以不同的分辨率加载位图。
- 文本
- 文本的字体大小(而非文本本身)缩放至与分辨率相符的大小。
- 布局
- 使用动态布局,以确保应用程序在缩放后能够正常显示。通常应尽量避免使用基于约束的布局,在这种布局中使用绝对值来指定像素边界。如果使用约束,应通过 applicationDPI 属性的值来进行缩放。
- 缩放
- 不要对应用程序对象使用 scaleX 和 scal Y 属性。设置 applicationDPI 属性时,Flex 会进行缩放。
- 样式
- 您可以使用样式表自定义目标设备的操作系统和应用程序 DPI 设置的样式属性。
- 外观
- 移动设备主题中的 Flex 外观使用应用程序 DPI 值来确定运行时所使用的资源。由 FXG 文件定义的所有可视外观资源都适用于目标设备。
- 应用程序大小
- 不要显式设置应用程序的高度和宽度。同样,在计算自定义组件或弹出窗口的大小时,不要使用 stageWidth 和 stageHeight 属性。而应使用 SystemManager.screen 属性。
确定运行时 DPI
应用程序在启动时,会从 Capabilities.screenDPI Flash Player 属性中获取 runtimeDPI 属性的值。此属性映射到 DPIClassification 类所定义的一个常量。例如,以 232 DPI 运行的 Droid 映射到 240 运行时 DPI 值。设备 DPI 值并不总是与 DPIClassification 常量(160、240 或 320)完全一致。而是根据目标值的范围,映射到这些分类。
DPIClassification 常量 |
160 DPI |
240 DPI |
320 DPI |
---|---|---|---|
实际设备 DPI |
<200 |
>=200 并 <280 |
>=280 |
您可以自定义这些映射关系来重写默认行为,或者调整错误报告了自己的 DPI 值的设备。有关更多信息,请参阅重写默认 DPI。
选择自动缩放或非自动缩放
选择使用自动缩放(通过设置 applicationDPI 属性值)是获得方便和确保像素精确的视觉保真度之间的一种折衷方法。如果将 applicationDPI 属性设置为自动缩放应用程序,Flex 会使用针对 applicationDPI 的外观。Flex 将根据设备的实际密度缩小或放大外观。应用程序和布局位置中的其它资源也将进行缩放。
-
创建针对所指定的 applicationDPI 的单组外观和视图/组件布局。
-
创建在外观或应用程序中的其它位置所使用的任何位图资源的多个版本,然后使用 MultiDPIBitmapSource 类进行指定。自动缩放时,外观中的矢量资源和文本不需要考虑密度。
-
不要在样式表中使用 @media 规则,因为应用程序仅考虑单个目标 DPI 值。
-
在不同密度的设备上测试应用程序,以确保缩放后的应用程序的外观在每个设备上都可接受。特别是要检查按非整数因子进行缩放的设备。例如,如果 applicationDPI 为 160,请在 DPI-240 设备上测试应用程序。
-
针对每个运行时 DPI 等级创建多组外观和布局,或创建可根据不同密度动态调整的单组外观和布局。(内置的 Flex 外观采用第二种方法,即每个外观类检查 applicationDPI 属性并对自身进行相应的设置。)
-
在样式表中使用 @media 规则,以根据设备的 DPI 等级过滤 CSS 规则。通常,应针对每个 DPI 值自定义字体大小和内边距值。
-
在不同密度的设备上测试应用程序,以确保外观和布局正确调整。
根据 DPI 选择样式
Flex 支持根据 CSS 中的目标操作系统和应用程序 DPI 值来应用样式。您可以在样式表中使用 @media 规则来应用样式。@media 规则是 CSS 规范的一部分;Flex 扩展了此规则,将额外的属性 application-dpi 和 os-platform 包括进来。通过这些属性,可以根据应用程序 DPI 和运行应用程序的平台来选择性地应用样式。
<?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>
application-dpi 属性的值
-
160
-
240
-
320
application-dpi 支持的每个值对应于 DPIClassification 类中的一个常量。
os-platform 属性的值
-
Android
-
iOS
-
Macintosh
-
Linux
-
QNX
-
Windows
匹配时不区分大小写。
如果所有条目均不匹配,则 Flex 会将前三个字符与受支持平台列表进行对照,来查找次要匹配项。
application-dpi 和 os-platform 属性的默认值
如果您未显式定义包含 application-dpi 或 os-platform 属性的表达式,则假定这些表达式都匹配。
@media 规则中的运算符
@media 规则支持常用的运算符“and”和“not”。同时还支持以逗号分隔的列表。如果以逗号分隔表达式,意味着各项之间是“or”的关系。
使用“not”运算符时,“not”必须是表达式中的第一个关键字。该运算符将对整个表达式求反,而不仅仅是对“not”后面的属性求反。由于存在 bug SDK-29191,“not”运算符后面必须跟有一个介质类型(例如“all”),然后是一个或多个表达式。
<?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>
根据 DPI 选择位图资源
位图图像资源通常只能在其设计分辨率下呈现出最佳效果。在设计适用于多种分辨率的应用程序时,这点局限性将带来许多难题。此问题的解决方法是创建多个位图,每个位图对应一个分辨率,然后根据应用程序的 runtimeDPI 属性值加载相应的位图。
Spark BitmapImage 和 Image 组件具有 Object 类型的 source 属性。通过该属性可以传递一个用于定义要使用的资源的类。在此情况下,应传递 MultiDPIBitmapSource 类,以根据 runtimeDPI 属性的值映射不同的源。
<?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>
在桌面应用程序中将 BitmapImage 和 Image 类与 MultiDPIBitmapSource 配合使用时,将对映射源使用 source160dpi 属性。
<?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>
<?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>
如果 runtimeDPI 属性映射到 sourceXXXdpi 属性,而该属性值为 null 或空字符串 (""),则 Flash Player 使用紧邻的更高密度的属性作为源。如果该值仍是 null 或空,则使用紧邻的更低密度。如果该值仍为null 或空,Flex 会分配 null 作为源且不显示任何图像。换句话说,您无法显式指定对特定 DPI 不显示图像。
根据 DPI 选择外观资源
默认移动设备外观的构造函数中的逻辑根据 applicationDPI 属性值选择资源。这些类将选择与目标 DPI 值最匹配的资源。在设计无论是否发生 DPI 缩放都能正常显示的自定义外观时,应使用 applicationDPI 属性,而不要使用 runtimeDPI 属性。
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; } }
除了有条件地选择 FXG 资源外,移动设备外观类还会设置其它样式属性的值,例如布局间隙和布局内边距。这些设置取决于目标设备的 DPI。
未设置 applicationDPI
如果未设置 applicationDPI 属性,则外观默认使用 runtimeDPI 属性。此机制可保证无论是否发生 DPI 缩放,值基于 applicationDPI 属性而非 runtimeDPI 属性的外观都将使用相应的资源。
创建自定义外观时,可以选择忽略 applicationDPI 设置。这样做的结果是,外观仍根据目标设备的 DPI 进行缩放,但如果资源不是专为该 DPI 值而设计的,则外观可能无法显示出最佳效果。
在 CSS 中使用 applicationDPI
可以在 CSS @media 选择器中使用 applicationDPI 属性值来自定义移动设备或平板电脑应用程序所用的样式,而无需创建自定义外观。有关更多信息,请参阅根据 DPI 选择样式。
手动确定缩放比例因子和当前 DPI
要手动指示移动设备或平板电脑应用程序根据目标设备的 DPI 值选择资源,可以在运行时计算缩放比例因子。为此,请将 runtimeDPI 属性值除以 applicationDPI 样式属性值。
import mx.core.FlexGlobals; var curDensity:Number = FlexGlobals.topLevelApplication.runtimeDPI; var curAppDPI:Number = FlexGlobals.topLevelApplication.applicationDPI; var currentScalingFactor:Number = curDensity / curAppDPI;
<?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>
<?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>
重写默认 DPI
设置应用程序 DPI 值后,应用程序将根据所在设备报告的 DPI 值来进行缩放。在某些情况下,设备会报告错误的 DPI 值,或者您希望重写默认 DPI 选择方法以使用自定义缩放方法。
可以通过重写默认 DPI 映射来重写应用程序的默认缩放行为。例如,如果某个设备将 160 DPI 错误地报告为 240 DPI,您可以创建自定义映射来查找此设备,并将设备划分到 160 DPI 类别中。
要重写特定设备的 DPI 值,请将 Application 类的 runtimeDPIProvider 属性指向 RuntimeDPIProvider 类的子类。在子类中,重写 runtimeDPI getter 并添加用于提供自定义 DPI 映射的逻辑。不要为框架中的其它类添加依赖项,例如 UIComponent。该子类只能调用到 Player API 中。
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; } } }
<?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>
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; } } }