ディーバ Blog

大阪発 C#の会社、株式会社ディーバの Blog です。

Xamarin.Android の Google Maps でカスタムした情報ウィンドウの表示

Xamarin.Android の Google Maps で、独自の情報ウィンドウ (info Window) を表示します。

Google Maps の表示 はこちらから。

カスタムした情報ウィンドウの表示

View の定義

axml ファイルで定義した View を情報ウィンドウとして表示します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_gravity="center_vertical"
        android:src="@drawable/ic_train_black"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:alpha="0.54" />
    <LinearLayout
        android:layout_gravity="center_vertical"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:orientation="vertical"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1">
        <TextView
            android:id="@+id/info_title"
            android:text="example"
            android:textColor="@color/primary_text"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:textSize="16sp" />
        <TextView
            android:id="@+id/info_snippet"
            android:text="example"
            android:textColor="@color/secondary_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="14sp" />
    </LinearLayout>
    <ImageView
        android:layout_gravity="center_vertical"
        android:src="@drawable/ic_navigate_next_black"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginRight="8dp"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:alpha="0.54" />
</LinearLayout>

IInfoWindowAdapter の実装

IInfoWindowAdapter を実装したクラスを作成します。

吹き出しの中身を置き換えるには、GetInfoContents で View を返します。情報ウィンドウすべてを置き換えるには GetInfoWindow で View を返すようにします。

using Android.Views;
using Android.Widget;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;

public class InfoWindowAdapter : Java.Lang.Object, GoogleMap.IInfoWindowAdapter
{
    private LayoutInflater inflater;

    public InfoWindowAdapter(LayoutInflater inflater)
    {
        this.inflater = inflater;
    }

    public View GetInfoContents(Marker marker)
    {
        var view = inflater.Inflate(Resource.Layout.InfoWindow, null);
        
        var title = view.FindViewById<TextView>(Resource.Id.info_title);
        title.Text = marker.Title;

        var snippet = view.FindViewById<TextView>(Resource.Id.info_snippet);
        snippet.Text = marker.Snippet;

        return view;
    }

    public View GetInfoWindow(Marker marker)
    {
        return null;
    }
}

InfoWindowAdapter の適用

作成した Adapter を、GoogleMap オブジェクトに設定します。

public void OnMapReady(GoogleMap map)
{
    map.SetInfoWindowAdapter(new InfoWindowAdapter(LayoutInflater));
}

結果

マーカーを追加すると、Adapter で処理した情報ウィンドウが表示されます。

var m = new MarkerOptions();
m.SetPosition(new LatLng(34.6925497, 135.5016865));
m.SetTitle("淀屋橋駅");
m.SetSnippet("大阪府大阪市中央区北浜3丁目1-25");
var marker = map.AddMarker(m);

f:id:jz5_diva:20160421154804p:plain

オマケ1: 情報ウィンドウのタップ時に処理するには

情報ウィンドウがタップされたときに何か処理する場合は、InfoWindowClick イベントを使います。

map.InfoWindowClick += Map_InfoWindowClick;

オマケ2: 情報ウィンドウをコードから表示するには

情報ウィンドウをコードから表示するには、Maker の ShowInfoWindow メソッドを使います。

maker.ShowInfoWindow();

Xamarin.Android で Google Maps の表示とマーカー・情報ウィンドウの表示

地図の表示

Xamarin.Android で Google Maps の表示は、Xamarin.Androidで地図を表示するには?(Google Maps使用) - Build Insider などを参考にしてください。

地図の操作

地図を操作するには、IOnMapReadyCallback を実装し、GoogleMap オブジェクトを取得します。

public class MapActivity : Activity, IOnMapReadyCallback
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        // MapFragment から GoogleMap の取得
        var mapFrag = (MapFragment)FragmentManager.FindFragmentById(Resource.Id.map);
        mapFrag.GetMapAsync(this);
    }

    public void OnMapReady(GoogleMap map)
    {
        // map を使って操作
        map.MyLocationEnabled = true;
        map.UiSettings.MyLocationButtonEnabled = true;
    }
}

地図の移動

少しだけめんどうです。

