اختيار النظام الأساسي: Android iOS JavaScript

مكوّن "تفاصيل المكان"

يتيح لك مكوّن "تفاصيل المكان" في Places UI Kit إضافة مكوّن فردي لواجهة المستخدم يعرض تفاصيل المكان في تطبيقك. ويمكن تخصيص هذا المكوّن.

المكوّن المدمج لتفاصيل المكان

يمكن استخدام مكوّن "تفاصيل المكان" بشكل مستقل أو بالاشتراك مع واجهات برمجة التطبيقات والخدمات الأخرى في "منصة خرائط Google". يستقبل المكوّن رقم تعريف المكان أو اسم المورد أو إحداثيات خطوط الطول والعرض، ويعرض معلومات تفاصيل المكان.

يمكن تخصيص مظهر مكوّن "تفاصيل المكان" بالكامل، ما يتيح لك تعديل الخطوط والألوان ونصف قطر الزوايا بما يتوافق مع حالة الاستخدام وإرشادات العلامة التجارية المرئية. يمكنك تخصيص مظهر تفاصيل المكان من خلال إنشاء مظهر يوسّع PlacesMaterialTheme ويوفّر عمليات إلغاء لسمات المظهر. يمكنك أيضًا تخصيص حقول تفاصيل المكان التي يتم تضمينها من خلال تحديد قائمة بإدخالات المحتوى، ويتوافق كل منها مع جزء من المعلومات المعروضة حول المكان.

أشكال التصميم

يتيح مكوّن "تفاصيل المكان" نوعَين رئيسيَين من التصميم:

  • مدمج: تخطيط لمعاينة المعلومات الأساسية.
  • كامل: تخطيط شامل يعرض جميع تفاصيل المكان المتاحة.

يمكن عرض التنسيق المكثّف بالاتجاه العمودي أو الأفقي. يتيح لك ذلك دمج المكوّن في مختلف تخطيطات التصميم وأحجام الشاشات. لا يمكن عرض التنسيق الكامل إلا عموديًا.

التنسيقات الأفقية والعمودية
التنسيقات الأفقية والعمودية

يمنحك مكوّن "تفاصيل المكان" تحكّمًا دقيقًا في المحتوى المعروض في المكوّن. يمكن إظهار أو إخفاء كل عنصر على حدة (مثل الصور والمراجعات ومعلومات الاتصال)، ما يتيح تخصيصًا دقيقًا لمظهر المكوّنات وكثافة المعلومات.

خيارات المحتوى الخاص بتفاصيل المكان
خيارات عرض المحتوى

العرض المصغّر لتفاصيل المكان

تعرض "القطعة المدمجة لتفاصيل المكان" (PlaceDetailsCompactFragment) تفاصيل مكان محدّد باستخدام مساحة صغيرة. قد يكون ذلك مفيدًا في نافذة معلومات تسلّط الضوء على مكان ما على الخريطة، أو في تجربة على وسائل التواصل الاجتماعي مثل مشاركة موقع جغرافي في محادثة، أو كاقتراح لاختيار موقعك الجغرافي الحالي، أو في مقالة إعلامية للإشارة إلى المكان على "خرائط Google".

عرض تفاصيل المكان بالكامل

يوفّر العرض الكامل لتفاصيل المكان (PlaceDetailsFragment) مساحة أكبر لعرض معلومات تفاصيل المكان، ويتيح لك عرض المزيد من أنواع المعلومات.

خيارات عرض المحتوى

يمكنك تحديد المحتوى الذي تريد عرضه باستخدام القيم التعدادية في PlaceDetailsCompactFragment.Content أو PlaceDetailsFragment.Content.

العرض المصغّر عرض كامل
  • صورة المكان
  • عنوان المكان
  • التقييم وعدد التقييمات
  • نوع المكان
  • السعر
  • معلومات عن المدخل المناسب
  • حالة "مفتوح الآن"
  • صور مجمّعة للمكان
  • عنوان المكان
  • التقييم وعدد التقييمات
  • نوع المكان
  • السعر
  • معلومات حول إمكانية الوصول
  • حالة "مفتوح الآن"
  • ساعات العمل
  • الملخّص التحريري
  • الموقع الإلكتروني
  • رقم الهاتف
  • عرض المراجعات في علامة تبويب مخصّصة
  • رمز Plus Codes
  • قائمة الميزات، معروضة في علامة تبويب مخصّصة
  • النقاط البارزة الخاصة بنوع المكان، مثل أسعار الوقود في محطات الوقود

