لمحة عن هذا الدرس التطبيقي حول الترميز
1. قبل البدء
يعلّمك هذا الدرس العملي كيفية دمج حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل Android مع تطبيقك واستخدام ميزاتها الأساسية من خلال إنشاء تطبيق يعرض خريطة لمتاجر الدراجات في سان فرانسيسكو، كاليفورنيا، الولايات المتحدة الأمريكية.
المتطلبات الأساسية
- معرفة أساسية بلغة Kotlin وتطوير تطبيقات Android
الإجراءات التي ستنفذّها
- فعِّل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android واستخدِمها لإضافة "خرائط Google" إلى تطبيق Android.
- إضافة العلامات وتخصيصها وتجميعها
- رسم خطوط متعددة الأضلاع ومضلّعات على الخريطة
- التحكّم في نقطة عرض الكاميرا آليًا
المتطلبات
- حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android
- حساب Google تم تفعيل الفوترة فيه
- الإصدار 2020.3.1 من "استوديو Android" أو إصدار أحدث
- تثبيت خدمات Google Play في "استوديو Android"
- جهاز Android أو محاكي Android يعمل بمنصة Google APIs استنادًا إلى الإصدار 4.2.2 من نظام التشغيل Android أو إصدار أحدث (راجِع تشغيل التطبيقات على "محاكي Android" لمعرفة خطوات التثبيت)
2. طريقة الإعداد
في خطوة التفعيل التالية، عليك تفعيل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android.
إعداد Google Maps Platform
إذا لم يكن لديك حساب على Google Cloud Platform ومشروع مفعَّل فيه نظام الفوترة، يُرجى الاطّلاع على دليل البدء باستخدام Google Maps Platform لإنشاء حساب فوترة ومشروع.
- في Cloud Console، انقر على القائمة المنسدلة الخاصة بالمشروع واختَر المشروع الذي تريد استخدامه في هذا الدرس العملي.
- فعِّل واجهات برمجة التطبيقات وحِزم تطوير البرامج (SDK) في Google Maps Platform المطلوبة لهذا الدرس العملي في Google Cloud Marketplace. لإجراء ذلك، اتّبِع الخطوات الواردة في هذا الفيديو أو هذه المستندات.
- أنشئ مفتاح واجهة برمجة التطبيقات في صفحة بيانات الاعتماد في Cloud Console. يمكنك اتّباع الخطوات الواردة في هذا الفيديو أو هذه المستندات. تتطلّب جميع الطلبات إلى "منصة خرائط Google" مفتاح واجهة برمجة تطبيقات.
3. البدء بسرعة
لمساعدتك في البدء بأسرع ما يمكن، إليك بعض الرموز البرمجية الأولية لمساعدتك في متابعة هذا الدرس العملي. يمكنك الانتقال إلى الحلّ مباشرةً، ولكن إذا أردت اتّباع جميع الخطوات لإنشائه بنفسك، يمكنك مواصلة القراءة.
- استنسِخ المستودع إذا كان لديك
git
مثبَّتًا.
git clone https://github.com/googlecodelabs/maps-platform-101-android.git
يمكنك بدلاً من ذلك النقر على الزر التالي لتنزيل رمز المصدر.
- بعد الحصول على الرمز، افتح المشروع الموجود في الدليل
starter
في "استوديو Android".
4. إضافة "خرائط Google"
في هذا القسم، ستضيف "خرائط Google" ليتم تحميلها عند تشغيل التطبيق.
إضافة مفتاح واجهة برمجة التطبيقات
يجب تزويد التطبيق بمفتاح واجهة برمجة التطبيقات الذي أنشأته في خطوة سابقة حتى تتمكّن حزمة تطوير البرامج (SDK) لخرائط Google لنظام التشغيل Android من ربط مفتاحك بتطبيقك.
- لتوفير ذلك، افتح الملف المسمّى
local.properties
في الدليل الجذر لمشروعك (المستوى نفسه الذي يظهر فيهgradle.properties
وsettings.gradle
). - في هذا الملف، حدِّد مفتاحًا جديدًا
GOOGLE_MAPS_API_KEY
تكون قيمته هي مفتاح واجهة برمجة التطبيقات الذي أنشأته.
local.properties
GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE
لاحظ أنّ local.properties
مدرَج في الملف .gitignore
في مستودع Git. ويرجع ذلك إلى أنّ مفتاح واجهة برمجة التطبيقات يُعدّ معلومات حساسة ويجب عدم تسجيله في نظام التحكّم بالمصادر، إذا أمكن ذلك.
- بعد ذلك، لعرض واجهة برمجة التطبيقات كي يمكن استخدامها في جميع أنحاء تطبيقك، أدرِج المكوّن الإضافي 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"
- بعد أن أصبح بإمكانك الوصول إلى مفتاح واجهة برمجة التطبيقات داخل التطبيق، تتمثّل الخطوة التالية في إضافة تبعية "حزمة تطوير البرامج (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'
}
- بعد ذلك، أضِف علامة
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}" />
- بعد ذلك، أنشئ ملف تصميم جديدًا باسم
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
الأساسي الذي تستخدمه في الخطوات اللاحقة.
- أخيرًا، عدِّل فئة
MainActivity
الموجودة فيapp/src/main/java/com/google/codelabs/buildyourfirstmap
من خلال إضافة الرمز التالي لتجاوز طريقةonCreate
حتى تتمكّن من ضبط محتوياتها باستخدام التصميم الجديد الذي أنشأته للتو.
MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
- الآن، شغِّل التطبيق. من المفترض أن تظهر لك الخريطة على شاشة جهازك.
5. تصميم الخرائط باستخدام السحابة الإلكترونية (اختياري)
يمكنك تخصيص نمط الخريطة باستخدام تصميم الخرائط باستخدام السحابة الإلكترونية.
إنشاء رقم تعريف خريطة
إذا لم يسبق لك إنشاء معرّف خريطة مرتبط بنمط خريطة، يمكنك الاطّلاع على دليل معرّفات الخرائط لإكمال الخطوات التالية:
- أنشئ معرّف خريطة.
- ربط رقم تعريف خريطة بنمط خريطة
إضافة رقم تعريف الخريطة إلى تطبيقك
لاستخدام رقم تعريف الخريطة الذي أنشأته، عدِّل الملف 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. إضافة علامات
في هذه المهمة، ستضيف علامات إلى الخريطة تمثّل نقاط الاهتمام التي تريد إبرازها على الخريطة. أولاً، عليك استرداد قائمة بالأماكن التي تم توفيرها في المشروع المبدئي، ثم إضافة هذه الأماكن إلى الخريطة. في هذا المثال، هذه هي متاجر الدراجات.
الحصول على مرجع إلى GoogleMap
أولاً، عليك الحصول على مرجع إلى الكائن 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
. داخل هذه الدالة الشرطية، يتم استدعاء استدعاء الطريقة addMarkers()
، والذي سيتم تعريفه بعد قليل.
فئة مقدَّمة: PlacesReader
في مشروع التطبيق التجريبي، تم توفير الفئة PlacesReader
لك. يقرأ هذا الصف قائمة تضم 49 مكانًا مخزّنة في ملف JSON باسم places.json
ويعرضها كـ List<Place>
. تمثّل الأماكن نفسها قائمة بمتاجر الدراجات حول سان فرانسيسكو، كاليفورنيا، الولايات المتحدة.
إذا كنت مهتمًا بمعرفة كيفية تنفيذ هذه الفئة، يمكنك الوصول إليها على GitHub أو فتح الفئة PlacesReader
في "استوديو Android".
PlacesReader
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
وعرِّفها على النحو التالي:
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
)
إضافة علامات إلى الخريطة
بعد تحميل قائمة الأماكن إلى الذاكرة، تتمثل الخطوة التالية في تمثيل هذه الأماكن على الخريطة.
- أنشِئ طريقة في
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
، ما يتيح لك تخصيص العلامة نفسها. في هذه الحالة، يتم توفير عنوان وعلامة الموقع الجغرافي، وهما يمثّلان اسم متجر الدراجات وإحداثياته على التوالي.
- يمكنك الآن تشغيل التطبيق والانتقال إلى سان فرانسيسكو للاطّلاع على العلامات التي أضفتها للتو.
7. تخصيص علامات التحديد
تتوفّر عدة خيارات لتخصيص العلامات التي أضفتها للتو لمساعدتها في التميّز ونقل معلومات مفيدة إلى المستخدمين. في هذه المهمة، ستستكشف بعضًا من هذه العناصر من خلال تخصيص صورة كل علامة بالإضافة إلى نافذة المعلومات التي تظهر عند النقر على علامة.
إضافة نافذة معلومات
تعرض نافذة المعلومات تلقائيًا عند النقر على علامة عنوانها ومقتطفها (في حال ضبطهما). يمكنك تخصيص هذه البطاقة لتتمكّن من عرض معلومات إضافية، مثل عنوان المكان وتقييمه.
إنشاء ملف marker_info_contents.xml
أولاً، أنشِئ ملف تصميم جديدًا باسم marker_info_contents.xml
.
- لإجراء ذلك، انقر بزر الماوس الأيمن على المجلد
app/src/main/res/layout
في عرض المشروع في "استوديو Android"، ثم اختَر جديد > ملف مصدر التنسيق.
- في مربع الحوار، اكتب
marker_info_contents
في حقل اسم الملف وLinearLayout
في حقلRoot element
، ثم انقر على حسنًا.
يتم بعد ذلك توسيع ملف التصميم هذا لتمثيل المحتوى داخل نافذة المعلومات.
- انسخ المحتوى في مقتطف الرمز التالي، والذي يضيف ثلاثة
TextViews
ضمن مجموعة عرضLinearLayout
عمودية، واكتب فوق الرمز التلقائي في الملف.
marker_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>
إنشاء تنفيذ لـ InfoWindowAdapter
بعد إنشاء ملف التنسيق لنافذة المعلومات المخصّصة، تتمثّل الخطوة التالية في تنفيذ واجهة GoogleMap.InfoWindowAdapter. تحتوي هذه الواجهة على طريقتَين، getInfoWindow()
وgetInfoContents()
. تعرض كلتا الطريقتَين عنصر View
اختياريًا، ويُستخدَم العنصر الأول لتخصيص النافذة نفسها، بينما يُستخدَم العنصر الثاني لتخصيص محتواها. في حالتك، عليك تنفيذ كليهما وتخصيص قيمة getInfoContents()
مع عرض قيمة فارغة في getInfoWindow()
، ما يشير إلى أنّه يجب استخدام الفترة التلقائية.
- أنشِئ ملف Kotlin جديدًا باسم
MarkerInfoWindowAdapter
في الحزمة نفسها التي تضمMainActivity
من خلال النقر بزر الماوس الأيمن على المجلدapp/src/main/java/com/google/codelabs/buildyourfirstmap
في عرض المشروع في "استوديو Android"، ثم اختَر جديد > ملف/فئة Kotlin.
- في مربّع الحوار، اكتب
MarkerInfoWindowAdapter
مع إبقاء ملف محدّدًا.
- بعد إنشاء الملف، انسخ المحتوى في مقتطف الرمز التالي إلى ملفك الجديد.
MarkerInfoWindowAdapter
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()
، يتم تحويل Marker المقدَّم في الطريقة إلى النوع Place
، وإذا لم يكن التحويل ممكنًا، تعرض الطريقة قيمة فارغة (لم تضبط سمة العلامة على Marker
بعد، ولكن يمكنك إجراء ذلك في الخطوة التالية).
بعد ذلك، يتم تضخيم التنسيق marker_info_contents.xml
ثم يتم ضبط النص على TextViews
الذي يحتوي على العلامة Place
.
تعديل MainActivity
لربط جميع المكوّنات التي أنشأتها حتى الآن، عليك إضافة سطرَين في فئة MainActivity
.
أولاً، لتمرير السمتَين المخصّصتَين InfoWindowAdapter
وMarkerInfoWindowAdapter
داخل استدعاء طريقة getMapAsync
، استدعِ طريقة setInfoWindowAdapter()
على الكائن GoogleMap
وأنشئ مثيلاً جديدًا من MarkerInfoWindowAdapter
.
- يمكنك إجراء ذلك عن طريق إضافة الرمز التالي بعد استدعاء الطريقة
addMarkers()
داخل تعبير lambdagetMapAsync()
.
MainActivity.onCreate()
// Set custom info window adapter
googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
أخيرًا، عليك ضبط كل "مكان" كسمة علامة على كل "علامة" تتم إضافتها إلى الخريطة.
- لإجراء ذلك، عدِّل استدعاء
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
، والذي ستستخدمه.
ضبط صورة نقطية مخصّصة على علامة
بعد توفّر رمز الدراجة النارية القابل للتوسيع، تتمثّل الخطوة التالية في ضبط هذا العنصر الرسومي كرمز لكل علامة على الخريطة. يحتوي 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
.
- باستخدام هذه السمة، يمكنك استدعاء طريقة
icon
منMarkerOptions
في طريقةaddMarkers()
لإكمال تخصيص الرمز. عند إجراء ذلك، من المفترض أن تظهر خاصية العلامة على النحو التالي:
MainActivity.addMarkers()
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
.icon(bicycleIcon)
)
- شغِّل التطبيق للاطّلاع على العلامات المعدَّلة.
8. علامات المجموعات
استنادًا إلى مدى تكبير الخريطة، ربما لاحظت أنّ العلامات التي أضفتها تتداخل. من الصعب جدًا التفاعل مع العلامات المتداخلة، كما أنّها تتسبب في حدوث تشويش كبير يؤثر في سهولة استخدام تطبيقك.
لتحسين تجربة المستخدم في هذه الحالة، ننصحك بتنفيذ تجميع العلامات عندما تكون لديك مجموعة بيانات كبيرة مجمّعة بشكل وثيق. باستخدام التجميع، عند تكبير الخريطة وتصغيرها، يتم تجميع العلامات القريبة من بعضها على النحو التالي:
لتنفيذ ذلك، عليك الاستعانة بمكتبة أدوات "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android".
مكتبة أدوات "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android"
تم إنشاء مكتبة أدوات "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android" كوسيلة لتوسيع وظائف "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android". وتوفّر ميزات متقدّمة، مثل تجميع العلامات، والخرائط الحرارية، وتوافق KML وGeoJson، وتشفير وفك تشفير الخطوط المتعددة، وعدد من الدوال المساعدة المتعلقة بالهندسة الكروية.
تعديل ملف build.gradle
بما أنّ مكتبة الأدوات يتم تجميعها بشكل منفصل عن "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android"، عليك إضافة ملحق إضافي إلى ملف build.gradle
.
- يمكنك المتابعة وتعديل القسم
dependencies
في ملفapp/build.gradle
.
build.gradle
implementation 'com.google.maps.android:android-maps-utils:1.1.0'
- بعد إضافة هذا السطر، عليك إجراء مزامنة للمشروع لجلب التبعيات الجديدة.
تنفيذ التجميع
لتنفيذ التجميع في تطبيقك، اتّبِع الخطوات الثلاث التالية:
- تنفيذ واجهة
ClusterItem
- أنشئ فئة فرعية من الفئة
DefaultClusterRenderer
. - أنشئ
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()
داخليًا، لذا يجب إعداد نافذة معلومات مخصّصة على عنصر MarkerManager.Collection
في ClusterManger
.
- للبدء، عدِّل محتوى lambda في استدعاء
getMapAsync()
فيMainActivity.onCreate()
. يمكنك إضافة تعليق إلى استدعاءaddMarkers()
وsetInfoWindowAdapter()
، واستخدام طريقة أخرى باسمaddClusteredMarkers()
بدلاً من ذلك، والتي ستحدّدها لاحقًا.
MainActivity.onCreate()
mapFragment?.getMapAsync { googleMap ->
//addMarkers(googleMap)
addClusteredMarkers(googleMap)
// Set custom info window adapter.
// googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
- التالي، في
MainActivity
، عرِّفaddClusteredMarkers()
.
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()
عندما تصبح الكاميرا غير نشطة.
- يمكنك الآن تشغيل التطبيق للاطّلاع على المتاجر المجمّعة الجديدة.
9. الرسم على الخريطة
بعد أن تعرّفت على إحدى طرق الرسم على الخريطة (من خلال إضافة علامات)، تتيح "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لنظام التشغيل Android العديد من الطرق الأخرى التي يمكنك من خلالها الرسم لعرض معلومات مفيدة على الخريطة.
على سبيل المثال، إذا أردت تمثيل المسارات والمناطق على الخريطة، يمكنك استخدام الخطوط المتعددة والمضلّعات لعرضها على الخريطة. أو إذا أردت تثبيت صورة على سطح الأرض، يمكنك استخدام تراكبات أرضية.
في هذه المهمة، ستتعلّم كيفية رسم أشكال، وتحديدًا دائرة، حول علامة عند النقر عليها.
إضافة أداة معالجة أحداث النقر
عادةً، تتم إضافة أداة معالجة النقرات إلى علامة من خلال تمرير أداة معالجة النقرات مباشرةً إلى العنصر GoogleMap
عبر setOnMarkerClickListener()
. ومع ذلك، بما أنّك تستخدم التجميع، يجب توفير أداة معالجة النقرات إلى ClusterManager
بدلاً من ذلك.
- في طريقة
addClusteredMarkers()
فيMainActivity
، أضِف السطر التالي مباشرةً بعد استدعاءcluster()
.
MainActivity.addClusteredMarkers()
// Show polygon
clusterManager.setOnClusterItemClickListener { item ->
addCircle(googleMap, item)
return@setOnClusterItemClickListener false
}
تضيف هذه الطريقة معالج أحداث وتستدعي الطريقة addCircle()
التي تحدّدها لاحقًا. أخيرًا، يتم عرض false
من هذه الطريقة للإشارة إلى أنّ هذه الطريقة لم تستهلك هذا الحدث.
- بعد ذلك، عليك تحديد السمة
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
بحيث يتمّ حذف الدائرة السابقة وإضافة دائرة جديدة كلّما تمّ النقر على علامة جديدة. لاحظ أنّ واجهة برمجة التطبيقات لإضافة دائرة تشبه إلى حد كبير واجهة برمجة التطبيقات لإضافة علامة.
- يمكنك الآن تشغيل التطبيق للاطّلاع على التغييرات.
10. التحكّم بالكاميرا
كخطوة أخيرة، يمكنك الاطّلاع على بعض عناصر التحكّم في الكاميرا حتى تتمكّن من تركيز العرض على منطقة معيّنة.
الكاميرا والعرض
إذا لاحظت عند تشغيل التطبيق أنّ الكاميرا تعرض قارة أفريقيا، عليك تحريك الكاميرا وتكبيرها يدويًا للعثور على العلامات التي أضفتها في سان فرانسيسكو. على الرغم من أنّها طريقة ممتعة لاستكشاف العالم، إلا أنّها غير مفيدة إذا كنت تريد عرض العلامات على الفور.
للمساعدة في ذلك، يمكنك ضبط موضع الكاميرا آليًا حتى يكون العرض في منتصف المكان الذي تريده.
- يمكنك المتابعة وإضافة الرمز التالي إلى طلب
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()
حتى لا يتم تحديث الكاميرا إلا بعد تحميل الخريطة. هذه الخطوة ضرورية لأنّه يجب احتساب خصائص الخريطة، مثل الأبعاد، قبل إجراء طلب تعديل الكاميرا.
في دالة lambda، يتم إنشاء عنصر LatLngBounds
جديد يحدّد منطقة مستطيلة على الخريطة. يتم إنشاء هذا المربع بشكل تدريجي من خلال تضمين جميع قيم LatLng
الخاصة بالمكان فيه لضمان أن تكون جميع الأماكن داخل الحدود. بعد إنشاء هذا العنصر، يتم استدعاء الطريقة moveCamera()
في GoogleMap
ويتم توفير CameraUpdate
لها من خلال CameraUpdateFactory.newLatLngBounds(bounds.build(), 20)
.
- شغِّل التطبيق ولاحظ أنّه تم الآن إعداد الكاميرا في سان فرانسيسكو.
الاستماع إلى تغييرات الكاميرا
بالإضافة إلى تعديل موضع الكاميرا، يمكنك أيضًا الاستماع إلى آخر المعلومات عن الكاميرا أثناء تنقّل المستخدم على الخريطة. قد يكون ذلك مفيدًا إذا أردت تعديل واجهة المستخدم أثناء تحرّك الكاميرا.
للتسلية فقط، يمكنك تعديل الرمز البرمجي لجعل العلامات شفافة كلما تم تحريك الكاميرا.
- في طريقة
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
، وبالتالي، كلما بدأت الكاميرا في التحرك، يتم تعديل قيم ألفا لجميع العلامات (سواء المجموعات أو العلامات) إلى 0.3f
لتظهر العلامات شفافة.
- أخيرًا، لتعديل العلامات الشفافة إلى علامات غير شفافة عند إيقاف الكاميرا، عدِّل محتوى
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()
}
- يمكنك الآن تشغيل التطبيق للاطّلاع على النتائج.
11. Maps KTX
بالنسبة إلى تطبيقات Kotlin التي تستخدم واحدة أو أكثر من حِزم تطوير البرامج (SDK) لنظام التشغيل Android في "منصة خرائط Google"، تتوفّر مكتبات Kotlin الإضافية أو KTX لتتيح لك الاستفادة من ميزات لغة Kotlin، مثل الروتينات الفرعية وخصائص/وظائف الإضافات وغير ذلك. تحتوي كل حزمة تطوير برامج (SDK) من "خرائط Google" على مكتبة KTX مقابلة كما هو موضّح أدناه:
في هذه المهمة، ستستخدم مكتبتَي Maps KTX وMaps Utils KTX في تطبيقك، وستعيد تصميم عمليات تنفيذ المهام السابقة لتتمكّن من استخدام ميزات اللغة الخاصة بلغة Kotlin في تطبيقك.
- تضمين تبعيات KTX في ملف build.gradle على مستوى التطبيق
بما أنّ التطبيق يستخدم كلاً من "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android" و"مكتبة أدوات حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android"، عليك تضمين مكتبات KTX المتوافقة مع هاتين المكتبتين. ستستخدم أيضًا ميزة مضمّنة في مكتبة AndroidX Lifecycle KTX في هذه المهمة، لذا أدرِج هذا العنصر التابع أيضًا في ملف 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'
}
- استخدام دالّتَي الإضافة GoogleMap.addMarker() وGoogleMap.addCircle()
توفّر مكتبة Maps KTX بديلاً لواجهة برمجة التطبيقات بأسلوب DSL لكل من 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.
- استخدام دالّتَي التعليق الإضافيتَين SupportMapFragment.awaitMap() وGoogleMap.awaitMapLoad()
توفّر مكتبة Maps KTX أيضًا إضافات دوال تعليق لاستخدامها في إجراءات روتينية. على وجه التحديد، هناك بدائل للدوال المعلقة لكل من SupportMapFragment.getMapAsync(OnMapReadyCallback)
وGoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback)
. يؤدي استخدام واجهات برمجة التطبيقات البديلة هذه إلى إزالة الحاجة إلى تمرير عمليات معاودة الاتصال، ويتيح لك بدلاً من ذلك تلقّي استجابة هذه الطرق بطريقة تسلسلية ومتزامنة.
بما أنّ هذه الطرق تعلّق الدوال، يجب استخدامها ضمن روتين فرعي. تقدّم مكتبة Lifecycle Runtime KTX إضافة لتوفير نطاقات روتينية متوافقة مع مراحل النشاط، وذلك لتشغيل الروتينات الفرعية وإيقافها عند حدث مرحلة النشاط المناسب.
من خلال الجمع بين هذه المفاهيم، عدِّل طريقة MainActivity.onCreate(Bundle)
على النحو التالي:
MainActivity.onCreate(Bundle)
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()
على التوالي. تتيح لك إعادة تصميم الرمز البرمجي باستخدام دوال التعليق المؤقت هذه كتابة الرمز البرمجي المكافئ المستند إلى معاودة الاتصال بطريقة تسلسلية.
- يمكنك الآن إعادة إنشاء التطبيق باستخدام التغييرات التي تمّت إعادة هيكلتها.
12. تهانينا
تهانينا! لقد تناولنا الكثير من المحتوى، ونأمل أن تكون لديك الآن فكرة أفضل عن الميزات الأساسية المتوفّرة في "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لنظام التشغيل Android.
مزيد من المعلومات
- حزمة تطوير برامج الأماكن لأجهزة Android: استكشِف المجموعة الغنية من بيانات الأماكن للعثور على الأنشطة التجارية من حولك.
- android-maps-ktx: هي مكتبة مفتوحة المصدر تتيح لك الدمج مع "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لنظام التشغيل Android و"مكتبة أدوات حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android" بطريقة متوافقة مع لغة Kotlin.
- android-place-ktx: هي مكتبة مفتوحة المصدر تتيح لك الدمج مع حزمة تطوير البرامج (SDK) للأماكن لنظام التشغيل Android بطريقة متوافقة مع لغة Kotlin.
- android-samples: عيّنات للتعليمات البرمجية على GitHub توضّح جميع الميزات التي تم تناولها في هذا الدرس العملي وغير ذلك.
- المزيد من دروس الترميز بلغة Kotlin لإنشاء تطبيقات Android باستخدام "منصة خرائط Google"