إضافة خريطة إلى تطبيق Android (Kotlin)

1. قبل البدء

يُعلّمك هذا الدرس التطبيقي حول كيفية دمج حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل Android مع تطبيقك واستخدام ميزاته الأساسية من خلال إنشاء تطبيق يعرض خريطة لمتاجر الدراجات في مدينة "سان فرانسيسكو" في ولاية "كاليفورنيا" بالولايات المتحدة الأمريكية.

f05e1ca27ff42bf6.png

المتطلّبات الأساسية

  • معرفة أساسية بالتطوير بلغة Kotlin وAndroid

الإجراءات التي ستنفذّها

  • يجب تفعيل حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل Android لإضافة "خرائط Google" إلى تطبيق متوافق مع Android.
  • يمكنك إضافة علامات وتخصيصها وجمعها.
  • ارسم خطوطًا ومضلّعات على الخريطة.
  • التحكم في نقطة عرض الكاميرا آليًا

الأشياء التي تحتاج إليها

2. الإعداد

بالنسبة إلى خطوة التفعيل التالية، عليك تفعيل Maps SDK لنظام التشغيل Android.

إعداد "منصة خرائط Google"

إذا لم يكن لديك حساب على Google Cloud Platform ومشروع تم تفعيل الفوترة فيه، يُرجى الاطّلاع على دليل بدء استخدام "منصة خرائط Google" لإنشاء حساب فوترة ومشروع.

  1. في Cloud Console، انقر على القائمة المنسدلة للمشروع واختَر المشروع الذي تريد استخدامه لهذا الدرس التطبيقي.

  1. فعِّل واجهات برمجة تطبيقات ومنصة SDK لمنصة "خرائط Google" المطلوبة لهذا الدرس التطبيقي في Google Cloud Marketplace. ولإجراء ذلك، اتّبِع الخطوات الواردة في هذا الفيديو أو هذه المستندات.
  2. يمكنك إنشاء مفتاح واجهة برمجة تطبيقات في صفحة بيانات الاعتماد في Cloud Console. يمكنك اتّباع الخطوات الواردة في هذا الفيديو أو هذه المستندات. تتطلب جميع الطلبات إلى "منصة خرائط Google" مفتاح واجهة برمجة تطبيقات.

3- البدء بسرعة

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

  1. إنشاء نسخة طبق الأصل من المستودع في حال تثبيت git.
git clone https://github.com/googlecodelabs/maps-platform-101-android.git

ويمكنك بدلاً من ذلك النقر على الزر التالي لتنزيل رمز المصدر.

  1. بعد الحصول على الرمز، يمكنك فتح المشروع الذي يمكن العثور عليه داخل دليل starter في"استوديو Android".

4. إضافة "خرائط Google"

في هذا القسم، ستضيف "خرائط Google" حتى يتم تحميلها عند تشغيل التطبيق.

d1d068b5d4ae38b9.png

إضافة مفتاح واجهة برمجة التطبيقات

يجب تقديم مفتاح واجهة برمجة التطبيقات الذي أنشأته في خطوة سابقة للتطبيق حتى تتمكن حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لأجهزة Android من ربط مفتاحك بتطبيقك.

  1. ولإجراء ذلك، افتح الملف باسم local.properties في الدليل الجذري لمشروعك (المستوى نفسه حيث gradle.properties وsettings.gradle).
  2. في هذا الملف، حدِّد مفتاحًا جديدًا GOOGLE_MAPS_API_KEY تكون قيمته هي واجهة برمجة التطبيقات التي أنشأتها.

local.properties

GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE

لاحِظ أن local.properties مدرَج في ملف .gitignore في مستودع Git. ويرجع ذلك إلى أن مفتاح واجهة برمجة التطبيقات يُعتبر معلومات حساسة ويجب عدم تسجيله للوصول إلى عنصر التحكم في المصدر، إن أمكن.

  1. بعد ذلك، لعرض واجهة برمجة التطبيقات بحيث يمكن استخدامها في تطبيقك، أضِف المكوّن الإضافي Secrets Gradle Plugin for Android في ملف build.gradle الخاص بتطبيقك في الدليل app/ وأضِف السطر التالي ضمن كتلة plugins:

build.gradle على مستوى التطبيق

plugins {
    // ...
    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}

ستحتاج أيضًا إلى تعديل ملف build.gradle على مستوى المشروع ليتضمن المسار التالي:

build.gradle على مستوى المشروع

buildscript {
    dependencies {
        // ...
        classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:1.3.0"
    }
}

سيتيح هذا المكوّن الإضافي أن تكون المفاتيح التي حدّدتها في ملف local.properties متاحة كمتغيّرات إنشاء في ملف بيان Android وكمتغيرات في فئة BuildConfig التي أنشأها Gradle في وقت الإصدار. ويؤدي استخدام هذا المكوّن الإضافي إلى إزالة الرمز النموذجي الذي قد يحتاج إلى قراءة خصائص من local.properties حتى يمكن الوصول إليه في تطبيقك.

إضافة تبعية "خرائط Google"

  1. الآن ويمكن الوصول إلى مفتاح واجهة برمجة التطبيقات داخل التطبيق، وتتمثل الخطوة التالية في إضافة اعتماد حزمة تطوير البرامج (SDK) لتطبيق "خرائط Google" لنظام التشغيل Android إلى ملف build.gradle لتطبيقك.

في مشروع المبتدئين الذي يتضمن هذا الدرس التطبيقي حول الترميز، تمت إضافة هذه الاعتمادية لك من قبل.

