Android מתקדם ב-Kotlin 04.1: מפות Google ל-Android

ה-codelab הזה הוא חלק מהקורס Advanced Android in Kotlin (פיתוח מתקדם ל-Android ב-Kotlin). כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר, אבל זה לא חובה. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא Android מתקדם ב-Kotlin.

פיתוח אפליקציות באמצעות מפות Google מאפשר לכם להוסיף לאפליקציה תכונות כמו תמונות לוויין, אמצעי בקרה חזקים בממשק המשתמש של המפות, מעקב אחר מיקום וסמני מיקום. אתם יכולים להוסיף ערך למפות Google הרגילות על ידי הצגת מידע ממערך הנתונים שלכם, כמו מיקומים של אזורים מוכרים לדיג או לטיפוס. אפשר גם ליצור משחקים שבהם השחקן חוקר את העולם הפיזי, כמו משחקי חפש את המטמון או משחקי מציאות רבודה.

בשיעור הזה תיצרו אפליקציה של מפות Google בשם Wander, שמציגה מפות בהתאמה אישית ומראה את מיקום המשתמש.

דרישות מוקדמות

ידע בנושאים הבאים:

  • איך ליצור אפליקציית Android בסיסית ולהריץ אותה באמצעות Android Studio.
  • איך ליצור ולנהל משאבים, כמו מחרוזות.
  • איך לבצע רפקטורינג של קוד ולשנות את השם של משתנים באמצעות Android Studio.
  • איך משתמשים במפת Google בתור משתמש.
  • איך מגדירים הרשאות בזמן ריצה.

מה תלמדו

  • איך מקבלים מפתח API מ-Google API Console ורושמים את המפתח באפליקציה
  • איך משלבים מפת Google באפליקציה
  • איך מציגים סוגים שונים של מפות
  • איך משנים את הסגנון של מפת Google
  • איך מוסיפים סמנים למפה
  • איך מאפשרים למשתמש להציב סמן על נקודת עניין (POI)
  • איך מפעילים את המעקב אחר המיקום
  • איך יוצרים את אפליקציית Wander עם מפת Google מוטמעת
  • איך ליצור תכונות מותאמות אישית לאפליקציה, כמו סמנים וסגנונות
  • איך מפעילים מעקב מיקום באפליקציה

ב-codelab הזה יוצרים את אפליקציית Wander, שמציגה מפת Google עם סגנון מותאם אישית. אפליקציית Wander מאפשרת להוסיף סמנים למיקומים, להוסיף שכבות-על ולראות את המיקום שלכם בזמן אמת.

נדרש מפתח API לשימוש ב-Maps SDK ל-Android. כדי לקבל את מפתח ה-API, צריך לרשום את הפרויקט בדף API & Services. מפתח ה-API קשור לאישור דיגיטלי שמקשר את האפליקציה ליוצר שלה. מידע נוסף על שימוש בתעודות דיגיטליות ועל חתימה על האפליקציה זמין במאמר בנושא חתימה על האפליקציה.

ב-codelab הזה משתמשים במפתח ה-API לאישור הניפוי באגים. אישור הניפוי באגים לא מאובטח מעצם הגדרתו, כפי שמתואר במאמר חתימה על גרסת ניפוי הבאגים. אפליקציות ל-Android שפורסמו ומשתמשות ב-Maps SDK for Android דורשות מפתח API שני: המפתח של אישור הפרסום. מידע נוסף על קבלת אישור הפצה זמין במאמר קבלת מפתח API.

‫Android Studio כולל תבנית פעילות של מפות Google, שמייצרת קוד תבנית שימושי. קוד התבנית כולל קובץ google_maps_api.xml שמכיל קישור שמפשט את קבלת מפתח API.

שלב 1: יצירת פרויקט Wander באמצעות תבנית המפות

  1. יוצרים פרויקט חדש ב-Android Studio.
  2. בוחרים בתבנית פעילות במפות Google.

  1. נותנים לפרויקט את השם Wander.
  2. מגדירים את רמת ה-API המינימלית ל-API 19. מוודאים שהשפה היא Kotlin.
  3. לוחצים על סיום.
  4. אחרי שהאפליקציה מסיימת את הבנייה, כדאי לעיין בפרויקט ובקבצים הבאים שקשורים למפות שנוצרו בשבילכם ב-Android Studio:

google_maps_api.xml – קובץ ההגדרות הזה משמש לאחסון מפתח ה-API. התבנית יוצרת שני קובצי google_maps_api.xml: אחד לניפוי באגים ואחד לפרסום. הקובץ של מפתח ה-API לאישור ניפוי הבאגים נמצא בתיקייה src/debug/res/values. הקובץ של מפתח ה-API לאישור הגרסה נמצא בתיקייה src/release/res/values. ב-codelab הזה, משתמשים רק באישור לניפוי באגים.

