คอมโพเนนต์รายละเอียดสถานที่
คอมโพเนนต์รายละเอียดสถานที่ของ UI Kit ของสถานที่ช่วยให้คุณเพิ่มคอมโพเนนต์ UI แต่ละรายการที่แสดงรายละเอียดสถานที่ในแอปได้

PlaceDetailsCompactFragment
จะแสดงรายละเอียดของสถานที่ที่เลือกโดยใช้พื้นที่น้อยที่สุด ซึ่งอาจมีประโยชน์ในหน้าต่างข้อมูลไฮไลต์สถานที่บนแผนที่ ในประสบการณ์การใช้งานโซเชียลมีเดีย เช่น การแชร์ตำแหน่งในแชท คำแนะนำในการเลือกตำแหน่งปัจจุบัน หรือภายในบทความสื่อเพื่ออ้างอิงสถานที่ใน Google Maps PlaceDetailsCompactFragment
สามารถแสดงชื่อ ที่อยู่ คะแนน ประเภท ราคา ไอคอนการช่วยเหลือพิเศษ สถานะ "เปิด" และรูปภาพ 1 รูป
คอมโพเนนต์รายละเอียดสถานที่สามารถใช้แยกต่างหากหรือร่วมกับ API และบริการอื่นๆ ของ Google Maps Platform ก็ได้ คอมโพเนนต์จะใช้รหัสสถานที่หรือพิกัดละติจูด/ลองจิจูด แล้วแสดงผลข้อมูลรายละเอียดสถานที่ที่ผ่านการจัดการแสดงผล
การเรียกเก็บเงิน
เมื่อใช้ชุดเครื่องมือ UI ของรายละเอียดสถานที่ ระบบจะเรียกเก็บเงินจากคุณทุกครั้งที่มีการเรียกใช้เมธอด .loadWithPlaceId()
หรือ .loadWithResourceName()
หากคุณโหลดสถานที่เดียวกันหลายครั้ง ระบบจะเรียกเก็บเงินสำหรับคำขอแต่ละรายการ
อย่าเพิ่ม .loadWithPlaceId()
หรือ .loadWithResourceName()
ในเมธอดวงจรชีวิตของ Android โดยตรงเพื่อหลีกเลี่ยงการเรียกเก็บเงินหลายครั้ง เช่น อย่าเรียก .loadWithPlaceId()
หรือ .loadWithResourceName()
ในเมธอด onResume()
โดยตรง
เพิ่มรายละเอียดสถานที่ลงในแอป
คุณสามารถเพิ่มรายละเอียดสถานที่ลงในแอปได้โดยเพิ่มข้อมูลโค้ดไปยังเลย์เอาต์ เมื่อสร้างอินสแตนซ์ของข้อมูลโค้ดที่ฝัง คุณจะปรับแต่งรูปลักษณ์ของข้อมูลรายละเอียดสถานที่ให้เหมาะกับความต้องการและเข้ากับลักษณะที่ปรากฏของแอปได้
คุณมี 2 วิธีที่ใช้ได้ทั้งใน Kotlin และ Java วิธีหนึ่งจะโหลดข้อมูลโค้ดที่ตัดตอนมาโดยใช้รหัสสถานที่ (loadWithPlaceId()
) และอีกวิธีจะโหลดข้อมูลโค้ดที่ตัดตอนมาโดยใช้ชื่อทรัพยากร (loadWithResourceName()
) คุณสามารถเลือกวิธีใดวิธีหนึ่งหรือทั้ง 2 วิธีก็ได้หากใช้ทั้งรหัสสถานที่และชื่อทรัพยากร
คุณสามารถระบุการวางแนว (แนวนอนหรือแนวตั้ง) การลบล้างธีม และเนื้อหา ตัวเลือกเนื้อหา ได้แก่ สื่อ ที่อยู่ คะแนน ราคา ประเภท ทางเข้าที่เข้าถึงได้ และสถานะ "เปิดอยู่" ดูข้อมูลเพิ่มเติมเกี่ยวกับการปรับแต่ง
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);
ดูโค้ดที่สมบูรณ์เพื่อโหลดคอมโพเนนต์รายละเอียดสถานที่
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 } }
ปรับแต่งรายละเอียดสถานที่
ชุด UI ของ Places นำเสนอแนวทางระบบการออกแบบสำหรับการปรับแต่งภาพโดยอิงตาม Material Design โดยคร่าวๆ (มีการแก้ไขบางอย่างสำหรับ Google Maps โดยเฉพาะ) ดูข้อมูลอ้างอิงเกี่ยวกับสีและการพิมพ์ของ Material Design โดยค่าเริ่มต้น สไตล์จะเป็นไปตามภาษาการออกแบบภาพของ Google Maps

เมื่อสร้างอินสแตนซ์ของข้อมูลโค้ด คุณสามารถระบุธีมที่ลบล้างแอตทริบิวต์สไตล์เริ่มต้นได้ แอตทริบิวต์ธีมที่ไม่ได้ลบล้างจะใช้รูปแบบเริ่มต้น หากต้องการรองรับธีมมืด คุณสามารถเพิ่มรายการสำหรับสีใน 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 Maps | |
placesColorAttributionLight |
ปุ่มการระบุแหล่งที่มาและการเปิดเผยข้อมูลของ Google Maps ในธีมสว่าง (ชุดค่าผสมสำหรับสีขาว เทา และสีดํา) |
placesColorAttributionDark |
ปุ่มการระบุแหล่งที่มาและการเปิดเผยข้อมูลของ Google Maps ในธีมมืด (ลิสต์สำหรับสีขาว สีเทา และสีดํา) |
ความกว้างและความสูง
สำหรับมุมมองแนวตั้ง ความกว้างที่แนะนำคือระหว่าง 180dp ถึง 300dp สำหรับมุมมองแนวนอน ความกว้างที่แนะนำคือระหว่าง 180dp ถึง 500dp มุมมองที่เล็กกว่า 160dp อาจแสดงผลไม่ถูกต้อง
แนวทางปฏิบัติแนะนำคืออย่าตั้งค่าความสูง ซึ่งจะช่วยให้เนื้อหาในหน้าต่างกำหนดความสูงได้เพื่อให้ข้อมูลทั้งหมดแสดง
สีการระบุแหล่งที่มา
ข้อกำหนดในการให้บริการของ Google Maps กำหนดให้คุณใช้สีของแบรนด์ 1 ใน 3 สีสำหรับการระบุแหล่งที่มาของ Google Maps การระบุแหล่งที่มานี้ต้องมองเห็นได้และเข้าถึงได้เมื่อมีการเปลี่ยนแปลงการปรับแต่ง
เรามีสีของแบรนด์ให้เลือก 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 )