build.gradle

dependencies {
   // Dependency to include Maps SDK for Android
   implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
  1. بعد ذلك، أضِف علامة meta-data جديدة في العلامة AndroidManifest.xml لتمرير مفتاح واجهة برمجة التطبيقات الذي أنشأته في خطوة سابقة. ولإجراء ذلك، يمكنك فتح هذا الملف في "استوديو Android" وإضافة العلامة meta-data التالية داخل العنصر application في الملف AndroidManifest.xml المتوفّر في app/src/main.

ملف AndroidManifest.xml

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="${GOOGLE_MAPS_API_KEY}" />
  1. بعد ذلك، عليك إنشاء ملف تنسيق جديد باسم activity_main.xml في الدليل app/src/main/res/layout/ وتحديده على النحو التالي:

activity_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

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

</FrameLayout>

يحتوي هذا التنسيق على FrameLayout واحد يحتوي على SupportMapFragment. يحتوي هذا الجزء على عنصر GoogleMaps الأساسي الذي تستخدمه في الخطوات اللاحقة.

  1. أخيرًا، عدِّل الفئة MainActivity في app/src/main/java/com/google/codelabs/buildyourfirstmap من خلال إضافة الرمز التالي لإلغاء طريقة onCreate بحيث يمكنك ضبط محتوياتها باستخدام التنسيق الجديد الذي أنشأته للتو.

النشاط الرئيسي

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
}
  1. والآن، شغِّل التطبيق وشغِّل خريطة الخريطة على شاشة جهازك.

5. تصميم الخرائط المستنِد إلى السحابة الإلكترونية (اختياري)

يمكنك تخصيص نمط الخريطة باستخدام نمط الخريطة المستنِدة إلى السحابة الإلكترونية.

إنشاء رقم تعريف للخريطة

إذا لم تكن قد أنشأت رقم تعريف خريطة باستخدام نمط خريطة مرتبط به، يمكنك مراجعة دليل أرقام تعريف الخريطة لإكمال الخطوات التالية:

  1. إنشاء رقم تعريف للخريطة.
  2. ربط رقم تعريف خريطة بنمط الخريطة.

إضافة معرّف الخريطة إلى تطبيقك

لاستخدام رقم تعريف الخريطة الذي أنشأته، عدِّل ملف activity_main.xml ومرِّر رقم تعريف الخريطة في السمة map:mapId من SupportMapFragment.

activity_main.xml

<fragment xmlns:map="http://schemas.android.com/apk/res-auto"
    class="com.google.android.gms.maps.SupportMapFragment"
    <!-- ... -->
    map:mapId="YOUR_MAP_ID" />

بعد إكمال ذلك، تابِع واعرض التطبيق لرؤية خريطتك بالنمط الذي اخترته.

6- إضافة علامات

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

bc5576877369b554.png

الحصول على مرجع إلى "خرائط Google"

عليك أولاً الحصول على مرجع إلى عنصر GoogleMap حتى تتمكن من استخدام أساليبه. ولإجراء ذلك، يمكنك إضافة الرمز التالي باستخدام طريقة MainActivity.onCreate() بعد المكالمة مباشرةً إلى setContentView():

MainActivity.onCreate()

val mapFragment = supportFragmentManager.findFragmentById(   
    R.id.map_fragment
) as? SupportMapFragment
mapFragment?.getMapAsync { googleMap ->
    addMarkers(googleMap)
}

تعثر عملية التنفيذ أولاً على SupportMapFragment التي أضفتها في الخطوة السابقة باستخدام طريقة findFragmentById() على الكائن SupportFragmentManager. بعد الحصول على مرجع، يتم استدعاء طلب getMapAsync() متبوعًا بتمرير lambda. لامدا هو العنصر الذي يتم فيه تمرير الكائن GoogleMap. داخل lambda، تم استدعاء طريقة addMarkers()، ويتم تحديدها قريبًا.

الصف المقدّم: PlacesReader

في مشروع المبتدئين، تم توفير الصف PlacesReader لك. يقرأ هذا الصف قائمة من 49 مكانًا تم تخزينها في ملف JSON يُسمى places.json وتعرض هذه الأماكن كـ List<Place>. تمثل الأماكن نفسها قائمة بمتاجر الدراجات حول مدينة نصر، القاهرة، الولايات المتحدة الأمريكية.

إذا كنت مهتمًا بالاطّلاع على طريقة تنفيذ هذا الصف، يمكنك الوصول إليه على GitHub أو فتح الصف PlacesReader في"استوديو Android".

أماكن القارئ

package com.google.codelabs.buildyourfirstmap.place

import android.content.Context
import com.google.codelabs.buildyourfirstmap.R
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.InputStream
import java.io.InputStreamReader

/**
* Reads a list of place JSON objects from the file places.json
*/
class PlacesReader(private val context: Context) {

   // GSON object responsible for converting from JSON to a Place object
   private val gson = Gson()

   // InputStream representing places.json
   private val inputStream: InputStream
       get() = context.resources.openRawResource(R.raw.places)

