Android uygulamanıza harita ekleme (Compose ile Kotlin)

1. Başlamadan Önce

Bu codelab'de, çeşitli işaret türlerini kullanarak ABD'nin Colorado eyaletindeki dağların haritasını gösteren bir uygulama oluşturarak Android için Haritalar SDK'sını uygulamanıza nasıl entegre edeceğinizi ve temel özelliklerini nasıl kullanacağınızı öğreneceksiniz. Ayrıca, haritada başka şekiller çizmeyi de öğreneceksiniz.

Codelab'i tamamladığınızda aşağıdaki gibi görünür:

Ön koşullar

Yapacaklarınız

  • Android uygulamasına GoogleMap eklemek için Android için Haritalar SDK'sında Haritalar Compose kitaplığını etkinleştirme ve kullanma
  • İşaretçi ekleme ve özelleştirme
  • Haritada poligon çizme
  • Kameranın bakış açısını programatik olarak kontrol etme

İhtiyacınız olanlar

2. Hazırlanın

Etkinleştirme işleminin bir sonraki adımında Android için Haritalar SDK'sı'nı etkinleştirmeniz gerekir.

Google Haritalar Platformu'nu ayarlama

Henüz bir Google Cloud Platform hesabınız ve faturalandırmanın etkinleştirildiği bir projeniz yoksa lütfen faturalandırma hesabı ve proje oluşturmak için Google Haritalar Platformu'nu Kullanmaya Başlama kılavuzuna bakın.

  1. Cloud Console'da proje açılır menüsünü tıklayın ve bu codelab için kullanmak istediğiniz projeyi seçin.

  1. Bu codelab için gereken Google Haritalar Platformu API'lerini ve SDK'larını Google Cloud Marketplace'te etkinleştirin. Bunun için bu videodaki veya bu dokümandaki adımları uygulayın.
  2. Cloud Console'un Kimlik Bilgileri sayfasında bir API anahtarı oluşturun. Bu videodaki veya bu dokümandaki adımları uygulayabilirsiniz. Google Haritalar Platformu'na yapılan tüm istekler için API anahtarı gerekir.

3. Hızlı başlangıç

Mümkün olduğunca hızlı bir şekilde başlamanıza yardımcı olmak için bu codelab'i takip etmenize yardımcı olacak başlangıç kodunu aşağıda bulabilirsiniz. Çözüme geçebilirsiniz ancak kendiniz oluşturmak için tüm adımları takip etmek istiyorsanız okumaya devam edin.

  1. git yüklüyse depoyu klonlayın.
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git

Alternatif olarak, kaynak kodunu indirmek için aşağıdaki düğmeyi tıklayabilirsiniz.

  1. Kodu aldıktan sonra Android Studio'da starter dizinindeki projeyi açın.

4. API anahtarınızı projeye ekleme

Bu bölümde, API anahtarınızı uygulamanız tarafından güvenli bir şekilde referans verilebilecek şekilde nasıl saklayacağınız açıklanmaktadır. API anahtarınızı sürüm kontrol sisteminize işlememeniz gerekir. Bu nedenle, projenizin kök dizininin yerel kopyasına yerleştirilecek olan secrets.properties dosyasında saklamanızı öneririz. secrets.properties dosyası hakkında daha fazla bilgi için Gradle özellik dosyaları konusuna bakın.

Bu görevi kolaylaştırmak için Android İçin Secrets Gradle Plugin'i kullanmanızı öneririz.