activity_maps.xml – קובץ הפריסה הזה מכיל רכיב fragment יחיד שממלא את כל המסך. הסיווג SupportMapFragment הוא סיווג משנה של הסיווג Fragment. ‫SupportMapFragment היא הדרך הכי פשוטה להציב מפה באפליקציה. זהו רכיב wrapper של תצוגת מפה, שמטפל אוטומטית בצרכים של מחזור החיים הנדרש.

אפשר לכלול את SupportMapFragment בקובץ פריסה באמצעות תג <fragment> בכל ViewGroup, עם מאפיין name נוסף.

android:name="com.google.android.gms.maps.SupportMapFragment"

MapsActivity.java – הקובץ MapsActivity.kt יוצר מופע של SupportMapFragment בשיטה onCreate(), ומשתמש ב-getMapAsync() של המחלקה כדי לאתחל באופן אוטומטי את מערכת המפות ואת התצוגה. הפעילות שמכילה את SupportMapFragment צריכה להטמיע את הממשק OnMapReadyCallback ואת השיטה onMapReady() של הממשק הזה. השיטה onMapReady() מופעלת כשהמפה נטענת.

שלב 2: קבלת מפתח ה-API

  1. פותחים את גרסת הניפוי באגים של הקובץ google_maps_api.xml.
  2. בקובץ, מחפשים תגובה עם כתובת URL ארוכה. הפרמטרים של כתובת ה-URL כוללים מידע ספציפי על האפליקציה.
  3. מעתיקים את כתובת ה-URL ומדביקים אותה בדפדפן.
  4. פועלים לפי ההנחיות ליצירת פרויקט בדף APIs & Services. בגלל הפרמטרים בכתובת ה-URL שצוינה, הדף יודע להפעיל אוטומטית את Maps SDK ל-Android.
  5. לוחצים על Create an API Key (יצירת מפתח API).
  6. בדף הבא, עוברים לקטע API Keys (מפתחות API) ולוחצים על המפתח שיצרתם.
  7. לוחצים על Restrict Key (הגבלת המפתח) ובוחרים באפשרות Maps SDK for Android (SDK של מפות ל-Android) כדי להגביל את השימוש במפתח לאפליקציות ל-Android.
  8. מעתיקים את מפתח ה-API שנוצר. היא מתחילה ב-"AIza".
  9. בקובץ google_maps_api.xml, מדביקים את המפתח במחרוזת google_maps_key במקום שבו מופיע הכיתוב YOUR_KEY_HERE.
  10. מריצים את האפליקציה. אמורה להופיע פעילות עם מפה מוטמעת וסמן שמוגדר בסידני, אוסטרליה. (הסמן של סידני הוא חלק מהתבנית ואפשר לשנות אותו בהמשך).

שלב 3: משנים את השם של mMap

ל-MapsActivity יש lateinit פרטי var בשם mMap, שהוא מסוג GoogleMap. כדי לפעול בהתאם למוסכמות למתן שמות ב-Kotlin, צריך לשנות את השם של mMap ל-map.

  1. ב-MapsActivity, לוחצים לחיצה ימנית על mMap ולוחצים על Refactor > Rename...

  1. משנים את שם המשתנה ל-map.

שימו לב שכל ההפניות אל mMap בפונקציה onMapReady() משתנות גם הן ל-map.

ב-Google Maps יש כמה סוגי מפות: רגילה, היברידית, לוויין, פני שטח ו'ללא' (אם לא רוצים מפה בכלל).

מפה רגילה

מפת לוויין

מפה היברידית

מפת פני השטח

כל סוג של מפה מספק סוגים שונים של מידע. לדוגמה, כשמשתמשים במפות לניווט ברכב, כדאי לראות את שמות הרחובות, ולכן אפשר להשתמש באפשרות הרגילה. כשאתם יוצאים לטיול רגלי, מפת הטופוגרפיה יכולה לעזור לכם להבין כמה עוד אתם צריכים לטפס כדי להגיע לפסגה.

במשימה הזו:

  1. מוסיפים סרגל אפליקציות עם תפריט אפשרויות שמאפשר למשתמש לשנות את סוג המפה.
  2. מזיזים את מיקום ההתחלה במפה למיקום הבית שלכם.
  3. הוספת תמיכה בסמנים, שמציינים מיקומים בודדים במפה ויכולים לכלול תווית.

הוספת תפריט לסוגי מפות

