[Maps SDK for Android] 地図にMarkerを置く(その1・基本編)


最近 Maps SDK for Android でアレコレ試しています。前にも記事を書きましたが、地図を使って色々な表現ができるのは面白いですね。
その中でも、特に活用の幅が広そうな Marker について、この記事と次の記事の2つに分けて書いておこうと思いました。

  1. 基本機能だけでできること(この記事)
  2. Utility Library を使ってできること(次の記事)

この記事でやること

  • 地図上に Marker を置く
  • Marker の角度、動き、色、アイコン画像を変える
  • ユーザーからのインタラクションに反応させる
  • Info Window をカスタマイズする

注意事項

Android Studio で Maps SDK for Android が使えることを前提にしています。そのための手順は以前まとめましたので、そちらご確認下さい。

環境

このページに書かれているコード等は以下の環境下で動作・検証しています。

macOS Mojave バージョン10.14.6
Android Studio 3.5
Pixel3a + Android 9

また、ここに書いた内容をもとにしたサンプルコードを GitHub で公開しています。

そもそも Marker とは

下の画像がそれで、地図上で任意の位置を示すピンみたいなものです。

タップに反応させて、ピンが示す場所の説明などを出すことができます。このときに表示される小さな吹き出しをInfo Windowと呼びます。これもカスタマイズが可能で、かつユーザーからのインタラクションに反応させることもできます。

地図上に Marker を置く

前の記事でも書きましたが、基本は GoogleMap#addMarker を呼び出して配置します。
Marker の位置やタイトル、詳細などは、addMarker の引数である MarkerOptions にセットしていきます。このとき title や snippet など MarkerOptions のメソッドはメソッドチェーンで書くことができます。上図の Marker なら以下のようなコードになりますね。

addMarker(MarkerOptions()
    .title("大阪駅")
    .snippet("JR西日本 大阪環状線の駅")
    .position(LatLng(34.702485, 135.495951))
    .zIndex(2.0f)

Marker の角度、動き、色、アイコン画像を変える

Marker の要素は、それぞれ MarkerOptions の下記メソッドで設定します。

メソッド 引数の型 変更するもの
title String タイトル。Info Window 内に太字で表示
snippet String 説明。Info Window 内に表示
position LatLng Marker を置く場所(緯度・経度)
alpha Float アイコンの透過度。1.0fが不透明
rotation Float 回転角(時計回り)
draggable Boolean ドラッグ操作の可否
flat Boolean アイコンのフラット表示の有無
icon BitmapDescriptor アイコンの画像
zIndex Float 重なり。大きい方が前面

前項と同じ Marker を、これらのメソッドも指定して配置すると以下のようになります。単にデフォルト値をわざわざ指定してるだけなんですが…。

addMarker(MarkerOptions()
    .title("大阪駅")
    .snippet("JR西日本 大阪環状線の駅")
    .position(LatLng(34.702485, 135.495951))
    .alpha(1.0f)
    .rotation(0.0f)
    .draggable(false)
    .flat(false)
    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))
    .zIndex(2.0f)

角度を変える

rotation に Float 値をセットすると、ピンが時計回りに指定した角度だけ回転します。
rotation(90.0f)とするとこんな感じ。

動きを変える

Google Map では、地図に2本指を立てて引き起こすことで3D表示にしたり、2本指で地図を回転させたりという操作が可能です。そのときマーカーは地図の動きからは独立しており、立ったままの状態を保ちます。しかし、これを地図の動きと連動させることができます。
flat(true)とすると以下のように、地図が回転すると一緒に回転し(左)、3D表示になると地面に張り付き(右)ます。

色を変える

icon メソッドを BitmapDescripter オブジェクトを引数にして呼び出すと、アイコンの画像を変更することができます。 BitmapDescripter オブジェクトを生成するには、 BitmapDescripterFactory クラスを使用します。

色を変更するだけなら BitmapDescripterFactory クラスの defaultMarker メソッドと "HUE_" で始まる名前の定数を利用します。例えば緑にするならicon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))となります。

アイコンの画像を変える