   /**
    * Reads the list of place JSON objects in the file places.json
    * and returns a list of Place objects
    */
   fun read(): List<Place> {
       val itemType = object : TypeToken<List<PlaceResponse>>() {}.type
       val reader = InputStreamReader(inputStream)
       return gson.fromJson<List<PlaceResponse>>(reader, itemType).map {
           it.toPlace()
       }
   }

تحميل الأماكن

لتحميل قائمة متاجر الدراجات، أضف موقعًا في MainActivity باسم places وحدّده على النحو التالي:

الأماكن الرئيسية.

private val places: List<Place> by lazy {
   PlacesReader(this).read()
}

يستدعي هذا الرمز الطريقة read() في PlacesReader، ما يعرض List<Place>. تشتمل السمة Place على خاصية تُسمى name واسم المكان وlatLng، وهي إحداثيات المكان.

المكان

data class Place(
   val name: String,
   val latLng: LatLng,
   val address: LatLng,
   val rating: Float
)

إضافة علامات إلى الخريطة

الآن وقد تم تحميل قائمة الأماكن إلى الذاكرة، فإن الخطوة التالية هي تمثيل هذه الأماكن على الخريطة.

  1. إنشاء طريقة في MainActivity باسم addMarkers() وتحديدها على النحو التالي:

MainActivity.addMarkers()

/**
* Adds marker representations of the places list on the provided GoogleMap object
*/
private fun addMarkers(googleMap: GoogleMap) {
   places.forEach { place ->
       val marker = googleMap.addMarker(
           MarkerOptions()
               .title(place.name)
               .position(place.latLng)
       )
   }
}

يتم تكرار هذه الطريقة في قائمة places متبوعة باستدعاء الطريقة addMarker() في الكائن GoogleMap المُقدّم. يتم إنشاء العلامة من خلال إنشاء مثيل لكائن MarkerOptions، مما يسمح لك بتخصيص العلامة نفسها. في هذه الحالة، يتم توفير عنوان وموضع العلامة، والذي يمثل اسم متجر الدراجات وإحداثياتها، على التوالي.

  1. واصِل تشغيل التطبيق وتوجِّهه إلى سان فرانسيسكو للاطّلاع على العلامات التي أضفتها للتو.

7- تخصيص العلامات

هناك العديد من خيارات التخصيص للعلامات التي أضفتها للتو لمساعدتهم على التميُّز وتقديم معلومات مفيدة للمستخدمين. في هذه المهمة، ستستكشف بعض هذه القيم من خلال تخصيص صورة كل علامة بالإضافة إلى نافذة المعلومات التي يتم عرضها عند النقر على علامة.

a26f82802fe838e9.png

إضافة نافذة معلومات

تعرض نافذة المعلومات بشكل تلقائي عندما تنقر على علامة عنوانها ومقتطفها (في حال ضبطها). ويمكنك تخصيص ذلك ليتمكّن من عرض معلومات إضافية، مثل عنوان المكان وتقييمه.

إنشاء tag_info_contents.xml

أولاً، أنشِئ ملف تنسيق جديدًا باسم marker_info_contents.xml.

  1. ولإجراء ذلك، انقر بزر الماوس الأيمن على المجلد app/src/main/res/layout في طريقة عرض المشروع في "استوديو Android"، ثم اختَر جديد > ملف موارد التنسيق.

8cac51fcbef9171b.png

  1. في مربع الحوار، اكتب marker_info_contents في الحقل اسم الملف وLinearLayout في الحقل Root element، ثم انقر على حسنًا.

8783af12baf07a80.png

ويتم تضخيم ملف التنسيق هذا لاحقًا لتمثيل المحتوى في نافذة المعلومات.

  1. انسخ المحتوى في مقتطف الرمز التالي، الذي يضيف ثلاثة TextViews ضمن مجموعة عرض LinearLayout عمودية، وتحلّل الرمز التلقائي في الملف.

mark_info_contents.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:orientation="vertical"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:gravity="center_horizontal"
   android:padding="8dp">

   <TextView
       android:id="@+id/text_view_title"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="18sp"
       android:textStyle="bold"
       tools:text="Title"/>

   <TextView
       android:id="@+id/text_view_address"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="16sp"
       tools:text="123 Main Street"/>

   <TextView
       android:id="@+id/text_view_rating"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textColor="@android:color/black"
       android:textSize="16sp"
       tools:text="Rating: 3"/>

</LinearLayout>

إنشاء تنفيذ InfoWindow يتوفر محوّل

بعد إنشاء ملف التنسيق لنافذة المعلومات المخصصة، تتمثل الخطوة التالية في تنفيذ واجهة GoogleMap.InfoWindowAdaptiveer. وتتضمّن هذه الواجهة طريقتَين، getInfoWindow() وgetInfoContents(). تعرض كلتا الطريقتَين عنصر View اختياري، حيث يتم استخدام العنصر السابق لتخصيص النافذة نفسها، والعكس صحيح، وهو تخصيص محتواها. وفي الحالة الخاصة بك، نفّذت كلاً من القيمتَين المخصّصتَين وعرض القيمة getInfoContents() مع عرض القيمة فارغة في getInfoWindow()، ما يشير إلى أنّه يجب استخدام النافذة التلقائية.

  1. أنشِئ ملف Kotlin جديدًا باسم MarkerInfoWindowAdapter في حزمة MainActivity نفسها عن طريق النقر بزر الماوس الأيمن على المجلد app/src/main/java/com/google/codelabs/buildyourfirstmap في عرض المشروع على "استوديو Android"، ثم اختيار New > Kotlin File/Class.

3975ba36eba9f8e1.png

  1. في مربع الحوار، اكتب MarkerInfoWindowAdapter واجعل الملف محددًا.

992235af53d3897f.png

