Android uygulamanıza harita ekleme (Kotlin)

1. Başlamadan Önce

Bu codelab'de, ABD için San Francisco, CA'daki bisiklet mağazalarının haritasını görüntüleyen bir uygulama oluşturarak Android için Haritalar SDK'sını uygulamanızla nasıl entegre edeceğinizi ve temel özelliklerini nasıl kullanacağınızı öğreneceksiniz.

f05e1ca27ff42bf6.png

Ön koşullar

  • Kotlin ve Android geliştirme ile ilgili temel bilgiler

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çi ekleyin, özelleştirin ve kümeleyin.
  • Haritaya çoklu çizgi ve poligon çizin.
  • Kameranın görüş noktasını programatik olarak kontrol edin.

Gerekenler

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 kurma

Henüz bir Google Cloud Platform hesabınız ve faturalandırmanın etkin olduğu bir projeniz yoksa faturalandırma hesabı ve proje oluşturmak için lütfen 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. Google Cloud Marketplace'te bu codelab için gerekli olan Google Haritalar Platformu API'lerini ve SDK'larını etkinleştirin. Bunu yapmak için bu video veya bu dokümanlardaki adımları uygulayın.
  2. Cloud Console'un Kimlik Bilgileri sayfasında API anahtarı oluşturun. Bu video veya bu dokümanlardaki adımları uygulayabilirsiniz. Google Haritalar Platformu'na gönderilen tüm istekler bir API anahtarı gerektirir.

3. Hızlı başlangıç

En kısa sürede başlamanıza yardımcı olmak için aşağıda, bu codelab'i takip etmenize yardımcı olacak bazı başlangıç kodları verilmiştir. Çözüme geçebilirsiniz, ancak çözümü derlemek için tüm adımları izlemek isterseniz okumaya devam edin.

  1. git dosyasını yüklediyseniz depoyu klonlayın.
git clone https://github.com/googlecodelabs/maps-platform-101-android.git

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

  1. Kodu aldıktan sonra Android Studio'daki starter dizininde bulunan projeyi açın.

4. Google Haritalar'ı Ekleyin

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

d1d068b5d4ae38b9.png

API anahtarınızı ekleme

Android için Haritalar SDK'sının anahtarınızı uygulamanızla ilişkilendirebilmesi için daha önceki adımlarda 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'nin yer aldığı seviyede) local.properties adlı dosyayı açın.
  2. Bu dosyada, değeri oluşturduğunuz API anahtarı olacak şekilde yeni bir GOOGLE_MAPS_API_KEY anahtarı tanımlayın.

local.properties

GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE

local.properties değerinin Git deposundaki .gitignore dosyasında listelendiğine dikkat edin. 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ılabilmesi için açığa çıkarmak amacıyla uygulamanızın app/ dizininde bulunan Secrets Gradle Plugin for Android eklentisini dahil edin ve aşağıdaki satırı plugins blokuna ekleyin:

uygulama düzeyinde build.gradle

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

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

proje düzeyinde 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ı, Android manifest dosyasında derleme değişkenleri olarak ve derleme sırasında Gradle tarafından oluşturulan BuildConfig sınıfında değişkenler olarak kullanılabilir hale getirir. Bu eklentinin kullanılması, local.properties uygulamasında bulunan özelliklerin okunması için gerekebilecek ortak kodu kaldırır. Böylece uygulamanız genelinde erişilebilir hale gelir.

Google Haritalar'a bağımlılık ekleyin

  1. Artık API anahtarınıza uygulamanın içinden erişilebilir. Sonraki adım, Android için Haritalar SDK'sının uygulamanızın build.gradle dosyasına eklenmesidir.

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, daha önceki bir adımda oluşturduğunuz API anahtarını iletmek için AndroidManifest.xml öğesine yeni bir meta-data etiketi ekleyin. Bunu yapmak için bu dosyayı Android Studio'da açın ve app/src/main dosyasında bulunan AndroidManifest.xml dosyanızdaki application nesnesinin içine şu meta-data etiketini ekleyin.

AndroidManifest.xml

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

activity_main.xml (etkinlik)