الفوترة

عند استخدام حزمة أدوات Place Details UI Kit، يتم تحصيل رسوم منك في كل مرة يتم فيها استدعاء الطريقة .loadWithPlaceId() أو .loadWithResourceName() أو loadWithCoordinates(). في حال تحميل المكان نفسه عدة مرات، يتم تحرير فاتورة لك مقابل كل طلب.

لتجنُّب تحصيل الرسوم منك عدة مرات، لا تُضِف .loadWithPlaceId() أو .loadWithResourceName() مباشرةً في طرق مراحل نشاط Android. على سبيل المثال، لا تتّصل مباشرةً بـ .loadWithPlaceId() أو .loadWithResourceName() في طريقة onResume().

إضافة تفاصيل الأماكن إلى تطبيقك

يمكنك إضافة تفاصيل عن الأماكن إلى تطبيقك من خلال إضافة جزء إلى التنسيق. عند إنشاء مثيل للجزء، يمكنك تخصيص شكل معلومات تفاصيل المكان وأسلوبها بما يتناسب مع احتياجاتك ويتوافق مع مظهر تطبيقك. مزيد من المعلومات عن التخصيص

تتوفّر ثلاث طرق في كلّ من Kotlin وJava: طريقة لتحميل الجزء باستخدام رقم تعريف المكان (loadWithPlaceId())، وطريقة لتحميل الجزء باستخدام اسم المورد (loadWithResourceName())، وطريقة لتحميل الجزء باستخدام إحداثيات خطوط الطول والعرض (loadWithCoordinates()). يمكنك اختيار أي طريقة أو عدة طرق.

يكون الموضع التلقائي للعرض المضغوط عموديًا. إذا كنت تريد تخطيطًا أفقيًا، حدِّد Orientation.HORIZONTAL. يمكنك أيضًا تحديد Orientation.VERTICAL اختياريًا لتوضيح المعنى. لا يمكن عرض العرض الكامل إلا عموديًا.

يمكنك الاطّلاع على أمثلة في قسم أمثلة على مكوّن "تفاصيل المكان".

تخصيص المظهر المرئي

تخصيص المظهر المرئي لتفاصيل المكان
أمثلة على التخصيص المرئي

تقدّم حزمة أدوات واجهة المستخدم الخاصة بالأماكن أسلوبًا لنظام التصميم يعتمد على التخصيص المرئي استنادًا إلى Material Design (مع بعض التعديلات الخاصة بـ "خرائط Google"). يمكنك الاطّلاع على مرجع Material Design الخاص باللون وأسلوب الخط. يتوافق النمط تلقائيًا مع لغة التصميم المرئي في "خرائط 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>

يمكنك تخصيص الأنماط التالية:

تخصيص لون مربع الحوار وأسلوب الخط
تخصيص لون مربّع الحوار والطباعة
سمة المظهر الاستخدام
Color
placesColorSurface خلفية الحاوية ومربّع الحوار
placesColorOutlineDecorative حدود الحاوية
placesColorPrimary الروابط ومؤشر التحميل ورموز النظرة العامة
placesColorOnSurface العناوين ومحتوى مربّعات الحوار
placesColorOnSurfaceVariant معلومات عن المكان
placesColorSecondaryContainer خلفية الزر
placesColorOnSecondaryContainer نص الزر ورمزه
placesColorNeutralContainer مراجعة شارة التاريخ وأشكال العناصر النائبة التي يتم تحميلها
placesColorOnNeutralContainer تاريخ المراجعة، حدث خطأ أثناء التحميل
placesColorPositiveContainer شارة محطة شحن المركبات الكهربائية المتوفّرة
placesColorOnPositiveContainer محتوى شارة محطة شحن المركبات الكهربائية المتوفّرة
placesColorPositive تصنيف "مفتوح" الآن
placesColorNegative تمت إضافة التصنيف "مغلق" إلى المكان
placesColorInfo رمز المدخل المناسب للكراسي المتحركة
placesColorButtonBorder زرّا "فتح في خرائط Google" و"حسنًا"
   