בשלב הזה מוסיפים סרגל אפליקציות עם תפריט אפשרויות שמאפשר למשתמש לשנות את סוג המפה.

  1. כדי ליצור קובץ XML חדש של תפריט, לוחצים לחיצה ימנית על ספריית res ובוחרים באפשרות New > Android Resource File (קובץ משאב של Android).
  2. בתיבת הדו-שיח, נותנים שם לקובץ map_options.
  3. בוחרים באפשרות תפריט בשדה 'סוג המשאב'.
  4. לוחצים על אישור.
  5. בכרטיסייה קוד, מחליפים את הקוד בקובץ החדש בקוד הבא כדי ליצור את אפשרויות התפריט של המפה. סוג המפה 'none' לא נכלל כי הוא גורם לכך שלא מוצגת מפה בכלל. בשלב הזה מתרחשת שגיאה, אבל פותרים אותה בשלב הבא.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item
       android:id="@+id/normal_map"
       android:title="@string/normal_map"
       app:showAsAction="never" />
   <item
       android:id="@+id/hybrid_map"
       android:title="@string/hybrid_map"
       app:showAsAction="never" />
   <item
       android:id="@+id/satellite_map"
       android:title="@string/satellite_map"
       app:showAsAction="never" />
   <item
       android:id="@+id/terrain_map"
       android:title="@string/terrain_map"
       app:showAsAction="never" />
</menu>
  1. ב-strings.xml, מוסיפים משאבים למאפיינים title כדי לפתור את השגיאות.
<resources>
   ...
   <string name="normal_map">Normal Map</string>
   <string name="hybrid_map">Hybrid Map</string>
   <string name="satellite_map">Satellite Map</string>
   <string name="terrain_map">Terrain Map</string>
   <string name="lat_long_snippet">Lat: %1$.5f, Long: %2$.5f</string>
   <string name="dropped_pin">Dropped Pin</string>
   <string name="poi">poi</string>
</resources>
  1. ב-MapsActivity, מבטלים את השיטה onCreateOptionsMenu() ומנפחים את התפריט מקובץ המשאבים map_options.
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
   val inflater = menuInflater
   inflater.inflate(R.menu.map_options, menu)
   return true
}
  1. ב-MapsActivity.kt, משנים את ברירת המחדל של אמצעי התשלום onOptionsItemSelected(). משנים את סוג המפה באמצעות קבועים של סוג המפה כדי לשקף את הבחירה של המשתמש.
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
   // Change the map type based on the user's selection.
   R.id.normal_map -> {
       map.mapType = GoogleMap.MAP_TYPE_NORMAL
       true
   }
   R.id.hybrid_map -> {
       map.mapType = GoogleMap.MAP_TYPE_HYBRID
       true
   }
   R.id.satellite_map -> {
       map.mapType = GoogleMap.MAP_TYPE_SATELLITE
       true
   }
   R.id.terrain_map -> {
       map.mapType = GoogleMap.MAP_TYPE_TERRAIN
       true
   }
   else -> super.onOptionsItemSelected(item)
}
  1. מפעילים את האפליקציה.
  2. לוחצים על כדי לשנות את סוג המפה. שימו לב איך המפה נראית במצבים השונים.

כברירת מחדל, הקריאה החוזרת onMapReady() כוללת קוד שמציב סמן בסידני, אוסטרליה, שבה נוצרה מפות Google. פונקציית הקריאה החוזרת שמוגדרת כברירת מחדל גם מפעילה אנימציה של המפה כדי להזיז אותה לסידני.

במשימה הזו, תגרמו למצלמה במפה לנוע לבית שלכם, להגדיל את התצוגה לרמה שתציינו ולהציב שם סמן.

שלב 1: מתקרבים לבית ומוסיפים סמן

  1. בקבץ MapsActivity.kt, מחפשים את השיטה onMapReady(). מסירים את הקוד שמוסיף את הסמן בסידני ומזיז את המצלמה. כך השיטה אמורה להיראות עכשיו.
override fun onMapReady(googleMap: GoogleMap) {
   map = googleMap

}
  1. כאן מוסבר איך למצוא את קו הרוחב וקו האורך של הבית.
  2. יוצרים ערך לקו הרוחב וערך לקו האורך, ומזינים את ערכי הנקודה הצפה שלהם.
val latitude = 37.422160
val longitude = -122.084270
  1. יוצרים אובייקט חדש מסוג LatLng בשם homeLatLng. באובייקט homeLatLng, מעבירים את הערכים שיצרתם.
val homeLatLng = LatLng(latitude, longitude)
  1. יוצרים val כדי להגדיר את רמת הזום הרצויה במפה. משתמשים ברמת זום של 15f.
val zoomLevel = 15f

רמת הזום קובעת עד כמה התצוגה של המפה מוגדלת. הרשימה הבאה נותנת לכם מושג לגבי רמת הפירוט שמוצגת בכל רמת זום:

  • 1: World
  • 5: מסת יבשה/יבשת
  • 10: עיר
  • 15: רחובות
  • 20: מבנים
  1. מעבירים את המצלמה אל homeLatLng על ידי קריאה לפונקציה moveCamera() באובייקט map והעברה של אובייקט CameraUpdate באמצעות CameraUpdateFactory.newLatLngZoom(). מעבירים את האובייקט homeLatLng ואת zoomLevel.
