最近、Amay師匠の影響でXamarinを触り始めたkochizufanです。
よろしくお願いします。
今までHTML5+ラッパー系ソリューションでのプラットフォーム横断開発環境の構築に、何度もがっかり感を感じてきてただけに、Xamarinにも最初は懐疑的でした。
が、Javaもc#も判らない(Obj-Cは経験あり)ながら、落ちているサンプルを動かしてみるとなんかすげえ動く!無理なく動いてる!
JavaScriptだとなんか無理矢理動かしてる感ありありで、ちょっと手を加えるとすぐに破綻する危うさがあったのが、全くそんな危うさを感じさせません。
そう、これですよこれ。これが欲しかったんです。
Write Once Run Anyware的なものも大切ですが、それで実現できる事のクオリティが下がってしまうと何にもなりません。
飽くまでUI部品なんかは各プラットフォームで最適なものを集めながら、それを使い慣れた環境で統合できて、知識リソースの最小化を図れる、これが理想じゃないでしょうか。
というわけで、一瞬でXamarinにハマってしまいました。
さっそく、師匠のこちらの記事のXamarin化を測ってみたのですが、さすがにJavaもc#も知識のない人間が、軽々すぐカスタマイズもできるほど甘くはなく、いくつかハマりどころ(言語仕様知識のある人ならハマらなかったかもですが)もありました。
いくつかをここで書いておきたいと思います。
元記事のJavaソースではOpenStreetMapのタイルプロバイダを生成する時に、UrlTileProviderのインスタンスを作って、そのインスタンスのメソッドをオーバーライドしてる感じでしたが、どうもこのやり方はc#ではできないみたいです。
なので素直にサブクラスを書いてやりました。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content.PM;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Android.OS;
using Android.Support.V4.App;
using Android.Widget;
using Java.Net;
namespace MapsAndLocationDemo
{
class OsmTileProvider : UrlTileProvider
{
private static string OsmUrlFormat =
"http://tile.openstreetmap.org/{0}/{1}/{2}.png";
public OsmTileProvider() : base (256, 256)
{
}
public override URL GetTileUrl(int x, int y, int zoom)
{
string s = String.Format(OsmUrlFormat, zoom, x, y);
URL url = null;
//try {
url = new URL(s);
//} catch ( MalformedURLException e) {
// throw new Java.Lang.AssertionError(e);
//}
return url;
}
}
}
Xamarin Studioが発行してくれた読み込みライブラリの他に、Maps SDKを使うための Android.Gms.Maps, Android.Gms.Maps.Model 等をusingに追加しています。
ハマりどころいくつかとしては、まずURLクラスというのがないと言われ、どう置き換えればいいのか判らなかったのですが、IDEの補完機能で GetTileUrlの戻り値定義はJava.Net.URLである、というのが出てきたので気付けました。
using Java.NetしてURLクラスでうまくいきました。
が、同様にtry 〜 catchのところも、Java.Net.MalformedURLExceptionとJava.Lang.AssertionErrorにしてやればうまくいくと思ったんですが、何故か「Java.Lang.AssertionErrorコンストラクタの引数はbool値だよ!」というエラーが出て怒られて解決できませんでした。
なのでとりあえずエラートラップは外してあります。
この辺師匠が教えてくれればいいな…。
もう一つは、言語仕様知ってれば当たり前なのでしょうが、コンストラクタの定義で親クラスコンストラクタとの関係を書かないといけない、というのに気付けず手間取りました。
public OsmTileProvider() : base (256, 256)
のbase〜のところで、親クラスのコンストラクタに食わせる引数を定義してやればよかったのですね。
さて、タイルプロバイダクラスの定義ができれば、次は地図への追加です。
今回、ベースプログラムは XamarinのMaps API v2サンプルコードにタイルを追加してやりました。
mapオブジェクトができているところでAddTileOverlayしてやらないといけないわけですが、あまりよく判ってないんですけどSupportFragmentManagerというのを使ってオブジェクトの遅延生成をやっている?っぽくて、コンストラクタや初期化メソッドの中ではまだmapオブジェクトができていません。
OnResumeイベントメソッドの中だと既に生成されているようなので、その末尾にAddTileOverlayを加えてやりました。
(略)
protected override void OnResume()
{
base.OnResume();
var mapFragment = (SupportMapFragment) SupportFragmentManager.FindFragmentByTag("map");
// The value of mapFragment.Map may be null if the mapFragment isn't completely initialize yet.
// This will cause problems with other things too, like the CameraUpdateFactory.
// By initializing our GoogleMap here in OnResume, the MapFragment should be
// properly instantiated and ready for use.
_map = mapFragment.Map;
Debug.Assert(_map != null, "The _map cannot be null!");
// We create an instance of CameraUpdate, and move the map to it.
var cameraUpdate = CameraUpdateFactory.NewLatLngZoom(VimyRidge, 15);
_map.MoveCamera(cameraUpdate);
//この辺追加
var tileProvider = new OsmTileProvider();
var tileOptions = new TileOverlayOptions();
_map.AddTileOverlay( tileOptions.InvokeTileProvider(tileProvider) );
}
(略)
これで実機実行してやると、いい感じでOSM表示されます。
ただ、このOnResumeメソッド、非アクティブ状態からアクティブになる度に呼ばれるので、その度にAddTileOverlayが走るのであまりよろしくない気がします。
本来は別のところでやるべきなのか(SupportFragmentManagerとよしなにする方法がある?)、それとも追加済みフラグ等を作って対応すべきなのか、一般的なやり方はどうなのか判りませんが、とりあえずXamarinでも間違いなく、Google Maps API Android v2の大きな特徴である、タイルオーバーレイ機能等が使える事がわかりました。
やっぱXamarinすごい…。