こんにちは。今回はGoogleMap風の、全画面地図 + BottomSheetを実装してみます
簡単そうに見えて結構苦戦したので、他の人にもお役に立てばと思います
最終形
作り方
準備
今回はお手軽にAndroidStudioのCreateNewProjectから、GoogleMapsActivityを選択して作成します
あらかじめ用意しておいた、GoogleMapsApi Keyをgoogle_maps_api.xmlに設定するだけで、全画面にGoogleMapを表示するようなActivityを自動作成してくれます。便利ですね!
BottomSheetを追加する
次はBottomSheetを追加します。レイアウトだけで実装できます
<?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のものであることを示すために、ロゴがついています。隠れてしまってますね?
注: Google Maps Platform 利用規約により、アプリで Google ロゴまたは著作権に関する通知を削除することや隠すことはできません。必要に応じて、地図のパディングを使用して、これらの要素の位置を変更することができます。地図の下部にカスタム UI を表示する場合は、地図の下部にパディングを追加して、ロゴと法的通知が常に表示されるようにしてください。
地図オブジェクト - Google Maps Platform
どのくらいGoogleがこれを厳しく見ているかは分かりませんが、このままではマズイですね
方法1:動的にパディングを操作する
上記引用文にあるように、GoogleMapのFragmentにpaddingを入れると、このGoogleロゴの位置を調整できます
なので、BottomSheetの変化をキャッチして、動的にpaddingを与えます
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)
}
})
}
できました!
しっかりとBottomSheetの動きにGoogleMapロゴが追従しています
(なおpaddingの初期値を設定していないので、最初は隠れています)
この場合、Mapの描画領域がpaddingの値によって変化するので注意が必要です。
getCameraPosition()やProjection.getVisibleRegion()を使っている場合や、特定の経路を含むようにカメラの位置を調整している場合など、
ユースケースによっては地図がBottomSheetの操作で伸び縮みして、鬱陶しくて使えないことがあります。
方法2: GoogleMapロゴの場所を変える
オフィシャルなやり方ではないかもしれませんが、実はGoogleロゴは場所を変えることができます
規約的にも「常に表示しろ」とは言っていますが、場所については制限されていません
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
}
}
Googleロゴが左上に動いたのがわかるかと思います。実は「GoogleWatermark」というタグが与えられているようですね
ちなみに元ネタはこちらです
Relocate Google logo in MapView - stack overflow
方法3: Googleロゴ画像を自前のImageViewに使う
方法2はお手軽ですが、GoogleMapのようにBottomSheetに動的についてこないので、ちょっとさみしいです。
そこで、方法2で取得したImageViewからDrawableを引っこ抜いて、自前で用意したImageViewに突っ込みましょう
<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>
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で動かしている時と見分けがつきません。
そして方法2と同様にGoogleWatermarkタグで取得したImageViewからDrawableを取得して、自前のImageViewに設定しています。
動的なコードもないので、全体的にスッキリかけるおすすめの方法です。
ちなみにImageViewはもちろん、FloatingActionButtonも置けるはずで、色々できそうですね