Kotlin 04.1 기반 Android 고급: Android Google 지도

1. 시작하기 전에

Google 지도로 앱을 빌드하면 위성 이미지, 지도를 위한 강력한 UI 컨트롤, 위치 추적, 위치 마커와 같은 기능을 앱에 추가할 수 있습니다. 유명한 낚시 또는 등산 구역의 위치와 같이 자체 데이터 세트의 정보를 표시하여 표준 Google 지도에 가치를 더할 수 있습니다. 보물찾기나 증강 현실 게임과 같이 플레이어가 실제 세계를 탐험하는 게임을 만들 수도 있습니다.

이 강의에서는 맞춤설정된 지도와 사용자의 위치를 보여주는 Wander라는 Google 지도 앱을 만듭니다.

기본 요건

다음 지식:

  • Android 스튜디오를 사용하여 기본 Android 앱을 만들고 실행하는 방법
  • 문자열과 같은 리소스를 만들고 관리하는 방법
  • Android 스튜디오를 사용하여 코드를 리팩터링하고 변수 이름을 바꾸는 방법
  • 사용자가 Google 지도를 사용하는 방법
  • 런타임 권한을 설정하는 방법

학습할 내용

  • Google API 콘솔에서 API 키를 가져와 앱에 등록하는 방법
  • 앱에 Google 지도를 통합하는 방법
  • 다양한 지도 유형을 표시하는 방법
  • Google 지도의 스타일을 지정하는 방법
  • 지도에 마커를 추가하는 방법
  • 사용자가 관심 장소에 마커를 배치할 수 있도록 하는 방법 (POI)
  • 위치 추적을 사용 설정하는 방법
  • Google 지도가 삽입된 Wander 앱을 만드는 방법
  • 마커 및 스타일 지정과 같은 앱의 맞춤 기능을 만드는 방법
  • 앱에서 위치 추적을 사용 설정하는 방법

2. 앱 개요

이 Codelab에서는 맞춤 스타일을 지정하여 Google 지도를 표시하는 Wander 앱을 만듭니다. Wander 앱을 사용하면 위치에 마커를 놓거나 오버레이를 추가하거나 위치를 실시간으로 확인할 수 있습니다.

5b12eda7f467bc2f.png

3. 작업: 프로젝트 설정 및 API 키 가져오기

Android용 Maps SDK를 사용하려면 API 키가 필요합니다. API 키를 가져오려면 프로젝트를 API 및 서비스 페이지를 참조하세요. API 키는 앱을 작성자와 연결하는 디지털 인증서에 연결됩니다. 디지털 인증서 사용 및 앱 서명에 관한 자세한 내용은 앱 서명을 참고하세요.

이 Codelab에서는 디버그 인증서에 API 키를 사용합니다. 디버그 인증서는 디버그 빌드 서명에 설명된 대로, 의도적으로 안전하지 않습니다. Android용 Maps SDK를 사용하는 게시된 Android 앱에는 두 번째 API 키, 즉 출시 인증서용 키가 필요합니다. 출시 인증서 가져오기에 대한 자세한 내용은 API 키 가져오기를 참고하세요.

Android 스튜디오에는 유용한 템플릿 코드를 생성하는 Google Maps Activity 템플릿이 포함되어 있습니다. 템플릿 코드에는 API 키 가져오기를 간소화하는 링크가 포함된 google_maps_api.xml 파일이 포함되어 있습니다.

1단계: 지도 템플릿으로 Wander 프로젝트 만들기

  1. 새 Android 스튜디오 프로젝트를 만듭니다.
  2. Google Maps Activity 템플릿을 선택합니다.

d6b874bb19ea68cd.png

  1. 프로젝트 이름을 Wander로 지정합니다.
  2. 최소 API 수준을 API 19로 설정합니다. 언어가 Kotlin인지 확인합니다.
  3. 마침을 클릭합니다.
  4. 앱 빌드가 완료되면 프로젝트와 Android 스튜디오에서 자동으로 생성되는 다음과 같은 지도 관련 파일을 살펴봅니다.

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은 앱에 지도를 배치하는 가장 간단한 방법입니다. 필요한 수명 주기 요구사항을 자동으로 처리하기 위한 지도 뷰를 둘러싸는 래퍼입니다.