  1. بعد إنشاء الملف، انسخ المحتوى المتوفّر في مقتطف الرمز التالي والصقه في الملف الجديد.

MarkerInfoWindowAdaptiveer

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.Marker
import com.google.codelabs.buildyourfirstmap.place.Place

class MarkerInfoWindowAdapter(
    private val context: Context
) : GoogleMap.InfoWindowAdapter {
   override fun getInfoContents(marker: Marker?): View? {
       // 1. Get tag
       val place = marker?.tag as? Place ?: return null

       // 2. Inflate view and set title, address, and rating
       val view = LayoutInflater.from(context).inflate(
           R.layout.marker_info_contents, null
       )
       view.findViewById<TextView>(
           R.id.text_view_title
       ).text = place.name
       view.findViewById<TextView>(
           R.id.text_view_address
       ).text = place.address
       view.findViewById<TextView>(
           R.id.text_view_rating
       ).text = "Rating: %.2f".format(place.rating)

       return view
   }

   override fun getInfoWindow(marker: Marker?): View? {
       // Return null to indicate that the 
       // default window (white bubble) should be used
       return null
   }
}

في محتوى طريقة getInfoContents()، يتم إرسال العلامة المحدّدة في الطريقة إلى النوع Place، وإذا لم يكن من الممكن الإرسال، تعرض الطريقة قيمة فارغة (لم يتم ضبط سمة العلامة على Marker بعد، ولكن سيتم ذلك في الخطوة التالية).

بعد ذلك، يتم تضخيم التنسيق marker_info_contents.xml من خلال ضبط النص الذي يحتوي على TextViews إلى العلامة Place.

تعديل النشاط الرئيسي

للصق جميع المكوّنات التي أنشأتها حتى الآن، عليك إضافة سطرين في صف MainActivity.

أولاً، لإدخال InfoWindowAdapter المخصّصة، MarkerInfoWindowAdapter، داخل استدعاء طريقة getMapAsync، عليك استدعاء طريقة setInfoWindowAdapter() على العنصر GoogleMap وإنشاء مثيل جديد من MarkerInfoWindowAdapter.

  1. يمكنك إجراء ذلك من خلال إضافة الرمز التالي بعد استدعاء طريقة addMarkers() داخل getMapAsync() lambda.

MainActivity.onCreate()

// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))

وأخيرًا، يجب تعيين كل مكان باعتباره موقع العلامة على كل علامة تتم إضافتها إلى الخريطة.

  1. لإجراء ذلك، عدِّل الاستدعاء places.forEach{} في الدالة addMarkers() باستخدام ما يلي:

MainActivity.addMarkers()

places.forEach { place ->
   val marker = googleMap.addMarker(
       MarkerOptions()
           .title(place.name)
           .position(place.latLng)
           .icon(bicycleIcon)
   )

   // Set place as the tag on the marker object so it can be referenced within
   // MarkerInfoWindowAdapter
   marker.tag = place
}

إضافة صورة محدّد موقع

يُعد تخصيص صورة العلامة إحدى الطرق المرحة لتوضيح نوع المكان الذي تمثله العلامة على خريطتك. بالنسبة إلى هذه الخطوة، يمكنك عرض الدراجات بدلاً من العلامات الحمراء التلقائية لتمثيل كل متجر على الخريطة. يتضمّن مشروع بدء الاستخدام رمز الدراجة ic_directions_bike_black_24dp.xml في app/src/res/drawable، والذي تستخدمه.

6eb7358bb61b0a88.png

ضبط صورة نقطية مخصّصة على محدّد الموقع

باستخدام رمز الدراجة القابل للرسم المتاح تحت تصرفك، تتمثّل الخطوة التالية في ضبط هذا الرسم كرمز لكل علامة على الخريطة. يشمل MarkerOptions طريقة icon، التي تعتمد على BitmapDescriptor التي تستخدمها لتنفيذ ذلك.

أولاً، يجب تحويل المتّجه القابل للرسم الذي أضفته للتو إلى BitmapDescriptor. يحتوي ملف باسم BitMapHelper مضمّن في مشروع إجراء التفعيل على وظيفة مساعد اسمها vectorToBitmap().

BitmapHelper

package com.google.codelabs.buildyourfirstmap

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory

object BitmapHelper {
   /**
    * Demonstrates converting a [Drawable] to a [BitmapDescriptor], 
    * for use as a marker icon. Taken from ApiDemos on GitHub:
    * https://github.com/googlemaps/android-samples/blob/main/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt
    */
   fun vectorToBitmap(
      context: Context,
      @DrawableRes id: Int, 
      @ColorInt color: Int
   ): BitmapDescriptor {
       val vectorDrawable = ResourcesCompat.getDrawable(context.resources, id, null)
       if (vectorDrawable == null) {
           Log.e("BitmapHelper", "Resource not found")
           return BitmapDescriptorFactory.defaultMarker()
       }
       val bitmap = Bitmap.createBitmap(
           vectorDrawable.intrinsicWidth,
           vectorDrawable.intrinsicHeight,
           Bitmap.Config.ARGB_8888
       )
       val canvas = Canvas(bitmap)
       vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
       DrawableCompat.setTint(vectorDrawable, color)
       vectorDrawable.draw(canvas)
       return BitmapDescriptorFactory.fromBitmap(bitmap)
   }
}

تأخذ هذه الطريقة رقم تعريف المورد Context القابل للرسم، بالإضافة إلى عدد صحيح للون وتنشئ BitmapDescriptor تمثيل له.

باستخدام الطريقة المساعدة، حدِّد خاصية جديدة تحمل الاسم bicycleIcon وامنحها التعريف التالي: MainActivity.bicycleIcon

private val bicycleIcon: BitmapDescriptor by lazy {
   val color = ContextCompat.getColor(this, R.color.colorPrimary)
   BitmapHelper.vectorToBitmap(this, R.drawable.ic_directions_bike_black_24dp, color)
}

تستخدم هذه الخاصية اللون colorPrimary المحدد مسبقًا في تطبيقك، وتستخدم هذا التعديل لتلوين رمز الدراجة وعرضه كـ BitmapDescriptor.

