1. قبل از شروع
این کد لبه به شما می آموزد که چگونه Maps SDK برای اندروید را با برنامه خود ادغام کنید و از ویژگی های اصلی آن با ساختن برنامه ای استفاده کنید که نقشه کوه های کلرادو، ایالات متحده را با استفاده از انواع مختلف نشانگرها نمایش می دهد. علاوه بر این، شما یاد خواهید گرفت که اشکال دیگر را روی نقشه بکشید.
وقتی کار با کد لبه تمام شد به این شکل خواهد بود:
پیش نیازها
- دانش اولیه Kotlin، Jetpack Compose و توسعه اندروید
کاری که خواهی کرد
- فعال کردن و استفاده از کتابخانه Maps Compose برای Maps SDK برای Android برای افزودن
GoogleMap
به برنامه Android - نشانگرها را اضافه و سفارشی کنید
- چند ضلعی ها را روی نقشه بکشید
- زاویه دید دوربین را به صورت برنامه ای کنترل کنید
آنچه شما نیاز دارید
- Maps SDK برای اندروید
- یک حساب Google با فعال کردن صورتحساب
- آخرین نسخه پایدار اندروید استودیو
- یک دستگاه Android یا یک شبیهساز Android که پلتفرم Google APIs مبتنی بر Android نسخه 5.0 یا بالاتر را اجرا میکند (برای مراحل نصب به اجرای برنامهها در شبیهساز Android مراجعه کنید.)
- یک اتصال اینترنتی
2. راه اندازی شوید
برای مرحله فعال سازی زیر، باید Maps SDK برای Android را فعال کنید.
پلتفرم نقشه های گوگل را راه اندازی کنید
اگر قبلاً یک حساب Google Cloud Platform و پروژهای با صورتحساب فعال ندارید، لطفاً راهنمای شروع به کار با Google Maps Platform را برای ایجاد یک حساب صورتحساب و یک پروژه ببینید.
- در Cloud Console ، روی منوی کشویی پروژه کلیک کنید و پروژه ای را که می خواهید برای این کد لبه استفاده کنید انتخاب کنید.
- APIها و SDKهای پلتفرم Google Maps مورد نیاز برای این لبه کد را در Google Cloud Marketplace فعال کنید. برای انجام این کار، مراحل این ویدیو یا این مستند را دنبال کنید.
- یک کلید API در صفحه Credentials در Cloud Console ایجاد کنید. می توانید مراحل این ویدئو یا این مستند را دنبال کنید. همه درخواستها به پلتفرم Google Maps به یک کلید API نیاز دارند.
3. شروع سریع
برای شروع هر چه سریعتر، در اینجا چند کد شروع وجود دارد که به شما کمک میکند تا این کد را دنبال کنید. میتوانید به سراغ راهحل بروید، اما اگر میخواهید تمام مراحل ساخت آن را خودتان دنبال کنید، به خواندن ادامه دهید.
- اگر
git
را نصب کرده اید، مخزن را کلون کنید.
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git
همچنین میتوانید روی دکمه زیر کلیک کنید تا کد منبع را دانلود کنید.
- پس از دریافت کد، ادامه دهید و پروژه ای را که در دایرکتوری
starter
در Android Studio یافت می شود، باز کنید.
4. کلید API خود را به پروژه اضافه کنید
این بخش نحوه ذخیره کلید API خود را توضیح می دهد تا بتواند به طور ایمن توسط برنامه شما ارجاع داده شود. شما نباید کلید API خود را در سیستم کنترل نسخه خود بررسی کنید، بنابراین توصیه می کنیم آن را در فایل secrets.properties
ذخیره کنید، که در کپی محلی شما از دایرکتوری ریشه پروژه شما قرار می گیرد. برای اطلاعات بیشتر در مورد فایل secrets.properties
، به فایلهای خصوصیات Gradle مراجعه کنید.
برای سادهسازی این کار، توصیه میکنیم از افزونه Secrets Gradle برای اندروید استفاده کنید.
برای نصب افزونه Secrets Gradle برای اندروید در پروژه Google Maps:
- در 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
را در دایرکتوری سطح بالای خود باز کنید و سپس کد زیر را اضافه کنید. کلید API خود را جایگزینYOUR_API_KEY
کنید. کلید خود را در این فایل ذخیره کنید زیراsecrets.properties
از بررسی سیستم کنترل نسخه حذف شده است.MAPS_API_KEY=YOUR_API_KEY
- فایل را ذخیره کنید.
- فایل
local.defaults.properties
را در پوشه سطح بالای خود، همان پوشه فایلsecrets.properties
ایجاد کنید و سپس کد زیر را اضافه کنید. هدف این فایل ارائه یک مکان پشتیبان برای کلید API در صورت یافت نشدن فایلMAPS_API_KEY=DEFAULT_API_KEY
secrets.properties
است تا بیلدها خراب نشوند. این زمانی اتفاق میافتد که برنامه را از یک سیستم کنترل نسخه کلون کنید و هنوز فایلsecrets.properties
را به صورت محلی برای ارائه کلید API خود ایجاد نکرده باشید. - فایل را ذخیره کنید.
- در فایل
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 Maps را اضافه کنید
در این بخش، نقشه گوگل را اضافه میکنید تا با راهاندازی برنامه، بارگیری شود.
افزودن نقشهها وابستگیهای نوشتن
اکنون که کلید API شما در داخل برنامه قابل دسترسی است، گام بعدی این است که وابستگی Maps SDK برای اندروید را به فایل build.gradle.kts
برنامه خود اضافه کنید. برای ساخت با Jetpack Compose، از کتابخانه Maps Compose استفاده کنید که عناصری از Maps SDK برای Android را بهعنوان توابع و انواع دادههای قابل نوشتن ارائه میکند.
build.gradle.kts
در فایل build.gradle.kts
در سطح برنامه ، Maps SDK غیرکامپوزیشن را برای وابستگیهای 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 Mapsable اضافه کنید
در MountainMap.kt
، GoogleMap
composable را در داخل Box
composable تو در تو در MapMountain
composable اضافه کنید.
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 }
)
// ...
}
}
اکنون برنامه را بسازید و اجرا کنید. ببین! شما باید نقشه ای را در مرکز جزیره بدنام نول ببینید که به نام های عرض جغرافیایی صفر و طول جغرافیایی صفر نیز شناخته می شود. بعداً یاد خواهید گرفت که چگونه نقشه را در موقعیت مکانی و سطح زوم مورد نظر خود قرار دهید، اما در حال حاضر اولین پیروزی خود را جشن بگیرید!
6. یک ظاهر طراحی نقشه مبتنی بر ابر
می توانید سبک نقشه خود را با استفاده از استایل نقشه مبتنی بر ابر سفارشی کنید.
یک شناسه نقشه ایجاد کنید
اگر هنوز شناسه نقشه با سبک نقشه مرتبط با آن ایجاد نکردهاید، برای تکمیل مراحل زیر، راهنمای Map IDs را ببینید:
- یک شناسه نقشه ایجاد کنید.
- شناسه نقشه را به سبک نقشه مرتبط کنید.
شناسه نقشه را به برنامه خود اضافه کنید
برای استفاده از شناسه نقشهای که ایجاد کردهاید، هنگام نمونهسازی GoogleMap
composable خود، از شناسه نقشه هنگام ایجاد یک شی GoogleMapOptions
استفاده کنید که به پارامتر googleMapOptionsFactory
در سازنده اختصاص داده شده است.
GoogleMap(
// ...
googleMapOptionsFactory = {
GoogleMapOptions().mapId("MyMapId")
}
)
پس از تکمیل این کار، برنامه را اجرا کنید تا نقشه خود را به سبکی که انتخاب کرده اید ببینید!
7. داده های نشانگر را بارگذاری کنید
وظیفه اصلی برنامه بارگیری مجموعه ای از کوه ها از ذخیره سازی محلی و نمایش آن کوه ها در GoogleMap
است. در این مرحله، زیرساخت های ارائه شده برای بارگذاری داده های کوه و ارائه آن به UI را مرور می کنید.
کوهستان
کلاس داده Mountain
تمام داده های مربوط به هر کوه را در خود جای می دهد.
data class Mountain(
val id: Int,
val name: String,
val location: LatLng,
val elevation: Meters,
)
توجه داشته باشید که بعداً کوه ها بر اساس ارتفاعشان تقسیم بندی خواهند شد. کوه هایی که حداقل 14000 فوت ارتفاع دارند، چهارده نامیده می شوند. کد شروع شامل یک تابع افزونه است که این کار را برای شما بررسی کنید.
/**
* 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>>
برمی گرداند.
مخزن کوهستان
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
داغ است که UI می تواند با استفاده از تابع extension collectAsState
آن را به عنوان یک حالت تغییرپذیر مشاهده کند.
با پیروی از اصول صحیح معماری، MountainsViewModel
تمام وضعیت برنامه را حفظ می کند. رابط کاربری تعاملات کاربر را با استفاده از روش onEvent
به مدل view ارسال می کند.
@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
در اندروید استودیو باز کنید.
از ViewModel استفاده کنید
مدل view در MainActivity
برای بدست آوردن viewState
استفاده می شود. شما از viewState
برای رندر کردن نشانگرها در این کد لبه استفاده خواهید کرد. توجه داشته باشید که این کد قبلاً در پروژه شروع گنجانده شده است و فقط برای مرجع در اینجا نشان داده شده است.
val viewModel: MountainsViewModel by viewModels()
val screenViewState = viewModel.mountainsScreenViewState.collectAsState()
val viewState = screenViewState.value
8. دوربین را قرار دهید
یک GoogleMap
پیشفرض روی عرض جغرافیایی صفر و طول جغرافیایی صفر متمرکز میشود. نشانگرهایی که ارائه خواهید کرد در ایالت کلرادو در ایالات متحده آمریکا قرار دارند. viewState
ارائه شده توسط مدل view یک 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
)
}
}
در مرحله بعد، هر زمان که محدوده اطراف مجموعه نشانگر تغییر می کند یا زمانی که کاربر روی دکمه وسعت بزرگنمایی در نوار TopApp کلیک می کند، کدی را برای فراخوانی تابع zoomAll
اضافه کنید. توجه داشته باشید که دکمه وسعت بزرگنمایی از قبل برای ارسال رویدادها به مدل view متصل شده است. شما فقط باید آن رویدادها را از مدل view جمع آوری کنید و تابع 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,
)
}
}
}
نشانگرها را اضافه کنید
با @GoogleMapComposable
BasicMarkersMapContent
حاشیه نویسی کنید. توجه داشته باشید که شما محدود به استفاده از توابع @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
از یک @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
سفارشی شده استفاده کنید. یکی برای چهارده نفر و دیگری برای کوه های معمولی. سپس از پارامتر icon
Marker
composable برای تنظیم آیکون استفاده کنید. همچنین، پارامتر 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
را تغییر دهید. کوه ها با توجه به کوه چهاردهی نشانگرهای مختلفی خواهند داشت.
10. نشانگرهای پیشرفته
AdvancedMarker
ویژگی های اضافی را به 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
سفارشی شده
آیکون ها از طرح های رنگی اولیه و ثانویه برای تمایز بین چهارده و سایر کوه ها استفاده می کنند. از تابع vectorToBitmap
برای ایجاد دو BitmapDescriptor
استفاده کنید. یکی برای چهارده ها و یکی برای کوه های دیگر. از این نمادها برای ایجاد یک 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
composable برای اضافه کردن گروه بندی آیتم های مبتنی بر زوم استفاده خواهید کرد.
Clustering
composable به مجموعه ای از 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 یک بلوک سفارشی را تنظیم می کند تا یک مورد غیر خوشه ای را ارائه دهد. برای ایجاد نشانگر یک تابع @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)
)
}
حالا یک طرح رنگی برای چهارده نفر و یک طرح رنگی دیگر برای کوه های دیگر ایجاد کنید. در بلوک clusterItemContent
، طرح رنگ را بر اساس چهارده بودن یا نبودن کوه داده شده انتخاب کنید.
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. روی نقشه بکشید
در حالی که قبلاً یک راه را برای ترسیم روی نقشه (با افزودن نشانگرها) بررسی کرده اید، Maps SDK برای Android از روش های متعدد دیگری برای نمایش اطلاعات مفید روی نقشه پشتیبانی می کند.
برای مثال، اگر میخواهید مسیرها و مناطق را بر روی نقشه نشان دهید، میتوانید از Polyline
s و 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 Markup Language یا KML است که تقریباً رشته کوه های مهم را مشخص می کند. Maps SDK for Android Utility Library عملکردی برای افزودن یک لایه KML به نقشه دارد. در MountainMap.kt
یک تماس MapEffect
در بلوک محتوای GoogleMap
بعد از بلوک when
اضافه کنید. تابع MapEffect
با یک شی GoogleMap
فراخوانی می شود. این می تواند به عنوان یک پل مفید بین API های غیرقابل ترکیب و کتابخانه هایی که به یک شی 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
a نیست
@GoogleMapComposable
و بنابراین نمی توان آن را به محتوای GoogleMap
اضافه کرد. در عوض آن را به Box
که نقشه را در خود نگه می دارد اضافه می کنید.
Box(
// ...
) {
GoogleMap(
// ...
) {
// ...
}
ScaleBar(
modifier = Modifier
.padding(top = 5.dp, end = 15.dp)
.align(Alignment.TopEnd),
cameraPositionState = cameraPositionState
)
// ...
}
برنامه را اجرا کنید تا کد لبه کاملاً پیاده سازی شده را ببینید.
14. کد راه حل را دریافت کنید
برای دانلود کد مربوط به Codelab تمام شده، می توانید از این دستورات استفاده کنید:
- اگر
git
را نصب کرده اید، مخزن را کلون کنید.
$ git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git
همچنین میتوانید روی دکمه زیر کلیک کنید تا کد منبع را دانلود کنید.
- پس از دریافت کد، ادامه دهید و پروژه موجود در پوشه
solution
را در Android Studio باز کنید.
15. تبریک می گویم
تبریک می گویم! شما مطالب زیادی را پوشش دادید و امیدواریم درک بهتری از ویژگیهای اصلی ارائه شده در Maps SDK برای Android داشته باشید.
بیشتر بدانید
- Maps SDK for Android - نقشههای پویا، تعاملی، سفارشیشده، موقعیت مکانی و تجربیات مکانی را برای برنامههای Android خود بسازید.
- Maps Compose Library - مجموعه ای از توابع و انواع داده های قابل نوشتن منبع باز که می توانید با Jetpack Compose برای ساخت برنامه خود استفاده کنید.
- android-maps-compose - نمونه کد در GitHub که تمام ویژگی های پوشش داده شده در این Codelab و موارد دیگر را نشان می دهد.
- کدهای Kotlin بیشتر برای ساخت برنامه های اندروید با پلتفرم نقشه های گوگل