추가 name 속성과 함께 ViewGroup에서 <fragment> 태그를 사용하여 레이아웃 파일에 SupportMapFragment를 포함할 수 있습니다.

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

MapsActivity.java: MapsActivity.java 파일은 onCreate() 메서드의 SupportMapFragment를 인스턴스화하고 getMapAsync(): 지도 시스템과 뷰를 자동으로 초기화합니다. SupportMapFragment가 포함된 활동은 OnMapReadyCallback 인터페이스와 인터페이스의 onMapReady() 메서드를 구현해야 합니다. onMapReady() 메서드는 지도가 로드될 때 호출됩니다.

2단계: API 키 가져오기

  1. google_maps_api.xml 파일의 디버그 버전을 엽니다.
  2. 파일에서 긴 URL이 포함된 댓글을 찾습니다. URL의 매개변수에는 앱에 대한 특정 정보가 포함됩니다.
  3. URL을 복사하여 브라우저에 붙여넣습니다.
  4. 안내에 따라 API 및 서비스 페이지를 참조하세요. 제공된 URL의 매개변수로 인해 페이지는 Android용 Maps SDK를 자동으로 사용 설정합니다.
  5. API 키 만들기를 클릭합니다.
  6. 다음 페이지에서 API 키 섹션으로 이동하여 방금 만든 키를 클릭합니다.
  7. 키 제한을 클릭하고 Android용 Maps SDK를 선택하여 키 사용을 Android 앱으로 제한합니다.
  8. 생성된 API 키를 복사합니다. 'AIza"'(으)로 시작합니다.
  9. google_maps_api.xml 파일에서 YOUR_KEY_HERE라고 표시된 google_maps_key 문자열에 키를 붙여넣습니다.
  10. 앱을 실행합니다. 호주 시드니에 설정된 마커가 포함된 삽입된 지도가 활동에 표시됩니다. 시드니 마커는 템플릿의 일부이며 나중에 변경할 수 있습니다.

34dc9dd877c90996.png

3단계: mMap 이름 바꾸기

MapsActivity에는 GoogleMap 유형인 mMap라는 비공개 lateinit var가 있습니다. Kotlin 이름 지정 규칙을 따르려면 mMap의 이름을 map로 변경합니다.

  1. MapsActivity에서 mMap를 마우스 오른쪽 버튼으로 클릭하고 Refactor >를 클릭합니다. 이름 바꾸기...

e713ccb3384450c6.png

  1. 변수 이름을 map로 변경합니다.

onMapReady() 함수에서 mMap에 대한 모든 참조도 map로 변경되는 것을 볼 수 있습니다.

4. 작업: 지도 유형 추가하기

Google 지도에는 일반, 하이브리드, 위성, 지형 및 '없음' 등 여러 지도 유형이 있습니다. (지도가 전혀 없을 때)

일반 맵

위성 지도

하이브리드 지도

지형 지도

각 지도 유형은 서로 다른 종류의 정보를 제공합니다. 예를 들어, 자동차에서 내비게이션을 위해 지도를 사용하는 경우 도로 이름을 확인하는 것이 유용하므로 일반 옵션을 사용할 수 있습니다. 하이킹을 할 때 지형 지도는 정상까지 올라가기 위해 얼마나 더 올라가야 하는지 파악하는 데 도움이 될 수 있습니다.

이 작업에서는 다음 작업을 수행합니다.

  1. 사용자가 지도 유형을 변경할 수 있는 옵션 메뉴가 있는 앱 바를 추가합니다.
  2. 지도의 시작 위치를 내 집 위치로 이동합니다.
  3. 지도에서 단일 위치를 나타내고 라벨을 포함할 수 있는 마커 지원을 추가합니다.