<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 bulunur. Bu parça, daha sonraki adımlarda kullandığınız temel GoogleMaps nesnesini içerir.

  1. Son olarak, app/src/main/java/com/google/codelabs/buildyourfirstmap yöntemini içeren MainActivity sınıfını, onCreate yöntemini geçersiz kılmak için aşağıdaki kodu ekleyerek oluşturun. Böylece, içeriğini yeni oluşturduğunuz düzenle ayarlayabilirsiniz.

Ana Etkinlik

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
}
  1. Şimdi uygulamayı çalıştırabilirsiniz. Artık harita yükünü cihazınızın ekranında görebilirsiniz.

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

Bulut tabanlı harita stili kullanarak haritanızın stilini özelleştirebilirsiniz.

Harita kimliği oluşturma

İlişkilendirilmiş harita stiliyle henüz 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. Bir harita kimliğini bir harita stiliyle ilişkilendirin.

Harita kimliğini uygulamanıza 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 (etkinlik)

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

Bunu tamamladıktan sonra devam edin ve haritanızı seçtiğiniz stilde görmek için uygulamayı çalıştırın.

6. İşaretçi ekle

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

bc5576877369b554.png

GoogleMap için referans alın

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

AnaEtkinlik.onCreate()

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

Uygulama, ilk olarak SupportFragmentManager nesnesinde findFragmentById() yöntemini kullanarak önceki adımda eklediğiniz SupportMapFragment öğesini bulur. Bir referans alındıktan sonra, getMapAsync() çağrısı çağrılır ve ardından bir lambda iletilir. Bu lambda, GoogleMap nesnesinin iletildiği yerdir. Bu lambdanın içinde, kısa bir süre içinde tanımlanan addMarkers() yöntem çağrısı çağrılır.

Sağlanan sınıf: PlacesReader

Başlangıç projesinde PlacesReader sınıfı size sunulmuştur. Bu sınıfta, places.json adlı bir JSON dosyasında depolanan 49 yerin listesini okur ve bunları List<Place> olarak döndürür. Bu yerler, San Francisco, CA, ABD çevresindeki bisiklet mağazalarının listesini temsil eder.

Bu sınıfın uygulanmasını merak ediyorsanız GitHub'da derse erişebilir veya PlacesReader sınıfını Android Studio'da açabilirsiniz.

Yerler Okuyucu

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

Yer yükle

Bisiklet mağazalarının listesini yüklemek için MainActivity varlığına places adlı bir mülk ekleyin ve aşağıdaki şekilde tanımlayın:

AnaEtkinlik.places

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

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

Yer

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

Eşlemek için işaretçiler ekleyin

Yer listesi belleğe yüklendiğine göre şimdi bu yerleri haritada temsil edebilirsiniz.

  1. MainActivity ürününde addMarkers() adlı bir yöntem oluşturun ve aşağıdaki şekilde tanımlayın:

AnaEtkinlik.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 listesini tarar ve ardından, sağlanan GoogleMap nesnesinde addMarker() yöntemi çağrılır. İşaretçi, işaretçinin kendisini özelleştirmenize olanak tanıyan bir MarkerOptions nesnesi örneklenerek oluşturulur. Bu durumda, işaretçinin başlığı ve konumu sağlanarak sırasıyla bisiklet mağazası adını ve koordinatlarını temsil eder.

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

7. İşaretçileri özelleştir

Az önce eklediğiniz işaretçilerin, öne çıkmalarına ve kullanıcılara yararlı bilgiler iletmelerine yardımcı olacak çeşitli özelleştirme seçenekleri vardır. Bu görevde, her bir işaretçinin resmini ve bir işaretçiye dokunulduğunda görüntülenen bilgi penceresini özelleştirerek bunların bazılarını keşfedeceksiniz.

a26f82802fe838e9.png

Bilgi penceresi ekleme

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

mark_info_contents.xml oluşturun

Önce 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 > Düzen Kaynağı Dosyası'nı seçin.

8cac51fcbef9171b.png

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

8783af12baf07a80.png

Bu düzen dosyası, daha sonra bilgi penceresinde içeriği temsil edecek şekilde şişirilir.

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

mark_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 uygulaması oluşturma