أسلوب الخط
placesTextAppearanceBodySmall معلومات عن المكان
placesTextAppearanceBodyMedium معلومات المكان ومحتوى مربّع الحوار
placesTextAppearanceLabelMedium محتوى الشارة
placesTextAppearanceLabelLarge محتوى الزر
placesTextAppearanceHeadlineMedium عناوين مربّعات الحوار
placesTextAppearanceDisplaySmall اسم المكان
placesTextAppearanceTitleSmall اسم المكان
   
المسافة
placesSpacingExtraSmall
placesSpacingSmall
placesSpacingMedium
placesSpacingLarge
placesSpacingExtraLarge
placesSpacingTwoExtraLarge
   
القياس
placesBorderWidth الحاوية
placesBorderWidthButton
   
الشكل
placesCornerRadius الحاوية
placesCornerRadiusButton الزرّان "فتح في خرائط Google" و"حسنًا" (لا يشمل زر الرمز الدائري)
placesCornerRadiusThumbnail وضع الصورة المصغّرة
placesCornerRadiusCollageOuter صورة مجمّعة للوسائط
placesCornerRadiusCard بطاقة المكان، بطاقة مراجعة المستخدم
placesCornerRadiusDialog مربّع حوار الإفصاح في "خرائط Google"
   
سمة العلامة التجارية على "خرائط Google"
placesColorAttributionLightTheme زر الإفصاح عن مصدر البيانات في "خرائط Google" والمظهر الفاتح (تعدادات للون الأبيض والرمادي والأسود)
placesColorAttributionDarkTheme زر الإفصاح عن مصدر البيانات في "خرائط Google" عند استخدام المظهر الداكن (تعدادات للون الأبيض والرمادي والأسود)

يمكنك الاطّلاع على أمثلة في قسم أمثلة على مكوّن "تفاصيل المكان".

تخصيص العرض والارتفاع

طُرق العرض المصغّرة

العروض المقترَحة:

  • الاتجاه العمودي: بين 180 و300 وحدة بكسل مستقلة الكثافة
  • الاتجاه الأفقي: بين 180 و500 وحدة بكسل مستقلة الكثافة

قد لا يتم عرض العروض الأصغر من 160 وحدة بكسل مستقل بشكل صحيح.

من أفضل الممارسات عدم ضبط ارتفاع للعروض المضغوطة. سيسمح ذلك للمحتوى في النافذة بتحديد الارتفاع، ما يتيح عرض جميع المعلومات.

مرات المشاهدة الكاملة

بالنسبة إلى طرق العرض الكاملة، يتراوح العرض المقترَح بين 250 و450 وحدة بكسل مستقلة عن الكثافة. قد لا يتم عرض العرض الأقل من 250 وحدة بكسل مستقل عن الكثافة بشكل صحيح.

يمكنك ضبط ارتفاع المكوّن: سيتم الانتقال عموديًا في عرض "تفاصيل المكان" العمودي ضمن المساحة المخصّصة.

من أفضل الممارسات ضبط ارتفاع للعروض الكاملة. سيتيح ذلك التنقّل بشكل صحيح بين المحتوى في النافذة.

ألوان تحديد المصدر

إحالة
تحديد المصدر

تتطلّب بنود خدمة "خرائط Google" استخدام أحد ألوان العلامة التجارية الثلاثة لتحديد المصدر في "خرائط Google". يجب أن يكون هذا البيان واضحًا ويمكن الوصول إليه عند إجراء تغييرات على التخصيص.

نوفّر 3 ألوان للعلامة التجارية يمكن الاختيار من بينها ويمكن ضبطها بشكل مستقل للمظهرين الفاتح والداكن:

  • المظهر الفاتح: placesColorAttributionLight مع قيم التعداد للأبيض والرمادي والأسود
  • المظهر الداكن: placesColorAttributionDark مع قيم التعداد للأبيض والرمادي والأسود

أمثلة على مكوّن "تفاصيل المكان"

إنشاء عرض مكثّف أو عرض كامل

