Android uygulamanıza harita ekleme (Kotlin)

1. Başlamadan Önce

Bu codelab'de, ABD'nin Kaliforniya eyaletindeki San Francisco şehrinde bulunan bisiklet mağazalarını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.

f05e1ca27ff42bf6.png

Ön koşullar

  • Kotlin ve Android geliştirme hakkında temel düzeyde bilgi sahibi olmak

Yapacaklarınız

  • Google Haritalar'ı bir Android uygulamasına eklemek için Android için Haritalar SDK'sını etkinleştirin ve kullanın.
  • İşaretçileri ekleme, özelleştirme ve gruplandırma
  • Haritada çoklu çizgiler ve poligonlar çizin.
  • Kameranın bakış açısını programatik olarak kontrol edin.

İhtiyacınız olanlar

2. Hazırlanın

Aşağıdaki etkinleştirme adımı için 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/googlecodelabs/maps-platform-101-android.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. Google Haritalar'ı ekleme

Bu bölümde, uygulamayı başlattığınızda yüklenmesi için Google Haritalar'ı ekleyeceksiniz.

d1d068b5d4ae38b9.png

API anahtarınızı ekleme

Android için Haritalar SDK'sının anahtarınızı uygulamanızla ilişkilendirebilmesi için önceki bir adımda oluşturduğunuz API anahtarının uygulamaya sağlanması gerekir.

  1. Bunu sağlamak için projenizin kök dizininde (gradle.properties ve settings.gradle ile aynı düzeyde) local.properties adlı dosyayı açın.
  2. Bu dosyada, değeri oluşturduğunuz API anahtarı olan yeni bir anahtar GOOGLE_MAPS_API_KEY tanımlayın.

local.properties

GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE

local.properties karakterinin, Git deposundaki .gitignore dosyasında listelendiğini fark edeceksiniz. Bunun nedeni, API anahtarınızın hassas bilgi olarak kabul edilmesi ve mümkünse kaynak kontrolüne dahil edilmemesi gerektiğidir.

  1. Ardından, API'nizi uygulamanızda kullanılabilir hale getirmek için app/ dizinindeki uygulamanızın build.gradle dosyasına Secrets Gradle Plugin for Android eklentisini ekleyin ve plugins bloğuna aşağıdaki satırı ekleyin:

app-level build.gradle

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

Ayrıca, proje düzeyindeki build.gradle dosyanızı aşağıdaki sınıf yolunu içerecek şekilde değiştirmeniz gerekir:

project-level build.gradle

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

Bu eklenti, local.properties dosyanızda tanımladığınız anahtarları derleme zamanında Android manifest dosyasında derleme değişkenleri ve Gradle tarafından oluşturulan BuildConfig sınıfında değişkenler olarak kullanılabilir hale getirir. Bu eklentiyi kullandığınızda, local.properties içindeki özelliklerin okunması için gerekli olan ve uygulamanızın her yerinden erişilebilen standart kod kaldırılır.

Google Haritalar bağımlılığı ekleme

  1. 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 dosyasına eklemektir.

Bu codelab ile birlikte gelen başlangıç projesinde bu bağımlılık sizin için zaten eklenmiştir.

build.gradle

dependencies {
   // Dependency to include Maps SDK for Android
   implementation 'com.google.android.gms:play-services-maps:17.0.0'
}
  1. Ardından, önceki adımda oluşturduğunuz API anahtarını iletmek için AndroidManifest.xml bölümüne yeni bir meta-data etiketi ekleyin. Bunu yapmak için bu dosyayı Android Studio'da açın ve app/src/main konumundaki AndroidManifest.xml dosyanızda application nesnesinin içine aşağıdaki meta-data etiketini ekleyin.

AndroidManifest.xml

<meta-data
   android:name="com.google.android.geo.API_KEY"
   android:value="${GOOGLE_MAPS_API_KEY}" />
  1. Ardından, app/src/main/res/layout/ dizininde activity_main.xml adlı yeni bir düzen dosyası oluşturun ve bunu aşağıdaki gibi tanımlayın:

activity_main.xml

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

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

</FrameLayout>

Bu düzende, SupportMapFragment içeren tek bir FrameLayout var. Bu parça, sonraki adımlarda kullanacağınız temel GoogleMaps nesnesini içerir.

  1. Son olarak, MainActivity sınıfını app/src/main/java/com/google/codelabs/buildyourfirstmap konumunda güncelleyin. Bunun için onCreate yöntemini geçersiz kılacak aşağıdaki kodu ekleyerek içeriklerini yeni oluşturduğunuz düzenle ayarlayabilirsiniz.

MainActivity

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
}
  1. Şimdi uygulamayı çalıştırın. Haritanın cihazınızın ekranına yüklendiğini görmeniz gerekir.

5. Bulut tabanlı harita stili (isteğe bağlı)

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.

Uygulamanıza harita kimliği ekleme

Oluşturduğunuz harita kimliğini kullanmak için activity_main.xml dosyasını değiştirin ve harita kimliğinizi SupportMapFragment öğesinin map:mapId özelliğinde iletin.

activity_main.xml

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

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

6. İşaretçi ekleme

Bu görevde, haritada vurgulamak istediğiniz önemli yerleri temsil eden işaretçiler ekleyeceksiniz. Öncelikle, başlangıç projesinde sizin için sağlanan yerlerin listesini alıp bu yerleri haritaya eklersiniz. Bu örnekte, bunlar bisiklet dükkanlarıdır.

bc5576877369b554.png

GoogleMap'e referans alma

Öncelikle, yöntemlerini kullanabilmek için GoogleMap nesnesine bir referans almanız gerekir. Bunu yapmak için MainActivity.onCreate() yönteminizde setContentView() çağrısından hemen sonra aşağıdaki kodu ekleyin:

MainActivity.onCreate()

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

Uygulama, SupportFragmentManager nesnesinde findFragmentById() yöntemini kullanarak önceki adımda eklediğiniz SupportMapFragment öğesini önce bulur. Bir referans alındıktan sonra getMapAsync() çağrısı yapılır ve ardından bir lambda iletilir. GoogleMap nesnesinin iletildiği yer bu lambda'dır. Bu lambda içinde, kısa süre sonra tanımlanacak olan addMarkers() yöntemi çağrısı başlatılır.

Sağlanan sınıf: PlacesReader

Başlangıç projesinde PlacesReader sınıfı sizin için sağlanmıştır. Bu sınıf, places.json adlı bir JSON dosyasında depolanan 49 yerin listesini okur ve bunları List<Place> olarak döndürür. Yerler, San Francisco, CA, ABD'deki bisiklet mağazalarının listesini temsil etmektedir.

Bu sınıfın uygulanmasıyla ilgili merak ettikleriniz varsa sınıfa GitHub'dan erişebilir veya Android Studio'da PlacesReader sınıfını açabilirsiniz.

PlacesReader

package com.google.codelabs.buildyourfirstmap.place

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

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

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

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

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

Yerleri yükleme

Bisiklet dükkanlarının listesini yüklemek için MainActivity içinde places adlı bir özellik ekleyin ve bu özelliği aşağıdaki gibi tanımlayın:

MainActivity.places

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

Bu kod, PlacesReader üzerinde read() yöntemini çağırır ve List<Place> döndürür. Bir Place, name adlı bir özelliğe (yerin adı) ve latLng adlı bir özelliğe (yerin bulunduğu koordinatlar) sahiptir.

Place

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

Haritaya işaretçi ekleme

Yer listesi belleğe yüklendiğine göre, bir sonraki adım bu yerleri haritada göstermektir.

  1. MainActivity içinde addMarkers() adlı bir yöntem oluşturun ve aşağıdaki gibi tanımlayın:

MainActivity.addMarkers()

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

Bu yöntem, places listesinde yinelenir ve sağlanan GoogleMap nesnesinde addMarker() yöntemini çağırır. İşaretçi, MarkerOptions nesnesi oluşturularak oluşturulur. Bu nesne, işaretçinin kendisini özelleştirmenize olanak tanır. Bu durumda, sırasıyla bisiklet dükkanının adını ve koordinatlarını temsil eden işaretin başlığı ve konumu sağlanır.

  1. Uygulamayı çalıştırın ve eklediğiniz işaretçileri görmek için San Francisco'ya gidin.