map.moveCamera(CameraUpdateFactory.newLatLngZoom(homeLatLng, zoomLevel))
  1. הוספת סמן למפה במיקום homeLatLng.
map.addMarker(MarkerOptions().position(homeLatLng))

השיטה הסופית אמורה להיראות כך:

override fun onMapReady(googleMap: GoogleMap) {
   map = googleMap

   //These coordinates represent the latitude and longitude of the Googleplex.
   val latitude = 37.422160
   val longitude = -122.084270
   val zoomLevel = 15f

   val homeLatLng = LatLng(latitude, longitude)
   map.moveCamera(CameraUpdateFactory.newLatLngZoom(homeLatLng, zoomLevel))
   map.addMarker(MarkerOptions().position(homeLatLng))
}
  1. מריצים את האפליקציה. המפה אמורה לבצע פנינג לבית שלכם, להתקרב לרמה הרצויה ולהציב סמן על הבית.

שלב 2: מאפשרים למשתמשים להוסיף סמן באמצעות לחיצה ארוכה

בשלב הזה, מוסיפים סמן כשהמשתמש לוחץ לחיצה ארוכה על מיקום במפה.

  1. יוצרים stub של שיטה ב-MapsActivity בשם setMapLongClick() שמקבלת GoogleMap כארגומנט.
  2. מצרפים מאזין setOnMapLongClickListener לאובייקט המפה.
private fun setMapLongClick(map:GoogleMap) {
   map.setOnMapLongClickListener { }
}
  1. ב-setOnMapLongClickListener(), מפעילים את ה-method‏ addMarker(). מעבירים אובייקט MarkerOptions חדש עם המיקום שמוגדר ל-LatLng שמועבר.
private fun setMapLongClick(map: GoogleMap) {
   map.setOnMapLongClickListener { latLng ->
       map.addMarker(
           MarkerOptions()
               .position(latLng)
       )
   }
}
  1. בסוף השיטה onMapReady(), קוראים ל-setMapLongClick() עם map.
override fun onMapReady(googleMap: GoogleMap) {
   ...
  
   setMapLongClick(map)
}
  1. מפעילים את האפליקציה.
  2. לוחצים לחיצה ארוכה על המפה כדי למקם סמן במיקום מסוים.
  3. מקישים על הסמן כדי למרכז אותו במסך.

שלב 3: מוסיפים חלון מידע לסמן

בשלב הזה מוסיפים רכיב InfoWindow שמציג את הקואורדינטות של הסמן כשמקישים על הסמן.

  1. ב-setMapLongClick()setOnMapLongClickListener(), יוצרים val עבור snippet. תקציר הוא טקסט נוסף שמוצג אחרי הכותרת. בקטע התקציר מוצגים קווי הרוחב והאורך של סמן.
private fun setMapLongClick(map: GoogleMap) {
   map.setOnMapLongClickListener { latLng ->
       // A snippet is additional text that's displayed after the title.
       val snippet = String.format(
           Locale.getDefault(),
           "Lat: %1$.5f, Long: %2$.5f",
           latLng.latitude,
           latLng.longitude
       )
       map.addMarker(
           MarkerOptions()
               .position(latLng)
       )
   }
}
  1. ב-addMarker(), מגדירים את title של הסמן ל-Dropped Pin (סיכה שהוצבה) באמצעות מחרוזת משאב R.string.dropped_pin.
  2. מגדירים את snippet של הסמן ל-snippet.

הפונקציה המלאה נראית כך:

private fun setMapLongClick(map: GoogleMap) {
   map.setOnMapLongClickListener { latLng ->
       // A Snippet is Additional text that's displayed below the title.
       val snippet = String.format(
           Locale.getDefault(),
           "Lat: %1$.5f, Long: %2$.5f",
           latLng.latitude,
           latLng.longitude
       )
       map.addMarker(
           MarkerOptions()
               .position(latLng)
               .title(getString(R.string.dropped_pin))
               .snippet(snippet)
              
       )
   }
}
  1. מפעילים את האפליקציה.
  2. לוחצים לחיצה ארוכה על המפה כדי להציב סמן מיקום.
  3. מקישים על הסמן כדי להציג את חלון המידע.

שלב 4: הוספת מאזין לנקודות עניין

כברירת מחדל, נקודות עניין (POI) מופיעות במפה עם הסמלים המתאימים שלהן. נקודות העניין כוללות פארקים, בתי ספר, בנייני ממשלה ועוד. כשסוג המפה מוגדר לnormal, גם נקודות עניין עסקיות מופיעות במפה. נקודות עניין עסקיות מייצגות עסקים, כמו חנויות, מסעדות ומלונות.