Google Haritalar projenize Android İçin Secrets Gradle Plugin'i yüklemek üzere:

  1. Android Studio'da üst düzey build.gradle.kts dosyanızı açın ve buildscript altındaki dependencies öğesine aşağıdaki kodu ekleyin.
    buildscript {
        dependencies {
            classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
        }
    }
    
  2. Modül düzeyindeki build.gradle.kts dosyanızı açın ve aşağıdaki kodu plugins öğesine ekleyin.
    plugins {
        // ...
        id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
    }
    
  3. Modül düzeyindeki build.gradle.kts dosyanızda targetSdk ve compileSdk değerlerinin en az 34 olarak ayarlandığından emin olun.
  4. Dosyayı kaydedin ve projenizi Gradle ile senkronize edin.
  5. secrets.properties dosyasını en üst düzey dizininizde açın ve aşağıdaki kodu ekleyin. YOUR_API_KEY kısmını API anahtarınızla değiştirin. secrets.properties, sürüm kontrol sistemine kaydedilmekten hariç tutulduğu için anahtarınızı bu dosyada saklayın.
    MAPS_API_KEY=YOUR_API_KEY
    
  6. Dosyayı kaydedin.
  7. local.defaults.properties dosyasını üst düzey dizininizde (secrets.properties dosyasıyla aynı klasörde) oluşturun ve aşağıdaki kodu ekleyin.
        MAPS_API_KEY=DEFAULT_API_KEY
    
    Bu dosyanın amacı, secrets.properties dosyası bulunamazsa API anahtarı için bir yedek konum sağlamaktır. Böylece derlemeler başarısız olmaz. Bu durum, uygulamayı bir sürüm kontrol sisteminden klonladığınızda ve API anahtarınızı sağlamak için henüz yerel olarak bir secrets.properties dosyası oluşturmadığınızda meydana gelir.
  8. Dosyayı kaydedin.
  9. AndroidManifest.xml dosyanızda com.google.android.geo.API_KEY bölümüne gidin ve android:value özelliğini güncelleyin. <meta-data> etiketi yoksa <meta-data> etiketinin alt öğesi olarak oluşturun.<application>
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />
    
  10. Android Studio'da modül düzeyindeki build.gradle.kts dosyanızı açın ve secrets özelliğini düzenleyin. secrets özelliği yoksa ekleyin.Eklentinin özelliklerini düzenleyerek propertiesFileName özelliğini secrets.properties, defaultPropertiesFileName özelliğini local.defaults.properties ve diğer özellikleri ayarlayın.
    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 Haritalar'ı ekleme

Bu bölümde, uygulamayı başlattığınızda yüklenecek bir Google Haritası ekleyeceksiniz.

Maps Compose bağımlılıklarını ekleme

API anahtarınıza artık uygulama içinden erişilebildiğine göre, bir sonraki adım Android için Haritalar SDK'sı bağımlılığını uygulamanızın build.gradle.kts dosyasına eklemektir. Jetpack Compose ile geliştirme yapmak için Android için Haritalar SDK'sının öğelerini composable işlevler ve veri türleri olarak sağlayan Maps Compose kitaplığını kullanın.

build.gradle.kts

Uygulama düzeyindeki build.gradle.kts dosyasında, Compose olmayan Android için Haritalar SDK'sı bağımlılıklarını aşağıdakiyle değiştirin:

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")
}

ile birleştirilebilir:

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 Haritası composable'ı ekleme

MountainMap.kt içinde, MapMountain composable'ın içine yerleştirilmiş Box composable'ın içine GoogleMap composable'ı ekleyin.

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 }
        )

        // ...
    }
}

Şimdi uygulamayı derleyip çalıştırın. İşte karşınızda! Enlem sıfır ve boylam sıfır olarak da bilinen kötü şöhretli Null Adası'nın ortasında bir harita görmeniz gerekir. Daha sonra haritayı istediğiniz konuma ve yakınlaştırma düzeyine nasıl yerleştireceğinizi öğreneceksiniz. Ancak şimdilik ilk başarınızı kutlayın.

6. Bulut tabanlı harita stili düzenleme

Bulut tabanlı harita stilleri'ni kullanarak haritanızın stilini özelleştirebilirsiniz.

Harita kimliği oluşturma

Henüz ilişkili bir harita stiline sahip bir harita kimliği oluşturmadıysanız aşağıdaki adımları tamamlamak için Harita Kimlikleri kılavuzuna bakın:

  1. Harita kimliği oluşturun.
  2. Harita kimliğini harita stiliyle ilişkilendirin.

Harita kimliğini uygulamanıza ekleme

Oluşturduğunuz harita kimliğini kullanmak için GoogleMap composable'ınızı oluştururken yapıcıda googleMapOptionsFactory parametresine atanan bir GoogleMapOptions nesnesi oluştururken harita kimliğini kullanın.

GoogleMap(
    // ...
    googleMapOptionsFactory = {
        GoogleMapOptions().mapId("MyMapId")
    }
)

Bu işlemi tamamladıktan sonra, seçtiğiniz stildeki haritanızı görmek için uygulamayı çalıştırın.

7. İşaretçi verilerini yükleme

Uygulamanın temel görevi, yerel depolama alanından bir dağ koleksiyonu yüklemek ve bu dağları GoogleMap içinde göstermektir. Bu adımda, dağ verilerini yüklemek ve kullanıcı arayüzünde göstermek için sağlanan altyapıyı keşfedeceksiniz.