7. İş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 ve bir işaretçiye dokunulduğunda görüntülenen bilgi penceresini özelleştirerek bu işaretçilerden bazılarını keşfedeceksiniz.

a26f82802fe838e9.png

Bilgi penceresi ekleme

Varsayılan olarak, bir işaretçiye dokunduğunuzda bilgi penceresinde başlığı ve snippet'i (ayarlanmışsa) gösterilir. Bunu, yerin adresi ve puanı gibi ek bilgileri gösterecek şekilde özelleştirirsiniz.

marker_info_contents.xml dosyasını oluşturun

Öncelikle marker_info_contents.xml adlı yeni bir düzen dosyası oluşturun.

  1. Bunu yapmak için Android Studio'daki proje görünümünde app/src/main/res/layout klasörünü sağ tıklayın ve Yeni > Layout Resource File'ı (Düzen Kaynak Dosyası) seçin.

8cac51fcbef9171b.png

  1. İletişim kutusunda, Dosya adı alanına marker_info_contents, Root element alanına LinearLayout yazıp Tamam'ı tıklayın.

8783af12baf07a80.png

Bu düzen dosyası daha sonra bilgi penceresindeki içerikleri temsil edecek şekilde genişletilir.

  1. Dikey LinearLayout görünüm grubuna üç TextViews ekleyen aşağıdaki kod snippet'indeki içeriği kopyalayın ve dosyadaki varsayılan kodun üzerine yazın.

marker_info_contents.xml

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

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

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

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

</LinearLayout>

InfoWindowAdapter'ın bir uygulamasını oluşturma

Özel bilgi penceresi için düzen dosyasını oluşturduktan sonraki adım, GoogleMap.InfoWindowAdapter arayüzünü uygulamaktır. Bu arayüzde getInfoWindow() ve getInfoContents() olmak üzere iki yöntem bulunur. Her iki yöntem de isteğe bağlı bir View nesnesi döndürür. Bu nesnelerden ilki pencerenin kendisini, ikincisi ise içeriğini özelleştirmek için kullanılır. Sizin durumunuzda, her ikisini de uygulayıp getInfoContents() değerini döndürürken getInfoWindow() değerini boş döndürerek varsayılan pencerenin kullanılması gerektiğini belirtiyorsunuz.

  1. Android Studio'daki proje görünümünde app/src/main/java/com/google/codelabs/buildyourfirstmap klasörünü sağ tıklayıp Yeni > Kotlin Dosyası/Sınıfı'nı seçerek MainActivity ile aynı pakette MarkerInfoWindowAdapter adlı yeni bir Kotlin dosyası oluşturun.

3975ba36eba9f8e1.png

  1. İletişim kutusunda MarkerInfoWindowAdapter yazın ve Dosya'yı vurgulanmış halde tutun.

992235af53d3897f.png

  1. Dosyayı oluşturduktan sonra aşağıdaki kod snippet'indeki içerikleri yeni dosyanıza kopyalayın.

MarkerInfoWindowAdapter

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

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

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

       return view
   }

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

getInfoContents() yönteminin içeriklerinde, yöntemde sağlanan işaretçi Place türüne dönüştürülür. Dönüştürme mümkün değilse yöntem null değerini döndürür (Marker üzerinde henüz etiket özelliğini ayarlamadınız, ancak bunu sonraki adımda yapacaksınız).

Ardından, düzen marker_info_contents.xml genişletilir ve metin, TextViews öğesinde Place etiketine ayarlanır.

MainActivity'yi güncelleme

Şimdiye kadar oluşturduğunuz tüm bileşenleri birleştirmek için MainActivity sınıfınıza iki satır eklemeniz gerekir.