Özel bilgi penceresi için düzen dosyası oluşturduktan sonra, sıradaki adım GoogleMap.InfowindowAdapter arayüzünü uygulamaktır. Bu arayüz iki yöntem içeriyor: getInfoWindow() ve getInfoContents(). Her iki yöntem de isteğe bağlı bir View nesnesi döndürür. Bunlardan ilki, pencerenin kendisini özelleştirmek için, diğeri de içeriğini özelleştirmek için kullanılır. Bu durumda, ikisini de uygular ve getInfoWindow() içinde null (döndürücü) döndürürken getInfoContents() dönüşünü özelleştirirsiniz. Bu, varsayılan pencerenin kullanılması gerektiğini belirtir.

  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 kutusuna MarkerInfoWindowAdapter yazın ve Dosya'yı vurgulanmış olarak tutun.

992235af53d3897f.png

  1. Dosyayı oluşturduktan sonra, aşağıdaki kod snippet'indeki içeriği 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çeriğinde, yöntemde sağlanan İşaretçi bir Place türüne yayınlanır ve yayınlama mümkün değilse yöntem sıfıra döner (henüz Marker öğesinde etiket özelliğini ayarlamadınız ancak bunu sonraki adımda yapabilirsiniz).

Ardından, marker_info_contents.xml düzeni şişirilir ve TextViews değerini içeren metin Place etiketine ayarlanır.

MainActivity'yi güncelleme

Şu ana kadar oluşturduğunuz tüm bileşenleri yapıştırmak için MainActivity sınıfınıza iki satır eklemeniz gerekir.

İlk olarak, getMapAsync yöntemi çağrısında özel InfoWindowAdapter, MarkerInfoWindowAdapter öğesini iletmek için GoogleMap nesnesinde setInfoWindowAdapter() yöntemini çağırın ve yeni bir MarkerInfoWindowAdapter örneği oluşturun.

  1. Bunu, getMapAsync() lambda içindeki addMarkers() yöntem çağrısından sonra aşağıdaki kodu ekleyerek yapabilirsiniz.

AnaEtkinlik.onCreate()

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

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

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

AnaEtkinlik.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 yerin türünü bildirmenin 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österirsiniz. Başlangıç projesi, app/src/res/drawable içinde kullandığınız bisiklet simgesini ic_directions_bike_black_24dp.xml içeriyor.

6eb7358bb61b0a88.png

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

Elinizdeki vektör çekilebilir bisiklet simgesi hazır olduğunda, bir sonraki adım, harita üzerindeki her bir işaretleyiciyi çekilebilir hale getirmektir. MarkerOptions, bunu yapmak için kullandığınız bir BitmapDescriptor olan icon yöntemine sahiptir.

Önce, az önce eklediğiniz vektörü BitmapDescriptor değerine dönüştürmeniz gerekir. Başlangıç projesinin içerdiği BitMapHelper adlı bir dosya, vectorToBitmap() adında bir yardımcı işlevi içerir.

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, bir Context, çekilebilir kaynak kimliği ve bir renk tam sayısı alır ve bunun bir BitmapDescriptor temsilini oluşturur.

Yardımcı yöntemi kullanarak, bicycleIcon adlı yeni bir özellik bildirip ş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 renklendirmek ve BitmapDescriptor olarak döndürmek için bunu kullanır.

  1. Bu özelliği kullanarak simge özelleştirmenizi tamamlamak için addMarkers() yöntemini kullanarak icon MarkerOptions yöntemini çağırın. Bunu yaptığınızda, işaretçi özelliği aşağıdaki gibi görünecektir:

AnaEtkinlik.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çilerin etkileşim kurması ve çok fazla gürültü oluşturması çok zordur. Bu da uygulamanızın kullanılabilirliğini etkiler.

68591edc86d73724.png

Bunun için kullanıcı deneyimini iyileştirmek amacıyla, yakından kümelenen büyük bir veri kümeniz olduğunda en iyi uygulama, işaretçi kümelemeyi uygulamaktır. Kümelemede, haritayı yakınlaştırdığınızda ve uzaklaştırdığınızda, yakın mesafedeki işaretçiler aşağıdaki gibi birleştirilir:

f05e1ca27ff42bf6.png

Bunu uygulamak için Android Yardımcı Kitaplık Kitaplığı'ndan yardım almanız gerekir.

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

Android için Haritalar SDK'sı, Android için Haritalar SDK'sının işlevselliğini genişletmenin bir yolu olarak oluşturuldu. İşaretçi kümeleme, ısı haritaları, KML ve GeoJson desteği, çoklu çizgi kodlama ve kod çözme gibi gelişmiş özellikler ve küresel geometri etrafında birkaç yardımcı işlev sunar.

