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 של מפות ל-Android דורש מפתח API. כדי לקבל את מפתח ה-API, צריך לרשום את הפרויקט בדף API & Services. מפתח ה-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 – קובץ הפריסה הזה מכיל רכיב 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 שצוינה, הדף יודע להפעיל באופן אוטומטי את 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. מריצים את האפליקציה. אמורה להופיע פעילות עם מפה מוטמעת וסמן שמוגדר בסידני, אוסטרליה. (הסמן של סידני הוא חלק מהתבנית ואפשר לשנות אותו בהמשך).

34dc9dd877c90996.png

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

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

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

e713ccb3384450c6.png

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

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

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

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

מפה רגילה

מפת לוויין

מפה היברידית

מפת פני השטח

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

במשימה הזו:

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

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

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

  1. כדי ליצור קובץ XML חדש של תפריט, לוחצים לחיצה ימנית על ספריית res ובוחרים באפשרות New (חדש) > Android Resource File (קובץ משאבים של 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() ויוצרים אוביקט תצוגה (inflate) של התפריט מקובץ המשאבים 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. משימה: הוספת סמנים

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

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

63f210e6e47dfa29.png

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

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

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

  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, באמצעות שיטות באובייקט 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. ב-Android Studio, בספרייה res, יוצרים ספריית משאבים ונותנים לה את השם raw. משתמשים במשאבי ספרייה כמו קוד JSON.raw
  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 וסגנון העיצוב החדש אמור להיות גלוי עם עיצוב רטרו וכבישים בצבע שבחרתם.

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

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() מהקריאה החוזרת 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 הבא

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