Öncelikle, özel InfoWindowAdapter, MarkerInfoWindowAdapter değerini getMapAsync yöntem çağrısında iletmek için GoogleMap nesnesinde setInfoWindowAdapter() yöntemini çağırın ve MarkerInfoWindowAdapter öğesinin yeni bir örneğini oluşturun.

  1. Bunu yapmak için addMarkers() yöntem çağrısından sonra getMapAsync() lambda'sının içine aşağıdaki kodu ekleyin.

MainActivity.onCreate()

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

Son olarak, haritaya eklenen her işaretçide her bir yeri etiket özelliği olarak ayarlamanız gerekir.

  1. Bunu yapmak için places.forEach{} işlevindeki addMarkers() çağrısını aşağıdaki şekilde değiştirin:

MainActivity.addMarkers()

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

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

Özel işaretçi resmi ekleme

İşaretçi resmini özelleştirmek, işaretçinin haritanızda temsil ettiği yer türünü belirtmenin eğlenceli yollarından biridir. Bu adımda, haritadaki her bir mağazayı temsil etmek için varsayılan kırmızı işaretçiler yerine bisikletler gösterilir. Başlangıç projesinde, kullandığınız app/src/res/drawable içinde bisiklet simgesi ic_directions_bike_black_24dp.xml bulunur.

6eb7358bb61b0a88.png

İşaretçide özel bit eşlem ayarlama

Kullanabileceğiniz vektör çizilebilir bisiklet simgesiyle bir sonraki adım, bu çizilebilir öğeyi haritadaki her işaretçinin simgesi olarak ayarlamaktır. MarkerOptions, bu işlemi gerçekleştirmek için kullandığınız bir BitmapDescriptor alan icon yöntemine sahiptir.

Öncelikle, yeni eklediğiniz vektör çizilebilir öğeyi BitmapDescriptor. Başlangıç projesine dahil edilen BitMapHelper adlı dosyada, tam olarak bunu yapan vectorToBitmap() adlı bir yardımcı işlev bulunur.

BitmapHelper

package com.google.codelabs.buildyourfirstmap

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

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

Bu yöntem, Context, çizilebilir kaynak kimliği ve renk tamsayısı alır ve bunun BitmapDescriptor gösterimini oluşturur.

Yardımcı yöntemi kullanarak bicycleIcon adlı yeni bir özellik bildirin ve bu özelliğe şu tanımı verin: MainActivity.bicycleIcon

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

Bu özellik, uygulamanızda önceden tanımlanmış colorPrimary rengini kullanır ve bisiklet simgesini bu renkle renklendirip BitmapDescriptor olarak döndürür.

  1. Bu özelliği kullanarak simge özelleştirmenizi tamamlamak için icon yöntemini MarkerOptions yönteminde çağırın.addMarkers() Bu durumda işaretçi özelliği şu şekilde görünmelidir:

MainActivity.addMarkers()

val marker = googleMap.addMarker(
    MarkerOptions()
        .title(place.name)
        .position(place.latLng)
        .icon(bicycleIcon)
)
  1. Güncellenen işaretçileri görmek için uygulamayı çalıştırın.

8. Küme işaretçileri

Haritayı ne kadar yakınlaştırdığınıza bağlı olarak, eklediğiniz işaretçilerin çakıştığını fark etmiş olabilirsiniz. Çakışan işaretçilerle etkileşim kurmak çok zordur ve çok fazla gürültü oluşturarak uygulamanızın kullanılabilirliğini etkiler.

68591edc86d73724.png

Bu durumda kullanıcı deneyimini iyileştirmek için, yakından kümelenmiş büyük bir veri kümeniz olduğunda işaretçi kümelemeyi uygulamak en iyi yöntemdir. Kümeleme özelliği sayesinde, haritayı yakınlaştırıp uzaklaştırdığınızda birbirine yakın işaretçiler aşağıdaki gibi kümelenir:

f05e1ca27ff42bf6.png

Bunu uygulamak için Android için Haritalar SDK'sı Yardımcı Kitaplığı'nın yardımına ihtiyacınız vardır.

Android için Haritalar SDK'sı Yardımcı Program Kitaplığı

