Place Details コンポーネント
Places UI キットの Place Details コンポーネントを使用すると、アプリに場所の詳細を表示する個別の UI コンポーネントを追加できます。

PlaceDetailsCompactFragment
は、最小限のスペースを使用して、選択した場所の詳細をレンダリングします。これは、地図上の場所をハイライト表示する情報ウィンドウ、チャットでの場所の共有などのソーシャル メディア エクスペリエンス、現在地の選択候補として、またはメディア記事内で Google マップ上の場所を参照する場合に役立ちます。PlaceDetailsCompactFragment
には、名前、住所、評価、タイプ、料金、バリアフリー アイコン、営業状況、1 枚の写真が表示されます。
プレイス詳細コンポーネントは、単独で使用することも、他の Google Maps Platform API やサービスと組み合わせて使用することもできます。このコンポーネントは、プレイス ID または緯度/経度の座標を受け取り、レンダリングされたプレイス情報を返します。
課金
Place Details UI キットを使用する場合、.loadWithPlaceId()
メソッドまたは .loadWithResourceName()
メソッドが呼び出されるたびに課金されます。同じ場所を複数回読み込む場合は、リクエストごとに課金されます。
複数回請求されないように、Android ライフサイクル メソッドに .loadWithPlaceId()
または .loadWithResourceName()
を直接追加しないでください。たとえば、onResume()
メソッドで .loadWithPlaceId()
や .loadWithResourceName()
を直接呼び出さないでください。
アプリに場所の詳細を追加する
場所の詳細をアプリに追加するには、レイアウトにフラグメントを追加します。フラグメントをインスタンス化すると、ニーズに合わせて、またアプリの外観に合わせて、プレイス詳細情報の外観をカスタマイズできます。
Kotlin と Java の両方で使用できるメソッドは 2 つあります。1 つはプレイス ID を使用してフラグメントを読み込むメソッド(loadWithPlaceId()
)、もう 1 つはリソース名を使用してフラグメントを読み込むメソッド(loadWithResourceName()
)です。どちらか一方のメソッドを選択できます。プレイス ID とリソース名の両方を使用する場合は、両方のメソッドを選択できます。
向き(横向きまたは縦向き)、テーマのオーバーライド、コンテンツを指定できます。コンテンツ オプションは、メディア、住所、評価、料金、タイプ、車椅子対応の入り口、現在営業中ステータスです。カスタマイズの詳細をご覧ください。
Kotlin
// Create a new instance of the fragment from the Places SDK. val fragment = PlaceDetailsCompactFragment.newInstance( PlaceDetailsCompactFragment.ALL_CONTENT, orientation, R.style.CustomizedPlaceDetailsTheme, ).apply { // Set a listener to be notified when the place data has been loaded. setPlaceLoadListener(object : PlaceLoadListener { override fun onSuccess(place: Place) { Log.d(TAG, "Place loaded: ${place.id}") // Hide loader, show the fragment container and the dismiss button binding.loadingIndicator.visibility = View.GONE binding.placeDetailsContainer.visibility = View.VISIBLE binding.dismissButton.visibility = View.VISIBLE } override fun onFailure(e: Exception) { Log.e(TAG, "Place failed to load", e) // Hide everything on failure dismissPlaceDetails() Toast.makeText(this@MainActivity, "Failed to load place details.", Toast.LENGTH_SHORT).show() } }) } // Add the fragment to the container in the layout. supportFragmentManager .beginTransaction() .replace(binding.placeDetailsContainer.id, fragment) .commitNow() // Use commitNow to ensure the fragment is immediately available. // **This is the key step**: Tell the fragment to load data for the given Place ID. binding.root.post { fragment.loadWithPlaceId(placeId) } }
Java
PlaceDetailsCompactFragment fragment = PlaceDetailsCompactFragment.newInstance( Orientation.HORIZONTAL, Arrays.asList(Content.ADDRESS, Content.TYPE, Content.RATING, Content.ACCESSIBLE_ENTRANCE_ICON), R.style.CustomizedPlaceDetailsTheme); fragment.setPlaceLoadListener( new PlaceLoadListener() { @Override public void onSuccess(Place place) { ... } @Override public void onFailure(Exception e) { ... } }); getSupportFragmentManager() .beginTransaction() .add(R.id.fragment_container, fragment) .commitNow(); // Load the fragment with a Place ID. fragment.loadWithPlaceId(placeId); // Load the fragment with a resource name. // fragment.loadWithResourceName(resourceName);
Place Details コンポーネントを読み込むコードの全文
Kotlin
package com.example.placedetailsuikit import android.Manifest import android.annotation.SuppressLint import android.content.pm.PackageManager import android.content.res.Configuration import android.location.Location import android.os.Bundle import android.util.Log import android.view.View import android.widget.Toast import androidx.activity.enableEdgeToEdge import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.lifecycle.ViewModel import com.example.placedetailsuikit.databinding.ActivityMainBinding import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.SupportMapFragment import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.PointOfInterest import com.google.android.libraries.places.api.Places import com.google.android.libraries.places.api.model.Place import com.google.android.libraries.places.widget.PlaceDetailsCompactFragment import com.google.android.libraries.places.widget.PlaceLoadListener import com.google.android.libraries.places.widget.model.Orientation private const val TAG = "PlacesUiKit" /** * A simple ViewModel to store UI state that needs to survive configuration changes. * In this case, it holds the ID of the selected place. */ class MainViewModel : ViewModel() { var selectedPlaceId: String? = null } /** * Main Activity for the application. This class is responsible for: * 1. Displaying a Google Map. * 2. Handling location permissions to center the map on the user's location. * 3. Handling clicks on Points of Interest (POIs) on the map. * 4. Displaying a [PlaceDetailsCompactFragment] to show details of a selected POI. */ class MainActivity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPoiClickListener { // ViewBinding for safe and easy access to views. private lateinit var binding: ActivityMainBinding private var googleMap: GoogleMap? = null // Client for retrieving the device's last known location. private lateinit var fusedLocationClient: FusedLocationProviderClient // Modern approach for handling permission requests and their results. private lateinit var requestPermissionLauncher: ActivityResultLauncher<Array<String>> // ViewModel to store state across configuration changes (like screen rotation). private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Initialize the permissions launcher. This defines what to do after the user // responds to the permission request dialog. requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true || permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) { // Permission was granted. Fetch the user's location. Log.d(TAG, "Location permission granted by user.") fetchLastLocation() } else { // Permission was denied. Show a message and default to a fallback location. Log.d(TAG, "Location permission denied by user.") Toast.makeText( this, "Location permission denied. Showing default location.", Toast.LENGTH_LONG ).show() moveToSydney() } } // Standard setup for ViewBinding and enabling edge-to-edge display. enableEdgeToEdge() binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Set up the dismiss button listener binding.dismissButton.setOnClickListener { dismissPlaceDetails() } // --- Crucial: Initialize Places SDK --- val apiKey = BuildConfig.PLACES_API_KEY if (apiKey.isEmpty() || apiKey == "YOUR_API_KEY") { Log.e(TAG, "No api key") Toast.makeText( this, "Add your own API_KEY in local.properties", Toast.LENGTH_LONG ).show() finish() return } // Initialize the SDK with the application context and API key. Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey) // Initialize the location client. fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) // ------------------------------------ // Obtain the SupportMapFragment and request the map asynchronously. val mapFragment = supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment? mapFragment?.getMapAsync(this) // After rotation, check if a place was selected. If so, restore the fragment. if (viewModel.selectedPlaceId != null) { viewModel.selectedPlaceId?.let { placeId -> Log.d(TAG, "Restoring PlaceDetailsFragment for place ID: $placeId") showPlaceDetailsFragment(placeId) } } } /** * Callback triggered when the map is ready to be used. */ override fun onMapReady(map: GoogleMap) { Log.d(TAG, "Map is ready") googleMap = map // Set a listener for clicks on Points of Interest. googleMap?.setOnPoiClickListener(this) // Check for location permissions to determine the initial map position. if (isLocationPermissionGranted()) { fetchLastLocation() } else { requestLocationPermissions() } } /** * Checks if either fine or coarse location permission has been granted. */ private fun isLocationPermissionGranted(): Boolean { return ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED } /** * Launches the permission request flow. The result is handled by the * ActivityResultLauncher defined in onCreate. */ private fun requestLocationPermissions() { Log.d(TAG, "Requesting location permissions.") requestPermissionLauncher.launch( arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ) } /** * Fetches the device's last known location and moves the map camera to it. * This function should only be called after verifying permissions. */ @SuppressLint("MissingPermission") private fun fetchLastLocation() { if (isLocationPermissionGranted()) { fusedLocationClient.lastLocation .addOnSuccessListener { location: Location? -> if (location != null) { // Move camera to user's location if available. val userLocation = LatLng(location.latitude, location.longitude) googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(userLocation, 13f)) Log.d(TAG, "Moved to user's last known location.") } else { // Fallback to a default location if the last location is null. Log.d(TAG, "Last known location is null. Falling back to Sydney.") moveToSydney() } } .addOnFailureListener { // Handle errors in fetching location. Log.e(TAG, "Failed to get location.", it) moveToSydney() } } } /** * A default fallback location for the map camera. */ private fun moveToSydney() { val sydney = LatLng(-33.8688, 151.2093) googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 13f)) Log.d(TAG, "Moved to Sydney") } /** * Callback for when a Point of Interest on the map is clicked. */ override fun onPoiClick(poi: PointOfInterest) { val placeId = poi.placeId Log.d(TAG, "Place ID: $placeId") // Save the selected place ID to the ViewModel to survive rotation. viewModel.selectedPlaceId = placeId showPlaceDetailsFragment(placeId) } /** * Instantiates and displays the [PlaceDetailsCompactFragment]. * @param placeId The unique identifier for the place to be displayed. */ private fun showPlaceDetailsFragment(placeId: String) { Log.d(TAG, "Showing PlaceDetailsFragment for place ID: $placeId") // Show the wrapper, hide the dismiss button, and show the loading indicator. binding.placeDetailsWrapper.visibility = View.VISIBLE binding.dismissButton.visibility = View.GONE binding.placeDetailsContainer.visibility = View.GONE binding.loadingIndicator.visibility = View.VISIBLE // Determine the orientation based on the device's current configuration. val orientation = if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { Orientation.HORIZONTAL } else { Orientation.VERTICAL } // Create a new instance of the fragment from the Places SDK. val fragment = PlaceDetailsCompactFragment.newInstance( PlaceDetailsCompactFragment.ALL_CONTENT, orientation, R.style.CustomizedPlaceDetailsTheme, ).apply { // Set a listener to be notified when the place data has been loaded. setPlaceLoadListener(object : PlaceLoadListener { override fun onSuccess(place: Place) { Log.d(TAG, "Place loaded: ${place.id}") // Hide loader, show the fragment container and the dismiss button binding.loadingIndicator.visibility = View.GONE binding.placeDetailsContainer.visibility = View.VISIBLE binding.dismissButton.visibility = View.VISIBLE } override fun onFailure(e: Exception) { Log.e(TAG, "Place failed to load", e) // Hide everything on failure dismissPlaceDetails() Toast.makeText(this@MainActivity, "Failed to load place details.", Toast.LENGTH_SHORT).show() } }) } // Add the fragment to the container in the layout. supportFragmentManager .beginTransaction() .replace(binding.placeDetailsContainer.id, fragment) .commitNow() // Use commitNow to ensure the fragment is immediately available. // **This is the key step**: Tell the fragment to load data for the given Place ID. binding.root.post { fragment.loadWithPlaceId(placeId) } } private fun dismissPlaceDetails() { binding.placeDetailsWrapper.visibility = View.GONE viewModel.selectedPlaceId = null } override fun onDestroy() { super.onDestroy() // Clear references to avoid memory leaks. googleMap = null } }
場所の詳細をカスタマイズする
Places UI キットは、マテリアル デザインをベースにしたビジュアル カスタマイズにデザインシステム アプローチを提供します(Google マップ固有の変更がいくつかあります)。色とタイポグラフィについては、マテリアル デザインのリファレンスをご覧ください。デフォルトでは、このスタイルは Google マップのビジュアル デザイン言語に準拠しています。

フラグメントをインスタンス化するときに、デフォルトのスタイル属性をオーバーライドするテーマを指定できます。オーバーライドされていないテーマ属性は、デフォルト スタイルを使用します。ダークモードをサポートする場合は、values-night/colors.xml
に色のエントリを追加できます。
<style name="CustomizedPlaceDetailsTheme" parent="PlacesMaterialTheme"> <item name="placesColorPrimary">@color/app_primary_color</item> <item name="placesColorOnSurface">@color/app_color_on_surface</item> <item name="placesColorOnSurfaceVariant">@color/app_color_on_surface</item> <item name="placesTextAppearanceBodySmall">@style/app_text_appearence_small</item> <item name="placesCornerRadius">20dp</item> </style>
カスタマイズできるスタイルは次のとおりです。
テーマ属性 | 用途 |
---|---|
色 | |
placesColorSurface |
コンテナとダイアログの背景 |
placesColorOnSurface |
見出し、ダイアログのコンテンツ |
placesColorOnSurfaceVariant |
お店/スポット情報 |
placesColorPrimary |
リンク |
placesColorOutlineDecorative |
コンテナの境界線 |
placesColorSecondaryContainer |
ボタンの背景 |
placesColorOnSecondaryContainer |
ボタンのテキストとアイコン |
placesColorPositive |
「営業中」ラベルを配置 |
placesColorNegative |
場所に「閉鎖済み」のラベルを追加 |
placesColorInfo |
入口がバリアフリーのアイコン |
タイポグラフィ | |
placesTextAppearanceHeadlineMedium |
ダイアログの見出し |
placesTextAppearanceTitleSmall |
場所の名前 |
placesTextAppearanceBodyMedium |
ダイアログのコンテンツ |
placesTextAppearanceBodySmall |
お店/スポット情報 |
placesTextAppearanceLabelLarge |
ボタンのラベル |
角 | |
placesCornerRadius |
コンテナの角 |
Google マップのブランド アトリビューション | |
placesColorAttributionLight |
明るいテーマの Google マップのアトリビューションと開示ボタン(白、グレー、黒の列挙型) |
placesColorAttributionDark |
ダークモードの Google マップのアトリビューションと開示ボタン(白、グレー、黒の列挙型) |
幅と高さ
縦向きの場合は、180 dp ~ 300 dp の幅が推奨されます。横向きの場合、推奨される幅は 180 dp ~ 500 dp です。160 dp 未満のビューは正しく表示されないことがあります。
高さを設定しないことをおすすめします。これにより、ウィンドウ内のコンテンツの高さを設定して、すべての情報を表示できるようになります。
アトリビューションの色
Google マップの利用規約では、Google マップの帰属表示に 3 つのブランドカラーのいずれかを使用することが義務付けられています。この帰属情報は、カスタマイズが変更されたときに表示され、アクセス可能である必要があります。
明るいテーマと暗いテーマで個別に設定できる 3 つのブランドカラーから選択できます。
- ライトモード:
placesColorAttributionLight
(白、グレー、黒の列挙型を使用)。 - ダークモード:
placesColorAttributionDark
(白、グレー、黒の列挙型)。
カスタマイズの例
このサンプルでは、標準コンテンツをカスタマイズします。
val fragmentStandardContent = PlaceDetailsCompactFragment.newInstance( PlaceDetailsCompactFragment.STANDARD_CONTENT, orientation, R.style.CustomizedPlaceDetailsTheme )
このサンプルでは、コンテンツ オプションをカスタマイズします。
val placeDetailsFragment = PlaceDetailsCompactFragment.newInstance( orientation, listOf( Content.ADDRESS, Content.ACCESSIBLE_ENTRANCE,Content.MEDIA ), R.style.CustomizedPlaceDetailsTheme )
このサンプルでは、すべての Content
オプションをカスタマイズします。
val fragmentAllContent = PlaceDetailsCompactFragment.newInstance( orientation, PlaceDetailsCompactFragment.ALL_CONTENT, R.style.CustomizedPlaceDetailsTheme )