GoogleMap風のBottomSheetを実装する

こんにちは。今回はGoogleMap風の、全画面地図 + BottomSheetを実装してみます
簡単そうに見えて結構苦戦したので、他の人にもお役に立てばと思います

# 最終形
![BottomSheet3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/ca42181d-b816-abc4-349c-6a6fa27e720d.gif)


# 作り方
## 準備
![スクリーンショット 2021-07-17 16.19.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/ef8db57d-2920-3f09-348d-ef0316455979.png)

今回はお手軽にAndroidStudioのCreateNewProjectから、GoogleMapsActivityを選択して作成します
あらかじめ用意しておいた、GoogleMapsApi Keyをgoogle_maps_api.xmlに設定するだけで、全画面にGoogleMapを表示するようなActivityを自動作成してくれます。便利ですね！

![スクリーンショット 2021-07-17 16.15.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/01cd649a-c6d8-8579-7926-6ad41e640df8.png)


## BottomSheetを追加する
![BottomSheet.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/2e565191-0378-4212-10f4-0e8c87dee917.gif)

次はBottomSheetを追加します。レイアウトだけで実装できます

```xml:activity_maps.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MapsActivity" >


    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="#ffffff"
        app:behavior_peekHeight="90dp"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="This is Bottom-Sheet" />

    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>
```

私も細かいところは知りませんが、ポイントは三つ。

 * 親レイアウトをCoordinatorLayoutにする
 * BottomSheet化したいレイアウトにapp:layout_behaviorで、BottomSheetBehaviorを指定する
 * 上記レイアウトを地図と同じ階層に並べる

## さて実はここで問題が...
実はこの時点で一つ問題があります。
GoogleMapには、この地図がGoogleのものであることを示すために、ロゴがついています。隠れてしまってますね？

![スクリーンショット 2021-07-17 16.15.092.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/7dc11816-0e29-67bc-c114-c1b38c60fe46.png)

> 注: Google Maps Platform 利用規約により、アプリで Google ロゴまたは著作権に関する通知を削除することや隠すことはできません。必要に応じて、地図のパディングを使用して、これらの要素の位置を変更することができます。地図の下部にカスタム UI を表示する場合は、地図の下部にパディングを追加して、**ロゴと法的通知が常に表示されるようにしてください。**

[地図オブジェクト - Google Maps Platform](https://developers.google.com/maps/documentation/android-sdk/map?hl=ja)

どのくらいGoogleがこれを厳しく見ているかは分かりませんが、このままではマズイですね

## 方法１：動的にパディングを操作する
上記引用文にあるように、GoogleMapのFragmentにpaddingを入れると、このGoogleロゴの位置を調整できます
なので、BottomSheetの変化をキャッチして、動的にpaddingを与えます

```kotlin:MapsActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        val mapFragment = supportFragmentManager
                .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)

        /* ここから下を追加 */
        topLayout = findViewById(R.id.coordinator_layout)
        val bottomSheet = findViewById<LinearLayout>(R.id.bottom_sheet)
        bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
        bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                /* do nothing */
            }

            override fun onSlide(bottomSheet: View, slideOffset: Float) {
                val padding = topLayout.height - bottomSheet.top
                mMap.setPadding(0, 0, 0, padding)
            }
        })
    }
```

![BottomSheet2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/82564ccf-d4fa-1a61-161a-a69a3c025843.gif)
できました！
しっかりとBottomSheetの動きにGoogleMapロゴが追従しています
（なおpaddingの初期値を設定していないので、最初は隠れています）

この場合、Mapの描画領域がpaddingの値によって変化するので注意が必要です。
getCameraPosition()やProjection.getVisibleRegion()を使っている場合や、特定の経路を含むようにカメラの位置を調整している場合など、
ユースケースによっては地図がBottomSheetの操作で伸び縮みして、鬱陶しくて使えないことがあります。


## 方法2: GoogleMapロゴの場所を変える
オフィシャルなやり方ではないかもしれませんが、実はGoogleロゴは場所を変えることができます
規約的にも「常に表示しろ」とは言っていますが、場所については制限されていません

```kotlin:MapsFragment.kt
override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        // Add a marker in Sydney and move the camera
        val sydney = LatLng(-34.0, 151.0)
        mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))

        // 以下を追加 //
        val mapView = findViewById<View>(R.id.map)
        val googleLogo = mapView.findViewWithTag<ImageView>("GoogleWatermark")
        if (googleLogo != null) {
            val googleLogoLayoutParams = googleLogo.layoutParams as RelativeLayout.LayoutParams
            googleLogoLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE)
            googleLogoLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 0)
            googleLogo.layoutParams = googleLogoLayoutParams
        }
    }
```

![スクリーンショット 2021-07-17 17.23.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/bfcd5781-65c0-1ad8-37de-c96df6e52fdb.png)

Googleロゴが左上に動いたのがわかるかと思います。実は「GoogleWatermark」というタグが与えられているようですね

ちなみに元ネタはこちらです
[Relocate Google logo in MapView - stack overflow](https://stackoverflow.com/questions/6923472/relocate-google-logo-in-mapview)

## 方法3: Googleロゴ画像を自前のImageViewに使う
方法2はお手軽ですが、GoogleMapのようにBottomSheetに動的についてこないので、ちょっとさみしいです。
そこで、方法2で取得したImageViewからDrawableを引っこ抜いて、自前で用意したImageViewに突っ込みましょう

```xml:activity_maps.xml
    <LinearLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:behavior_peekHeight="90dp"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/google_logo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:background="#ffffff"
            android:elevation="8dp"
            app:cardCornerRadius="8dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="This is Bottom-Sheet" />
        </androidx.cardview.widget.CardView>
    </LinearLayout>
```

```kotlin:MapsActivity.kt
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        // Add a marker in Sydney and move the camera
        val sydney = LatLng(-34.0, 151.0)
        mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))

        // 以下を追加
        val mapView = findViewById<View>(R.id.map)
        val googleLogo = mapView.findViewWithTag<ImageView>("GoogleWatermark")
        val imageView = findViewById<ImageView>(R.id.google_logo)
        imageView.setImageDrawable(googleLogo.drawable)
    }
```

BottomSheetはかなり柔軟に作ることが可能です。
この方法では、Googleロゴを表示するであろうImageViewを自前で用意し、BottomSheetに含めてしまいました。
背景色がついているのはCardだけなので、paddingで動かしている時と見分けがつきません。

そして方法２と同様にGoogleWatermarkタグで取得したImageViewからDrawableを取得して、自前のImageViewに設定しています。
動的なコードもないので、全体的にスッキリかけるおすすめの方法です。
ちなみにImageViewはもちろん、FloatingActionButtonも置けるはずで、色々できそうですね

![スクリーンショット 2021-07-17 17.35.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924658/2ae0c6ea-e177-6a57-9494-773520313701.png)