Kotlin

              
        // We create a new instance of the fragment using its factory method.
        // We can specify which content to show, the orientation, and a custom theme.
        val fragment = PlaceDetailsCompactFragment.newInstance(
            PlaceDetailsCompactFragment.ALL_CONTENT, // Show all available content.
            orientation,
            R.style.CustomizedPlaceDetailsTheme,
        ).apply {
            // The PlaceLoadListener provides callbacks for when the place data is successfully
            // loaded or when an error occurs. This is where we update our UI state.
            setPlaceLoadListener(object : PlaceLoadListener {
                override fun onSuccess(place: Place) {
                    Log.d(TAG, &quot;Place loaded: ${place.id}&quot;)
                    // Once the data is loaded, we hide the loading indicator and show the fragment.
                    binding.loadingIndicatorMain.visibility = View.GONE
                    binding.placeDetailsContainer.visibility = View.VISIBLE
                    binding.dismissButton.visibility = View.VISIBLE
                }

                override fun onFailure(e: Exception) {
                    Log.e(TAG, &quot;Place failed to load&quot;, e)
                    // On failure, we hide the UI and notify the user.
                    dismissPlaceDetails()
                    Toast.makeText(this@MainActivity, &quot;Failed to load place details.&quot;, Toast.LENGTH_SHORT).show()
                }
            })
        }

        // We add the fragment to our layout&#39;s container view.
        // `commitNow()` is used to ensure the fragment is immediately added and available,
        // which is important because we need to call a method on it right after.
        supportFragmentManager
            .beginTransaction()
            .replace(binding.placeDetailsContainer.id, fragment)
            .commitNow()

        // **This is the key step**: After adding the fragment, we call `loadWithPlaceId`
        // to trigger the data loading process for the selected place.
        // We use `post` to ensure this runs after the layout has been measured,
        // which can prevent potential timing issues.
        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 = &quot;PlacesUiKit&quot;

/**
 * A simple ViewModel to store UI state that needs to survive configuration changes.
 * In this case, it holds the ID of the selected place. Using a ViewModel is good practice
 * as it prevents data loss during events like screen rotation, ensuring a
 * seamless user experience.
 */
class MainViewModel : ViewModel() {
    var selectedPlaceId: String? = null
}

/**
 * This activity serves as a basic example of integrating the Place Details UI Kit.
 * It demonstrates the fundamental steps required:
 * 1. Setting up a Google Map.
 * 2. Requesting location permissions to center the map.
 * 3. Handling clicks on Points of Interest (POIs) to get a Place ID.
 * 4. Using the Place ID to load and display place details in a [PlaceDetailsCompactFragment].
 */
class MainActivity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPoiClickListener {
    // ViewBinding provides type-safe access to views defined in the XML layout,
    // eliminating the need for `findViewById` and preventing null pointer exceptions.
    private lateinit var binding: ActivityMainBinding
    private var googleMap: GoogleMap? = null

    // The FusedLocationProviderClient is the main entry point for interacting with the
    // fused location provider, which intelligently manages the underlying location technologies.
    private lateinit var fusedLocationClient: FusedLocationProviderClient

    // Using registerForActivityResult is the modern, recommended approach for handling
    // permission requests. It decouples the request from the handling logic, making the
    // code cleaner and easier to manage compared to the older `onRequestPermissionsResult` callback.
    private lateinit var requestPermissionLauncher: ActivityResultLauncher&lt;Array&lt;String&gt;&gt;