var builder = CameraPosition.InvokeBuilder();
builder.Target(new LatLng(34.686564, 135.503155)); // 経緯度
builder.Zoom(8); // ズームレベル
var cameraUpdate = CameraUpdateFactory.NewCameraPosition(builder.Build());
map.MoveCamera(cameraUpdate);

移動アニメーションを行うには、MoveCamera の代わりに AnimateCamera メソッドを使います。

マーカーと情報ウィンドウの表示

デフォルトのマーカーと情報ウィンドウを表示する方法です。

var m = new MarkerOptions();
m.SetPosition(new LatLng(34.686564, 135.503155));
m.SetTitle("株式会社ディーバ");
m.SetSnippet("大阪発 C#の会社");

// マーカー追加
var marker = map.AddMarker(m);

f:id:jz5_diva:20160421151748p:plain

次の記事では、カスタムした情報ウィンドウを表示します。

UWP アプリで Google Maps の表示

UWP (Universal Windows Platform) で Google Maps を表示するには、WebView と Google Maps JavaScript API を使うしかないようです。

WebView の配置

XAML に WebView を配置します。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <WebView x:Name="webView"></WebView>
</Grid>

地図を表示する HTML ファイル の作成

HTML ファイルとして Google Maps を表示するコードを記述します。

プロジェクトに HTML ファイルを追加し(ここでは map.html)、ファイルのビルドアクションは「コンテンツ」を選び、「出力ディレクトリにコピー」は、コピーする項目を選びます。

また、Google Developer Console から、Google Maps JavaScript API を有効にし、ブラウザーキーを取得します。