アイコンの画像を変えることもできます。
前項でアイコンの色を変えたのと同じように、BitmapDescripterFactory クラスの fromXXXXX メソッドからの戻り値を MarkerOptions#icon メソッドに指定することで、通常のアイコンが任意のアイコンに変更されます。メソッド名の "XXXXX" は、その画像をどこから取得するかによって変わります。
例えば駅舎の画像が drawable/station_1.png にあるとして、それをアイコンに設定する場合はicon(BitmapDescriptorFactory.fromResource(R.drawable.station_1)) というようになります。

ユーザーからのインタラクションに反応させる

GoogleMap クラスにはいくつものリスナが定義されており、その中に Marker で発生したイベントに対するものがあります。

リスナ イベント
GoogleMap.OnMarkerClickListener Marker をクリック
GoogleMap.OnMarkerDragListener Marker をドラッグもしくはドロップ
GoogleMap.OnInfoWindowClickListener Info Window をクリック

上記は主なもので、他にも複数のリスナがあります。API リファレンスでご確認ください。
なお、リスナは Marker 自身にではなく GoogleMap に対して設定する ことに注意してください。

以下は、Marker をどこからどこまでドラッグ&ドロップしたのか、ドロップしたタイミングで Snackbar に表示しています。

googleMap.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener {
    private var start: LatLng? = null
    private var end: LatLng? = null

    override fun onMarkerDragStart(marker: Marker) {
        marker.position.let { start = LatLng(it.latitude, it.longitude) }
    }

    override fun onMarkerDrag(marker: Marker) {
        // Do Nothing.
    }

    override fun onMarkerDragEnd(marker: Marker) {
        marker.position.let {
            end = LatLng(it.latitude, it.longitude)

            if (start != null && end != null) {
                val message = String.format("(%.4f, %.4f) -> (%.4f, %.4f)", start!!.latitude, start!!.longitude, end!!.latitude, end!!.longitude)
                Snackbar.make(container, message, Snackbar.LENGTH_LONG).show()
            }
        }
    }
})

Info Window をカスタマイズする

Marker をタップすると表示される Info Window もカスタマイズすることができます。例えば、以下のような具合です。

GoogleMap.InfoWindowAdapter を使います。ここにどのようなレイアウトにしたいかを定義して、そのアダプタを GoogleMap#setInfoWindowAdapter でセットします。
上記の Info Window を表示するアダプタのコードは以下の通りです(レイアウトのコードは省略しています)。

class StationInfoWindowAdapter(private val context: Context) : GoogleMap.InfoWindowAdapter {
    override fun getInfoContents(marker: Marker): View? = null

    override fun getInfoWindow(marker: Marker): View? = setupWindow(marker)

    private fun setupWindow(marker: Marker): View =
        LayoutInflater.from(context).inflate(R.layout.info_window_station, null, false).apply {
            val station = marker.tag as Station

            findViewById<ImageView>(R.id.imageView).setImageResource(station.imageResId)
            findViewById<TextView>(R.id.textTitle).text = marker.title
            findViewById<TextView>(R.id.textSnippet).text = marker.snippet
        }
}

GoogleMap.InfoWindowAdapter では以下のメソッドを実装して View オブジェクトを返す必要があります。

メソッド 戻り値
getInfoWindow(Marker marker) Info Window 自体の View
getInfoContents(Marker marker) Info Window の中に表示される View

例えば上述のコードで getInfoWindow と getInfoContents の戻り値を逆にすると、以下のようになります。分かりにくいですが Info Window の中にパディングができており、レイアウトが入り込んでいます。

Info Window に適用される View をどのメソッドから得るかは、以下のように判断されます。

  1. getInfoWindow が View を返す場合は getInfoWindow
  2. getInfoWindow が null が返し、 getInfoContents が View を返す場合は getInfoContents
  3. getInfoWindow と getInfoContents の両方とも null を返す場合はデフォルトの View

まとめ

Maps SDK for Android に標準で用意されている手段で Marker や Info Window をカスタマイズする方法についてまとめてみました。
追加コンポーネントである Google Maps Android API Utility Library を使用すると、更に Marker でできることが広がります。
次の記事では Utility Library を使用して Marker をカスタマイズする方法を書きたいと思います。