移动设备关于显示效果的问题尤其突出,分辨率、屏幕尺寸各异,处理起来相对比较麻烦,好在Adobe本身已进行了相关处理,它主要是根据3个DPI来进行相应处理的。(用 Google Android SDK 的 Project 中的draw 目录默认也是按 low、medium、high 区分的,所以按3个DPI处理应该是比较正确的方法啦)。
相关资料:
Android手机分辨率基础知识
手机屏幕技术浅述(TFT、SLCD、AMOLED、NOVA、IPS、ASV)
Adobe官方文 档:在一个手机应用程序中支持多个屏幕大小和 DPI 值
为多个屏幕大小创作移动 Flash 内容
Flex移动skin – 第2部分:处理不同的像素密度
Mobile Development: scaling content depending on the screen resolution
Define a mobile application and a splash screen (适用于Flex SDK v4.6.0)
一、简单小结
官方给出的解决方法主要有以 下3种:(殊途同归:最终都是靠DPI定位的)
1. 使用自动缩放:(方便和确保像素精确的视觉保真度之间的一种折衷方法)
设置 application 的 applicationDPI 属性值为某个DPI;
只需要针对目标DPI值创建外观和组件布局;
位图需最好要有多个版本,使用 MultiDPIBitmapSource 类进行设置;
css中不需要使用@media。
2. 不使用自动缩放:
需要针对3个DPI来创建外观或组件布局或支持根据DPI动态调整的外观和布局;
在css中通过@media分别设置3套值(主要要设置字体大小、内边距)。
3. 自定义设备的DPI归类:(无论是否自动缩放均可)
编写一个 extends RuntimeDPIProvider 的类;
设置 application 的 runtimeDPIProvider 为该类;
该类 override get runtimeDPI 方法,然后在该方法中可以根据Capabilities.screenDPI、Capabilities.screenResolutionY 等自定义设备的DPI 属于哪一个。
其他几篇文章提供的思路:
1. 尽量根据%、stage.stageWidth、stage.stageHeight 来设置组件宽度、高度;
2. 如果一定要用绝对值,则最好使用物理单位(如inch、cm),然后将该值转化为像素。
3. 使用绝对值时,在分辨率较高的屏幕上,组件会显得较小,此时可以根据分辨率计算比例来对组件进行缩放。
二 、UI设计要点
1. 图标大小:
1) 类似置于状态栏、标题栏位置的图标,比较合适的大小分别为24、32(似乎36更合适)、48px;
2) 另一套比 1) 稍大的图标大小标准分别为36、48、72px。
2. itemrenderer 的高度,查看SDK源码得知其默认被处理为44、66、88px,可以按此标准考虑应用的某些尺寸。(调试发现,实际输出高度分别为:52、72、104)
三 、与分辨率、DPI相关的API
Capabilities.screenResolutionX 设备的横向分辨率,在移动设备该值通常是较小的那个,如480*800的480。
Capabilities.screenResolutionY 设备的竖向分辨率。
Capabilities.screenDPI 设备的实际DPI。
FlexGlobals.topLevelApplication.runtimeDPI 设备的近似DPI,只有160、240、320这3个值。
DPIClassification.DPI_160, DPIClassification.DPI_240, DPIClassification.DPI_320 160,240,320对应的常量。
FlexGlobals.topLevelApplication.applicationDPI 应用的DPI,如未指定,其值=runtimeDPI。
四、实践记录(FlashBuilder4.5.1 + Flex SDK4.5.1 +AIRSDK2.7)
1. 桌面环境调试mobile应用时,Capabilities.screenResolutionX, Capabilities.screenResolutionY 返回的值是当前PC的分辨率,此时如果需要用到这2个值时只能hardcode。
2. 如果 view 的 actionBarVisible=true,则标题栏在3个DPI下的高度会有所不同,DPI320下会比较高,此时可能会影响到这个view最底部的元素的可见度,另外navigationContent、actionContent的宽度也与DPI有关(1024*600 DPI160时点击范围略微有些小),所以最好不要使用标题栏,若使用则需要测试下view内容是否能完整显示,要不然只能给整个view加上scroll(仅为了这一点加的话似乎不太划算)。可以考虑做个仿真的标题栏。
3. 实测我的应用,之前开发时未指定 applicationDPI,主要以480*800 240DPI(目前Android设备市场主流)为参考进行UI设置的,字体大小为28px,在 3.7 inch 寸设备上看起来大小比较适中。
然后分别在不同分辨率和DPI下进行测试,除了 320*480 DPI160下由于字体过大无法显示完整的整屏内容外,在其他设备上均显示良好,当然在分辨率高于480*800的设备上,原刚好一屏的内容会显得有些“营养不良” 。
指定 applicationDPI=240 之后再次测试,分辨率在 600*1024 及其以上的设备上时,由于其默认DPI被归于160,所以字体等被缩小了,显得有些过小,不太合适。
4. 我的应用最终所采用的方案,共有以下几点(几乎把能用的都用上了):
1) 不使用自动缩放;(自动缩放存在一些问题)
2) 自定义 extends RuntimeDPIProvider 类,改写默认的 runtimeDPI 策略;
主要改写之处:
a) 分辨率在600*1024以上的设备的DPI,默认情况下均近似为160,而这些设备的物理尺寸相对大部分手机来说是绝对够大的,所以将其改写为归于 240之列;
b) Motorola 的几款手机分辨率高于480*800,如 Atrix 4G,其分辨率为540*960,实际DPI为275,默认情况下被归于240之列,但由于其物理尺寸并未明显加大,导致应用的字体等看起来偏小,所以将其改写为归于320之列。
注:Moto Atrix 4G真机下运行测试,发现所得到的screenDPI居然是240,其桌面模拟环境是275,于是自定义的RuntimeDPIProvider 还得略作调整。
3) 位图资源按ldpi、mdpi、hdpi分别准备3套 (这个无论是否自动缩放均需要);
4) css中使用@media 设置fontsize;
5) 使用物理单位,设置时将其换算成pixels,主要用在需要固定高度的地方,如view的顶部、底部、某些item等;
6) 根据分辨率scale组件,主要用在弹出窗口的宽度设置上,当分辨率较高时适当加宽以保证较佳的显示效果;
7) 自定义style属性,将诸如高度、间距等既需要固定值又需要根据DPI调整的地方,以这种方式绑定到css中,通过@media方式设置其值。(查看SDK源码发现其对Button组件icon的处理也是采用style方式实现的 , 所以还是得多爬爬源码啊 )
8) 启动时所显示的图片,如果在应用程序mxml文件里通过 splashScreenImage 指定1个图片的话(同时通过 splashScreenScaleMode 设置拉伸方式),则在某几个分辨率下,图片拉伸后的效果是欠佳的。
Flex移动skin – 第2部分:处理不同的像素密度 和 Dynamic Splash Screen Improvements 中提到的方法经过实践,无法通过编译,可能是SDK(版本应该是4.6.0的)不同的缘故。根据文章提供的思路,目前我采用以下方法来解决:
注:FlexSDK v4.6.0中已经添加了 SplashScreenImage、SplashScreenImageSource,以下方法仅适用于 4.5.x 版本,当运行于 4.6.0 环境时,启动界面会出现2次,第2次所显示的图片有点不正确,有空白出现(这个问题MS是另外写了个首页导致的)。
a) 复制spark.preloaders.SplashScreen 为1个新类,application 的 preloader 属性设为该新类,splashScreenScaleMode 属性可用可不用(依赖于你对SplashScreen所进行的改动);
b) 新类相较于原SplashScreen类,主要改动 initialize() 方法中 if ("splashScreenImage" in info) 处,原 splashScreenImage 值是1个固定的图片source,这里要把它改成根据不同的dpi、分辨率来得到其图片source,所以我在 if ("splashScreenImage" in info) 之前增加了获取图片source的逻辑,同时对 if ("splashScreenImage" in info) 这个语句也进行了相应修改,修改后的代码片段示例如下:
public function initialize():void { ... if (!info) return; // 以下是我增加的 var runtimeDPIClass:Class = info["runtimeDPIProvider"]; var runtimeDPIProvider:RuntimeDPIProvider = new runtimeDPIClass(); buildSplashImage(runtimeDPIProvider.runtimeDPI); // 原if改为如下 // if ("splashScreenImage" in info) if (splashImage != null) { // 注释掉以下5行 /*var SplashImageClass:Class = info["splashScreenImage"]; this.splashImage = new SplashImageClass(); this.splashImageWidth = splashImage.width; this.splashImageHeight = splashImage.height; addChild(splashImage as DisplayObject);*/ ... } // 根据不同的dpi获取不同的图片源 private function buildSplashImage(dpi:Number):void { // 具体的图片源逻辑主要通过类 MultiDPISplashScreen 的 getImageClass 方法中进行处理,这里就不贴源码了 var splashImageClass:Class = new MultiDPISplashScreen().getImageClass(dpi); splashImage = new splashImageClass(); if (splashImage != null) { splashImageWidth = splashImage.width; splashImageHeight = splashImage.height; addChild(splashImage as DisplayObject); } }
总之,Adobe 对开发mobile应用的支持还是很不错的,本身提供的模拟器在调试UI方面比较完善,常见的设备均有,并且也可以自行添加,显示效果与真机相仿,为我们调试在不同设备的显示效果提供了良好的支持。
另外,这个模拟器虽然只是提供了一个显示屏幕和menu、back、search按钮、旋转屏幕功能,但也是因为这样使得它在PC上运行的速度较快,不像Android模拟器那样耗资源。