בשלב הזה מוסיפים GoogleMap.OnPoiClickListener למפה. המאזין לקליקים הזה מציב סמן במפה באופן מיידי כשמשתמש לוחץ על נקודת עניין. בנוסף, מאזין הקליקים מציג חלון מידע שמכיל את שם הנקודה.

  1. יוצרים stub של שיטה ב-MapsActivity בשם setPoiClick() שמקבלת GoogleMap כארגומנט.
  2. בשיטה setPoiClick(), מגדירים OnPoiClickListener ב-GoogleMap שמועבר.
private fun setPoiClick(map: GoogleMap) {
   map.setOnPoiClickListener { poi ->

   }
}
  1. בsetOnPoiClickListener(), יוצרים val poiMarker לסמן .
  2. מגדירים אותו לסמן באמצעות map.addMarker() עם MarkerOptions, ומגדירים את title לשם של נקודת העניין.
private fun setPoiClick(map: GoogleMap) {
   map.setOnPoiClickListener { poi ->
       val poiMarker = map.addMarker(
           MarkerOptions()
               .position(poi.latLng)
               .title(poi.name)
       )
   }
}
  1. בפונקציה setOnPoiClickListener(), קוראים ל-showInfoWindow() ב-poiMarker כדי להציג מיד את חלון המידע.
poiMarker.showInfoWindow()

הקוד הסופי של הפונקציה setPoiClick() אמור להיראות כך.

private fun setPoiClick(map: GoogleMap) {
   map.setOnPoiClickListener { poi ->
       val poiMarker = map.addMarker(
           MarkerOptions()
               .position(poi.latLng)
               .title(poi.name)
       )
       poiMarker.showInfoWindow()
   }
}
  1. בסוף onMapReady(), מתקשרים אל setPoiClick() ומעבירים את map.
override fun onMapReady(googleMap: GoogleMap) {
   ...

   setPoiClick(map)
}
  1. מריצים את האפליקציה ומחפשים נקודת עניין, כמו פארק או בית קפה.
  2. מקישים על הנקודה כדי להציב עליה סמן ולהציג את השם שלה בחלון מידע.

יש הרבה דרכים להתאים אישית את מפות Google, כך שהמפה תיראה ותורגש ייחודית.

אפשר להתאים אישית אובייקט MapFragment באמצעות מאפייני ה-XML שזמינים, כמו שמתאימים אישית כל קטע אחר. עם זאת, בשלב הזה מתאימים אישית את המראה והתחושה של התוכן של MapFragment באמצעות שיטות באובייקט GoogleMap.

כדי ליצור סגנון מותאם אישית למפה, יוצרים קובץ JSON שמציין איך התכונות במפה מוצגות. לא צריך ליצור את קובץ ה-JSON הזה באופן ידני. ‫Google מספקת את אשף הסגנונות של הפלטפורמה של מפות Google, שיוצר את ה-JSON בשבילכם אחרי שאתם מעצבים את המפה באופן ויזואלי. במשימה הזו, תעצבו את המפה עם עיצוב רטרו, כלומר המפה תשתמש בצבעי וינטג' ותוסיפו כבישים צבעוניים.

שלב 1: יוצרים סגנון למפה

  1. בדפדפן, עוברים אל https://mapstyle.withgoogle.com/.
  2. בוחרים באפשרות יצירת סגנון.
  3. בוחרים באפשרות רטרו.

  1. לוחצים על אפשרויות נוספות.

  1. ברשימה סוג התכונה, בוחרים באפשרות כביש > מילוי.
  2. משנים את צבע הכבישים לצבע הרצוי (למשל, ורוד).

  1. לוחצים על סיום.

  1. מעתיקים את קוד ה-JSON מתיבת הדו-שיח שמופיעה, ואם רוצים, שומרים אותו בהערה בטקסט פשוט לשימוש בשלב הבא.

שלב 2: הוספת הסגנון למפה

  1. ב-Android Studio, בספרייה res , יוצרים ספריית משאבים ונותנים לה את השם raw. משתמשים במשאבי הספרייה raw כמו קוד JSON.
  2. יוצרים קובץ ב-res/raw בשם map_style.json.
  3. מדביקים את קוד ה-JSON שהועבר לסטאש בקובץ המשאבים החדש.
  4. ב-MapsActivity, יוצרים משתנה של מחלקת TAG מעל שיטת onCreate(). הפרמטר הזה משמש למטרות רישום ביומן.