지도 유형 메뉴 추가

이 단계에서는 사용자가 지도 유형을 변경할 수 있는 옵션 메뉴가 있는 앱 바를 추가합니다.

  1. 새 메뉴 XML 파일을 만들려면 res 디렉터리를 마우스 오른쪽 버튼으로 클릭하고 New > Android 리소스 파일.
  2. 대화상자에서 파일 이름을 map_options로 지정합니다.
  3. 리소스 유형으로 메뉴를 선택합니다.
  4. 확인을 클릭합니다.
  5. 코드 탭에서 새 파일의 코드를 다음 코드로 바꿔 지도 메뉴 옵션을 만듭니다. '없음' '없음'으로 인해 지도 유형이 생략되었습니다. 지도가 전혀 없을 수 있습니다. 이 단계에서 오류가 발생하지만 다음 단계에서 해결할 수 있습니다.
<?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. 작업: 마커 추가하기

기본적으로 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. homeLatLng라는 새 LatLng 객체를 만듭니다. homeLatLng 객체에서 방금 만든 값을 전달합니다.
val homeLatLng = LatLng(latitude, longitude)
  1. 지도에서 얼마나 확대하고 싶은지에 관한 val를 만듭니다. 확대/축소 수준 15f를 사용합니다.
val zoomLevel = 15f

확대/축소 수준은 지도에서 얼마나 확대된 정도를 제어합니다. 다음 목록을 통해 각 확대/축소 수준에서 표시되는 세부정보 수준을 알 수 있습니다.

  • 1: 전 세계
  • 5: 대륙
  • 10: 도시
  • 15: 도로
  • 20: 건물
  1. map 객체에서 moveCamera() 함수를 호출하여 카메라를 homeLatLng로 이동하고 CameraUpdateFactory.newLatLngZoom()를 사용하여 CameraUpdate 객체를 전달합니다. 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. MapsActivityGoogleMap를 인수로 사용하는 setMapLongClick()라는 메서드 스텁을 만듭니다.
  2. 지도 객체에 setOnMapLongClickListener 리스너를 연결합니다.
private fun setMapLongClick(map:GoogleMap) {
   map.setOnMapLongClickListener { }
}
  1. setOnMapLongClickListener()에서 addMarker() 메서드를 호출합니다. 전달된 LatLng로 설정된 위치를 사용하여 새 MarkerOptions 객체를 전달합니다.
private fun setMapLongClick(map: GoogleMap) {
   map.setOnMapLongClickListener { latLng ->
       map.addMarker(
           MarkerOptions()
               .position(latLng)
       )
   }
}
  1. onMapReady() 메서드 끝에서 map를 사용하여 setMapLongClick()를 호출합니다.
override fun onMapReady(googleMap: GoogleMap) {
   ...
  
   setMapLongClick(map)
}
  1. 앱을 실행합니다.
  2. 지도를 길게 터치하여 원하는 위치에 마커를 배치합니다.
  3. 마커를 탭하면 화면 중앙에 배치됩니다.

4ff8d1c1db3bca9e.png

3단계: 마커에 대한 정보 창 추가

이 단계에서는 마커를 탭할 때 마커의 좌표를 표시하는 InfoWindow를 추가합니다.

  1. setMapLongClick()setOnMapLongClickListener()에서 snippetval를 만듭니다. 스니펫은 제목 뒤에 표시되는 추가 텍스트입니다. 스니펫은 마커의 위도와 경도를 표시합니다.
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()에서 R.string.dropped_pin 문자열 리소스를 사용하여 마커의 title를 고정된 핀으로 설정합니다.
  2. 마커의 snippetsnippet로 설정합니다.

