1. قبل البدء
يعلّمك هذا الدرس التطبيقي كيفية دمج حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل Android مع تطبيقك واستخدام ميزاتها الأساسية من خلال إنشاء تطبيق يعرض خريطة للجبال في ولاية كولورادو الأمريكية باستخدام أنواع مختلفة من العلامات. بالإضافة إلى ذلك، ستتعلّم كيفية رسم أشكال أخرى على الخريطة.
في ما يلي الشكل الذي ستظهر به بعد الانتهاء من تجربة الترميز:
المتطلبات الأساسية
- معرفة أساسية بلغة Kotlin وJetpack Compose وتطوير تطبيقات Android
الإجراءات التي ستنفذّها
- تفعيل مكتبة Maps Compose واستخدامها مع "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لتطبيقات Android من أجل إضافة
GoogleMap
إلى تطبيق Android - إضافة علامات وتخصيصها
- رسم مضلّعات على الخريطة
- التحكّم في نقطة عرض الكاميرا آليًا
المتطلبات
- حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android
- حساب Google تم تفعيل الفوترة فيه
- أحدث إصدار ثابت من استوديو Android
- جهاز Android أو محاكي Android يعمل بمنصة Google APIs استنادًا إلى الإصدار 5.0 من نظام التشغيل 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/googlemaps-samples/codelab-maps-platform-101-compose.git
يمكنك بدلاً من ذلك النقر على الزر التالي لتنزيل رمز المصدر.
- بعد الحصول على الرمز، افتح المشروع الموجود في الدليل
starter
في "استوديو Android".
4. إضافة مفتاح واجهة برمجة التطبيقات إلى المشروع
يوضّح هذا القسم كيفية تخزين مفتاح واجهة برمجة التطبيقات حتى يتمكّن تطبيقك من الرجوع إليه بأمان. يجب عدم إدخال مفتاح واجهة برمجة التطبيقات في نظام التحكّم في الإصدارات، لذا ننصح بتخزينه في الملف secrets.properties
، الذي سيتم وضعه في نسختك المحلية من الدليل الجذر لمشروعك. لمزيد من المعلومات عن ملف secrets.properties
، يُرجى الاطّلاع على ملفات Gradle.
لتبسيط هذه المهمة، ننصحك باستخدام المكوّن الإضافي Secrets Gradle لأجهزة Android.
لتثبيت المكوّن الإضافي Secrets Gradle لأجهزة Android في مشروع "خرائط Google"، اتّبِع الخطوات التالية:
- في Android Studio، افتح ملف
build.gradle.kts
ذي المستوى الأعلى وأضِف الرمز التالي إلى العنصرdependencies
ضمنbuildscript
.buildscript { dependencies { classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1") } }
- افتح ملف
build.gradle.kts
على مستوى الوحدة وأضِف الرمز التالي إلى العنصرplugins
.plugins { // ... id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") }
- في ملف
build.gradle.kts
على مستوى الوحدة، تأكَّد من ضبطtargetSdk
وcompileSdk
على 34 على الأقل. - احفظ الملف وزامِن مشروعك مع Gradle.
- افتح ملف
secrets.properties
في الدليل ذي المستوى الأعلى، ثم أضِف الرمز التالي. استبدِلYOUR_API_KEY
بمفتاح واجهة برمجة التطبيقات. خزِّن مفتاحك في هذا الملف لأنّsecrets.properties
مستبعد من إمكانية التحقّق من نظام التحكّم بالإصدارات.MAPS_API_KEY=YOUR_API_KEY
- احفظ الملف.
- أنشئ ملف
local.defaults.properties
في الدليل على المستوى الأعلى، أي المجلد نفسه الذي يحتوي على ملفsecrets.properties
، ثم أضِف الرمز التالي. الغرض من هذا الملف هو توفير موقع احتياطي لمفتاح واجهة برمجة التطبيقات في حال تعذّر العثور على الملفMAPS_API_KEY=DEFAULT_API_KEY
secrets.properties
، وذلك لضمان عدم تعذّر إنشاء الإصدارات. سيحدث ذلك عند استنساخ التطبيق من نظام التحكّم في الإصدارات ولم تنشئ بعد ملفsecrets.properties
على جهازك لتوفير مفتاح واجهة برمجة التطبيقات. - احفظ الملف.
- في ملف
AndroidManifest.xml
، انتقِل إلىcom.google.android.geo.API_KEY
وعدِّل سمةandroid:value
. إذا لم تكن العلامة<meta-data>
متوفّرة، أنشئها كعلامة فرعية للعلامة<application>
.<meta-data android:name="com.google.android.geo.API_KEY" android:value="${MAPS_API_KEY}" />
- في Android Studio، افتح ملف
build.gradle.kts
على مستوى الوحدة وعدِّل السمةsecrets
. إذا لم تكن السمةsecrets
متوفّرة، أضِفها.عدِّل سمات المكوّن الإضافي لضبطpropertiesFileName
علىsecrets.properties
وdefaultPropertiesFileName
علىlocal.defaults.properties
وضبط أي سمات أخرى.secrets { // Optionally specify a different file name containing your secrets. // The plugin defaults to "local.properties" propertiesFileName = "secrets.properties" // A properties file containing default secret values. This file can be // checked in version control. defaultPropertiesFileName = "local.defaults.properties" }
5- إضافة "خرائط Google"
في هذا القسم، ستضيف خريطة Google ليتم تحميلها عند تشغيل التطبيق.
إضافة تبعيات Maps Compose
بعد أن أصبح بإمكانك الوصول إلى مفتاح واجهة برمجة التطبيقات داخل التطبيق، تتمثّل الخطوة التالية في إضافة تبعية "حزمة تطوير البرامج (SDK) لخرائط Google" لنظام التشغيل Android إلى ملف build.gradle.kts
الخاص بتطبيقك. لإنشاء تطبيقات باستخدام Jetpack Compose، استخدِم مكتبة Maps Compose التي توفّر عناصر "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google للتطبيقات المتوافقة مع Android" كدوال قابلة للإنشاء وأنواع بيانات.
build.gradle.kts
في ملف build.gradle.kts
على مستوى التطبيق، استبدِل التبعيات غير المتوافقة مع Compose لحزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android:
dependencies {
// ...
// Google Maps SDK -- these are here for the data model. Remove these dependencies and replace
// with the compose versions.
implementation("com.google.android.gms:play-services-maps:18.2.0")
// KTX for the Maps SDK for Android library
implementation("com.google.maps.android:maps-ktx:5.0.0")
// KTX for the Maps SDK for Android Utility Library
implementation("com.google.maps.android:maps-utils-ktx:5.0.0")
}
مع نظيراتها القابلة للإنشاء:
dependencies {
// ...
// Google Maps Compose library
val mapsComposeVersion = "4.4.1"
implementation("com.google.maps.android:maps-compose:$mapsComposeVersion")
// Google Maps Compose utility library
implementation("com.google.maps.android:maps-compose-utils:$mapsComposeVersion")
// Google Maps Compose widgets library
implementation("com.google.maps.android:maps-compose-widgets:$mapsComposeVersion")
}
إضافة عنصر قابل للإنشاء في "خرائط Google"
في MountainMap.kt
، أضِف العنصر GoogleMap
القابل للإنشاء داخل العنصر Box
القابل للإنشاء المتداخل مع العنصر MapMountain
القابل للإنشاء.
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.GoogleMapComposable
// ...
@Composable
fun MountainMap(
paddingValues: PaddingValues,
viewState: MountainsScreenViewState.MountainList,
eventFlow: Flow<MountainsScreenEvent>,
selectedMarkerType: MarkerType,
) {
var isMapLoaded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// Add GoogleMap here
GoogleMap(
modifier = Modifier.fillMaxSize(),
onMapLoaded = { isMapLoaded = true }
)
// ...
}
}
الآن، أنشئ التطبيق وشغِّله. من المفترض أن تظهر لك خريطة في وسطها جزيرة Null الشهيرة، المعروفة أيضًا باسم خط العرض صفر وخط الطول صفر. في وقت لاحق، ستتعرّف على كيفية تحديد موضع الخريطة على الموقع الجغرافي ومستوى التكبير/التصغير الذي تريده، ولكن في الوقت الحالي، احتفل بفوزك الأول.
6. تصميم الخرائط باستخدام السحابة الإلكترونية
يمكنك تخصيص نمط الخريطة باستخدام تصميم الخرائط باستخدام السحابة الإلكترونية.
إنشاء رقم تعريف خريطة
إذا لم يسبق لك إنشاء معرّف خريطة مرتبط بنمط خريطة، يمكنك الاطّلاع على دليل معرّفات الخرائط لإكمال الخطوات التالية:
- أنشئ معرّف خريطة.
- ربط رقم تعريف خريطة بنمط خريطة
إضافة معرّف الخريطة إلى تطبيقك
لاستخدام معرّف الخريطة الذي أنشأته، استخدِم معرّف الخريطة عند إنشاء عنصر GoogleMap
القابل للإنشاء، وذلك عند إنشاء عنصر GoogleMapOptions
يتم تعيينه إلى المَعلمة googleMapOptionsFactory
في الدالة الإنشائية.
GoogleMap(
// ...
googleMapOptionsFactory = {
GoogleMapOptions().mapId("MyMapId")
}
)
بعد إكمال هذه الخطوات، يمكنك تشغيل التطبيق للاطّلاع على الخريطة بالنمط الذي اخترته.
7. تحميل بيانات العلامات
المهمة الرئيسية للتطبيق هي تحميل مجموعة من الجبال من وحدة التخزين المحلية وعرض هذه الجبال في GoogleMap
. في هذه الخطوة، ستتعرّف على البنية الأساسية المتوفّرة لتحميل بيانات الجبال وعرضها في واجهة المستخدم.
جبل
يحتوي فئة البيانات Mountain
على جميع البيانات المتعلقة بكل جبل.
data class Mountain(
val id: Int,
val name: String,
val location: LatLng,
val elevation: Meters,
)
يُرجى العِلم أنّه سيتم تقسيم الجبال لاحقًا استنادًا إلى ارتفاعها. تُعرف الجبال التي يبلغ ارتفاعها 14,000 قدم على الأقل باسم الجبال الأربعة عشر. يتضمّن الرمز البرمجي المُعد مسبقًا دالة إضافية لإجراء عملية التحقّق هذه نيابةً عنك.
/**
* Extension function to determine whether a mountain is a "14er", i.e., has an elevation greater
* than 14,000 feet (~4267 meters).
*/
fun Mountain.is14er() = elevation >= 14_000.feet
MountainsScreenViewState
يحتوي الصف MountainsScreenViewState
على جميع البيانات اللازمة لعرض طريقة العرض. يمكن أن تكون الحالة Loading
أو MountainList
استنادًا إلى ما إذا تم الانتهاء من تحميل قائمة الجبال.
/**
* Sealed class representing the state of the mountain map view.
*/
sealed class MountainsScreenViewState {
data object Loading : MountainsScreenViewState()
data class MountainList(
// List of the mountains to display
val mountains: List<Mountain>,
// Bounding box that contains all of the mountains
val boundingBox: LatLngBounds,
// Switch indicating whether all the mountains or just the 14ers
val showingAllPeaks: Boolean = false,
) : MountainsScreenViewState()
}
الصفوف المقدَّمة: MountainsRepository
وMountainsViewModel
في مشروع التطبيق التجريبي، تم توفير الفئة MountainsRepository
لك. يقرأ هذا الصف قائمة بأماكن الجبال المخزّنة في GPS Exchange Format
أو ملف GPX، top_peaks.gpx
. يؤدي الاتصال بالرقم mountainsRepository.loadMountains()
إلى عرض StateFlow<List<Mountain>>
.
MountainsRepository
class MountainsRepository(@ApplicationContext val context: Context) {
private val _mountains = MutableStateFlow(emptyList<Mountain>())
val mountains: StateFlow<List<Mountain>> = _mountains
private var loaded = false
/**
* Loads the list of mountains from the list of mountains from the raw resource.
*/
suspend fun loadMountains(): StateFlow<List<Mountain>> {
if (!loaded) {
loaded = true
_mountains.value = withContext(Dispatchers.IO) {
context.resources.openRawResource(R.raw.top_peaks).use { inputStream ->
readMountains(inputStream)
}
}
}
return mountains
}
/**
* Reads the [Waypoint]s from the given [inputStream] and returns a list of [Mountain]s.
*/
private fun readMountains(inputStream: InputStream) =
readWaypoints(inputStream).mapIndexed { index, waypoint ->
waypoint.toMountain(index)
}.toList()
// ...
}
MountainsViewModel
MountainsViewModel
هي فئة ViewModel
التي تحمّل مجموعات الجبال وتعرض هذه المجموعات بالإضافة إلى أجزاء أخرى من حالة واجهة المستخدم من خلال mountainsScreenViewState
. mountainsScreenViewState
هو تدفق نشط StateFlow
يمكن لواجهة المستخدم مراقبته كحالة قابلة للتغيير باستخدام دالة الإضافة collectAsState
.
وفقًا لمبادئ التصميم السليم، يحتفظ MountainsViewModel
بجميع حالات التطبيق. ترسل واجهة المستخدم تفاعلات المستخدم إلى نموذج العرض باستخدام الطريقة onEvent
.
@HiltViewModel
class MountainsViewModel
@Inject
constructor(
mountainsRepository: MountainsRepository
) : ViewModel() {
private val _eventChannel = Channel<MountainsScreenEvent>()
// Event channel to send events to the UI
internal fun getEventChannel() = _eventChannel.receiveAsFlow()
// Whether or not to show all of the high peaks
private var showAllMountains = MutableStateFlow(false)
val mountainsScreenViewState =
mountainsRepository.mountains.combine(showAllMountains) { allMountains, showAllMountains ->
if (allMountains.isEmpty()) {
MountainsScreenViewState.Loading
} else {
val filteredMountains =
if (showAllMountains) allMountains else allMountains.filter { it.is14er() }
val boundingBox = filteredMountains.map { it.location }.toLatLngBounds()
MountainsScreenViewState.MountainList(
mountains = filteredMountains,
boundingBox = boundingBox,
showingAllPeaks = showAllMountains,
)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = MountainsScreenViewState.Loading
)
init {
// Load the full set of mountains
viewModelScope.launch {
mountainsRepository.loadMountains()
}
}
// Handle user events
fun onEvent(event: MountainsViewModelEvent) {
when (event) {
OnZoomAll -> onZoomAll()
OnToggleAllPeaks -> toggleAllPeaks()
}
}
private fun onZoomAll() {
sendScreenEvent(MountainsScreenEvent.OnZoomAll)
}
private fun toggleAllPeaks() {
showAllMountains.value = !showAllMountains.value
}
// Send events back to the UI via the event channel
private fun sendScreenEvent(event: MountainsScreenEvent) {
viewModelScope.launch { _eventChannel.send(event) }
}
}
إذا كنت مهتمًا بمعرفة كيفية تنفيذ هذه الفئات، يمكنك الوصول إليها على GitHub أو فتح الفئتين MountainsRepository
وMountainsViewModel
في "استوديو Android".
استخدام ViewModel
يتم استخدام نموذج العرض في MainActivity
للحصول على viewState
. ستستخدِم viewState
لعرض العلامات لاحقًا في هذا الدرس العملي. يُرجى العِلم أنّ هذا الرمز مضمّن في المشروع المبدئي ويظهر هنا كمرجع فقط.
val viewModel: MountainsViewModel by viewModels()
val screenViewState = viewModel.mountainsScreenViewState.collectAsState()
val viewState = screenViewState.value
8. ضبط موضع الكاميرا
يتم ضبط GoogleMap
تلقائيًا على خط العرض صفر وخط الطول صفر. تقع العلامات التي ستعرضها في ولاية كولورادو في الولايات المتحدة الأمريكية. تقدّم viewState
التي يوفّرها نموذج العرض LatLngBounds يحتوي على جميع العلامات.
في MountainMap.kt
، أنشئ CameraPositionState
تم ضبط قيمته الأولية على مركز مربّع الإحاطة. اضبط المَعلمة cameraPositionState
الخاصة بـ GoogleMap
على المتغيّر cameraPositionState
الذي أنشأته للتو.
fun MountainMap(
// ...
) {
// ...
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition.fromLatLngZoom(viewState.boundingBox.center, 5f)
}
GoogleMap(
// ...
cameraPositionState = cameraPositionState,
)
}
الآن شغِّل الرمز وشاهِد الخريطة وهي تتوسّط ولاية كولورادو.
التكبير/التصغير إلى حدود العلامة
لتركيز الخريطة على العلامات، أضِف الدالة zoomAll
إلى نهاية الملف MountainMap.kt
. يُرجى العِلم أنّ هذه الدالة تحتاج إلى CoroutineScope
لأنّ تحريك الكاميرا إلى موقع جغرافي جديد هو عملية غير متزامنة تستغرق وقتًا لإكمالها.
fun zoomAll(
scope: CoroutineScope,
cameraPositionState: CameraPositionState,
boundingBox: LatLngBounds
) {
scope.launch {
cameraPositionState.animate(
update = CameraUpdateFactory.newLatLngBounds(boundingBox, 64),
durationMs = 1000
)
}
}
بعد ذلك، أضِف رمزًا برمجيًا لاستدعاء الدالة zoomAll
كلما تغيّرت الحدود المحيطة بمجموعة العلامات أو عندما ينقر المستخدم على زر "تكبير المدى" في شريط TopApp. يُرجى العِلم أنّه تمّ ربط زرّ تكبير/تصغير المدى لإرسال الأحداث إلى نموذج العرض. ما عليك سوى جمع هذه الأحداث من نموذج العرض واستدعاء الدالة zoomAll
استجابةً لها.
fun MountainMap(
// ...
) {
// ...
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = viewState.boundingBox) {
zoomAll(scope, cameraPositionState, viewState.boundingBox)
}
LaunchedEffect(true) {
eventFlow.collect { event ->
when (event) {
MountainsScreenEvent.OnZoomAll -> {
zoomAll(scope, cameraPositionState, viewState.boundingBox)
}
}
}
}
}
عند تشغيل التطبيق الآن، ستبدأ الخريطة بالتركيز على المنطقة التي ستظهر فيها العلامات. ويمكنك إعادة ضبط موضع الخريطة وتغيير مستوى التكبير، وسيؤدي النقر على زر "تكبير/تصغير كامل" إلى إعادة تركيز الخريطة حول منطقة العلامات. هذا تقدّم للأمام. ولكن يجب أن تتضمّن الخريطة شيئًا يستحق المشاهدة. وهذا ما ستفعله في الخطوة التالية.
9- العلامات الأساسية
في هذه الخطوة، يمكنك إضافة علامات إلى الخريطة تمثّل نقاط الاهتمام التي تريد إبرازها على الخريطة. ستستخدم قائمة الجبال التي تم توفيرها في مشروع البداية وتضيف هذه الأماكن كعلامات على الخريطة.
ابدأ بإضافة مربّع محتوى إلى GoogleMap
. ستتوفّر أنواع متعدّدة من العلامات، لذا أضِف عبارة when
للتفرّع إلى كل نوع، وستنفّذ كل نوع بالتسلسل في الخطوات التالية.
GoogleMap(
// ...
) {
when (selectedMarkerType) {
MarkerType.Basic -> {
BasicMarkersMapContent(
mountains = viewState.mountains,
)
}
MarkerType.Advanced -> {
AdvancedMarkersMapContent(
mountains = viewState.mountains,
)
}
MarkerType.Clustered -> {
ClusteringMarkersMapContent(
mountains = viewState.mountains,
)
}
}
}
إضافة علامات
إضافة تعليق توضيحي إلى BasicMarkersMapContent
باستخدام @GoogleMapComposable
يُرجى العِلم أنّه لا يمكنك استخدام سوى دوال @GoogleMapComposable
في كتلة المحتوى GoogleMap
. يحتوي العنصر mountains
على قائمة بالعناصر Mountain
. ستضيف علامة لكل جبل في تلك القائمة، باستخدام الموقع الجغرافي والاسم والارتفاع من الكائن Mountain
. يتم استخدام الموقع الجغرافي لضبط مَعلمة الحالة Marker
التي تتحكّم بدورها في موضع العلامة.
// ...
import com.google.android.gms.maps.model.Marker
import com.google.maps.android.compose.GoogleMapComposable
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.rememberMarkerState
@Composable
@GoogleMapComposable
fun BasicMarkersMapContent(
mountains: List<Mountain>,
onMountainClick: (Marker) -> Boolean = { false }
) {
mountains.forEach { mountain ->
Marker(
state = rememberMarkerState(position = mountain.location),
title = mountain.name,
snippet = mountain.elevation.toElevationString(),
tag = mountain,
onClick = { marker ->
onMountainClick(marker)
false
},
zIndex = if (mountain.is14er()) 5f else 2f
)
}
}
يمكنك الآن تشغيل التطبيق وستظهر لك العلامات التي أضفتها للتو.
تخصيص علامات التحديد
تتوفّر عدة خيارات لتخصيص العلامات التي أضفتها للتو لمساعدتها في التميّز ونقل معلومات مفيدة إلى المستخدمين. في هذه المهمة، ستتعرّف على بعض هذه الخيارات من خلال تخصيص صورة كل علامة.
يتضمّن مشروع البداية دالة مساعدة، vectorToBitmap
، لإنشاء BitmapDescriptor
s من @DrawableResource
.
يتضمّن الرمز الأوّلي رمز جبل، baseline_filter_hdr_24.xml
، ستستخدمه لتخصيص العلامات.
تحوّل الدالة vectorToBitmap
صورة متجهة قابلة للرسم إلى BitmapDescriptor
لاستخدامها مع مكتبة الخرائط. يتم ضبط ألوان الرموز باستخدام مثيل BitmapParameters
.
data class BitmapParameters(
@DrawableRes val id: Int,
@ColorInt val iconColor: Int,
@ColorInt val backgroundColor: Int? = null,
val backgroundAlpha: Int = 168,
val padding: Int = 16,
)
fun vectorToBitmap(context: Context, parameters: BitmapParameters): BitmapDescriptor {
// ...
}
استخدِم الدالة vectorToBitmap
لإنشاء BitmapDescriptor
مخصّصَين، أحدهما للجبال التي يزيد ارتفاعها عن 14,000 قدم والآخر للجبال العادية. بعد ذلك، استخدِم المَعلمة icon
في العنصر القابل للإنشاء Marker
لضبط الرمز. اضبط أيضًا المَعلمة anchor
لتغيير موقع نقطة الارتكاز بالنسبة إلى الرمز. ويكون استخدام المركز أفضل لهذه الرموز الدائرية.
@Composable
@GoogleMapComposable
fun BasicMarkersMapContent(
// ...
) {
// Create mountainIcon and fourteenerIcon
val mountainIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.secondary.toArgb(),
backgroundColor = MaterialTheme.colorScheme.secondaryContainer.toArgb(),
)
)
val fourteenerIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
backgroundColor = MaterialTheme.colorScheme.primary.toArgb(),
)
)
mountains.forEach { mountain ->
val icon = if (mountain.is14er()) fourteenerIcon else mountainIcon
Marker(
// ...
anchor = Offset(0.5f, 0.5f),
icon = icon,
)
}
}
شغِّل التطبيق واستمتع بالعلامات المخصّصة. انقر على زر التبديل Show all
للاطّلاع على المجموعة الكاملة من الجبال. ستتضمّن الجبال علامات مختلفة حسب ما إذا كان الجبل يبلغ ارتفاعه 14,000 قدم أو أكثر.
10. العلامات المتقدّمة
تضيف AdvancedMarker
s ميزات إضافية إلى Markers
الأساسي. في هذه الخطوة، عليك ضبط سلوك التصادم وتحديد نمط الدبوس.
أضِف @GoogleMapComposable
إلى الدالة AdvancedMarkersMapContent
. كرِّر mountains
مع إضافة AdvancedMarker
لكل منها.
@Composable
@GoogleMapComposable
fun AdvancedMarkersMapContent(
mountains: List<Mountain>,
onMountainClick: (Marker) -> Boolean = { false },
) {
mountains.forEach { mountain ->
AdvancedMarker(
state = rememberMarkerState(position = mountain.location),
title = mountain.name,
snippet = mountain.elevation.toElevationString(),
collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
onClick = { marker ->
onMountainClick(marker)
false
}
)
}
}
لاحظ المَعلمة collisionBehavior
. من خلال ضبط هذه المَعلمة على REQUIRED_AND_HIDES_OPTIONAL
، سيحلّ المؤشر محلّ أي مؤشر ذي أولوية أقل. يمكنك ملاحظة ذلك من خلال تكبير محدّد أساسي مقارنةً بمحدّد متقدّم. من المحتمل أن يتضمّن المحدّد الأساسي كلاً من المحدّد الخاص بك والمحدّد الذي تم وضعه في الموقع الجغرافي نفسه في الخريطة الأساسية. سيؤدي استخدام العلامة المتقدّمة إلى إخفاء العلامة ذات الأولوية الأقل.
شغِّل التطبيق ��لاطّلاع على العلامات المتقدّمة. احرص على النقر على علامة التبويب Advanced markers
في صف شريط التنقل في أسفل الشاشة.
مخصّص AdvancedMarkers
تستخدم الرموز أنظمة الألوان الأساسية والثانوية للتمييز بين الجبال التي يزيد ارتفاعها عن 14,000 قدم والجبال الأخرى. استخدِم الدالة vectorToBitmap
لإنشاء BitmapDescriptor
، أحدهما للجبال التي يزيد ارتفاعها عن 14,000 قدم والآخر للجبال الأخرى. استخدِم هذه الرموز لإنشاء pinConfig
مخصّص لكل نوع. أخيرًا، طبِّق الدبوس على AdvancedMarker
المناسب استنادًا إلى الدالة is14er()
.
@Composable
@GoogleMapComposable
fun AdvancedMarkersMapContent(
mountains: List<Mountain>,
onMountainClick: (Marker) -> Boolean = { false },
) {
val mountainIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.onSecondary.toArgb(),
)
)
val mountainPin = with(PinConfig.builder()) {
setGlyph(PinConfig.Glyph(mountainIcon))
setBackgroundColor(MaterialTheme.colorScheme.secondary.toArgb())
setBorderColor(MaterialTheme.colorScheme.onSecondary.toArgb())
build()
}
val fourteenerIcon = vectorToBitmap(
LocalContext.current,
BitmapParameters(
id = R.drawable.baseline_filter_hdr_24,
iconColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
)
)
val fourteenerPin = with(PinConfig.builder()) {
setGlyph(PinConfig.Glyph(fourteenerIcon))
setBackgroundColor(MaterialTheme.colorScheme.primary.toArgb())
setBorderColor(MaterialTheme.colorScheme.onPrimary.toArgb())
build()
}
mountains.forEach { mountain ->
val pin = if (mountain.is14er()) fourteenerPin else mountainPin
AdvancedMarker(
state = rememberMarkerState(position = mountain.location),
title = mountain.name,
snippet = mountain.elevation.toElevationString(),
collisionBehavior = AdvancedMarkerOptions.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
pinConfig = pin,
onClick = { marker ->
onMountainClick(marker)
false
}
)
}
}
11. علامات مجمّعة
في هذه الخطوة، ستستخدم Clustering
القابلة للإنشاء لإضافة تجميع العناصر المستند إلى التكبير.
يتطلّب العنصر القابل للإنشاء Clustering
مجموعة من عناصر ClusterItem
. تنفّذ MountainClusterItem
الواجهة ClusterItem
. أضِف هذا الصف إلى الملف ClusteringMarkersMapContent.kt
.
data class MountainClusterItem(
val mountain: Mountain,
val snippetString: String
) : ClusterItem {
override fun getPosition() = mountain.location
override fun getTitle() = mountain.name
override fun getSnippet() = snippetString
override fun getZIndex() = 0f
}
الآن، أضِف الرمز لإنشاء MountainClusterItem
s من قائمة الجبال. يُرجى العِلم أنّ هذا الرمز يستخدم UnitsConverter
للتحويل إلى وحدات عرض مناسبة للمستخدم استنادًا إلى لغته. يتم إعداد ذلك في MainActivity
باستخدام CompositionLocal
.
@OptIn(MapsComposeExperimentalApi::class)
@Composable
@GoogleMapComposable
fun ClusteringMarkersMapContent(
mountains: List<Mountain>,
// ...
) {
val unitsConverter = LocalUnitsConverter.current
val resources = LocalContext.current.resources
val mountainClusterItems by remember(mountains) {
mutableStateOf(
mountains.map { mountain ->
MountainClusterItem(
mountain = mountain,
snippetString = unitsConverter.toElevationString(resources, mountain.elevation)
)
}
)
}
Clustering(
items = mountainClusterItems,
)
}
باستخدام هذا الرمز، يتم تجميع العلامات استنادًا إلى مستوى التكبير/التصغير. مرتبة ونظيفة
تخصيص المجموعات
وكما هو الحال مع أنواع العلامات الأخرى، يمكن تخصيص العلامات المجمّعة. تضبط المَعلمة clusterItemContent
في العنصر القابل للإنشاء Clustering
كتلة قابلة للإنشاء مخصّصة لعرض عنصر غير مجمّع. نفِّذ الدالة @Composable
لإنشاء العلامة. تعرض الدالة SingleMountain
عنصر Icon
قابلاً للإنشاء من Material 3 مع نظام ألوان مخصّص للخلفية.
في ClusteringMarkersMapContent.kt
، أنشئ فئة بيانات تحدّد نظام الألوان لعلامة:
data class IconColor(val iconColor: Color, val backgroundColor: Color, val borderColor: Color)
بالإضافة إلى ذلك، أنشئ في ClusteringMarkersMapContent.kt
دالة قابلة للإنشاء لعرض رمز لنظام ألوان معيّن:
@Composable
private fun SingleMountain(
colors: IconColor,
) {
Icon(
painterResource(id = R.drawable.baseline_filter_hdr_24),
tint = colors.iconColor,
contentDescription = "",
modifier = Modifier
.size(32.dp)
.padding(1.dp)
.drawBehind {
drawCircle(color = colors.backgroundColor, style = Fill)
drawCircle(color = colors.borderColor, style = Stroke(width = 3f))
}
.padding(4.dp)
)
}
الآن، أنشئ نظام ألوان للجبال التي يزيد ارتفاعها عن 14,000 قدم ونظام ألوان آخر للجبال الأخرى. في المربّع clusterItemContent
، اختَر نظام الألوان استنادًا إلى ما إذا كان الجبل المحدّد يبلغ ارتفاعه 14, 000 قدم أم لا.
fun ClusteringMarkersMapContent(
mountains: List<Mountain>,
// ...
) {
// ...
val backgroundAlpha = 0.6f
val fourteenerColors = IconColor(
iconColor = MaterialTheme.colorScheme.onPrimary,
backgroundColor = MaterialTheme.colorScheme.primary.copy(alpha = backgroundAlpha),
borderColor = MaterialTheme.colorScheme.primary
)
val otherColors = IconColor(
iconColor = MaterialTheme.colorScheme.secondary,
backgroundColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = backgroundAlpha),
borderColor = MaterialTheme.colorScheme.secondary
)
// ...
Clustering(
items = mountainClusterItems,
clusterItemContent = { mountainItem ->
val colors = if (mountainItem.mountain.is14er()) {
fourteenerColors
} else {
otherColors
}
SingleMountain(colors)
},
)
}
الآن، شغِّل التطبيق للاطّلاع على إصدارات مخصّصة من العناصر الفردية.
12. الرسم على الخريطة
بعد أن تعرّفت على إحدى طرق الرسم على الخريطة (من خلال إضافة علامات)، تتيح "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لنظام التشغيل Android العديد من الطرق الأخرى التي يمكنك من خلالها الرسم لعرض معلومات مفيدة على الخريطة.
على سبيل المثال، إذا أردت تمثيل المسارات والمناطق على الخريطة، يمكنك استخدام Polyline
وPolygon
لعرضها على الخريطة. أو إذا أردت تثبيت صورة على سطح الأرض، يمكنك استخدام GroundOverlay
.
في هذه المهمة، ستتعرّف على كيفية رسم أشكال، وتحديدًا رسم مخطط تفصيلي حول ولاية كولورادو. يقع خط الحدود بين ولاية كولورادو والولايات المجاورة بين خط العرض 37° شمالاً و41° شمالاً وخط الطول 102°03' غربًا و109°03' غربًا. وهذا يجعل رسم المخطط التفصيلي أمرًا بسيطًا للغاية.
يتضمّن الرمز الأولي فئة DMS
للتحويل من تدوين الدرجات والدقائق والثواني إلى الدرجات العشرية.
enum class Direction(val sign: Int) {
NORTH(1),
EAST(1),
SOUTH(-1),
WEST(-1)
}
/**
* Degrees, minutes, seconds utility class
*/
data class DMS(
val direction: Direction,
val degrees: Double,
val minutes: Double = 0.0,
val seconds: Double = 0.0,
)
fun DMS.toDecimalDegrees(): Double =
(degrees + (minutes / 60) + (seconds / 3600)) * direction.sign
باستخدام فئة DMS، يمكنك رسم حدود كولورادو من خلال تحديد مواقع الزوايا الأربع LatLng
وعرضها كـ Polygon
. أضِف الرمز التالي إلى MountainMap.kt
@Composable
@GoogleMapComposable
fun ColoradoPolygon() {
val north = 41.0
val south = 37.0
val east = DMS(WEST, 102.0, 3.0).toDecimalDegrees()
val west = DMS(WEST, 109.0, 3.0).toDecimalDegrees()
val locations = listOf(
LatLng(north, east),
LatLng(south, east),
LatLng(south, west),
LatLng(north, west),
)
Polygon(
points = locations,
strokeColor = MaterialTheme.colorScheme.tertiary,
strokeWidth = 3F,
fillColor = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.3f),
)
}
استخدِم الآن ColoradoPolyon()
داخل وحدة المحتوى GoogleMap
.
@Composable
fun MountainMap(
// ...
) {
Box(
// ...
) {
GoogleMap(
// ...
) {
ColoradoPolygon()
}
}
}
ي��رض ال��طبيق الآن حدود ولاية كولورادو مع تعبئتها بشكل خفيف.
13. إضافة طبقة KML وشريط مقياس
في هذا القسم الأخير، ستحدّد بشكل تقريبي السلاسل الجبلية المختلفة وتضيف مقياسًا إلى الخريطة.
تحديد سلاسل الجبال
في السابق، رسمتَ مخططًا تفصيليًا حول كولورادو. ستضيف هنا أشكالاً أكثر تعقيدًا إلى الخريطة. يتضمّن رمز البداية ملف لغة ترميز Keyhole أو KML يحدّد بشكل تقريبي السلاسل الجبلية المهمة. تتضمّن مكتبة أدوات "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لتطبيقات Android دالة لإضافة طبقة KML إلى الخريطة. في MountainMap.kt
، أضِف MapEffect
مكالمة في قسم المحتوى GoogleMap
بعد القسم when
. يتم استدعاء الدالة MapEffect
باستخدام العنصر GoogleMap
. ويمكن أن يكون هذا النوع من الدوال مفيدًا في الربط بين واجهات برمجة التطبيقات والمكتبات غير القابلة للإنشاء والتي تتطلّب كائن GoogleMap
.
fun MountainMap(
// ...
) {
var isMapLoaded by remember { mutableStateOf(false) }
val context = LocalContext.current
GoogleMap(
// ...
) {
// ...
when (selectedMarkerType) {
// ...
}
// This code belongs inside the GoogleMap content block, but outside of
// the 'when' statement
MapEffect(key1 = true) {map ->
val layer = KmlLayer(map, R.raw.mountain_ranges, context)
layer.addLayerToMap()
}
}
إضافة مقياس خريطة
في مهمتك الأخيرة، ستضيف مقياسًا إلى الخريطة. تنفِّذ السمة ScaleBar
عنصرًا قابلاً للإنشاء يمكن إضافته إلى الخريطة. يُرجى العِلم أنّ ScaleBar
ليس
@GoogleMapComposable
، وبالتالي لا يمكن إضافته إلى محتوى GoogleMap
. بدلاً من ذلك، يمكنك إضافته إلى Box
الذي يحتوي على الخريطة.
Box(
// ...
) {
GoogleMap(
// ...
) {
// ...
}
ScaleBar(
modifier = Modifier
.padding(top = 5.dp, end = 15.dp)
.align(Alignment.TopEnd),
cameraPositionState = cameraPositionState
)
// ...
}
شغِّل التطبيق للاطّلاع على الدرس التطبيقي المبرمَج الذي تم تنفيذه بالكامل.
14. الحصول على رمز الحلّ
لتنزيل الرمز البرمجي الخاص بدرس البرمجة المكتمل، يمكنك استخدام الأوامر التالية:
- استنسِخ المستودع إذا كان لديك
git
مثبَّتًا.
$ git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git
يمكنك بدلاً من ذلك النقر على الزر التالي لتنزيل رمز المصدر.
- بعد الحصول على الرمز، افتح المشروع الموجود في الدليل
solution
في "استوديو Android".
15. تهانينا
تهانينا! لقد تناولنا الكثير من المحتوى، ونأمل أن تكون لديك الآن فكرة أفضل عن الميزات الأساسية المتوفّرة في "حزمة تطوير البرامج بالاستناد إلى بيانات خرائط Google" لنظام التشغيل Android.
مزيد من المعلومات
- حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android: يمكنك إنشاء خرائط ومواقع جغرافية وتجارب جغرافية مكانية ديناميكية وتفاعلية ومخصّصة لتطبيقات Android.
- مكتبة Maps Compose: هي مجموعة من الدوال القابلة للإنشاء وأنواع البيانات المفتوحة المصدر التي يمكنك استخدامها مع Jetpack Compose لإنشاء تطبيقك.
- android-maps-compose: نموذج للرمز البرمجي على GitHub يوضّح جميع الميزات التي تم تناولها في هذا الدرس العملي وغير ذلك.
- المزيد من دروس الترميز بلغة Kotlin لإنشاء تطبيقات Android باستخدام "منصة خرائط Google"