  1. باستخدام هذه الخاصية، يمكنك استدعاء الطريقة icon من MarkerOptions في طريقة addMarkers() لإكمال تخصيص الرمز. عند إجراء ذلك، يجب أن تظهر خاصية العلامة كما يلي:

MainActivity.addMarkers()

val marker = googleMap.addMarker(
    MarkerOptions()
        .title(place.name)
        .position(place.latLng)
        .icon(bicycleIcon)
)
  1. شغِّل التطبيق للاطلاع على العلامات المحدَّثة.

8- علامات المجموعة

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

68591edc86d73724.png

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

f05e1ca27ff42bf6.png

لتنفيذ هذا، تحتاج إلى مساعدة SDK للخرائط لمكتبة برامج خدمات Android.

"خرائط Google" لمكتبة Android Utility

تم إنشاء "حزمة تطوير البرامج" (SDK) لتطبيق "خرائط Google" لنظام التشغيل Android كوسيلة لتوسيع نطاق عمل حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل Android. يقدم هذا الإصدار ميزات متقدمة، مثل تجميع العلامات وخرائط التمثيل اللوني ودعم KML وGeoJson، وترميز الترميز المتعدد الخطوط ومجموعة من الوظائف المساعدة حول الهندسة الكروية.

تحديث ملف build.gradle

نظرًا لأن مكتبة الأدوات المساعدة مجمّعة بشكل منفصل عن"خرائط Google"SDK لنظام التشغيل Android، عليك إضافة تبعية إضافية إلى ملف build.gradle.

  1. عدِّل القسم dependencies من ملف app/build.gradle.

build.gradle

implementation 'com.google.maps.android:android-maps-utils:1.1.0'
  1. عند إضافة هذا السطر، يجب إجراء مزامنة للمشروع لجلب تبعيات جديدة.

b7b030ec82c007fd.png

تنفيذ التجميع

لتنفيذ التجميع على تطبيقك، اتّبع الخطوات الثلاث التالية:

  1. تنفيذ واجهة ClusterItem.
  2. الفئة الفرعية DefaultClusterRenderer.
  3. يمكنك إنشاء ClusterManager وإضافة عناصر.

تنفيذ واجهة ClusterItem

تحتاج جميع العناصر التي تمثل علامة قابلة للتجميع على الخريطة إلى تنفيذ واجهة ClusterItem. وفي هذه الحالة، يعني ذلك أن النموذج Place يجب أن يتوافق مع ClusterItem. يمكنك فتح ملف Place.kt وإجراء التعديلات التالية عليه:

المكان

data class Place(
   val name: String,
   val latLng: LatLng,
   val address: String,
   val rating: Float
) : ClusterItem {
   override fun getPosition(): LatLng =
       latLng

   override fun getTitle(): String =
       name

   override fun getSnippet(): String =
       address
}

تحدد ClusterItem الطرق الثلاث التالية:

  • getPosition()، الذي يمثل LatLng.
  • getTitle()، الذي يمثل اسم المكان
  • getSnippet()، الذي يمثل عنوان المكان.

الفئة الفرعية DefaultClusterRenderer

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

ابدأ بإنشاء ملف Kotlin PlaceRenderer.kt في الحزمة com.google.codelabs.buildyourfirstmap.place وحدِّده على النحو التالي:

PlaceRenderer

package com.google.codelabs.buildyourfirstmap.place

import android.content.Context
import androidx.core.content.ContextCompat
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.google.codelabs.buildyourfirstmap.BitmapHelper
import com.google.codelabs.buildyourfirstmap.R
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.clustering.view.DefaultClusterRenderer

/**
* A custom cluster renderer for Place objects.
*/
class PlaceRenderer(
   private val context: Context,
   map: GoogleMap,
   clusterManager: ClusterManager<Place>
) : DefaultClusterRenderer<Place>(context, map, clusterManager) {

   /**
    * The icon to use for each cluster item
    */
   private val bicycleIcon: BitmapDescriptor by lazy {
       val color = ContextCompat.getColor(context,
           R.color.colorPrimary
       )
       BitmapHelper.vectorToBitmap(
           context,
           R.drawable.ic_directions_bike_black_24dp,
           color
       )
   }

   /**
    * Method called before the cluster item (the marker) is rendered.
    * This is where marker options should be set.
    */
   override fun onBeforeClusterItemRendered(
      item: Place,
      markerOptions: MarkerOptions
   ) {
       markerOptions.title(item.name)
           .position(item.latLng)
           .icon(bicycleIcon)
   }

   /**
    * Method called right after the cluster item (the marker) is rendered.
    * This is where properties for the Marker object should be set.
    */
   override fun onClusterItemRendered(clusterItem: Place, marker: Marker) {
       marker.tag = clusterItem
   }
}

تلغي هذه الفئة الوظيفتين التاليتين:

  • onBeforeClusterItemRendered()، وهو ما يتم استدعاءه قبل عرض المجموعة على الخريطة. يمكنك هنا توفير التخصيصات من خلال MarkerOptions، وفي هذه الحالة، يتم ضبط عنوان العلامة وموضعها ورمزها.
  • onClusterItemRenderer()، والذي يتم استدعاءه بعد عرض العلامة مباشرةً على الخريطة. وهذا هو المكان الذي يمكنك من خلاله الوصول إلى عنصر Marker الذي تم إنشاؤه - في هذه الحالة، يتم ضبط خاصية العلامة المحدّد.

إنشاء ClusterManager وإضافة عناصر

أخيرًا، لبدء العمل في تجميع، عليك تعديل MainActivity لإنشاء ClusterManager وإنشاء تبعيات ضرورية له. يتعامل ClusterManager مع إضافة العلامات (عناصر ClusterItem) داخليًا، لذلك بدلاً من إضافة العلامات مباشرةً على الخريطة، يتم تفويض هذه المسؤولية إلى ClusterManager. بالإضافة إلى ذلك، يستدعي ClusterManager أيضًا setInfoWindowAdapter() داخليًا، لذا يجب ضبط نافذة معلومات مخصّصة على الكائن ClusterManger's MarkerManager.Collection.

  1. للبدء، عدِّل محتوى لمدا في استدعاء getMapAsync() في MainActivity.onCreate(). أَرْجُو الْاسْتِمْرَارْ وِتَعْلِيقِ الْاتِّصَالْ بِـ addMarkers() وِsetInfoWindowAdapter()، وِبَدْلِهَا اسْتِدْعَاءْ دِلْوَقْتِي اسْمُهْ addClusteredMarkers()، الْلِّي بَيْتِمّْ تَحْدِيدُهْ.

MainActivity.onCreate()

mapFragment?.getMapAsync { googleMap ->
    //addMarkers(googleMap)
    addClusteredMarkers(googleMap)

    // Set custom info window adapter.
    // googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
  1. بعد ذلك، عليك تحديد addClusteredMarkers() في MainActivity.

MainActivity.addClusteredMarkers()

/**
* Adds markers to the map with clustering support.
*/
private fun addClusteredMarkers(googleMap: GoogleMap) {
   // Create the ClusterManager class and set the custom renderer.
   val clusterManager = ClusterManager<Place>(this, googleMap)
   clusterManager.renderer =
       PlaceRenderer(
           this,
           googleMap,
           clusterManager
       )

   // Set custom info window adapter
   clusterManager.markerCollection.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))

   // Add the places to the ClusterManager.
   clusterManager.addItems(places)
   clusterManager.cluster()

   // Set ClusterManager as the OnCameraIdleListener so that it
   // can re-cluster when zooming in and out.
   googleMap.setOnCameraIdleListener {
       clusterManager.onCameraIdle()
   }
}

تعمل هذه الطريقة على إنشاء ClusterManager، وتمرير العارض المخصص PlacesRenderer، وإضافة جميع الأماكن، واستدعاء الطريقة cluster(). وبما أنّ ClusterManager يستخدم الطريقة setInfoWindowAdapter() على عنصر الخريطة، سيكون عليك ضبط نافذة المعلومات المخصّصة على العنصر ClusterManager.markerCollection. وأخيرًا، إذا أردت أن يتغير التجميع أثناء تحريك المستخدم وتكبير/تصغيره على الخريطة، يتم توفير OnCameraIdleListener إلى googleMap، بحيث عندما تكون الكاميرا في وضع عدم النشاط، يتم استدعاء clusterManager.onCameraIdle().

  1. واصِل تشغيل التطبيق لرؤية المتاجر المُجمَّعة الجديدة.

9- الرسم على الخريطة

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

على سبيل المثال، إذا كنت تريد تمثيل المسارات والمناطق على الخريطة، يمكنك استخدام الخطوط المتعددة والمضلعات لعرضها على الخريطة. أو إذا كنت تريد إصلاح صورة على سطح الأرض، يمكنك استخدام تراكبات الأرض.

وستتعلّم في هذه المهمة كيفية رسم أشكال، بشكل خاص على شكل دائرة، حول محدّد عند النقر عليها.

f98ce13055430352.png

إضافة أداة معالجة النقر

عادةً ما تتمثّل الطريقة التي يمكنك من خلالها إضافة أداة معالجة نقرة إلى علامة في تمرير الملف من خلال أداة معالجة نقرة على عنصر GoogleMap مباشرةً من خلال setOnMarkerClickListener(). ومع ذلك، نظرًا لأنك تستخدم التجميع، يجب تقديم أداة معالجة النقر إلى ClusterManager بدلاً من ذلك.

  1. في طريقة addClusteredMarkers() في MainActivity، يمكنك إضافة السطر التالي مباشرةً بعد الاستدعاء إلى cluster().

MainActivity.addClusteredMarkers()

// Show polygon
clusterManager.setOnClusterItemClickListener { item ->
   addCircle(googleMap, item)
   return@setOnClusterItemClickListener false
}

تضيف هذه الطريقة أداة معالجة وتستدعي الطريقة addCircle() التي تحدّدها بعد ذلك. وأخيرًا، يتم عرض false من هذه الطريقة للإشارة إلى أن هذه الطريقة لم تستهلك هذا الحدث.

  1. بعد ذلك، يجب تحديد الخاصية circle والطريقة addCircle() في MainActivity.

MainActivity.addCircle()

private var circle: Circle? = null

/**
* Adds a [Circle] around the provided [item]
*/
private fun addCircle(googleMap: GoogleMap, item: Place) {
   circle?.remove()
   circle = googleMap.addCircle(
       CircleOptions()
           .center(item.latLng)
           .radius(1000.0)
           .fillColor(ContextCompat.getColor(this, R.color.colorPrimaryTranslucent))
           .strokeColor(ContextCompat.getColor(this, R.color.colorPrimary))
   )
}

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

  1. واصِل تشغيل التطبيق الآن للاطّلاع على التغييرات.