Mountain

Mountain veri sınıfı, her dağla ilgili tüm verileri içerir.

data class Mountain(
    val id: Int,
    val name: String,
    val location: LatLng,
    val elevation: Meters,
)

Dağların daha sonra yüksekliğe göre bölümlere ayrılacağını unutmayın. En az 4.267 metre yüksekliğindeki dağlara dört binlikler denir. Başlangıç kodu, bu kontrolü sizin için yapacak bir uzantı işlevi içerir.

/**
 * 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 sınıfı, görünümü oluşturmak için gereken tüm verileri içerir. Dağ listesinin yüklenmesi tamamlanıp tamamlanmamasına bağlı olarak Loading veya MountainList durumunda olabilir.

/**
 * 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()
}

Sunulan sınıflar: MountainsRepository ve MountainsViewModel

Başlangıç projesinde MountainsRepository sınıfı sizin için sağlanmıştır. Bu sınıf, GPS Exchange Format veya GPX dosyasında depolanan dağlık yerlerin listesini okur top_peaks.gpx. mountainsRepository.loadMountains() aranırken StateFlow<List<Mountain>> döndürülür.

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, dağ koleksiyonlarını yükleyen ve bu koleksiyonların yanı sıra kullanıcı arayüzü durumunun diğer bölümlerini mountainsScreenViewState aracılığıyla gösteren bir ViewModel sınıfıdır. mountainsScreenViewState, collectAsState uzantı işlevi kullanılarak kullanıcı arayüzünün değiştirilebilir durum olarak gözlemleyebileceği bir akış StateFlow'dır.

MountainsViewModel, sağlam mimari ilkeler doğrultusunda uygulamanın tüm durumunu tutar. Kullanıcı arayüzü, onEvent yöntemini kullanarak kullanıcı etkileşimlerini görünüm modeline gönderir.

@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) }
  }
}

Bu sınıfların uygulanması hakkında bilgi edinmek isterseniz GitHub'da bunlara erişebilir veya Android Studio'da MountainsRepository ve MountainsViewModel sınıflarını açabilirsiniz.

ViewModel'i kullanma

Görünüm modeli, MainActivity içinde viewState değerini almak için kullanılır. Bu işaretçileri oluşturmak için viewState simgesini bu codelab'in ilerleyen bölümlerinde kullanacaksınız. Bu kodun başlangıç projesine zaten dahil edildiğini ve burada yalnızca referans amacıyla gösterildiğini unutmayın.

val viewModel: MountainsViewModel by viewModels()
val screenViewState = viewModel.mountainsScreenViewState.collectAsState()
val viewState = screenViewState.value

8. Kamerayı konumlandırma

GoogleMap varsayılan olarak enlem sıfır, boylam sıfır olarak ortalanır. Oluşturacağınız işaretçiler ABD'nin Colorado eyaletinde bulunuyor. Görünüm modeli tarafından sağlanan viewState, tüm işaretçileri içeren bir LatLngBounds sunar.

MountainMap.kt, sınırlayıcı kutunun merkezine başlatılmış bir CameraPositionState oluşturun. GoogleMap öğesinin cameraPositionState parametresini, yeni oluşturduğunuz cameraPositionState değişkeni olarak ayarlayın.

fun MountainMap(
    // ...
) {
    // ...
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(viewState.boundingBox.center, 5f)
    }

    GoogleMap(
        // ...
        cameraPositionState = cameraPositionState,
    )
}

Şimdi kodu çalıştırın ve haritanın Colorado'da ortalandığını görün.

İşaretçi kapsamına yakınlaştırma

Haritayı işaretçilere gerçekten odaklamak için zoomAll işlevini MountainMap.kt dosyasının sonuna ekleyin. Bu işlevin CoroutineScope gerektirdiğini unutmayın. Bunun nedeni, kameranın yeni bir konuma animasyonunun tamamlanması zaman alan eşzamansız bir işlem olmasıdır.

fun zoomAll(
    scope: CoroutineScope,
    cameraPositionState: CameraPositionState,
    boundingBox: LatLngBounds
) {
    scope.launch {
        cameraPositionState.animate(
            update = CameraUpdateFactory.newLatLngBounds(boundingBox, 64),
            durationMs = 1000
        )
    }
}

Ardından, işaretçi koleksiyonunun etrafındaki sınırlar her değiştiğinde veya kullanıcı TopApp çubuğundaki yakınlaştırma düğmesini tıkladığında zoomAll işlevini çağıran kodu ekleyin. Tümünü göster düğmesinin, etkinlikleri görünüm modeline gönderecek şekilde zaten bağlandığını unutmayın. Bu etkinlikleri yalnızca görünüm modelinden toplamanız ve yanıt olarak zoomAll işlevini çağırmanız gerekir.

Kapsamlar düğmesi

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)
                }
            }
        }
    }
}

Artık uygulamayı çalıştırdığınızda harita, işaretçilerin yerleştirileceği alana odaklanarak başlar. Yeniden konumlandırabilir ve yakınlaştırmayı değiştirebilirsiniz. Yakınlaştırma sınırları düğmesini tıkladığınızda harita, işaretçi alanına odaklanacak şekilde yeniden ayarlanır. İlerleme kaydediyorsunuz. Ancak haritada gerçekten bakılacak bir şey olmalıdır. Bir sonraki adımda bu işlemi yapacaksınız.

9. Temel işaretçiler

Bu adımda, haritada vurgulamak istediğiniz ilgi çekici yerleri temsil eden işaretçiler ekleyeceksiniz. Başlangıç projesinde sağlanan dağ listesini kullanacak ve bu yerleri haritaya işaretçi olarak ekleyeceksiniz.

Başlamak için GoogleMap bölümüne bir içerik bloğu ekleyin. Birden fazla işaretçi türü olacaktır. Bu nedenle, her türe dallanmak için bir when ifadesi ekleyin. Sonraki adımlarda her birini sırayla uygulayacaksınız.

GoogleMap(
    // ...
) {
    when (selectedMarkerType) {
        MarkerType.Basic -> {
            BasicMarkersMapContent(
                mountains = viewState.mountains,
            )
        }

        MarkerType.Advanced -> {
            AdvancedMarkersMapContent(
                mountains = viewState.mountains,
            )
        }

        MarkerType.Clustered -> {
            ClusteringMarkersMapContent(
                mountains = viewState.mountains,
            )
        }
    }
}

İşaretçi ekleme

BasicMarkersMapContent öğesine @GoogleMapComposable ile not ekleyin. GoogleMap içerik bloğunda yalnızca @GoogleMapComposable işlevlerini kullanabileceğinizi unutmayın. mountains nesnesi, Mountain nesnelerinin listesini içerir. Mountain nesnesindeki konum, ad ve yükseklik bilgilerini kullanarak listedeki her dağ için bir işaretçi eklersiniz. Konum, Marker durum parametresini ayarlamak için kullanılır. Bu parametre de işaretçinin konumunu kontrol eder.

// ...
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
        )
    }
}

Uygulamayı çalıştırın. Eklediğiniz işaretçileri göreceksiniz.

İşaretçileri özelleştirme

Yeni eklediğiniz işaretçileri öne çıkarmanıza ve kullanıcılara faydalı bilgiler vermenize yardımcı olacak çeşitli özelleştirme seçenekleri vardır. Bu görevde, her işaretçinin resmini özelleştirerek bu özelliklerden bazılarını keşfedeceksiniz.

Başlangıç projesinde, @DrawableResource öğesinden BitmapDescriptor oluşturmak için vectorToBitmap yardımcı işlevi bulunur.

Başlangıç kodunda, işaretçileri özelleştirmek için kullanacağınız bir dağ simgesi baseline_filter_hdr_24.xml bulunur.

vectorToBitmap işlevi, haritalar kitaplığıyla kullanılmak üzere bir drawable vektörü BitmapDescriptor biçimine dönüştürür. Simge renkleri, BitmapParameters örneği kullanılarak ayarlanır.

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 işlevini kullanarak iki özelleştirilmiş BitmapDescriptor oluşturun. Bunlardan biri 4.267 metreden yüksek dağlar, diğeri ise normal dağlar için olsun. Ardından, simgeyi ayarlamak için Marker composable'ın icon parametresini kullanın. Ayrıca, simgeye göre sabitleme konumunu değiştirmek için anchor parametresini ayarlayın. Bu dairesel simgeler için ortayı kullanmak daha iyi sonuç verir.

@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,
        )
    }
}

Uygulamayı çalıştırın ve özelleştirilmiş işaretçilere göz atın. Dağların tamamını görmek için Show all anahtarını açın. Dört bin metrelik dağlar, farklı işaretlere sahip olur.

10. Gelişmiş işaretçiler

AdvancedMarker'ler, temel Markers'e ekstra özellikler ekler. Bu adımda, çakışma davranışını ayarlayacak ve raptiye stilini yapılandıracaksınız.

@GoogleMapComposable işlevine AdvancedMarkersMapContent işlevini ekleyin. Her biri için AdvancedMarker ekleyerek mountains üzerinde döngü oluşturun.

@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 parametresine dikkat edin. Bu parametreyi REQUIRED_AND_HIDES_OPTIONAL olarak ayarladığınızda işaretçiniz, daha düşük öncelikli tüm işaretçilerin yerini alır. Bunu, temel bir işaretleyiciyi gelişmiş bir işaretleyiciyle karşılaştırarak görebilirsiniz. Temel işaretçi, büyük olasılıkla hem işaretçinizi hem de işaretçinin temel haritada aynı konuma yerleştirilmiş halini içerir. Gelişmiş işaretleyici, daha düşük öncelikli işaretleyicinin gizlenmesine neden olur.

Gelişmiş işaretçileri görmek için uygulamayı çalıştırın. Alt gezinme satırında Advanced markers sekmesini seçtiğinizden emin olun.

Özelleştirilmiş AdvancedMarkers

Simgelerde, 4.267 metreden yüksek dağlar ile diğer dağlar arasında ayrım yapmak için birincil ve ikincil renk şemaları kullanılır. vectorToBitmap işlevini kullanarak iki BitmapDescriptor oluşturun. Biri 4.267 metreden yüksek dağlar, diğeri ise diğer dağlar için olsun. Bu simgeleri kullanarak her tür için özel bir pinConfig oluşturun. Son olarak, AdvancedMarker işlevine göre pimi ilgili is14er()'ye uygulayın.

@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. Gruplandırılmış işaretçiler

Bu adımda, yakınlaştırmaya dayalı öğe gruplandırma eklemek için Clustering composable'ı kullanacaksınız.

Clustering composable'ı için ClusterItem koleksiyonu gerekir. MountainClusterItem, ClusterItem arayüzünü uygular. Bu sınıfı ClusteringMarkersMapContent.kt dosyasına ekleyin.

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
}

Şimdi de dağ listesinden MountainClusterItem oluşturmak için kodu ekleyin. Bu kodun, kullanıcının yerel ayarlarına göre uygun görüntüleme birimlerine dönüştürmek için UnitsConverter kullandığını unutmayın. Bu, MainActivity içinde CompositionLocal kullanılarak ayarlanır.

@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,
    )
}

Bu kodla işaretçiler, yakınlaştırma düzeyine göre gruplandırılır. Tertemiz!

Kümeleri özelleştirme

Diğer işaretçi türlerinde olduğu gibi, gruplandırılmış işaretçiler de özelleştirilebilir. Clustering composable'ının clusterItemContent parametresi, kümelenmemiş bir öğeyi oluşturmak için özel bir composable blok ayarlar. İşareti oluşturmak için bir @Composable işlevi uygulayın. SingleMountain işlevi, özelleştirilmiş bir arka plan renk şemasına sahip bir composable Material 3 Icon oluşturur.

ClusteringMarkersMapContent.kt içinde, bir işaretçinin renk şemasını tanımlayan bir veri sınıfı oluşturun:

data class IconColor(val iconColor: Color, val backgroundColor: Color, val borderColor: Color)

Ayrıca, ClusteringMarkersMapContent.kt içinde belirli bir renk şeması için simge oluşturacak bir composable işlevi oluşturun:

@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)
    )
}

Şimdi 4.267 metreden yüksek dağlar için bir renk şeması, diğer dağlar içinse başka bir renk şeması oluşturun. clusterItemContent bloğunda, verilen dağın 4.267 metreden yüksek olup olmadığına göre renk düzenini seçin.

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)
      },
  )
}

Şimdi, tek tek öğelerin özelleştirilmiş sürümlerini görmek için uygulamayı çalıştırın.

12. Harita üzerinde çizin

Haritada çizim yapmanın bir yolunu (işaretçi ekleyerek) zaten incelemiş olsanız da Android için Haritalar SDK'sı, haritada yararlı bilgiler göstermek için kullanabileceğiniz birçok başka çizim yöntemini destekler.

Örneğin, haritada rotaları ve alanları göstermek istiyorsanız bunları haritada görüntülemek için Polyline ve Polygon simgelerini kullanabilirsiniz. Alternatif olarak, bir resmi yer yüzeyine sabitlemek istiyorsanız GroundOverlay kullanabilirsiniz.

Bu görevde, şekil çizmeyi, özellikle de Colorado Eyaleti'nin etrafında bir ana hat oluşturmayı öğreneceksiniz. Colorado sınırı, 37°N ile 41°N enlemi ve 102°03'W ile 109°03'W arasında tanımlanır. Bu sayede ana hatları çizmek oldukça kolaylaşır.

Başlangıç kodu, derece-dakika-saniye gösteriminden ondalık dereceye dönüştürmek için bir DMS sınıfı içerir.

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 sınıfıyla, dört köşe LatLng konumunu tanımlayıp bunları Polygon olarak oluşturarak Colorado'nun sınırını çizebilirsiniz. Aşağıdaki kodu MountainMap.kt dosyasına ekleyin.

@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),
    )
}

Şimdi GoogleMap içerik bloğunun içinde ColoradoPolyon() işlevini çağırın.

@Composable
fun MountainMap(
    // ...
) {
   Box(
    // ...
    ) {
        GoogleMap(
            // ...
        ) {
            ColoradoPolygon()
        }
    }
}

Uygulama artık Colorado eyaletinin sınırlarını çiziyor ve eyaleti hafifçe dolduruyor.

13. KML katmanı ve ölçek çubuğu ekleme

Bu son bölümde, farklı dağ sıralarını kabaca özetleyecek ve haritaya bir ölçek çubuğu ekleyeceksiniz.

Sıradağları özetleyin

Daha önce Kolorado'nun etrafına bir ana hat çizdiniz. Burada haritaya daha karmaşık şekiller ekleyeceksiniz. Başlangıç kodu, önemli dağ sıralarını kabaca belirten bir Keyhole Biçimlendirme Dili (KML) dosyası içerir. Android için Haritalar SDK'sı Yardımcı Program Kitaplığı'nda haritaya KML katmanı ekleme işlevi bulunur. MountainMap.kt bölümünde, when bloğundan sonraki GoogleMap içerik bloğuna MapEffect çağrısı ekleyin. MapEffect işlevi, GoogleMap nesnesiyle çağrılır. Bu, GoogleMap nesnesi gerektiren, birleştirilemeyen API'ler ve kitaplıklar arasında faydalı bir köprü görevi görebilir.

  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()
      }
    }

Harita ölçeği ekleme

Son görev olarak haritaya ölçek ekleyeceksiniz. ScaleBar, haritaya eklenebilen bir ölçek composable'ı uygular. ScaleBar bir

@GoogleMapComposable ve bu nedenle GoogleMap içeriğine eklenemez. Bunun yerine, haritayı içeren Box öğesine ekleyin.

Box(
  // ...
) {
    GoogleMap(
      // ...
    ) {
        // ...
    }

    ScaleBar(
        modifier = Modifier
            .padding(top = 5.dp, end = 15.dp)
            .align(Alignment.TopEnd),
        cameraPositionState = cameraPositionState
    )
    // ...
}

Tam olarak uygulanmış codelab'i görmek için uygulamayı çalıştırın.

14. Çözüm kodunu alma

Tamamlanmış codelab'in kodunu indirmek için aşağıdaki komutları kullanabilirsiniz:

  1. git yüklüyse depoyu klonlayın.
$ git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-compose.git

Alternatif olarak, kaynak kodunu indirmek için aşağıdaki düğmeyi tıklayabilirsiniz.

  1. Kodu aldıktan sonra Android Studio'da solution dizinindeki projeyi açın.

15. Tebrikler

Tebrikler! Birçok konuya değindik. Umarım Android için Haritalar SDK'sında sunulan temel özellikler hakkında daha iyi bir anlayışa sahipsinizdir.

Daha fazla bilgi

  • Android için Haritalar SDK'sı: Android uygulamalarınız için dinamik, etkileşimli, özelleştirilmiş haritalar, konum ve coğrafi deneyimler oluşturun.
  • Maps Compose Kitaplığı: Uygulamanızı oluşturmak için Jetpack Compose ile kullanabileceğiniz bir dizi açık kaynaklı composable işlev ve veri türü.
  • android-maps-compose: Bu kod laboratuvarında ele alınan tüm özellikleri ve daha fazlasını gösteren GitHub'daki örnek kod.
  • Google Haritalar Platformu ile Android uygulamaları geliştirme hakkında daha fazla Kotlin codelab