星期四, 4月 26, 2012

使用 Air Native Extension(ANE) 在 Air Mobile App


Air SDK 好像是到了 3.0 之後的版本都可以使用 Air Native Extension(ANE)
網路上有些神人們寫好且免費的 ANE 可以使用 以 iOS 來說就用過 iNativeAlert, AdMob iOS, Vibration, NetworkInfo 等
可以到 http://www.adobe.com/devnet/air/native-extensions-for-air.html 或是 http://extensionsforair.com/ 找找

紀錄一下怎麼使用 ANE 以 iOS Maps (http://code.google.com/p/air-maps-ane/) 當範例
1. 下載 ANE 主要要有 2 個檔 airmaps.ane & iMaps.swc
2. airmaps.ane 就在專案下開個目錄 ext 放到裡面吧
3. 在專案中把 iMaps.swc 加到元件庫 並設定外部元件庫 (就是編譯不包進去)

4. 設定 ANE 進 application.xml 裡
<extensions>
 <extensionID>com.sampleNativeExtensions.Maps</extensionID>
</extensions>

5. 寫 CODE
private var googleMap:Map;  
public function iMapTest() {   
 var sp:Sprite = new Sprite();
 sp.graphics.beginFill(0xDD0000);
 sp.graphics.drawCircle(0, 0, 40);
 sp.graphics.endFill();
 addChild(sp);
 sp.x = stage.stageWidth >> 1;
 sp.y = 40;
 
 var viewPortMap:Rectangle = new Rectangle(0, 80, 320, 400);
 googleMap = new Map();
 googleMap.viewPort = viewPortMap;
 
 googleMap.setSize(new Point(320, 400));
 googleMap.setMapType(0);
 googleMap.setZoom(17);  
 googleMap.setCenter(new LatLng(25.18446, 121.7515));
 googleMap.visible = true;
 
 var marker:Marker = new Marker(new LatLng(25.18446, 121.7515));
 marker.title = "TEST";
 marker.subtitle = "AWESOME!!";
 googleMap.addOverlay(marker);
}

6. Build 專案 產出 SWF 檔 ((有用ANE好像都沒法在 Air Player 上先試跑...))
7. 編譯 IPA 檔 這邊比較特別的是 因為使用 ANE 原本的編譯指令後面要加上 -extdir 放置 .ane 檔目錄 一般情況的 ANE 應該這樣就可以產出 IPA 檔 不過 iOS Maps 這個 ANE 有用到 iOS SDK 的 MapKit 所以得再加上 -platformsdk iOS SDK 的路徑 也就是說如果要用這 ANE 必須在 Mac 電腦下執行... 在 Mac 下開啟終端機 輸入

adt 
-package
-target ipa-{看要包哪種形式}
-storetype pkcs12
-provisioning-profile {MOBILE_PROVISION 檔案位置}
-keystore {P12 檔案位置}
-storepass {P12 檔密碼}
iOSNativeMaps.ipa
application.xml
bin/iOSNativeMaps.swf
-extdir ext
-platformsdk /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk

很奇怪 我生成時有 warning 不過 IPA 還是產生出來了


8. 安裝到手機上看看 OK出來了~


使用原生的地圖效能真的比較好 不過這個 ANE 目前提供的 API 還不是很多 應該還是夠用... 如果嫌這 ANE 功能不足的話 就還是得用 StageWebView 來解決 然後再搭配 StageWebViewBridge 功能會更齊全

星期一, 4月 23, 2012

增加取消登入 FB 在 Adobe Air Mobile 發生錯誤


在Air上面使用 FB Mobile Api遇到一個問題...
就是預設的 FB 登入畫面並沒有取消登入的按鈕 所以就自己加了一顆按鈕在 FB 登入視窗的 StageWebView 外面(右上角X)
本想要讓 USER 點了按鈕後可以將 StageWebView 給拿掉 不管是改變 viewport 或是直接 dispose
所以一開始就先宣告了一個 StageWebView 使用這個 StageWebView 來處理有關 FB 的部分
private var _swv:StageWebView;

當點了取消登入 就去
_swv.viewport=new Rectangle();

不過有時要做這動作時 都會發生錯誤 錯誤原因是原本給 FB 用的 StageWebView 已經變成 null 當然就沒法進行操作

看了一下 當登入成功或失敗 在 MobileLoginWindow.as 的 handleLocationChange() 會將原本的 StageWebView 給 dispose
所以這時就不能再對餵給 FB 的 StageWebView 進行操作 看起來似乎沒有問題 不過 如果這時 USER 執行登出 並再次登入
因為原本的 _swv 已經被 dispose 所以當要再重新指定 viewport 或餵給 FB 就會發生錯誤

所以改了一下 FB 的 API

MobileLoginWindow.as
protected function handleLocationChange(event:Event):void
{
 var location:String = webView.location;
 if (location.indexOf(FacebookURLDefaults.LOGIN_FAIL_URL) == 0 || location.indexOf(FacebookURLDefaults.LOGIN_FAIL_SECUREURL) == 0)
 {
  webView.removeEventListener(Event.COMPLETE, handleLocationChange);
  webView.removeEventListener(LocationChangeEvent.LOCATION_CHANGE, handleLocationChange);
  loginCallback(null, FacebookDataUtils.getURLVariables(location).error_reason);
  userClosedWindow =  false;
  //webView.dispose();
  //webView=null;
 }

 else if (location.indexOf(FacebookURLDefaults.LOGIN_SUCCESS_URL) == 0 || location.indexOf(FacebookURLDefaults.LOGIN_SUCCESS_SECUREURL) == 0)
 {
  webView.removeEventListener(Event.COMPLETE, handleLocationChange);
  webView.removeEventListener(LocationChangeEvent.LOCATION_CHANGE, handleLocationChange);
  loginCallback(FacebookDataUtils.getURLVariables(location), null);
  
  userClosedWindow =  false;
  //webView.dispose();
  //webView=null;
 }
}

另外在 FacebookMobile.as 的 login() 不知WHY要每次呼叫登入都會產生一個新的 MobileLoginWindow
((可能是 FB 認為每次 StageWebView 都是全新的))
protected function login(callback:Function, stageRef:Stage, extendedPermissions:Array, webView:StageWebView = null, display:String = 'touch'):void {
 this.loginCallback = callback;
 this.stageRef = stageRef;
 if (!webView) {
  this.webView = this.createWebView();
 } else {
  this.webView = webView;
  this.webView.stage = this.stageRef;
 }
 this.webView.assignFocus();
 if (applicationId == null) {
  throw new Error(
   'FacebookMobile.init() needs to be called first.'
  );
 }
 //開過了就不再多開了
 if (loginWindow == null) {
  loginWindow = new MobileLoginWindow(handleLogin);
  loginWindow.open(this.applicationId,
   this.webView,
   FacebookDataUtils.flattenArray(extendedPermissions),
   display
  );
 }else {
  loginWindow.directShowWindow();
 }   
}

還有
protected function handleLogin(result:Object, fail:Object):void {
 //loginWindow.loginCallback = null; //因為不重複開LOGINWINDOW所以不清掉CALLBACK
....


回到 MobileLoginWindow.as 加上
public function directShowWindow():void {  
 showWindow(loginRequest);
}


這樣就 OK 了~
之後要關閉 FB 登入視窗 就直接 _swv.viewport=new Rectangle();
記得原本登入成功或失敗 都會去 call 的 loginCallback 自己加上 _swv 的後續處理
不太確定這方法對不對 還是有誤會 FB 原本的用法 但至少目前用起來還蠻 OK 的~

星期三, 4月 18, 2012

Sprite Sheet Packer

網路上找到的用 C# 寫的 Open Source 的 Sprite Sheet 產生軟體 http://spritesheetpacker.codeplex.com/
蠻簡單好用的 不過因為我都是要倒給 Starling 的 TextureAltas 用的 所以稍微改一下他的原始碼
在 \sspack\Exporters\TxtMapExporter.cs 檔
public string MapExtension
{
 get { return "xml"; }
}

public void Save(string filename, Dictionary<string, Rectangle> map)
{
 // copy the files list and sort alphabetically
 string[] keys = new string[map.Count];
 map.Keys.CopyTo(keys, 0);
 List<string> outputFiles = new List<string>(keys);
 outputFiles.Sort();

 using (StreamWriter writer = new StreamWriter(filename))
 {
  writer.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  writer.WriteLine("<TextureAtlas imagePath=\"" + Path.GetFileNameWithoutExtension(filename) + ".png\">");
  foreach (var image in outputFiles) {
   // get the destination rectangle
   Rectangle destination = map[image];

   // write out the destination rectangle for this bitmap
   writer.WriteLine(string.Format(
    "   <SubTexture name=\"{0}\" x=\"{1}\" y=\"{2}\" width=\"{3}\" height=\"{4}\" />", 
    Path.GetFileNameWithoutExtension(image), 
    destination.X, 
    destination.Y, 
    destination.Width, 
    destination.Height));
  }
  writer.WriteLine("</TextureAtlas>");
 }
}
有改的都標記出來了, 再重新編譯產出 sspack.exe 檔覆蓋掉原本的即可, 產出 XML 的看起來就會像是
<?xml version="1.0" encoding="UTF-8"?>
<TextureAtlas imagePath="AllTexture.png">
 <SubTexture name="apple" x="1284" y="221" width="299" height="35" />
 <SubTexture name="arrow" x="1584" y="221" width="20" height="11" />
 <SubTexture name="arrow_left" x="1140" y="975" width="23" height="29" />
 <SubTexture name="arrow_right" x="1939" y="197" width="23" height="29" />
</TextureAtlas>
這樣就可以直接把產出檔案 XML & PNG 餵給 Starling Framework 的 TextureAtlas 用囉~
如果沒錢買 TexturePacker 可以考慮試試看這個免費的軟體~