10- التحكّم بالكاميرا

بالنسبة إلى المهمة الأخيرة، يمكنك الاطّلاع على بعض عناصر التحكّم في الكاميرا حتى تتمكن من التركيز على منطقة معيّنة.

الكاميرا والعرض

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

للمساعدة في ذلك، يمكنك ضبط موضع الكاميرا آليًا بحيث يتم توسيط العرض في المكان الذي تريده.

  1. يمكنك إضافة الرمز التالي إلى مكالمة getMapAsync() لضبط وضع عرض الكاميرا بحيث يتم إعداده في سان فرانسيسكو عند تشغيل التطبيق.

MainActivity.onCreate()

mapFragment?.getMapAsync { googleMap ->
   // Ensure all places are visible in the map.
   googleMap.setOnMapLoadedCallback {
       val bounds = LatLngBounds.builder()
       places.forEach { bounds.include(it.latLng) }
       googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))
   }
}

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

في لامدا، يتم إنشاء كائن LatLngBounds جديد، يحدد منطقة مستطيلة على الخريطة. يتم إنشاء هذا تدريجيًا من خلال تضمين جميع قيم المكان LatLng فيه لضمان أن جميع الأماكن داخل الحدود. بعد إنشاء هذا الكائن، يتم استدعاء طريقة moveCamera() على GoogleMap ويتم تقديم CameraUpdate إليه من خلال CameraUpdateFactory.newLatLngBounds(bounds.build(), 20).

  1. شغِّل التطبيق ولاحظ أن الكاميرا تبدأ الآن في شرم الشيخ.

جارٍ الاستماع إلى تغييرات الكاميرا

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

للترفيه فقط، يمكنك تعديل الرمز لجعل العلامات شفّافة كلما تم تحريك الكاميرا.

  1. في الطريقة addClusteredMarkers()، استمر في إضافة الأسطر التالية باتجاه الجزء السفلي من الطريقة:

MainActivity.addClusteredMarkers()

// When the camera starts moving, change the alpha value of the marker to translucent.
googleMap.setOnCameraMoveStartedListener {
   clusterManager.markerCollection.markers.forEach { it.alpha = 0.3f }
   clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 0.3f }
}

يؤدي ذلك إلى إضافة OnCameraMoveStartedListener بحيث كلما بدأت الكاميرا في الحركة، تم تعديل كل علامات الإصدار&#39؛ (كل من المجموعات والعلامات) إلى 0.3f بحيث تبدو العلامات شبه شفافة.

  1. أخيرًا، لتعديل العلامات الشفافة مرة أخرى على التعتيم عندما تتوقف الكاميرا، عدِّل محتوى setOnCameraIdleListener في طريقة addClusteredMarkers() على النحو التالي:

MainActivity.addClusteredMarkers()

googleMap.setOnCameraIdleListener {
   // When the camera stops moving, change the alpha value back to opaque.
   clusterManager.markerCollection.markers.forEach { it.alpha = 1.0f }
   clusterManager.clusterMarkerCollection.markers.forEach { it.alpha = 1.0f }

   // Call clusterManager.onCameraIdle() when the camera stops moving so that reclustering
   // can be performed when the camera stops moving.
   clusterManager.onCameraIdle()
}
  1. واصِل تشغيل التطبيق للاطّلاع على النتائج.

11- خرائط KTX

بالنسبة إلى تطبيقات Kotlin التي تستخدم حزمة تطوير برامج (SDK) واحدة أو أكثر لنظام التشغيل Android على "منصة خرائط Google"، تتوفّر إضافة Kotlin أو مكتبات KTX لمساعدتك في الاستفادة من ميزات لغة Kotlin، مثل الكوروتينات وخصائص الإضافات/وظائفها والمزيد. لكل حِزمة تطوير برامج (SDK) في "خرائط Google" مكتبة KTX مطابقة كما هو موضّح أدناه:

مخطط KTX لمنصّة &quot;خرائط Google&quot;

في هذه المَهمّة، ستستخدم مكتبات KTX للخرائط و"خرائط Utils لـ KTX" في تطبيقك كما ستعيد تنفيذ المهام السابقة&#39؛ وعمليات التنفيذ بحيث يمكنك استخدام ميزات اللغة الخاصة بلغة Kotlin في تطبيقك.

  1. تضمين تبعيات KTX في ملف build.gradle على مستوى التطبيق

ونظرًا لاستخدام التطبيق لكلٍّ من حِزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل Android وحزمة تطوير برامج "خرائط Google" لمكتبة برامج خدمات Android، عليك تضمين مكتبات KTX المقابلة لهذه المكتبات. ستستخدم أيضًا ميزة متوفّرة في مكتبة"دورة حياة KTX"في AndroidX في هذه المهمة، لذا أدرِج هذه الاعتمادية أيضًا في ملف build.gradle على مستوى التطبيق.

build.gradle

dependencies {
    // ...

    // Maps SDK for Android KTX Library
    implementation 'com.google.maps.android:maps-ktx:3.0.0'

    // Maps SDK for Android Utility Library KTX Library
    implementation 'com.google.maps.android:maps-utils-ktx:3.0.0'

    // Lifecycle Runtime KTX Library
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
}
  1. استخدام دالّة الإضافة GoogleMap.addMarker() وGoogleMap.addCircle()