build.gradle dosyanızı güncelleme

Hizmet 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. Şimdi 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 bir proje senkronizasyonu gerçekleştirmeniz gerekir.

b7b030ec82c007fd.png

Kümelemeyi uygulama

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

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

ClusterItem arayüzünü uygulama

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

Yer

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 aşağıdaki üç yöntemi tanımlar:

  • Yerin LatLng temsil eden getPosition().
  • Yerin adını temsil eden getTitle()
  • Yerin adresini temsil eden getSnippet().

DefaultClusterRenderer sınıfının alt sınıfı

Kümeleri uygulamaktan sorumlu sınıf (ClusterManager), haritayı kaydırırken ve yakınlaştırırken küme oluşturma işlemini işlemek için dahili olarak bir ClusterRenderer sınıfı kullanır. Varsayılan olarak, ClusterRenderer uygulayan varsayılan oluşturucu olan DefaultClusterRenderer ile birlikte gelir. Basit durumlarda bu yeterlidir. Ancak sizin durumunuzda, işaretçilerin özelleştirilmesi gerektiğinden bu sınıfı genişletmeniz ve özelleştirmeleri buraya eklemeniz gerekir.

Şimdi com.google.codelabs.buildyourfirstmap.place paketindeki PlaceRenderer.kt Kotlin dosyasını oluşturup şu şekilde 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 üzerinden özelleştirmeler sağlayabilirsiniz. Bu durumda, işaretçinin başlığını, konumunu ve simgesini ayarlarsınız.
  • İşaretçi, haritada oluşturulduktan hemen sonra çağrılan onClusterItemRenderer(). Burada, oluşturulan Marker nesnesine erişebilirsiniz. Bu durumda, işaretçinin etiket özelliği ayarlanır.

ClusterManager oluşturma ve öğe ekleme

Son olarak, kümenin çalışması için MainActivity öğesini bir ClusterManager örneklendirecek ve ona bağımlı olacak şekilde değiştirmeniz gerekir. ClusterManager, işaretçileri (ClusterItem nesnesi) dahili olarak ekler. Bu nedenle, işaretçileri doğrudan haritaya eklemek yerine bu sorumluluk ClusterManager adlı kişiye aittir. Ayrıca ClusterManager, setInfoWindowAdapter() içindeki öğeleri de çağırır. Bu nedenle, ClusterManger&MarkerManager.Collection nesnesinde özel bir bilgi penceresi ayarlanması gerekir.

  1. Başlamak için, MainActivity.onCreate() içinde getMapAsync() görüşmesindeki lambdanın içeriğini değiştirin. Devam edip addMarkers() ve setInfoWindowAdapter() çağrısına yorum yapın ve bunun yerine, addClusteredMarkers() adlı bir yöntem tanımlayın.

AnaEtkinlik.onCreate()

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

    // Set custom info window adapter.
    // googleMap.setInfoWindowAdapter(MarkerInfoWindowAdapter(this))
}
  1. Sonra, MainActivity dilinde addClusteredMarkers() 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 bir ClusterManager örneği oluşturur, PlacesRenderer özel oluşturucusunu iletir, tüm yerleri ekler ve cluster() yöntemini çağırır. Ayrıca, ClusterManager, harita nesnesinde setInfoWindowAdapter() yöntemini kullandığından özel bilgi penceresi ayarlamanın ClusterManager.markerCollection nesnesinde yapılması gerekir. Son olarak, kullanıcı haritayı kaydırırken ve yakınlaştırırken kümeleme ayarının değiştirilmesini istediğiniz için googleMap cihazına OnCameraIdleListener sağlanır. Böylece, kamera boşta kaldığında clusterManager.onCameraIdle() çağrılır.

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

9. Harita üzerinde çizin

Haritada çizim yapmanın bir yolunu daha önce keşfetmiş olsanız da (işaretçiler ekleyerek), Android için Haritalar SDK'sı, haritada yararlı bilgiler görüntülemek üzere çizebileceğiniz diğer birçok yöntemi destekler.

Örneğin, haritada rotaları ve alanları temsil etmek isterseniz bunları harita üzerinde göstermek için poligon ve poligonlar kullanabilirsiniz. Bir resmi zemin yüzeyine düzeltmek istiyorsanız zemin yer paylaşımları kullanabilirsiniz.