Android için Haritalar SDK'sı Yardımcı Kitaplığı, Android için Haritalar SDK'sının işlevselliğini genişletmek amacıyla oluşturulmuştur. İşaretçi kümeleme, ısı haritaları, KML ve GeoJson desteği, çoklu çizgi kodlama ve kod çözme gibi gelişmiş özellikler ile küresel geometriyle ilgili çeşitli yardımcı işlevler sunar.

build.gradle dosyanızı güncelleme

Yardımcı program kitaplığı, Android için Haritalar SDK'sından ayrı olarak paketlendiğinden build.gradle dosyanıza ek bir bağımlılık eklemeniz gerekir.

  1. app/build.gradle dosyanızın dependencies bölümünü güncelleyin.

build.gradle

implementation 'com.google.maps.android:android-maps-utils:1.1.0'
  1. Bu satırı ekledikten sonra yeni bağımlılıkları getirmek için proje senkronizasyonu yapmanız gerekir.

b7b030ec82c007fd.png

Kümelemeyi uygulama

Uygulamanızda kümelemeyi uygulamak için şu üç adımı uygulayın:

  1. ClusterItem arayüzünü uygulayın.
  2. DefaultClusterRenderer sınıfını alt sınıfa ayırın.
  3. ClusterManager oluşturun ve öğe ekleyin.

ClusterItem arayüzünü uygulama

Haritada kümelenebilir bir işaretçiyi temsil eden tüm nesneler ClusterItem arayüzünü uygulamalıdır. Bu durumda, Place modelinin ClusterItem ile uyumlu olması gerekir. Place.kt dosyasını açın ve aşağıdaki değişiklikleri yapın:

Place

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

   override fun getTitle(): String =
       name

   override fun getSnippet(): String =
       address
}

ClusterItem bu üç yöntemi tanımlar:

  • getPosition(), yerin LatLng değerini gösterir.
  • getTitle(): Yer adını gösterir.
  • getSnippet(), yerin adresini gösterir.

DefaultClusterRenderer sınıfını alt sınıflandırma

Kümelemeyi uygulamaktan sorumlu olan ClusterManager sınıfı, haritada kaydırma ve yakınlaştırma yaparken kümelerin oluşturulmasını yönetmek için dahili olarak bir ClusterRenderer sınıfı kullanır. Varsayılan olarak, ClusterRenderer'ı uygulayan varsayılan oluşturucu DefaultClusterRenderer ile birlikte gelir. Basit durumlarda bu yeterli olacaktır. Ancak sizin durumunuzda işaretçilerin özelleştirilmesi gerektiğinden bu sınıfı genişletmeniz ve özelleştirmeleri buraya eklemeniz gerekir.

com.google.codelabs.buildyourfirstmap.place paketinde PlaceRenderer.kt Kotlin dosyasını oluşturun ve aşağıdaki gibi tanımlayın:

PlaceRenderer

package com.google.codelabs.buildyourfirstmap.place

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

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

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

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

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

Bu sınıf, şu iki işlevi geçersiz kılar:

  • onBeforeClusterItemRendered(), küme haritada oluşturulmadan önce çağrılır. Burada, MarkerOptions aracılığıyla özelleştirmeler sağlayabilirsiniz. Bu durumda, işaretçinin başlığı, konumu ve simgesi ayarlanır.
  • onClusterItemRenderer(), işaretçi haritada oluşturulduktan hemen sonra çağrılır. Oluşturulan Marker nesnesine buradan erişebilirsiniz. Bu örnekte, işaretçinin etiket özelliği ayarlanır.

ClusterManager oluşturma ve öğe ekleme