완성된 함수는 다음과 같습니다.

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를 지도에 추가합니다. 이 클릭 리스너는 사용자가 관심 장소를 클릭하는 즉시 지도에 마커를 배치합니다. 클릭 리스너는 관심 장소 이름이 포함된 정보 창도 표시합니다.

  1. MapsActivityGoogleMap를 인수로 사용하는 setPoiClick()라는 메서드 스텁을 만듭니다.
  2. setPoiClick() 메서드에서 전달된 GoogleMapOnPoiClickListener를 설정합니다.
private fun setPoiClick(map: GoogleMap) {
   map.setOnPoiClickListener { poi ->

   }
}
  1. setOnPoiClickListener()에서 마커의 val poiMarker를 만듭니다 .
  2. MarkerOptions와 함께 map.addMarker()를 사용하여 마커에 설정하고 title을 관심 장소의 이름으로 설정합니다.
private fun setPoiClick(map: GoogleMap) {
   map.setOnPoiClickListener { poi ->
       val poiMarker = map.addMarker(
           MarkerOptions()
               .position(poi.latLng)
               .title(poi.name)
       )
   }
}
  1. setOnPoiClickListener() 함수에서 poiMarkershowInfoWindow()를 호출하여 정보 창을 즉시 표시합니다.
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 지도를 맞춤설정하여 나만의 지도를 만들 수 있습니다.

다른 프래그먼트를 맞춤설정하는 것처럼 사용 가능한 XML 속성을 사용하여 MapFragment 객체를 맞춤설정할 수 있습니다. 그러나 이 단계에서는 GoogleMap 객체의 메서드를 사용하여 MapFragment 콘텐츠의 디자인과 분위기를 맞춤설정합니다.

지도에 맞춤설정된 스타일을 만들려면 지도의 지형지물이 표시되는 방식을 지정하는 JSON 파일을 생성합니다. 이 JSON 파일을 수동으로 만들 필요가 없습니다. Google에서는 지도의 스타일을 시각적으로 지정한 후 JSON을 생성해 주는 지도 플랫폼 스타일 지정 마법사를 제공합니다. 이 작업에서는 레트로 테마로 지도의 스타일을 지정합니다. 즉, 지도는 빈티지 색상을 사용하고 색상이 지정된 도로를 추가합니다.

1단계: 지도의 스타일 만들기

  1. 브라우저에서 https://mapstyle.withgoogle.com/으로 이동합니다.
  2. 스타일 만들기를 선택합니다.
  3. Retro를 선택합니다.

208b3d3aeab0d9b6.png

  1. 옵션 더보기를 클릭합니다.

4a35faaf9535ee82.png

  1. 도로를 선택합니다. 채우기.
  2. 도로의 색상을 원하는 색상 (예: 분홍색)으로 변경합니다.

92c3293749293a4c.png

  1. 마침을 클릭합니다.

f1bfe8585eb69480.png

  1. 결과 대화상자에서 JSON 코드를 복사하고 원하는 경우 다음 단계에서 사용할 수 있도록 일반 텍스트 메모에 보관합니다.

3c32168b299d6420.png

2단계: 지도에 스타일 추가

  1. Android 스튜디오의 res 디렉터리에서 리소스 디렉터리를 만들고 이름을 raw로 지정합니다. JSON 코드와 같은 raw 디렉터리 리소스를 사용합니다.
  2. res/rawmap_style.json라는 파일을 만듭니다.
  3. 저장된 JSON 코드를 새 리소스 파일에 붙여넣습니다.
  4. MapsActivity에서 onCreate() 메서드 위에 TAG 클래스 변수를 만듭니다. 로깅 목적으로 사용됩니다.
