[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 が使えることを前提にしています。そのための手順は[以前まとめました]((https://qiita.com/outerlet/items/0d74061fa5d625a33dfb))ので、そちらご確認下さい。

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

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

また、ここに書いた内容をもとにしたサンプルコードを [GitHub](https://github.com/outerlet/MarkerImpl) で公開しています。

# そもそも Marker とは
下の画像がそれで、地図上で任意の位置を示すピンみたいなものです。
<img width="130" alt="marker_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/27c021a3-f516-9094-ef2b-4c2d2ed17646.png" />

タップに反応させて、ピンが示す場所の説明などを出すことができます。このときに表示される小さな吹き出しを[Info Window](https://developers.google.com/maps/documentation/android-sdk/infowindows)と呼びます。これもカスタマイズが可能で、かつユーザーからのインタラクションに反応させることもできます。
<img width="220" alt="marker_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/b66b049a-3488-a1dc-7752-3a1601c4a333.png" />

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

```kotlin
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 を、これらのメソッドも指定して配置すると以下のようになります。単にデフォルト値をわざわざ指定してるだけなんですが…。

```kotlin
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)```とするとこんな感じ。
<img width="120" alt="marker_3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/614f986d-3651-1897-5c54-7099b548d0b4.png">

### 動きを変える
Google Map では、地図に2本指を立てて引き起こすことで3D表示にしたり、2本指で地図を回転させたりという操作が可能です。そのときマーカーは地図の動きからは独立しており、立ったままの状態を保ちます。しかし、これを地図の動きと連動させることができます。
```flat(true)```とすると以下のように、地図が回転すると一緒に回転し（左）、3D表示になると地面に張り付き（右）ます。
<img width="90" alt="marker_4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/d1562829-127d-09ac-f951-4f5c6c8be187.png"> <img width="120" alt="marker_5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/0d9a2b00-194f-92c7-c60f-14ab58eb6ad8.png">

### 色を変える
icon メソッドを BitmapDescripter オブジェクトを引数にして呼び出すと、アイコンの画像を変更することができます。 BitmapDescripter オブジェクトを生成するには、 [BitmapDescripterFactory](https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/model/BitmapDescriptorFactory) クラスを使用します。

色を変更するだけなら BitmapDescripterFactory クラスの defaultMarker メソッドと "HUE_" で始まる名前の定数を利用します。例えば緑にするなら```icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))```となります。
<img width="82" alt="marker_6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/932fae61-0000-7871-f460-1a95b00fb395.png">

### アイコンの画像を変える

アイコンの画像を変えることもできます。
前項でアイコンの色を変えたのと同じように、BitmapDescripterFactory クラスの fromXXXXX メソッドからの戻り値を MarkerOptions#icon メソッドに指定することで、通常のアイコンが任意のアイコンに変更されます。メソッド名の "XXXXX" は、その画像をどこから取得するかによって変わります。
例えば駅舎の画像が drawable/station_1.png にあるとして、それをアイコンに設定する場合は```icon(BitmapDescriptorFactory.fromResource(R.drawable.station_1))``` というようになります。
<img width="99" alt="marker_7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/79ae41f6-3d55-a674-b1db-1cd100398558.png">

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

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

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

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

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

```kotlin
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 もカスタマイズすることができます。例えば、以下のような具合です。
<img width="140" alt="marker_8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/0cce0a43-9c01-4633-7c79-24f7669aba55.png">
[GoogleMap.InfoWindowAdapter](https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/GoogleMap.InfoWindowAdapter) を使います。ここにどのようなレイアウトにしたいかを定義して、そのアダプタを GoogleMap#setInfoWindowAdapter でセットします。
上記の Info Window を表示するアダプタのコードは以下の通りです（レイアウトのコードは省略しています）。

```kotlin
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 の中にパディングができており、レイアウトが入り込んでいます。

<img width="140" alt="marker_9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67520/f8dbbb3e-c0aa-45cd-0d2a-c1f197d3bcca.png">

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 をカスタマイズする方法を書きたいと思います。