    // The `by viewModels()` delegate provides a lazy-initialized ViewModel scoped to this Activity.
    // This ensures that we get the same ViewModel instance across configuration changes.
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // The ActivityResultLauncher is initialized here. The lambda defines the callback
        // that will be executed once the user responds to the permission dialog.
        requestPermissionLauncher =
            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -&gt;
                // We check if either fine or coarse location permission was granted.
                if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true || permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {
                    Log.d(TAG, &quot;Location permission granted by user.&quot;)
                    fetchLastLocation()
                } else {
                    // If permission is denied, we inform the user and default to a known location.
                    // This ensures the app remains functional even without location access.
                    Log.d(TAG, &quot;Location permission denied by user.&quot;)
                    Toast.makeText(
                        this,
                        &quot;Location permission denied. Showing default location.&quot;,
                        Toast.LENGTH_LONG
                    ).show()
                    moveToSydney()
                }
            }

        // enableEdgeToEdge() allows the app to draw behind the system bars for a more immersive experience.
        enableEdgeToEdge()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.dismissButton.setOnClickListener {
            dismissPlaceDetails()
        }

        // --- Crucial: Initialize Places SDK ---
        // It&#39;s essential to initialize the Places SDK before making any other Places API calls.
        // This should ideally be done once, for example, in the Application&#39;s `onCreate`.
        val apiKey = BuildConfig.PLACES_API_KEY
        if (apiKey.isEmpty() || apiKey == &quot;YOUR_API_KEY&quot;) {
            // A valid API key is required for the Places SDK to function.
            Log.e(TAG, &quot;No api key&quot;)
            Toast.makeText(
                this,
                &quot;Add your own API_KEY in local.properties&quot;,
                Toast.LENGTH_LONG
            ).show()
            finish()
            return
        }

        // `initializeWithNewPlacesApiEnabled` is used to opt-in to the new SDK version.
        Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        // ------------------------------------

        // The SupportMapFragment is the container for the map. `getMapAsync` allows us to
        // work with the GoogleMap object via a callback once it&#39;s fully initialized.
        val mapFragment =
            supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment?
        mapFragment?.getMapAsync(this)

        // This block handles restoration after a configuration change (e.g., screen rotation).
        // If a place was selected before the rotation, its ID is stored in the ViewModel.
        // We use this ID to immediately show the details fragment again.
        if (viewModel.selectedPlaceId != null) {
            viewModel.selectedPlaceId?.let { placeId -&gt;
                Log.d(TAG, &quot;Restoring PlaceDetailsFragment for place ID: $placeId&quot;)
                showPlaceDetailsFragment(placeId)
            }
        }
    }

    /**
     * This callback is triggered when the GoogleMap object is ready to be used.
     * All map setup logic should be placed here.
     */
    override fun onMapReady(map: GoogleMap) {
        Log.d(TAG, &quot;Map is ready&quot;)
        googleMap = map
        // Setting the OnPoiClickListener allows us to capture user taps on points of interest.
        googleMap?.setOnPoiClickListener(this)

        // After the map is ready, we determine the initial camera position based on location permissions.
        if (isLocationPermissionGranted()) {
            fetchLastLocation()
        } else {
            requestLocationPermissions()
        }
    }

    /**
     * A helper function to centralize the check for location permissions.
     */
    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
    }

    /**
     * This function triggers the permission request flow. The result is handled by the
     * ActivityResultLauncher defined in `onCreate`.
     */
    private fun requestLocationPermissions() {
        Log.d(TAG, &quot;Requesting location permissions.&quot;)
        requestPermissionLauncher.launch(
            arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION
            )
        )
    }

    /**
     * Fetches the device&#39;s last known location. This is a fast and battery-efficient way
     * to get a location fix. It should only be called after verifying permissions.
     */
    @SuppressLint(&quot;MissingPermission&quot;)
    private fun fetchLastLocation() {
        // Double-checking permissions here is a good practice, although the call sites are already guarded.
        if (isLocationPermissionGranted()) {
            fusedLocationClient.lastLocation
                .addOnSuccessListener { location: Location? -&gt;
                    if (location != null) {
                        val userLocation = LatLng(location.latitude, location.longitude)
                        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(userLocation, 13f))
                        Log.d(TAG, &quot;Moved to user&#39;s last known location.&quot;)
                    } else {
                        // `lastLocation` can be null if the location has never been recorded.
                        // In this case, we fall back to a default location.
                        Log.d(TAG, &quot;Last known location is null. Falling back to Sydney.&quot;)
                        moveToSydney()
                    }
                }
                .addOnFailureListener {
                    // This listener handles errors in the location fetching process.
                    Log.e(TAG, &quot;Failed to get location.&quot;, it)
                    moveToSydney()
                }
        }
    }

    /**
     * Moves the map camera to a default, hardcoded location (Sydney).
     * This serves as a reliable fallback.
     */
    private fun moveToSydney() {
        val sydney = LatLng(-33.8688, 151.2093)
        googleMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 13f))
        Log.d(TAG, &quot;Moved to Sydney&quot;)
    }

    /**
     * This is the callback for the `OnPoiClickListener`. It&#39;s triggered when a user
     * taps a POI on the map.
     */
    override fun onPoiClick(poi: PointOfInterest) {
        val placeId = poi.placeId
        Log.d(TAG, &quot;Place ID: $placeId&quot;)

        // We save the selected place ID to the ViewModel. This is critical for surviving
        // configuration changes. If the user rotates the screen now, the `onCreate`
        // method will be able to restore the place details view.
        viewModel.selectedPlaceId = placeId
        showPlaceDetailsFragment(placeId)
    }

    /**
     * This function is the core of the integration. It creates, configures, and displays
     * the [PlaceDetailsCompactFragment].
     * @param placeId The unique identifier for the place to be displayed.
     */
    private fun showPlaceDetailsFragment(placeId: String) {
        Log.d(TAG, &quot;Showing PlaceDetailsFragment for place ID: $placeId&quot;)

        // We manage the visibility of UI elements to provide feedback to the user.
        // The wrapper is shown, and a loading indicator is displayed while the data is fetched.
        binding.placeDetailsWrapper.visibility = View.VISIBLE
        binding.dismissButton.visibility = View.GONE
        binding.placeDetailsContainer.visibility = View.GONE
        binding.loadingIndicatorMain.visibility = View.VISIBLE

        // The Place Details widget can be displayed vertically or horizontally.
        // We dynamically choose the orientation based on the device&#39;s current configuration.
        val orientation =
            if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                Orientation.HORIZONTAL
            } else {
                Orientation.VERTICAL
            }

        
        // We create a new instance of the fragment using its factory method.
        // We can specify which content to show, the orientation, and a custom theme.
        val fragment = PlaceDetailsCompactFragment.newInstance(
            PlaceDetailsCompactFragment.ALL_CONTENT, // Show all available content.
            orientation,
            R.style.CustomizedPlaceDetailsTheme,
        ).apply {
            // The PlaceLoadListener provides callbacks for when the place data is successfully
            // loaded or when an error occurs. This is where we update our UI state.
            setPlaceLoadListener(object : PlaceLoadListener {
                override fun onSuccess(place: Place) {
                    Log.d(TAG, &quot;Place loaded: ${place.id}&quot;)
                    // Once the data is loaded, we hide the loading indicator and show the fragment.
                    binding.loadingIndicatorMain.visibility = View.GONE
                    binding.placeDetailsContainer.visibility = View.VISIBLE
                    binding.dismissButton.visibility = View.VISIBLE
                }

                override fun onFailure(e: Exception) {
                    Log.e(TAG, &quot;Place failed to load&quot;, e)
                    // On failure, we hide the UI and notify the user.
                    dismissPlaceDetails()
                    Toast.makeText(this@MainActivity, &quot;Failed to load place details.&quot;, Toast.LENGTH_SHORT).show()
                }
            })
        }

        // We add the fragment to our layout&#39;s container view.
        // `commitNow()` is used to ensure the fragment is immediately added and available,
        // which is important because we need to call a method on it right after.
        supportFragmentManager
            .beginTransaction()
            .replace(binding.placeDetailsContainer.id, fragment)
            .commitNow()

        // **This is the key step**: After adding the fragment, we call `loadWithPlaceId`
        // to trigger the data loading process for the selected place.
        // We use `post` to ensure this runs after the layout has been measured,
        // which can prevent potential timing issues.
        binding.root.post {
            fragment.loadWithPlaceId(placeId)
        }
    }


    /**
     * Hides the place details view and clears the selected place ID from the ViewModel.
     */
    private fun dismissPlaceDetails() {
        binding.placeDetailsWrapper.visibility = View.GONE
        // Clearing the ID in the ViewModel is important so that if the user rotates the
        // screen after dismissing, the details view doesn&#39;t reappear.
        viewModel.selectedPlaceId = null
    }

    override fun onDestroy() {
        super.onDestroy()
        // It&#39;s a good practice to nullify references to objects that have a lifecycle
        // tied to the activity, like the GoogleMap object, to prevent potential memory leaks.
        googleMap = null
    }
}
        
  
ملاحظة: يمكنك الوصول إلى نماذج الرموز البرمجية الكاملة على GitHub.

إنشاء مظهر

عند إنشاء مثيل لجزء، يمكنك تحديد مظهر يلغي أيًّا من سمات النمط التلقائي. تستخدم أي سمات للمظهر لم يتم إلغاؤها الأنماط التلقائية. إذا أردت توفير مظهر داكن، يمكنك إضافة إدخال للون في 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>

استخدام محتوى عادي

يستخدم هذا النموذج المحتوى العادي.

  val fragmentStandardContent = PlaceDetailsCompactFragment.newInstance(
    PlaceDetailsCompactFragment.STANDARD_CONTENT,
    orientation,
    R.style.CustomizedPlaceDetailsTheme
  )

تخصيص محتوى معيّن

يختار هذا النموذج خيارات العنوان والمدخل المناسب لذوي الاحتياجات الخاصة والوسائط Content فقط لعرض مضغوط، ويعرضها باستخدام 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
  )