Bu görevde, işaretçiye her dokunulduğunda şeklin, özellikle bir dairenin nasıl çizileceğini öğrenirsiniz.

f98ce13055430352.png

Tıklama işleyici ekleme

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

  1. MainActivity ürününde addClusteredMarkers() yönteminde, devam etmeden önce aşağıdaki satırı cluster() adresine ekleyin.

MainActivity.addClusteredMarkers()

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

Bu yöntem, bir işleyici ekler ve daha sonra tanımladığınız addCircle() yöntemini çağırır. Son olarak, false bu yöntemin bu etkinliği kullanmadığını belirtmek için bu yöntemden döndürülür.

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

AnaEtkinlik.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ılacağı ve yeni bir dairenin ekleneceği şekilde ayarlanır. Daire ekleme API'sının işaretçi eklemeye çok benzer olduğuna dikkat edin.

  1. Şimdi değişiklikleri görmek için uygulamayı çalıştırın.

10. Kamera Kontrolü

Son göreviniz olarak, görünümü belirli bir bölgeye odaklamak için bazı kamera kontrollerini kullanırsınız.

Kamera ve görünüm

Uygulamayı çalıştırdığınızda dikkatinizi çekerek kameranın Afrika kıtasını görüntülemesini ve eklediğiniz işaretçileri bulmak için San Francisco'da titizlikle kaydırmanız ve yakınlaştırmanız gerekir. Dünyayı keşfetmenin eğlenceli bir yolu olsa da, işaretçileri hemen göstermek istiyorsanız yararlı olmaz.

Bu konuda yardımcı olmak için, kameranın konumunu programlı bir şekilde ayarlayarak görünümün istediğiniz yere ortalanmasını sağlayabilirsiniz.

  1. Şimdi kamera uygulamasını getMapAsync() başlatıldıktan sonra uygulama başlatıldığında San Francisco'da başlatılacak şekilde ayarlamak için aşağıdaki kodu ekleyin.

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

Önce setOnMapLoadedCallback(), kamera güncellemesinin yalnızca harita yüklendikten sonra gerçekleştirilmesi için çağrılır. Kamera güncellemesi çağrısı yapmadan önce boyutlar gibi harita özelliklerinin hesaplanması gerektiğinden bu adım gereklidir.

Lambda'da, harita üzerinde dikdörtgen bir bölgeyi tanımlayan yeni bir LatLngBounds nesnesi oluşturulur. Bu yöntem, tüm yerlerin sınırın içinde olmasını sağlamak için yere LatLng değerlerini ekleyerek kademeli olarak oluşturulur. Bu nesne oluşturulduktan sonra GoogleMap üzerindeki moveCamera() yöntemi çağrılır ve CameraUpdateFactory.newLatLngBounds(bounds.build(), 20) üzerinden buna bir CameraUpdate sağlanır.

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

Kamera değişikliklerini dinleme

Kamera konumunu değiştirmenin yanı sıra kullanıcı harita üzerinde gezinirken kamera güncellemelerini de dinleyebilirsiniz. Bu, kamera hareket ederken kullanıcı arayüzünü değiştirmek isterseniz yararlı olabilir.

Sadece eğlence için, kodu değiştirdiğinizde işaretçiler şeffaf olacak şekilde değişir.

  1. addClusteredMarkers() yönteminde, devam edip yöntemin alt kısmı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 işlem, kamera hareket etmeye başladığında tüm işaretçilerin (kümeler ve işaretçiler) alfa değerlerinin 0.3f olarak değiştirilmesini sağlayarak işaretçilerin yarı saydam olması için bir OnCameraMoveStartedListener ekler.

  1. Son olarak, kamera durduğunda yarı saydam işaretçileri tekrar opak hale getirmek için addClusteredMarkers() yönteminde setOnCameraIdleListener içeriğini aşağıdaki şekilde 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. Şimdi sonuçları görmek için uygulamayı çalıştırın.

11. Haritalar KTX

Bir veya daha fazla Google Haritalar Platformu Android SDK'sı kullanan Kotlin uygulamaları için Kotlin uzantısı veya KTX kitaplıkları, eş yordam, uzantı özellikleri/işlevleri gibi Kotlin dili özelliklerinden yararlanmanızı sağlar. Her Google Haritalar SDK'sı, aşağıda gösterildiği gibi ilgili bir KTX kitaplığına sahiptir:

Google Haritalar Platformu KTX Şeması

Bu görevde uygulamanız için Haritalar KTX ve Haritalar Utils KTX kitaplıklarını kullanacak ve Kotlin'e özgü dil özelliklerini uygulamanızda kullanabilmek için önceki görevlerin uygulamalarını yeniden düzenleyeceksiniz.

  1. Uygulama düzeyindeki build.gradle dosyanıza KTX bağımlılarını ekleyin

Uygulama hem Android için Haritalar SDK'sını hem de Android Yardımcı Program Kitaplığı'nın Haritalar SDK'sını kullandığından, bu kitaplıklar için ilgili KTX kitaplıklarını eklemeniz gerekir. Ayrıca, bu görevde AndroidX Lifecycle KTX kitaplığında bulunan bir özelliği de kullanacaksınız. Bu nedenle, 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 bir DSL stili API alternatifi sağlar. Yukarıda bahsedilen API'ları kullanmak için işaretçi veya daire seçenekleri içeren bir sınıfın oluşturulması gerekir. KTX alternatifleriyle, sağladığınız lambdada işaretçi veya daire seçeneklerini ayarlayabilirsiniz.

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

MainActivity.addMarkers(GoogleHarita)

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

/**
 * 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öntemlerin bu şekilde yeniden yazılması çok daha özlüdür. Bu nedenle, Kotlin’in alıcı işleviyle mümkün olmuştur.

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

Maps KTX kitaplığı, eş yordamlarda kullanılmak üzere askıya alınan işlev uzantıları da sağlar. Özellikle SupportMapFragment.getMapAsync(OnMapReadyCallback) ve GoogleMap.setOnMapLoadedCallback(OnMapLoadedCallback) için askıya alınacak işlev alternatifleri mevcuttur. Bu alternatif API'leri kullanmak, geri çağırma isteğinin ortadan kalkmasını sağlar ve bu yöntemlerin yanıtını seri ve eşzamanlı şekilde almanızı sağlar.

Bu yöntemler işlevleri askıya aldığından, kullanımlarının eş yordam içinde gerçekleşmesi gerekir. Yaşam Döngüsü Çalışma Zamanı KTX kitaplığı, yaşam döngüsüne duyarlı eş yordam kapsamları sağlamak için bir uzantı sunar. Böylece kapsamlar, uygun yaşam döngüsü etkinliğinde çalıştırılıp durdurulur.

Bu kavramları bir araya getirerek MainActivity.onCreate(Bundle) yöntemini güncelleyin:

MainActivity.onCreate(Paket)

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 eş yordamı kapsamı, etkinlik en azından oluşturulmuş durumda olduğunda engelleme işlemini yürütür. Ayrıca, GoogleMap nesnesini alma ve haritanın yüklenmesinin tamamlanmasını beklemenin çağrılarının sırasıyla SupportMapFragment.awaitMap() ve GoogleMap.awaitMapLoad() ile değiştirildiğini unutmayın. Bu askıya alma işlevlerini kullanarak kodu yeniden düzenlemek, eşdeğer geri çağırmaya dayalı kodu sıralı bir şekilde yazmanıza olanak tanır.

  1. Devam edin ve yeniden yapılan değişikliklerinizle uygulamayı yeniden oluşturun!

12. Tebrikler

Tebrikler! Çok sayıda içeriği ele aldınız ve Android için Haritalar SDK'sında sunulan temel özellikleri daha iyi anladığınızı umuyoruz.

Daha fazla bilgi

  • Android için Yerler SDK'sı - Çevrenizdeki işletmeleri keşfetmek için zengin yer verileri kümesini keşfedin.
  • android-maps-ktx: Android için Haritalar SDK'sı ve Android için Yardımcı Program Kitaplığı'nın Kotlin dostu bir şekilde entegre edilmesini sağlayan açık kaynaklı bir kitaplık.
  • android-place-ktx: Android için Yerler SDK'sıyla Kotlin dostu bir şekilde entegrasyon yapmanıza olanak tanıyan açık kaynak bir kitaplık.
  • android-samples: Bu codelab'de yer alan tüm özellikleri gösteren GitHub'daki örnek kod ve daha fazlası.
  • Google Haritalar Platformu ile Android uygulamaları geliştirmek için diğer Kotlin codelab'leri