private val TAG = MapsActivity::class.java.simpleName
  1. בנוסף, ב-MapsActivity, צריך ליצור פונקציה setMapStyle() שמקבלת GoogleMap.
  2. ב-setMapStyle(), מוסיפים בלוק try{}.
  3. בבלוק try{}, יוצרים val success כדי שהסגנון יצליח. (מוסיפים את בלוק ה-catch הבא).
  4. בבלוק try{}, מגדירים את סגנון ה-JSON למפה, קוראים ל-setMapStyle() באובייקט GoogleMap. מעבירים אובייקט MapStyleOptions, שמעלה את קובץ ה-JSON.
  5. הקצאת התוצאה ל-success. השיטה setMapStyle() מחזירה ערך בוליאני שמציין את סטטוס ההצלחה של ניתוח קובץ הסגנון והגדרת הסגנון.
private fun setMapStyle(map: GoogleMap) {
   try {
       // Customize the styling of the base map using a JSON object defined
       // in a raw resource file.
       val success = map.setMapStyle(
           MapStyleOptions.loadRawResourceStyle(
               this,
               R.raw.map_style
           )
       )
   }
}
  1. מוסיפים משפט if לערך success שהוא false. אם העיצוב לא מצליח, מדפיסים יומן שבו מצוין שהניתוח נכשל.
private fun setMapStyle(map: GoogleMap) {
   try {
       ...
       if (!success) {
           Log.e(TAG, "Style parsing failed.")
       }
   }
}
  1. מוסיפים בלוק catch{} כדי לטפל במצב של קובץ סגנון חסר. בבלוק catch, אם אי אפשר לטעון את הקובץ, צריך להפעיל את Resources.NotFoundException.
private fun setMapStyle(map: GoogleMap) {
   try {
       ...
   } catch (e: Resources.NotFoundException) {
       Log.e(TAG, "Can't find style. Error: ", e)
   }
}

השיטה המוגמרת צריכה להיראות כמו קטע הקוד הבא:

private fun setMapStyle(map: GoogleMap) {
   try {
       // Customize the styling of the base map using a JSON object defined
       // in a raw resource file.
       val success = map.setMapStyle(
           MapStyleOptions.loadRawResourceStyle(
               this,
               R.raw.map_style
           )
       )

       if (!success) {
           Log.e(TAG, "Style parsing failed.")
       }
   } catch (e: Resources.NotFoundException) {
       Log.e(TAG, "Can't find style. Error: ", e)
   }
}
  1. לבסוף, קוראים לשיטה setMapStyle() בשיטה onMapReady() ומעבירים את האובייקט GoogleMap.
override fun onMapReady(googleMap: GoogleMap) {
   ...
   setMapStyle(map)
}
  1. מפעילים את האפליקציה.
  2. מגדירים את המפה למצב normal והסגנון החדש אמור להיות גלוי עם עיצוב רטרו וכבישים בצבע שבחרתם.

שלב 3: עיצוב הסמן

אפשר להתאים אישית את המפה עוד יותר על ידי שינוי הסגנון של סמני המפה. בשלב הזה, משנים את הסמנים האדומים שמוגדרים כברירת מחדל למשהו מגניב יותר.

  1. בשיטה onMapLongClick(), מוסיפים את שורת הקוד הבאה ל-MarkerOptions() של ה-constructor כדי להשתמש בסמן ברירת המחדל, אבל לשנות את הצבע לכחול.
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE))

עכשיו onMapLongClickListener() נראה כך:

map.setOnMapLongClickListener { latLng ->
   // A snippet is additional text that's displayed after the title.
   val snippet = String.format(
       Locale.getDefault(),
       "Lat: %1$.5f, Long: %2$.5f",
       latLng.latitude,
       latLng.longitude
   )
   map.addMarker(
       MarkerOptions()
           .position(latLng)
           .title(getString(R.string.dropped_pin))
           .snippet(snippet)
         .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE))
   )
}
  1. מפעילים את האפליקציה. הסמנים שמופיעים אחרי לחיצה ארוכה מוצגים עכשיו בכחול. שימו לב שסמני הנקודות עדיין אדומים כי לא הוספתם סגנון לשיטה onPoiClick().

אחת הדרכים להתאים אישית את מפת Google היא לצייר עליה. הטכניקה הזו שימושית אם רוצים להדגיש סוג מסוים של מיקום, כמו מקומות פופולריים לדיג.

  • צורות: אפשר להוסיף למפה קווי פוליגון, פוליגונים ומעגלים.
  • GroundOverlay אובייקטים: שכבת-על של קרקע היא תמונה שמקובעת למפה. שלא כמו סמנים, שכבות-על של הקרקע מכוונות לפני השטח של כדור הארץ ולא למסך. סיבוב, הטיה או שינוי הזום של המפה משנים את הכיוון של התמונה. כדאי להשתמש בשכבות על של תמונות כשרוצים להציג תמונה אחת באזור מסוים במפה.

שלב: הוספת שכבת-על של תמונה