Son olarak, kümelemeyi çalıştırmak için MainActivity öğesini değiştirerek bir ClusterManager oluşturmanız ve gerekli bağımlılıkları sağlamanız gerekir. ClusterManager, işaretçilerin (ClusterItem nesneleri) eklenmesini dahili olarak yönetir. Bu nedenle, işaretçileri doğrudan haritaya eklemek yerine bu sorumluluk ClusterManager'ya devredilir. Ayrıca, ClusterManager dahili olarak setInfoWindowAdapter() yöntemini de çağırdığı için özel bilgi penceresi ayarlama işlemi ClusterManger'nin MarkerManager.Collection nesnesinde yapılmalıdır.

  1. Başlamak için MainActivity.onCreate() içindeki getMapAsync() çağrısında lambda'nın içeriğini değiştirin. addMarkers() ve setInfoWindowAdapter() çağrısını yorum satırı haline getirin ve bunun yerine, bir sonraki adımda tanımlayacağınız addClusteredMarkers() adlı bir yöntemi çağırın.

MainActivity.onCreate()

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

    // Set custom info window adapter.
    // googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
  1. Ardından, MainActivity bölümünde addClusteredMarkers() öğesini tanımlayın.

MainActivity.addClusteredMarkers()

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

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

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

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

Bu yöntem, ClusterManager öğesini oluşturur, özel oluşturucuyu PlacesRenderer öğesine iletir, tüm yerleri ekler ve cluster() yöntemini çağırır. Ayrıca, ClusterManager, harita nesnesinde setInfoWindowAdapter() yöntemini kullandığından özel bilgi penceresinin ayarlanması ClusterManager.markerCollection nesnesinde yapılmalıdır. Son olarak, kullanıcı haritada kaydırma ve yakınlaştırma yaptığında kümelemenin değişmesini istediğiniz için OnCameraIdleListener, googleMap'ye sağlanır. Böylece kamera boşta kaldığında clusterManager.onCameraIdle() çağrılır.

  1. Yeni gruplandırılmış mağazaları görmek için uygulamayı çalıştırın.

9. 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 çoklu çizgiler ve poligonlar kullanabilirsiniz. Alternatif olarak, bir görüntüyü yer yüzeyine sabitlemek isterseniz yer paylaşımlarını kullanabilirsiniz.

Bu görevde, bir işaretçiye dokunulduğunda etrafına şekil (özellikle daire) çizmeyi öğreneceksiniz.

f98ce13055430352.png

Tıklama işleyici ekleme

Genellikle, bir işaretçiye tıklama işleyici eklemenin yolu, GoogleMap nesnesine setOnMarkerClickListener() üzerinden doğrudan bir tıklama işleyici iletmektir. Ancak kümeleme kullandığınız için tıklama işleyicinin ClusterManager öğesine sağlanması gerekir.

  1. addClusteredMarkers() yönteminde MainActivity, cluster() için çağırmanın hemen ardından aşağıdaki satırı ekleyin.

MainActivity.addClusteredMarkers()

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

Bu yöntem, bir dinleyici ekler ve ardından tanımlayacağınız addCircle() yöntemini çağırır. Son olarak, bu yöntemin etkinliği kullanmadığını belirtmek için bu yöntemden false değeri döndürülür.

  1. Ardından, circle özelliğini ve addCircle() yöntemini MainActivity içinde tanımlamanız gerekir.

MainActivity.addCircle()

private var circle: Circle? = null

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

circle özelliği, yeni bir işaretçiye her dokunulduğunda önceki dairenin kaldırılıp yeni bir dairenin ekleneceği şekilde ayarlanır. Daire eklemeye yönelik API'nin, işaretçi eklemeye yönelik API'ye oldukça benzediğini unutmayın.

  1. Şimdi uygulamayı çalıştırarak değişiklikleri görebilirsiniz.

10. Kamera Kontrolü

Son görev olarak, görünümü belirli bir bölgeye odaklayabilmek için bazı kamera kontrollerine bakıyorsunuz.

Kamera ve görünüm

Uygulamayı çalıştırdığınızda kameranın Afrika kıtasını gösterdiğini ve eklediğiniz işaretçileri bulmak için San Francisco'ya zahmetli bir şekilde kaydırma ve yakınlaştırma yapmanız gerektiğini fark ettiniz. Dünyayı keşfetmek için eğlenceli bir yöntem olsa da işaretçileri hemen göstermek istiyorsanız bu yöntem işe yaramaz.

