本项目的目标是使用户更方便的筛选和选购手机,通过手机图片的动画和过渡等效果让用户在视觉和筛选流程上得到更满意的体验,保留最近四次选择的手机历史列表。选中一个手机会在后端通过关联算法得到用户最感兴趣的6个手机组成的列表。下面简单介绍下程序中所使用的技术细节。
RSL(Runtime Shared Libaray)即为运行时共享库,只有当程序运行时才加载所需要的资源,包括图形元件,图片,定制化的组件等,这样就减小了编译后的swf文件的大小,如果一次性的都包含到主程序中,编译后的文件会很大,用户加载时就会变慢。共享库的格式有两种,一种是后缀名为swz的文件,这是adobe的官方签名,普通用户是无权创建的,它可以缓存在用户本地的磁盘文件中(C:\Documents and Settings\{user}\Application Data\Adobe\Flash Player\AssetCache),如果不手动删除会一直存在,flash player 9以上的版本才支持;另一种就是常规的swf文件,可以由普通用户创建,它缓存在浏览器中。
当使用一个元件时,它编译后的大小可能有几KB到十几KB,这时可以考虑把编译后的文件单独提取出来,在运行时加载使用。先建一个fla文件,在库里面建所需要的元件,类型为影片剪辑,这里命名为Topbar,然后导出。
之后发布生成一个叫做source.swf的文件。在主应用程序使用这个元件的时候,用Loader类去加载它,加载的代码如下:
- loader = new Loader();loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handler);loader.load(new URLRequest("rsl/source.swf")); private function handler(event:Event):void { var BarClass:Class = loader.contentLoaderInfo.applicationDomain.getDefinition('Topbar') as Class; var bar:* = new BarClass(); addChild(bar); }
在被加载程序的应用域下得到类引用,实例化后就可以使用。项目中是使用BulkLoader[1]类加载的:
- var sourceMC:MovieClip = bulkLoader.getMovieClip("source"); var BarClass:Class = sourceMC.loaderInfo.applicationDomain.getDefinition('Topbar') as Class;
不同点在于加载成功后applicationDomain的所有者不同:contentLoaderInfo和loaderInfo。Loader对象(Loader Object)的contentLoaderInfo属性就是被加载对象(Loaded Object)的loaderInfo属性,当被加载对象成功加载后,这两者完全等同。sourceMC是被加载的对象,所以sourceMC.loaderInfo.applicationDomain就是被加载程序的应用域。
如果用AS3写一个定制化的组件,也想用动态加载的方式引入到程序中来,就需要制作Actionscript Module了,这样做的好处不仅是可以通过module的缓存增加加载速度,同时在多个应用程序部署时,组件可以被多次使用,而不是把代码到处拷来拷去。
在项目中新建一个模块类,在模块类中声明要使用的组件类引用,这里想把BulkLoader类组件在运行时刻加载。
- package module{ import br.com.stimuli.loading.BulkLoader; import br.com.stimuli.loading.lazyloaders.LazyXMLLoader; import flash.display.Sprite; public class BulkLoader_rsl extends Sprite{ public function BulkLoader_rsl(){ BulkLoader; //声明想要引用的类 LazyXMLLoader;//声明想要引用的类 } } }
之后在flexbuilder中右击项目的属性,选中ActionScript模块,添加模块类路径
src\module\ BulkLoader_rsl.as:
点确定后会重新编译module模块,在发布目录中生成module/ BulkLoader_rsl.swf。重点是如何加载这个模块:
- var request:URLRequest = new URLRequest("module/BulkLoader_rsl.swf"); var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain); this.loader = new Loader(); this.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); this.loader.load(request, context);
和之前相比不同的是,其中要将被加载对象的应用域指向当前应用域(如果被加载swf和主swf不在同一个服务器域下,设应用域是无效的),执行加载的swf和被加载的swf必须处于同一个应用域。new LoaderContext(false, ApplicationDomain.currentDomain)通过这条语句实现[2]。ApplicationDomain 类的用途是存储 ActionScript 3.0 定义表,SWF 文件中的所有代码被定义为存在于应用程序域中。这样才可以访问到被加载swf中定义的类引用。
- this.loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onComplete); this.LazyXMLLoader = getDefinitionByName("br.com.stimuli.loading.lazyloaders::LazyXMLLoader") as Class;
同样通过反射得到当前的类引用,同时要写清楚自定义组件的命名空间,这时这个类引用可以使用了。
优点:
缺点:
对于程序中动态加载的几个资源(如元件和背景图),本来想写一个外部配置文件来读取的,这样即使更换背景图也不需要重新编译,但是BulkLoade中的LazyLoader组件已经帮我们做好的这件事,而且功能更加高级。它约定了一种XML格式,读取之后内部转化为相应的LoadingItem来加载,这个过程是透明的。
- <BulkLoader> <name>lazyLoader</name> <numConnections>5</numConnections> <stringSubstitutions> <base_path>http://localhost/</base_path> </stringSubstitutions> <allowsAutoIDFromFileName>true</allowsAutoIDFromFileName> <files> <file> <url>assets/sfbg.jpg</url> <id>bg</id> <!–<url>{base_path}assets/sfbg.jpg</url> –> <!–<checkPolicyFile>true</checkPolicyFile> –> <!– <preventCache>false</preventCache>–> <!–<type>image</type>–> </file> <file> <url>rsl/source.swf</url> <id>source</id> </file> </files> </BulkLoader>
numConnections是加载时的并发连接数,base_path可以更容易的更换服务器根路径;每一个file标签定义一个加载资源的相关信息:url路径,是否检查策略文件,是否使用缓存(通过加随机数方式),资源类型等。可以在资源全部加载完成后加监听,也可以通过引用相应的id在每个资源加载完成后分别加监听:
- private function onConfigLoaded(event:Event):void{ lazy.addEventListener("complete", onAllItemsLoaded); lazy.get("bg").addEventListener("complete", onImgComplete); lazy.get("source").addEventListener("complete", onAssetsComplete); }
这里在背景图加载完成后,做设置相应大小,位图处理等工作;在元件加载完成后,将引用的元件放在舞台上,资源都加载完成后再初始化舞台上的其他组件。
每次拿到一张手机图片,背景底色都是白色的,做图片展示效果的时候很难看,这里通过读取位图像素的方式将白色的底色去掉。
算法思路如下:
首先设置一个阀值,纵向遍历位图中的每一个像素,如果低于这个阀值,统一设为透明值。遍历为纵向从上至下遍历,根据实际的图片情况,通常一开始都是阀值以上的白色值,如果遇到第一个阀值以上的点,表明可能已经到了手机的边缘,这时break这个循环,x坐标不变,反过来从下而上进行同一个轨道上的遍历,直到又遇到阀值以上的点。这样就可以避免手机中有白色部分的点被错误的过滤掉。同时,x坐标从中间开始向两边遍历,这样就省去了左右两侧白底无用的遍历查找。
主要算法部分:
- for (x = middle; x < width; x += 1){ for (y= 0; y < height; y += 1){ pixelValue = result.getPixel32(x, y); if(pixelValue <= 0xFFFFFFFF && pixelValue >= 0xffededed){ result.setPixel32(x, y, 0×00FFFFFF); }else { //arrive top edge if (y < top) { top = y; //confirm top } inTopEdge = true; break; } } if (inTopEdge) { for (m = height-1; m >= 0; m -= 1){ pixelValue = result.getPixel32(x, m); if(pixelValue <= 0xFFFFFFFF && pixelValue >= 0xffededed){//0xffeaeaea result.setPixel32(x, m, 0×00FFFFFF); }else { if (m > bottom) { //confirm bottm bottom = m; } break; } } }else{ //along the y to the bottom right = x; //confirm right break; //stop outer loop } inTopEdge = false; }
程序体一个大循环套两次小循环,外面的大循环沿着x轴走,里面的小循环一次沿着y轴从上而下走,一次沿着y轴从下而上走。所有在0xFFFFFFFF和0xffededed这两个值之间的点,全部设为0×00FFFFFF全透明,其中0xffededed为在实际尝试中得到的一个比较好的效果值。在遍历的过程中,记录下手机所占矩形四个点top,bottom,left,right的位置,之后利用copyPixels方法得到一个新的位图,这个位图就是所需要的大小刚好为手机所占位置且背景为透明的图片。这个图片边缘上可能会有一些锯齿,加上一个轻微的模糊效果可以掩盖住。
前后结果如下:
参考资料:
[1] BulkLoader是一个开源的AS3组件,它对加载的资源队列进行管理,具体细节参看以下链接。
参考资料:http://code.google.com/p/bulk-loader/
[2] LoaderContext的三个属性
1.checkPolicyFile,只有加载图像时才使用这个属性,执行加载的swf和要加载的图像所在的域不同,要在图片所在的服务器添加url策略文件,将checkPolicyFile设为TRUE。
2.securityDomain,只有加载swf文件时使用此属性。两个swf处于不同的域中,如果执行通信有两种做法,第一种是将执行加载的loader的loaderContext中的securityDomain设为SecurityDomain.currentDomain,这意味着被加载的swf导入到执行加载的swf所在的沙箱,好比从自己的服务器上加载自己的swf一样,但前提是被加载swf所在的服务器上有url策略文件;第二种方式是通过在被加载文件中加入Security.allowDomain()来允许加载。区别在于后者的沙箱仍然保持在原始沙箱,所以可以访问自己服务器的资源,但前种方式不可以。
3.applicationDomain,仅当加载使用 ActionScript 3.0 编写的 SWF 文件(不是图像或使用 ActionScript 1.0 或 2.0 编写的 SWF 文件),只有当被加载的swf文件位于执行加载的swf的安全域时,才可以指定这个属性,这有两种情况,一种是两者处于同一个服务器,另一种是上文说的securityDomain属性中的第一种导入的加载方式。通过将加载的 SWF 文件放在同一个应用程序域中,可以直接访问它的类,或通过其关联的类名访问嵌入的元件等其他资源。