<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    <meta charset="utf-8" />
    <title>Map</title>
    <style type="text/css">
        html, body {
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #map {
            height: 100%;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script type="text/javascript">

        var map;
        function initMap() {
            map = new google.maps.Map(document.getElementById('map'), {
                center: { lat: 34.686564, lng: 135.503155 },
                zoom: 8
            });
        }

        function setCenter(lat, lng) {
            var latLng = new google.maps.LatLng(lat, lng);
            map.setCenter(latLng);
        }
    </script>
    <script async defer
            src="https://maps.googleapis.com/maps/api/js?key=**API_KEY**&callback=initMap">
    </script>
</body>
</html>

HTML ファイルを読み込み WebView に表示

プロジェクトに追加した HTML ファイルを読み込み、WebView に表示します。

string html;

var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///map.html"));
using (var st = (await file.OpenReadAsync()).AsStream())
using (var r = new StreamReader(st))
{
    html = await r.ReadToEndAsync();
}
            
// 文字列から WevView に表示する内容を設定
webView.NavigateToString(html);

実行結果

f:id:jz5_diva:20160419234223p:plain

Windows 10 Mobile MADOSMAでは、かなりストレスのある操作性でした。

地図が表示されず、map.html を直接 Web ブラウザーで開いて「このページでは Google マップの要素を表示できませんでした。」と表示される場合は、Google Developer Console で JavaScript API を有効にしているか確認しましょう。

JavaScript と C# のコード間でのやりとり

C# → JavaScript

JavaScript の関数を WebView 経由で呼び出して地図の操作ができます。

await webView.InvokeScriptAsync("setCenter", new string[] { "34.686564", "135.503155" });

JavaScript → C#

C# で JavaScript からの呼び出しを受けるには、WebView.ScriptNotify を使います。

webView.ScriptNotify += (sender, args) =>
{
    System.Diagnostics.Debug.WriteLine(args.Value);
};

JavaScript 側は、window.external.notify で値を送ります。

window.external.notify("value");

共通のコードで iOS/Android/UWP のダイアログを表示できる ACR User Dialogs for Xamarin and Windows

ACR User Dialogs for Xamarin and Windows

ACR User Dialogs for Xamarin and Windows を使うと、MvvmCrossMVVM パターンの ViewModel のコード(PCL プロジェクト側)から、ダイアログを表示するコードを簡単に書けます。MvvmCross 用のプラグインもあり MvvmCross のサイトからも紹介されいています

ダイアログ (Alert) だけでなく、ローディング画面なども呼び出せます。機能は次の通り。

  • Action Sheet (multiple choice menu)
  • Alert
  • Confirm
  • Loading
  • Login
  • Progress
  • Prompt
  • Toasts

NuGet からプロジェクトにインストールします。ViewModel 側(PCL プロジェクト)と、View 側の各プラットフォームのプロジェクトそれぞれにインストールします。

公式のサンプルコードは GitHub の examples にあります。

Dialog

using using Acr.UserDialogs;

UserDialogs.Instance.Alert("たこ焼きです", "確認", "OK");

f:id:jz5_diva:20160421102435p:plain

Action Sheet

using using Acr.UserDialogs;

var config = new ActionSheetConfig();
config.Title = "メニュー";
config.Add("たこ焼き", () => {/* Do something */ });
config.Add("お好み焼き", () => {/* Do something */ });
UserDialogs.Instance.ActionSheet(config);

f:id:jz5_diva:20160421103041p:plain

Confirm

using using Acr.UserDialogs;

var config = new ConfirmConfig();
config.Title = "確認";
config.Message = "たこ焼きを購入します";
config.OnConfirm = (result) =>
{
    /* Do something */
};
config.OkText = "購入";
config.CancelText = "キャンセル";
UserDialogs.Instance.Confirm(config);

f:id:jz5_diva:20160421102456p:plain

おわりに

上記以外にももう少し細かなオプションや、非同期呼び出しなども用意されています。使用感として、View 側に一切コードが不要のため手軽で便利ですが、Android ではスタイルを適用できないなど、少しこだわったアプリでは使いづらいところもあります。

Xamarin.iOS の Google Maps で独自マーカーと情報ウィンドウの表示

Xamarin.iOS の Google Maps でマーカー画像の変更と、カスタムした情報ウィンドウ (InfoWindow) の表示方法です。

Google Maps の表示方法 はこちらから。

マーカーと情報ウィンドウの表示

デフォルトのマーカーと情報ウィンドウの場合。Title・Snippet プロパティを指定すれば情報ウィンドウもタップ時に表示されます。

var marker1 = new Marker()
{
    Title = "株式会社ディーバ",
    Snippet = "大阪発 C# の会社",
    Position = new CLLocationCoordinate2D(34.686564, 135.503155),
    Map = mapView
};

f:id:diva_osaka:20160419162542p:plain

マーカーの画像の変更

Icon プロパティを変更するだけです。InfoWindowAnchor プロパティで、情報ウィンドウの表示位置を調整できます。

var marker1 = new Marker()
{
    Title = "株式会社ディーバ",
    Snippet = "大阪発 C#の会社",
    Position = new CLLocationCoordinate2D(34.686564, 135.503155),
    Icon = UIImage.FromBundle("ic_place_48pt"),
    InfoWindowAnchor = new CGPoint(0.5, 0),
    Map = mapView
};

f:id:diva_osaka:20160419162552p:plain

カスタムした情報ウィンドウの表示

すべて置き換え

情報ウィンドウすべてを独自に置き換える場合は、MarkerInfoWindow プロパティを使って View を返すメソッドを指定します。

mapView.MarkerInfoWindow = new GMSInfoFor((view, marker) =>
{
    var label = new UILabel();
    label.Text = marker.Title;
    label.BackgroundColor = UIColor.Red;
    label.SizeToFit();
    return label;
});

f:id:diva_osaka:20160419164322p:plain

xib ファイルに見た目を定義して、View として返すには、次のようになります(参考: Using the View .xib Template - Xamarin)。

mapView.MarkerInfoContents = new GMSInfoFor((view, marker) =>
{
    var arr = NSBundle.MainBundle.LoadNib("InfoWindowView", null, null);
    return Runtime.GetNSObject<UIView>(arr.ValueAt(0));
});

中身のみ置き換え

情報ウィンドウの中身のみ独自に置き換える場合は、MarkerInfoContents プロパティを使って View を返すメソッドを指定しますが、吹き出しが隠れてしまうことがあり、いい感じにできません。

mapView.MarkerInfoContents = new GMSInfoFor((view, marker) =>
{
    var v = new UIView();
    v.Frame = new CGRect(0, 0, 200, 100);
    v.BackgroundColor = UIColor.Red;
    return v;
});

f:id:diva_osaka:20160419165306p:plain

解決方法は、不明です。View が正方形であれば(?)、正しく吹き出し内に表示されるようです。

Xamarin.iOS で Google Maps の表示

Google Maps SDK for iOS を利用できる Xamarin.Google.iOS.Maps ライブラリを NuGet からインストールします。

古くから Xamarin 用コンポーネントがあったようですが、一時期 Google Maps のアップデートに対応できず使えない状態だったようです。

API Key の設定

Google Developer Console から Google Maps SDK for iOS を有効にし、iOS API キーを取得します。

キーは、AppDelegate 内で設定します。

using Google.Maps;

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    MapServices.ProvideAPIKey("iOS API Key");
    ...
}