במשימה הזו מוסיפים שכבת-על של קרקע בצורת Android למיקום הבית.

  1. מורידים את התמונה הזו של Android ושומרים אותה בתיקייה res/drawable. (חשוב לוודא ששם הקובץ הוא android.png).

  1. ב-onMapReady(), אחרי הקריאה להזזת המצלמה לעמדת המוצא, יוצרים אובייקט GroundOverlayOptions.
  2. מקצים את האובייקט למשתנה שנקרא androidOverlay.
val androidOverlay = GroundOverlayOptions()
  1. משתמשים בשיטה BitmapDescriptorFactory.fromResource() כדי ליצור אובייקט BitmapDescriptor ממשאב התמונה שהורד.
  2. מעבירים את אובייקט BitmapDescriptor שנוצר לשיטה image() של אובייקט GroundOverlayOptions.
val androidOverlay = GroundOverlayOptions()
   .image(BitmapDescriptorFactory.fromResource(R.drawable.android))
  1. יוצרים float overlaySize לרוחב במטרים של שכבת העל הרצויה. בדוגמה הזו, רוחב של 100f מתאים.

מגדירים את המאפיין position לאובייקט GroundOverlayOptions על ידי קריאה למתודה position(), ומעבירים את האובייקט homeLatLng ואת overlaySize.

val overlaySize = 100f
val androidOverlay = GroundOverlayOptions()
   .image(BitmapDescriptorFactory.fromResource(R.drawable.android))
   .position(homeLatLng, overlaySize)
  1. מתקשרים אל addGroundOverlay() באובייקט GoogleMap ומעבירים את האובייקט GroundOverlayOptions.
map.addGroundOverlay(androidOverlay)
  1. מפעילים את האפליקציה.
  2. כדי לראות את תמונת Android כשכבת-על, משנים את הערך של zoomLevel ל-18f.

משתמשים לרוב במפות Google כדי לראות את המיקום הנוכחי שלהם. כדי להציג את מיקום המכשיר במפה, אפשר להשתמש בשכבת נתוני המיקום.

שכבת נתוני המיקום מוסיפה את המיקום שלי למפה. כשהמשתמש מקיש על הכפתור, המפה מתמקדת במיקום המכשיר. המיקום מוצג כנקודה כחולה אם המכשיר נייח, וכשרון כחול אם המכשיר בתנועה.

במשימה הזו מפעילים את שכבת נתוני המיקום.

שלב: שליחת בקשה להרשאות מיקום

כדי להפעיל את מעקב המיקום במפות Google, צריך להוסיף שורת קוד אחת. עם זאת, אתם צריכים לוודא שהמשתמש העניק הרשאות מיקום (באמצעות מודל ההרשאות בזמן הריצה).

בשלב הזה, שולחים בקשה להרשאות מיקום ומפעילים את מעקב המיקום.

  1. בקובץ AndroidManifest.xml, מוודאים שההרשאה FINE_LOCATION כבר קיימת. ההרשאה הזו נוספה על ידי Android Studio כשבחרתם בתבנית של מפות Google.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  1. ב-MapsActivity, יוצרים משתנה מחלקה REQUEST_LOCATION_PERMISSION.
private val REQUEST_LOCATION_PERMISSION = 1
  1. כדי לבדוק אם ההרשאות ניתנו, יוצרים שיטה ב-MapsActivity בשם isPermissionGranted(). בשיטה הזו, בודקים אם המשתמש העניק את ההרשאה.