توفر مكتبة Maps KTX بديل لـ DSL API لـ GoogleMap.addMarker(MarkerOptions) وGoogleMap.addCircle(CircleOptions) المستخدمَين في الخطوات السابقة. لاستخدام واجهات برمجة التطبيقات المذكورة أعلاه، من الضروري إنشاء فئة تحتوي على خيارات لعلامة أو دائرة، بينما من خلال بدائل KTX، يمكنك إعداد خيارات العلامة أو الدائرة في lambda التي تقدمها.

لاستخدام واجهات برمجة التطبيقات هذه، عدِّل طريقتَي MainActivity.addMarkers(GoogleMap) وMainActivity.addCircle(GoogleMap):

MainActivity.addMarkers(GoogleMap)

/**
 * Adds markers to the map. These markers won't be clustered.
 */
private fun addMarkers(googleMap: GoogleMap) {
    places.forEach { place ->
        val marker = googleMap.addMarker {
            title(place.name)
            position(place.latLng)
            icon(bicycleIcon)
        }
        // Set place as the tag on the marker object so it can be referenced within
        // MarkerInfoWindowAdapter
        marker.tag = place
    }
}

MainActivity.addCircle(GoogleMap)

/**
 * Adds a [Circle] around the provided [item]
 */
private fun addCircle(googleMap: GoogleMap, item: Place) {
    circle?.remove()
    circle = googleMap.addCircle {
        center(item.latLng)
        radius(1000.0)
        fillColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimaryTranslucent))
        strokeColor(ContextCompat.getColor(this@MainActivity, R.color.colorPrimary))
    }
}

يُرجى العِلم أنّ إعادة كتابة الطرق المذكورة أعلاه بهذه الطريقة تكون أكثر إيجازًا ووضوحًا، ما يجعلها ممكنة باستخدام دالة Kotlin's مع المُستقبِل.

  1. استخدِم دالّتَي التعليق على الإضافة MapMapFragment.aانتظارMap() وGoogleMap.aWayMapLoad().

وتوفّر مكتبة KTX للخرائط أيضًا إضافات وظائف معلّقة لاستخدامها في الكوروتين. على وجه التحديد، هناك بدائل مرتبطة بوظائف SupportMapFragment.getMapAsync(OnMapReadyCallback) وGoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback). وباستخدام واجهات برمجة التطبيقات البديلة هذه، لا تكون هناك حاجة لتمرير استدعاءات، بدلاً من ذلك، السماح لك بتلقي استجابة هذه الطرق بطريقة تسلسلية ومتزامنة.

بما أن هذه الطرق تعمل على تعليق الوظائف، يلزم استخدامها في الكوروتين. توفّر مكتبة وقت تشغيل دورة حياة KTX امتدادًا لتوفير نطاقات الكوروتين الواعية بدورة الحياة بحيث يتم تشغيل الكوروتينات في حدث دورة الحياة المناسب وإيقافها.

عند الجمع بين هذه المفاهيم، يمكنك تعديل طريقة MainActivity.onCreate(Bundle):

MainActivity.onCreate(حزمة)

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val mapFragment =
        supportFragmentManager.findFragmentById(R.id.map_fragment) as SupportMapFragment
    lifecycleScope.launchWhenCreated {
        // Get map
        val googleMap = mapFragment.awaitMap()

        // Wait for map to finish loading
        googleMap.awaitMapLoad()

        // Ensure all places are visible in the map
        val bounds = LatLngBounds.builder()
        places.forEach { bounds.include(it.latLng) }
        googleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds.build(), 20))

        addClusteredMarkers(googleMap)
    }
}

ينفّذ نطاق كورتين lifecycleScope.launchWhenCreated على مستوى المجموعة عندما يكون النشاط على الأقل في الحالة التي تم إنشاؤها. تجدر الإشارة أيضًا إلى أنه تم استبدال طلبات استرداد العنصر GoogleMap وانتظار انتهاء تحميل الخريطة بـ SupportMapFragment.awaitMap() وGoogleMap.awaitMapLoad() على التوالي. تتيح لك إعادة إنشاء الرمز باستخدام دوال التعليق هذه كتابة الرمز المكافئ المستند إلى رد الاتصال بطريقة تسلسلية.

  1. هيّا، أعِد تصميم تطبيقك باستخدام التغييرات التي أصلحتها.

12- تهانينا

تهانينا. لقد تناولت الكثير من المحتوى ونأمل أن تكون على دراية أفضل بالميزات الأساسية المتوفرة في حزمة تطوير البرامج (SDK) لتطبيق "خرائط Google" لنظام التشغيل Android.

مزيد من المعلومات

  • الأماكن المخصصة لنظام التشغيل Android: يمكنك الاطّلاع على مجموعة بيانات وافية عن الأماكن لاستكشاف الأنشطة التجارية القريبة منك.
  • android-maps-ktx: مكتبة مفتوحة المصدر تسمح لك بالدمج مع Maps SDK لنظام التشغيل Android وMaps لـ Android Utility ومكتبة متوافقة مع لغة Kotlin.
  • android-place-ktx: مكتبة مفتوحة المصدر تسمح لك بالدمج مع Places SDK لنظام التشغيل Android بطريقة متوافقة مع لغة Kotlin.
  • android-samples: نموذج رمز على GitHub يوضّح جميع الميزات المشمولة في هذا الدرس التطبيقي حول الترميز والمزيد.
  • المزيد من الدروس التطبيقية حول ترميز Kotlin لإنشاء تطبيقات متوافقة مع Android باستخدام "منصة خرائط Google"