地図の表示

ViewController 内で生成して View に表示します。

using Google.Maps;
using CoreGraphics;

private MapView mapView;

public override void ViewDidLoad()
{
    base.ViewDidLoad();

    var camera = CameraPosition.FromCamera(34.686564, 135.503155, 6);
    mapView = MapView.FromCamera(CGRect.Empty, camera);

    View = mapView;
}

古いコードサンプルでは、mapView の StartRendering, StopRendering メソッドを記載していますが、deprecated のメソッドで不要のようです。

マーカーの表示

var marker1 = new Marker()
{
    Title = "株式会社ディーバ",
    Position = new CLLocationCoordinate2D(34.686564, 135.503155),
    Map = mapView
};

f:id:jz5_diva:20160419102955p:plain

リンク

Xamarin.Android + MvvmCross でアイコン付きリスト形式の AlertDialog を表示

Xamarin.Android と MvvmCross 4 でリスト形式の AlertDialog を表示します。

リスト形式の AlertDialog

普通のリスト形式の場合。MvvmCross は関係ありません。

f:id:jz5_diva:20160418154254p:plain

var alert = new AlertDialog.Builder(this);
alert.SetTitle("Title");

var items = new string[]
{
    "Item 1",
    "Item 2",
    "Item 3",
};

alert.SetItems(items, (s, a) =>
{
    var selectedItem = items[a.Which];
});

alert.Show();

アイコン付きリスト形式の AlertDialog

f:id:jz5_diva:20160418154259p:plain

AlertDialog の表示

呼び出し部分から。

var alert = new AlertDialog.Builder(this);
alert.SetTitle("Title");

var items = new List<ListItem>()
{
    new ListItem("Item 1", "res:ic_call_black"),
    new ListItem("Item 2", "res:ic_chat_black"),
    new ListItem("Item 3", "res:ic_email_black"),
};

var adapter = new ListAdapter(this, (IMvxAndroidBindingContext)BindingContext);

adapter.ItemsSource = items;

alert.SetAdapter(adapter, (s, a) =>
{
    var selectedItem = items[a.Which];
});

alert.Show();

MvxImageView の ImageUrl にリソース画像を指定するには「res:」を付けます。

リスト項目の View

アイコンとテキストを表示する MvxImageView と TextView を配置した axml ファイルを用意します。ImageUrl と Text プロパティを Binding 指定しています。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="56dp">
    <MvxImageView
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="24dp"
        android:layout_width="40dp"
        android:layout_height="40dp"
        local:MvxBind="ImageUrl ImageUrl" />
    <TextView
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="24dp"
        android:gravity="center_vertical"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:ellipsize="end"
        android:text="example"
        local:MvxBind="Text Text"
        android:textColor="@color/primary_text"
        android:textSize="16sp" />
</LinearLayout>

リスト項目の Class

リストの項目を表すクラスを作ります。

private class ListItem
{
    public string Text { get; set; }

    public string ImageUrl { get; set; }

    public ListItem(string text, string imageUrl)
    {
        this.Text = text;
        this.ImageUrl = imageUrl;
    }
}

カスタム Adapter

カスタム Adapter を作り、GetView 内で作成した View の Resource ID を指定します。

private class ListAdapter : MvxAdapter
{
    public ListAdapter(Context context, IMvxAndroidBindingContext bindingContext) : base(context, bindingContext)
    {
    }

    protected override View GetView(int position, View convertView, ViewGroup parent, int templateId)
    {
        templateId = Resource.Layout.ListItem; // 作成したリスト項目の View
        return base.GetView(position, convertView, parent, templateId);
    }
}