private fun isPermissionGranted() : Boolean {
  return ContextCompat.checkSelfPermission(
       this,
      Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
  1. כדי להפעיל את מעקב המיקום באפליקציה, יוצרים שיטה ב-MapsActivity בשם enableMyLocation() שלא מקבלת ארגומנטים ולא מחזירה כלום. בתוך הקובץ, בודקים אם יש הרשאה לACCESS_FINE_LOCATION. אם ההרשאה ניתנה, מפעילים את שכבת המיקום. אם לא, צריך לבקש את ההרשאה.
private fun enableMyLocation() {
   if (isPermissionGranted()) {
       map.isMyLocationEnabled = true 
   }
   else {
       ActivityCompat.requestPermissions(
           this,
           arrayOf<String>(Manifest.permission.ACCESS_FINE_LOCATION),
           REQUEST_LOCATION_PERMISSION
       )
   }
}
  1. מתקשרים אל enableMyLocation() מהקריאה החוזרת onMapReady() כדי להפעיל את שכבת המיקום.
override fun onMapReady(googleMap: GoogleMap) {
   ...
   enableMyLocation()
}
  1. מבטלים את השיטה onRequestPermissionsResult(). אם requestCode שווה ל-REQUEST_LOCATION_PERMISSION, ההרשאה ניתנת, ואם המערך grantResults לא ריק והערך PackageManager.PERMISSION_GRANTED נמצא במשבצת הראשונה שלו, מתבצעת קריאה לפונקציה enableMyLocation().
override fun onRequestPermissionsResult(
   requestCode: Int,
   permissions: Array<String>,
   grantResults: IntArray) {
   if (requestCode == REQUEST_LOCATION_PERMISSION) {
       if (grantResults.contains(PackageManager.PERMISSION_GRANTED)) {
           enableMyLocation()
       }
   }
}
  1. מריצים את האפליקציה. אמור להופיע תיבת דו-שיח עם בקשה לגישה למיקום המכשיר. אפשר לאשר את ההרשאה.

המיקום הנוכחי של המכשיר מוצג במפה באמצעות נקודה כחולה. שימו לב שיש כפתור למיקום. אם מרחיקים את המפה מהמיקום שלכם ולוחצים על הלחצן הזה, המפה מתמרכזת בחזרה למיקום של המכשיר.

מורידים את הקוד של ה-codelab המוגמר.

$  git clone https://github.com/googlecodelabs/android-kotlin-geo-maps


אפשר גם להוריד את המאגר כקובץ ZIP, לבטל את הדחיסה שלו ולפתוח אותו ב-Android Studio.

הורדת קובץ ZIP

  • כדי להשתמש ב-Maps API, צריך מפתח API מ-Google API Console.
  • ב-Android Studio, שימוש בתבנית Google Maps Activity יוצר Activity עם SupportMapFragment יחיד בפריסת האפליקציה. בנוסף, התבנית מוסיפה את ACCESS_FINE_PERMISSION למניפסט של האפליקציה, מטמיעה את OnMapReadyCallback בפעילות ומבטלת את השיטה הנדרשת onMapReady().

כדי לשנות את סוג המפה של GoogleMap בזמן ריצה, משתמשים בשיטה GoogleMap.setMapType(). מפה של Google יכולה להיות אחד מסוגי המפות הבאים:

  • רגילה: מפת דרכים רגילה. התצוגה הזו מציגה כבישים, חלק מהמאפיינים שנבנו על ידי בני אדם ומאפיינים טבעיים חשובים כמו נהרות. אפשר לראות גם את התוויות של הכבישים והמאפיינים.
  • משולב: נתונים של צילומי לוויין עם מפות דרכים. אפשר לראות גם את התוויות של הכבישים והמאפיינים.
  • לוויין: נתוני תמונה. לא רואים את התוויות של הכבישים והמאפיינים.
  • פני השטח: נתונים טופוגרפיים. המפה כוללת צבעים, קווי מתאר ותוויות, והצללה פרספקטיבית. אפשר לראות גם חלק מהכבישים והתוויות.
  • ללא: אין משבצות של מפת בסיס.

מידע על מפות Google:

  • סמן הוא אינדיקטור למיקום גיאוגרפי ספציפי.
  • כשמקישים על הסמן, התנהגות ברירת המחדל שלו היא להציג חלון מידע עם פרטים על המיקום.
  • כברירת מחדל, נקודות עניין (POI) מופיעות במפת הבסיס יחד עם הסמלים המתאימים שלהן. נקודות העניין כוללות פארקים, בתי ספר, בנייני ממשלה ועוד.
  • בנוסף, נקודות עניין עסקיות (חנויות, מסעדות, בתי מלון ועוד) מופיעות במפה כברירת מחדל כשסוג המפה הוא normal.
  • אפשר לתעד קליקים על נקודות עניין באמצעות OnPoiClickListener.
  • אפשר לשנות את המראה של כמעט כל הרכיבים במפת Google באמצעות אשף הסגנונות. אשף הסגנונות יוצר קובץ JSON שמעבירים למפה של Google באמצעות השיטה setMapStyle().
  • אתם יכולים לשנות את צבע ברירת המחדל של הסמנים או להחליף את סמל ברירת המחדל של הסמן בתמונה מותאמת אישית.

עוד מידע חשוב:

  • כדי להצמיד תמונה למיקום גיאוגרפי, משתמשים בשכבת-על של תמונה על הקרקע.
  • משתמשים באובייקט GroundOverlayOptions כדי לציין את התמונה, את הגודל שלה במטרים ואת המיקום שלה. מעבירים את האובייקט הזה לשיטה GoogleMap.addGroundOverlay() כדי להגדיר את שכבת העל במפה.
  • אם לאפליקציה שלכם יש הרשאה ACCESS_FINE_LOCATION, אתם יכולים להפעיל את מעקב המיקום על ידי הגדרת map.isMyLocationEnabled = true.
  • הנושא הזה לא מכוסה ב-codelab הזה, אבל אפשר לספק מידע נוסף על מיקום באמצעות Google Street View, שכולל תמונה פנורמית עם אפשרות ניווט של מיקום נתון.

מסמכי תיעוד למפתחי Android:

מאמרי עזרה:

קישורים למדריכי Codelab נוספים בקורס הזה מופיעים בדף הנחיתה של מדריכי Codelab בנושא Android מתקדם ב-Kotlin.