private val TAG = MapsActivity::class.java.simpleName
  1. 또한 MapsActivity에서 GoogleMap를 사용하는 setMapStyle() 함수를 만듭니다.
  2. setMapStyle()에서 try{} 블록을 추가합니다.
  3. try{} 블록에서 성공적인 스타일 지정을 위한 val success를 만듭니다. 다음 catch 블록을 추가합니다.
  4. try{} 블록에서 JSON 스타일을 지도에 설정하고 GoogleMap 객체에서 setMapStyle()를 호출합니다. JSON 파일을 로드하는 MapStyleOptions 객체를 전달합니다.
  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. false인 success의 if 문을 추가합니다. 스타일 지정에 실패하면 파싱에 실패한 로그를 출력합니다.
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. 마지막으로 GoogleMap 객체를 전달하는 onMapReady() 메서드에서 setMapStyle() 메서드를 호출합니다.
override fun onMapReady(googleMap: GoogleMap) {
   ...
   setMapStyle(map)
}
  1. 앱을 실행합니다.
  2. 지도를 normal 모드로 설정하면 새로운 스타일이 레트로 테마 설정과 선택한 색상의 도로로 표시됩니다.

b59d6cb81f02a14f.png

3단계: 마커 스타일 지정

지도 마커의 스타일을 지정하여 지도를 더욱 맞춤설정할 수 있습니다. 이 단계에서는 기본 빨간색 마커를 더 멋진 것으로 변경합니다.

  1. onMapLongClick() 메서드에서 생성자의 MarkerOptions()에 다음 코드 줄을 추가하여 기본 마커를 사용하지만 색상은 파란색으로 변경합니다.
.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 객체를 GroundOverlayOptions 객체의 image() 메서드에 전달합니다.
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. GoogleMap 객체에서 addGroundOverlay()를 호출하고 GroundOverlayOptions 객체를 전달합니다.
map.addGroundOverlay(androidOverlay)
  1. 앱을 실행합니다.
  2. Android 이미지를 오버레이로 보려면 zoomLevel 값을 18f로 변경합니다.

b1b25b0acd6a9807.png

8. 작업: 위치 추적 사용 설정

사용자는 종종 Google 지도를 사용하여 현재 위치를 확인합니다. 지도에 기기 위치를 표시하려면 위치 데이터 레이어를 사용하면 됩니다.

위치 데이터 레이어는 지도에 내 위치 아이콘을 추가합니다.

f317f84dcb3ac3a1.png

사용자가 버튼을 탭하면 기기의 위치가 지도 중앙에 배치됩니다. 기기가 움직이지 않으면 위치는 파란색 점으로, 기기가 움직이는 경우 파란색 V자형으로 표시됩니다.

이 작업에서는 위치 데이터 레이어를 사용 설정합니다.

단계: 위치 정보 액세스 권한 요청

Google 지도에서 위치 추적을 사용하려면 한 줄의 코드가 필요합니다. 그러나 런타임 권한 모델을 사용하여 사용자가 위치 정보 액세스 권한을 부여했는지 확인해야 합니다.

이 단계에서는 위치 정보 액세스 권한을 요청하고 위치 추적을 사용 설정합니다.

  1. AndroidManifest.xml 파일에 FINE_LOCATION 권한이 이미 있는지 확인합니다. Google 지도 템플릿을 선택할 때 Android 스튜디오가 이 권한을 삽입했습니다.
<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. onMapReady() 콜백에서 enableMyLocation()를 호출하여 위치 레이어를 사용 설정합니다.
override fun onMapReady(googleMap: GoogleMap) {
   ...
   enableMyLocation()
}
  1. onRequestPermissionsResult() 메서드를 재정의합니다. requestCodeREQUEST_LOCATION_PERMISSION와 같은지 확인합니다. 설정된 경우 권한이 부여되었음을 의미합니다. 권한이 부여되면 grantResults 배열의 첫 번째 슬롯에 PackageManager.PERMISSION_GRANTED가 포함되어 있는지도 확인합니다. true이면 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 스튜디오에서 열어도 됩니다.

10. 요약

축하합니다. Android Kotlin 앱에 Google 지도를 추가하고 스타일을 지정했습니다.

11. 자세히 알아보기

Android 개발자 문서:

참조 문서

12. 다음 Codelab

이 과정의 다른 Codelab 링크는 Kotlin 기반 Android 고급 Codelab 방문 페이지를 참고하세요.