Bunu kolaylaştırmak için kameranın konumunu programatik olarak ayarlayarak görünümü istediğiniz yerde ortalayabilirsiniz.

  1. Uygulama başlatıldığında kamera görünümünü San Francisco'ya göre başlatılacak şekilde ayarlamak için aşağıdaki kodu getMapAsync() çağrısına ekleyin.

MainActivity.onCreate()

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

İlk olarak, kamera güncellemesinin yalnızca harita yüklendikten sonra yapılması için setOnMapLoadedCallback() çağrılır. Boyutlar gibi harita özelliklerinin kamera güncelleme çağrısı yapılmadan önce hesaplanması gerektiğinden bu adım gereklidir.

Lambda'da, haritada dikdörtgen bir bölgeyi tanımlayan yeni bir LatLngBounds nesnesi oluşturulur. Bu, tüm yerlerin sınırlar içinde olduğundan emin olmak için tüm yer LatLng değerlerini içerecek şekilde kademeli olarak oluşturulur. Bu nesne oluşturulduktan sonra GoogleMap üzerindeki moveCamera() yöntemi çağrılır ve CameraUpdateFactory.newLatLngBounds(bounds.build(), 20) aracılığıyla CameraUpdate sağlanır.

  1. Uygulamayı çalıştırın ve kameranın artık San Francisco'da başlatıldığını fark edin.

Kameradaki değişiklikleri dinleme

Kamera konumunu değiştirmenin yanı sıra, kullanıcı haritada hareket ederken kamera güncellemelerini de dinleyebilirsiniz. Kamera hareket ederken kullanıcı arayüzünü değiştirmek istiyorsanız bu özellik yararlı olabilir.

Eğlence amaçlı olarak, kamera her hareket ettirildiğinde işaretçileri yarı saydam hale getirmek için kodu değiştiriyorsunuz.

  1. addClusteredMarkers() yönteminde, yöntemin en altına aşağıdaki satırları ekleyin:

MainActivity.addClusteredMarkers()

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

Bu, OnCameraMoveStartedListener ekler. Böylece kamera hareket etmeye başladığında tüm işaretçilerin (hem kümeler hem de işaretçiler) alfa değerleri 0.3f olarak değiştirilir ve işaretçiler yarı saydam görünür.

  1. Son olarak, kamera durduğunda yarı saydam işaretçileri tekrar opak hale getirmek için addClusteredMarkers() yöntemindeki setOnCameraIdleListener içeriğini aşağıdaki gibi değiştirin:

MainActivity.addClusteredMarkers()

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

   // Call clusterManager.onCameraIdle() when the camera stops moving so that reclustering
   // can be performed when the camera stops moving.
   clusterManager.onCameraIdle()
}
  1. Sonuçları görmek için uygulamayı çalıştırın.

11. Maps KTX

Bir veya daha fazla Google Haritalar Platformu Android SDK'sı kullanan Kotlin uygulamalarında, Kotlin dil özelliklerinden (ör. eşzamanlı rutinler, uzantı özellikleri/işlevleri) yararlanabilmeniz için Kotlin uzantısı veya KTX kitaplıkları kullanılabilir. Her Google Haritalar SDK'sının, aşağıda gösterildiği gibi karşılık gelen bir KTX kitaplığı vardır:

Google Haritalar Platformu KTX Şeması

Bu görevde, uygulamanıza Maps KTX ve Maps Utils KTX kitaplıklarını ekleyecek ve önceki görevlerdeki uygulamaları yeniden düzenleyerek uygulamanızda Kotlin'e özgü dil özelliklerini kullanabileceksiniz.

  1. KTX bağımlılıklarını uygulama düzeyindeki build.gradle dosyanıza ekleyin

Uygulama hem Android için Haritalar SDK'sını hem de Android için Haritalar SDK'sı Yardımcı Kitaplığı'nı kullandığından bu kitaplıklar için ilgili KTX kitaplıklarını eklemeniz gerekir. Bu görevde AndroidX Lifecycle KTX kitaplığında bulunan bir özelliği de kullanacağınız için bu bağımlılığı uygulama düzeyindeki build.gradle dosyanıza da ekleyin.

