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

1. לפני שמתחילים

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

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

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

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

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

מה תלמדו

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

2. סקירה כללית של האפליקציה

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

5b12eda7f467bc2f.png

3. משימה: הגדרת הפרויקט וקבלת מפתח API

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

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

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

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

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

d6b874bb19ea68cd.png

  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 – קובץ הפריסה הזה מכיל מקטע יחיד שממלא את כל המסך. המחלקה SupportMapFragment היא מחלקה משנית של המחלקה Fragment. SupportMapFragment הוא הדרך הפשוטה ביותר למקם מפה באפליקציה. מדובר ב-wrapper של תצוגה של מפה כדי לטפל אוטומטית בצרכים הנדרשים של מחזור החיים.

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

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

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

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

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

34dc9dd877c90996.png

שלב 3: שינוי השם של MMap

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

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

e713ccb3384450c6.png

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

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

4. משימה: הוספת סוגי מפות

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

מפה רגילה

מפת לוויין

מפה משולבת

מפת פני השטח

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

במשימה:

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

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

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

  1. כדי ליצור קובץ XML חדש של תפריט, לוחצים לחיצה ימנית על ספריית res ובוחרים באפשרות New (חדש) > קובץ משאב של Android.
  2. בתיבת הדו-שיח, נותנים לקובץ את השם map_options.
  3. בוחרים באפשרות תפריט כדי להציג את סוג המשאב.
  4. לוחצים על אישור.
  5. בכרטיסייה Code (קוד), מחליפים את הקוד שבקובץ החדש בקוד הבא כדי ליצור את האפשרויות של תפריט המפה. האפשרות "ללא" סוג המפה הושמט מפני ש'ללא' התוצאה תהיה היעדר מפה בכלל. שלב זה גורם לשגיאה, אבל פותר אותה בשלב הבא.
<?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. לוחצים על 428da163b831115b.png כדי לשנות את סוג המפה. שימו לב איך מראה המפה משתנה בין המצבים השונים.

6fa42970d87f5dc7.png

5. משימה: הוספת סמנים

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

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

שלב 1: מגדילים את התצוגה של הבית ומוסיפים סמן

  1. בקובץ MapsActivity.kt, מחפשים את ה-method 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: עולם
  • 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. מפעילים את האפליקציה. המפה צריכה להזיז את המפה אל הבית, להתקרב לרמה הרצויה ולמקם סמן על הבית שלכם.

fc939024778ee76.png

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

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

  1. יוצרים stub של method ב-MapsActivity בשם setMapLongClick() שלוקח את GoogleMap כארגומנט.
  2. צירוף אוזן setOnMapLongClickListener לאובייקט המפה.
private fun setMapLongClick(map:GoogleMap) {
   map.setOnMapLongClickListener { }
}
  1. ב-setOnMapLongClickListener(), מפעילים את השיטה 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. מקישים על הסמן, שמרכז אותו במסך.

4ff8d1c1db3bca9e.png

שלב 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 של הסמן כסיכה שמוקמה באמצעות משאב מחרוזת מסוג 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. מקישים על הסמן כדי להציג את חלון המידע.

63f210e6e47dfa29.png

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

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

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

  1. יוצרים stub של method ב-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. מקישים על נקודת העניין כדי להציב עליה סמן ולהציג את השם שלה בחלון מידע.

f4b0972c75d5fa5f.png

6. משימה: עיצוב המפה

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

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

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

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

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

208b3d3aeab0d9b6.png

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

4a35faaf9535ee82.png

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

92c3293749293a4c.png

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

f1bfe8585eb69480.png

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

3c32168b299d6420.png

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

  1. בספרייה res ב-Android Studio, יוצרים ספריית משאבים ונותנים לה את השם 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 כדי להצליח של העיצוב. (מוסיפים את בלוק הקליטה הבא).
  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. לסיום, קוראים ל-method setMapStyle() ב-method onMapReady() שמועברים באובייקט GoogleMap.
override fun onMapReady(googleMap: GoogleMap) {
   ...
   setMapStyle(map)
}
  1. מפעילים את האפליקציה.
  2. אם מגדירים את המפה למצב normal, העיצוב החדש יהיה גלוי עם עיצוב רטרו וכבישים בצבע שבחרתם.

b59d6cb81f02a14f.png

שלב 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. מפעילים את האפליקציה. הסמנים שמופיעים אחרי לחיצה ארוכה הופכים עכשיו לכחולים. לתשומת ליבך, הסמנים של נקודות העניין עדיין אדומים כי לא הוספת עיצוב ל-method onPoiClick().

b9916bca3c367e3.png

7. משימה: הוספת שכבת-על

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

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

שלב: הוספת שכבת-על של קרקע

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

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

61fabd56a0841b44.png

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

מגדירים את המאפיין position לאובייקט GroundOverlayOptions על ידי קריאה ל-method 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. משנים את הערך של zoomLevel ל-18f כדי לראות את תמונת Android כשכבת-על.

b1b25b0acd6a9807.png

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

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

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

f317f84dcb3ac3a1.png

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

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

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

כדי להפעיל מעקב אחר מיקום במפות 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() מהקריאה החוזרת (callback) של 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. מפעילים את האפליקציה. אמורה להופיע תיבת דו-שיח עם בקשת גישה למיקום של המכשיר. אשרו את ההרשאה.

da7e23e00ec762c1.png

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

5b12eda7f467bc2f.png

9. קוד פתרון

מורידים את הקוד עבור ה-Codelab הסופי.

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

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

10. סיכום

מעולה! הוספת מפת Google לאפליקציית Android Kotlin וסגינת אותה.

11. מידע נוסף

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

מסמכי עזר:

12. ה-Codelab הבא

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