📌 구글 플레이 서비스의 Google Maps API를 사용하면 구글 지도 데이터를 기반으로 앱에 지도를 추가할 수 있음
- 구글 지도는 Google Maps Platform 서비스 중의 하나이며, 교통정보 기반의 경로 찾기와 장소 정보, 검색 등의 기능을 제공.
- 국내에서는 구글 지도의 경로찾기 메뉴중 버스만 사용할 수 있음.
1. 구글 지도 시작하기
안드로이드 스튜디오는 구글 지도를 쉽게 사용할 수 있도록 프로젝트 생성 시 프로젝트의 종류를 선택하는 메뉴에서 Google Maps Activity를 제공
➡️ MainActivity 대신 MapsActivity가 생성됨
Google Maps API 키 설정
구글 지도를 포함한 구글 플레이 서비스에 엑세스하려면 구글 플레이 서비스의 API 키가 필요
이전에는 google_maps_api.xml 파일이 자동 생성되고, 해당 파일에 API키를 입력했으나, 범블비와 칩멍크 버전 부터는 AndroidManifest.xml에 API키를 입력하도록 변경이 됨
<!--
TODO: Before you run your application, you need a Google Maps API key.
To get one, follow the directions here:
https://developers.google.com/maps/documentation/android-sdk/get-api-key
Once you have your API key (it starts with "AIza"), define a new property in your
project's local.properties file (e.g. MAPS_API_KEY=Aiza...), and replace the
"YOUR_API_KEY" string in this file with "${MAPS_API_KEY}".
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY" />
키를 입력 후에 안드로이드 스튜디오에서 앱을 빌드하고 시작하면, 시드니에 마커가 표시된 지도를 표시
📍 지도가 안 뜨면 구글 cloud 접속 하여 API key 수정에서 패키지를 추가해주면 됨 ! ▶️ 패키지명은 무조건 소문자 !!
2. 구글 지도 코드 살펴보기
activity_maps.xml의 SupportMapFragment
프로젝트를 생성하면 activity_maps.xml 파일이 자동 생성됨. 코드를 보면 android:name에 "com.google.android.gms.maps.SupportMapFragment"가 설정되어 있음.
Google Maps API는 SupportMapFragment에 구글 지도를 표시.
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MapsActivity" />
MapsActivity.kt의 SupportMapFragment.getMapAsync
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
MapsActivity.kt 파일을 열면 onCreate() 메서드 블록 안에서는 supportFragmentManager의 findFragmentById() 메서드로 id가 map인 SupportMapFragment를 찾은 후 getMapAsync()를 호출해서 안드로이드에 구글 지도를 그려달라는 요청을 함.
MapsActivity.kt의 OnMapReadyCallback
class MapsActivity : AppCompatActivity(), OnMapReadyCallback { }
안드로이드는 구글 지도가 준비되면 OnMapReadyCallback 인터페이스의 onMapReady() 메서드를 호출하면서 파라미터로 준비된 GoogleMap을 전달해 줌.
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
// Add a marker in Sydney and move the camera
val sydney = LatLng(-34.0, 151.0)
mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
}
메서드 안에서 미리 선언된 mMap 프로퍼티에 GoogleMap을 저장해두면 액티비티 전체에서 맵을 사용할 수 있음.
구글에서 기본으로 제공하는 시드니의 좌표 코드가 있음.
* API Level 12 이하 버전에서의 호환성이 필요없다면 SupportMapFragment 대신 MapFragment를 사용할 수 있음.
* 스마트폰에 구글 플레이 서비스가 설치되어 있지 않으면 사용자가 구글 플레이 서비스를 설치할 때까지 onMapReady() 메서드가 호출되지 않음.
3. 카메라와 지도 뷰
구글 지도에서는 카메라를 통해 현재 화면의 지도 뷰를 변경할 수 있음.
✓ 지도 뷰는 평면에서 아래를 내려다 보면서 모델링 되며 카메라의 포지션은 위도/경도, 방위, 기울기 및 확대/축소 속성으로 지정
✓ 카메라의 위치는 CameraPosition 클래스에 각종 옵션을 사용해서 조절할 수 있음
CameraPosition.Builder().옵션1.옵션2.build()
Target
✓ 카메라의 목표 지점 Target은 지도 중심의 위치이며 위도 및 경도 좌표로 지정
CameraPosition.Builder().target(LatLng(-34.0, 151.0))
Zoom
✓ 카메라의 줌 Zoom (확대/축소) 레벨에 따라 지도의 배율이 결정. 줌 레벨이 높을 수록 더 자세한 지도를 볼 수 있는 반면, 줌 레벨이 작을수록 더 넓은 지도를 볼 수 있음.
CameraPosition.Builder().zoom(15.5f)
📍 줌 레벨이 0인 지도의 배율은 전 세계의 너비가 약 256dp가 되며 레벨의 범위는 다음과 같음
레벨 | 설명 |
1.0 | 세계 |
5.0 | 대륙 |
10.0 | 도시 |
15.0 | 거리 |
20.0 | 건물 |
Bearing
✓ 카메라의 베어링 Bearing은 지도의 수직선이 북쪽을 기준으로 시계 방향 단위로 측정되는 방향.
CameraPosition.Builder().bearing(300f)
Tilt
✓ 카메라의 기울기 Tilt는 지도의 중앙 위치와 지구 표면 사이의 원호에서 카메라 위치를 지정
✓ 기울기로 시야각을 변경하면 멀리 떨어진 지형이 더 작게 나타나고 주변 지형이 더 켜져 맵이 원근으로 나타남
CameraPosition.Builder().tilt(50f)
4. 소스 코드에서 카메라 이동하기
옵션을 이용해서 CameraPosition 객체를 생성하고 moveCamera() 메서드로 카메라의 위치를 이동시켜 지도을 변경할 수 있음.
➡️ MapsActivity.kt 파일의 onMapReady() 메서드 안에 작성.
CameraPosition.Builder 객체로 카메라 포지션을 설정
📍 build() 메서드를 호출해서 CameraPosition 객체를 생성
val cameraPosition = CameraPosition.Builder()
.target(LATLNG)
.zoom(15.0f)
.build()
CameraUpdateFactory.newCameraPosition() 메서드에 CameraPosition 객체를 전달하면 카메라 포지션에 지도에서 사용할 수 있는 카메라 정보가 생성
val cameraUpdate =
CameraUpdateFactory.newCameraPosition(cameraPosition)
변경된 카메라 정보를 GoogleMap의 () 메서드에 전달하면 카메라 포지션을기준으로 지도의 위치, 배율, 기울기 등이 변경되어 표시
mMap.moveCamera(cameraUpdate)
5. 마커
마커 Marker는 지도에 위치를 표시. 아이콘의 색상, 이미지, 위치를 변경할 수 있으며 대화식으로 설계되었기 때문에 마커를 클릭하면 정보창을 띄우거나 클릭리스너처럼 클릭에 대한 코드 처리를 할 수 있음.
마커 표시하기
1. mMap = googleMap 코드 아래에 특정 장소의 위도와 경도 좌표값으로 LatLng 객체를 생성
val LATLNG = LatLng(35.8715, 128.6017)
2. 마커를 추가하려면 마커의 옵션을 정의한 MarkerOptions 객체가 필요.
MarkerOptions 객체를 생성하고 머커의 좌표와 제목을 설정.
val markerOptions = MarkerOptions()
.position(LATLNG)
.title("Marker")
3. GoogleMap 객체의 addMarker() 메서드에 MarkerOptions를 전달하면 구글 지도에 마커가 추가.
mMap.addMarker(markerOptions)
4. 카메라를 마커의 좌표로 이동하고 줌을 거리 레벨로 확대
val cameraPosition = CameraPosition.Builder()
.target(LATLNG)
.zoom(15.0f)
.build()
val cameraUpdate =
CameraUpdateFactory.newCameraPosition(cameraPosition)
mMap.moveCamera(cameraUpdate)
5. MarkerOptions 객체의 title(), snippet() 메서드로 정보 창을 수정할 수 있으며 마커를 클릭하면 정보창이 표시
val markerOptions = MarkerOptions()
.position(LATLNG)
.title("Daegu City Hall")
.snippet("35.8715, 128.6017")
마커 아이콘 변경하기
마커 아이콘은 기본적으로 제공되는 아이콘뿐만 아니라 비트맵 이미지로 변경할수 있음.
PNG 이미지 파일을 프로젝트에 추가하고 비트맵으로 변환해서 아이콘으로 변경.
1. drawable 디렉토리에 마커 아이콘으로 적용할 PNG 이미지 파일을 추가
2. onMapReady() 안에 아래의 코드를 추가
val bitmapDrawable: BitmapDrawable
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
bitmapDrawable = getDrawable(R.drawable.marker) as BitmapDrawable
} else {
bitmapDrawable = resources.getDrawable(R.drawable.marker) as BitmapDrawable
}
3. BitmapDescriptorFactory.fromBitmap() 메서드에 BitmapDrawable의 비트맵 객체를 전달하는 마커 아이콘을 위한
BitmapDescriptor 객체를 생성하고 import
var discriptor =
BitmapDescriptorFactory.fromBitmap(bitmapDrawable.bitmap)
4. MarkerOptions 객체의 icon() 메서드를 호출해서 BitmapDescriptor 객체의 아이콘을 마커에 적용하도록 수정
val markerOptions = MarkerOptions()
.position(LATLNG)
.icon(discriptor)
mMap.addMarker(markerOptions)
* 아이콘의 크기가 클 경우 Bitmap.createScaledBitmap() 메서드를 호출해서 크기를 줄인 비트맵 객체를 반환받아야 함
6. 현재 위치 검색하기
스마트폰처럼 모바일 환경에서는 사용자가 위치를 이동하고 그 위치를 기반으로하는 서비스를 제공할 수 있음.
✓ 앱에서 스마트폰의 현재 위치를 검색하려면 위치 권한이 필요
✓ 안드로이드 플랫폼은 현재 위치를 검색하는 FusedLocationProviderClinet API를 제공
➡️ FusedLocationProviderClinet API는 GPS Global Positioning System 신호 및 와이파이와 통신사 네트워크 위치를 결합해서 최소한의 배터리 사용량으로 빠르고 정확하게 위치를 검색
Google Play Service 의존성 추가하기
FusedLocationProviderClinet API를 사용하기 위해서 build.gradle 파일에 구글 플레이 서비스의 Loacation 라이브러리 의존성을 추가. Location 라이브러리는 Maps 라이브러리와 버전이 같아야 함. Location 라이브러리와 같아지도록 Maps의 라이브러리 버전을 맞춰줌.
implementation 'com.google.android.gms:play-services-location:18.2.0'
implementation 'com.google.android.gms:play-services-maps:18.2.0'
권한을 명세하고 요청/ 처리하기
스마트폰의 위치 기능에 접근하기 위해 AndroidManifest.xml 파일에 위치 권한을 선언. 위치 권한은 두 가지가 있으며 기능은 다음과 같음.
<!-- 도시 블록 내에서 정확한 위치 (네트워크 위치) -->
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- 정확한 위치 확보 (네트워크 위치 + GPS 위치) -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
MapsActivity에 OnMapReadyCallback 인터페이스를 상속 받음.
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {}
권한 처리를 위해서 onCreate() 메서드 위에 런처를 선언. 여기서는 한 번에 2개의 권한에 대한 승인을 요청하기 때문에 Contract로 RequestMultiplePermissions()를 사용해야 함. 따라서 런처의 제네릭은 문자열 배열인 <Array<String>>이 됨
lateinit var locationPermission: ActivityResultLauncher<Array<String>>
onCreate() 메서드 아래에 빈 startProcess() 메서드를 미리 만들어 둠.
fun startProcess() {
// 승인 후 실행할 코드를 입력
}
onCreate() 메서드 안에 런처를 생성하는 코드를 작성하고 앞에서 선언해 둔 변수에 저장.
locationPermission =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
results -> if (results.all{ it.value }) {
startProcess()
} else {
Toast.makeText(this, "권한 승인이 필요합니다.",
Toast.LENGTH_LONG).show()
}
}
바로 아래줄에 런처를 실행해서 권한 승인을 요청. 2개의 권한을 파라미터에 전달해야되기 때문에 arrayOf()를 사용해서 권한 2개를 같이 launch()의 파라미터로 입력.
locationPermission.launch(arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
))
위치 권한이 승인되면 startProcess() 메서드에서 구글 지도를 준비하는 작업을 진행하도록 코드를 수정. onCreate()에 작성되어 있는 val mapFragment 로 시작하는 세 줄을 잘라내기 한 후 startProcess() 메서드 안에 붙여넣기 하면 됨.
fun startProcess() {
// 승인 후 실행할 코드를 입력
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
이제 권한이 모두 승인되고 맵이 준비되면 onMapReady() 메서드가 정상적으로 호출.
현재 위치 검색하기
1. onCreate() 위에 onMapReady() 위치를 처리하기 위한 변수 2개를 선언
✓ fusedLocationClient는 위치값을 사용하기 위해서 필요하고, locationCallback은 위칫값 요청에 대한 갱신 정보를 받는 데 필요
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
2. onMapReady() 안의 시드니 좌표 코드를 삭제한 다음, 위치 검색 클라이언트를 생성하는 코드를 추가하고 updateLocation() 메서드를 호출
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
fusedLocationClient =
LocationServices.getFusedLocationProviderClient(this)
updateLocation()
}
3. updateLocation() 메서드를 작성.
- 위치 정보를 요청할 정확도와 주기를 설정할 locationRequest를 먼저 생성하고, 해당 주기마다 반환받을 locationCallback을 생성
- onMapReady에서 생성한 위치 검색 클라이언트의 requestLocationUpdates()에 앞에서 생성한 2개와 루퍼 정보를 넘겨줌
- 이제 1초(1,000밀리초)에 한 번씩 변화된 위치정보가 LocationCallback의 onLocationResult()로 전달이 됨.
- onLocationResult()는 반환받은 정보에서 위치 정보를 setLastLoation()으로 전달.
- fusedLocationClient.requestLocationUpdates 코드는 권한 처리가 필요한데 현재 코드에서는 확인할 수 없음.
- 따라서 메서드 상단에 해당 코드를 체크하지 않아도 된다는 의미로 @SuppressLint("MissingPermission") 애너테이션을 달아줌.
@SuppressLint("MissingPermission")
fun updateLocation() {
val locationRequest = LocationRequest.create()
locationRequest.run {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 1000
}
locationCallback = object: LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult?.let {
for ((i, location) in it.locations.withIndex()) {
Log.d("Location", "$i ${location.latitude}, ${location.longitude}")
setLastLoation(location)
}
}
}
}
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
4. updateLocation() 메서드 아래에 위치 정보를 받아서 마커를 그리고 화면을 이동하는 setLastLoation()을 작성
fun setLastLoation(lastLocatin: Location) { }
5. 전달받은 위치 정보로 좌표를 생성하고 해당 좌표로 마커를 생성
fun setLastLoation(lastLocatin: Location) {
val LATLNG = LatLng(lastLocatin.latitude, lastLocatin.longitude)
val markerOptions =
MarkerOptions().position(LATLNG).title("Here!")
}
6. 카메라 위치를 현재 위치로 세팅하고 마커에 함께 지도에 반영.
✓ 마커를 지도에 반영하기 전에 mMap.clear()를 호출해서 이전에 그려진 마커가있으면 지움
fun setLastLoation(lastLocatin: Location) {
val LATLNG = LatLng(lastLocatin.latitude, lastLocatin.longitude)
val markerOptions =
MarkerOptions().position(LATLNG).title("Here!")
val cameraPosition =
CameraPosition.Builder().target(LATLNG).zoom(15.0f).build()
mMap.clear()
mMap.addMarker(markerOptions)
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)
}
[ 내용 참고 : IT 학원 강의 ]
'Android Studio' 카테고리의 다른 글
[Android Studio] SQLite 예제 (2) 화면을 만들고 소스 코드 연결 (1) | 2024.05.23 |
---|---|
[Android Studio] SQLite 예제 (1) MyDBHelper를 외부 클래스로 작성 (0) | 2024.05.21 |
[Android Studio] SQLite (1) | 2024.05.16 |
[Android Studio] 액티비티와 인텐트의 응용 (0) | 2024.05.12 |
[Android Studio] 인텐트 활용한 명화 투표 (0) | 2024.05.11 |