build.gradle

dependencies {
    // ...

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

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

    // Lifecycle Runtime KTX Library
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
}
  1. GoogleMap.addMarker() ve GoogleMap.addCircle() uzantı işlevlerini kullanma

Maps KTX kitaplığı, önceki adımlarda kullanılan GoogleMap.addMarker(MarkerOptions) ve GoogleMap.addCircle(CircleOptions) için DSL tarzı bir API alternatifi sunar. Yukarıda belirtilen API'leri kullanmak için işaretçi veya daire seçeneklerini içeren bir sınıf oluşturmak gerekirken KTX alternatiflerinde işaretçi veya daire seçeneklerini sağladığınız lambda'da ayarlayabilirsiniz.

Bu API'leri kullanmak için MainActivity.addMarkers(GoogleMap) ve MainActivity.addCircle(GoogleMap) yöntemlerini güncelleyin:

MainActivity.addMarkers(GoogleMap)

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

MainActivity.addCircle(GoogleMap)

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

Yukarıdaki yöntemleri bu şekilde yeniden yazmak, Kotlin'in alıcılı işlev değişmezi kullanılarak mümkün kılınan, okunması çok daha kısa ve öz bir yöntemdir.

  1. SupportMapFragment.awaitMap() ve GoogleMap.awaitMapLoad() uzantı askıya alma işlevlerini kullanma

Maps KTX kitaplığı, coroutine'lerde kullanılacak askıya alma işlevi uzantıları da sağlar. Özellikle SupportMapFragment.getMapAsync(OnMapReadyCallback) ve GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback) için askıya alma işlevi alternatifleri vardır. Bu alternatif API'leri kullandığınızda geri çağırma işlevlerini iletmeniz gerekmez. Bunun yerine, bu yöntemlerin yanıtını seri ve senkron bir şekilde alabilirsiniz.

Bu yöntemler işlevleri askıya aldığından, bunların kullanımı bir ortak yordam içinde gerçekleşmelidir. Lifecycle Runtime KTX kitaplığı, uygun yaşam döngüsü etkinliğinde çalıştırılıp durdurulmaları için yaşam döngüsünden haberdar coroutine kapsamları sağlayan bir uzantı sunar.

Bu kavramları birleştirerek MainActivity.onCreate(Bundle) yöntemini güncelleyin:

MainActivity.onCreate(Bundle)

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

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

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

        addClusteredMarkers(googleMap)
    }
}

lifecycleScope.launchWhenCreated coroutine kapsamı, etkinlik en az oluşturulmuş durumdayken bloğu yürütür. Ayrıca, GoogleMap nesnesini almak ve haritanın yüklenmesinin tamamlanmasını beklemek için yapılan çağrıların sırasıyla SupportMapFragment.awaitMap() ve GoogleMap.awaitMapLoad() ile değiştirildiğini de unutmayın. Bu askıya alma işlevlerini kullanarak kodu yeniden düzenlemek, eşdeğer geri çağırma tabanlı kodu sıralı bir şekilde yazmanızı sağlar.

  1. Uygulamayı yeniden düzenlenmiş değişikliklerinizle yeniden oluşturabilirsiniz.

12. 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 Yerler SDK'sı: Çevrenizdeki işletmeleri keşfetmek için zengin yer verileri setini inceleyin.
  • android-maps-ktx: Android için Haritalar SDK'sı ve Android için Haritalar SDK'sı Yardımcı Kitaplığı ile Kotlin dostu bir şekilde entegrasyon yapmanıza olanak tanıyan açık kaynaklı bir kitaplık.
  • android-place-ktx: Android için Yerler SDK'sını Kotlin dostu bir şekilde entegre etmenize olanak tanıyan açık kaynaklı bir kitaplık.
  • android-samples: Bu codelab'de ele alınan tüm özelliklerin ve daha fazlasının gösterildiği GitHub'daki örnek kod.
  • Google Haritalar Platformu ile Android uygulamaları geliştirme hakkında daha fazla Kotlin codelab