오늘은 카카오 지도 API를 사용해서 현재 위치를 지도 위에 표시해 보겠습니다.
카카오 지도 API를 사용하기 위한 설정 방법은 아래 글을 참고해 주세요.
2024.04.13 - [안드로이드 자바] - [JAVA][Android] 카카오 지도 API Android v2 사용하기
시연 영상 먼저 보여드리겠습니다.
우선은 기기의 현재 위치 정보를 받아와야 합니다.
안드로이드에서는 위치 정보를 가져오기 위해서 LocationManager나 FusedLocationProviderClient를 사용할 수 있습니다.
이 글에서는 FusedLocationProviderClient를 사용해 보겠습니다.
FusedLocationProviderClient(통합위치정보제공자) API는 Google Play 서비스의 Location API 중 하나로, 해당 API를 사용하면 간편하게 기기의 위치정보를 얻을 수 있습니다.
1. 의존성 추가
우선 build.gradle 에 Google Play 서비스 location에 대한 의존성을 추가합니다.
dependencies {
implementation 'com.google.android.gms:play-services-location:21.1.0'
}
2. 매니페스트 설정
앱 매니페스트에 위치정보 사용 권한을 선언합니다.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
3. 위치정보 액세스 권한 요청하기
다음으로 액티비티에서 사용자에게 위치 권한을 승인할 것을 요청합니다. 만약 사용자가 권한을 거부할 경우 FusedLocationProviderClient를 사용할 수 없습니다.
따라서 이 예제에서는 우선 해당 앱의 위치권한 승인 여부를 확인한 뒤, 만약 권한이 허용되지 않았다면 alert 다이얼로그를 띄워서 사용자가 위치권한을 허용하거나 앱을 종료하기를 선택할 수 있도록 하였습니다.
//onRequestPermissionsResult에서 권한 요청 결과를 받기 위한 request code입니다.
private final int PERMISSION_REQUEST_CODE = 1001;
//요청할 위치 권한 목록입니다.
private String[] locationPermissions = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION};
// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
// 이미 앱에 위치 권한이 있는지 확인합니다.
if (ContextCompat.checkSelfPermission(this, locationPermissions[0]) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, locationPermissions[1]) == PackageManager.PERMISSION_GRANTED) {
// 위치 권한이 이미 허용되어 있는 경우입니다. 여기서 원하는 작업을 진행하면 됩니다.
getCurLocation(); // 현재 위치 가져오는 메서드 - 4번 항목 참고
} else {
// 위치 권한이 없는 경우, 권한 요청 다이얼로그를 띄웁니다.
ActivityCompat.requestPermissions(this, locationPermissions, LOCATION_PERMISSION_REQUEST_CODE);
}
}
// 사용자가 권한 요청 다이얼로그에 응답하면 이 메소드가 실행됩니다.
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 위치 권한 요청에 대한 응답일 경우
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 사용자가 위치 권한을 허가했을 경우입니다. 여기서 원하는 작업을 진행하면 됩니다.
getCurLocation();// 현재 위치 가져오는 메서드 - 4번 항목 참고
} else {
// 위치 권한이 거부되었을 경우, 다이얼로그를 띄워서 사용자에게 앱을 종료할지, 권한 설정 화면으로 이동할지 선택하게 합니다.
showPermissionDeniedDialog();
}
}
}
private void showPermissionDeniedDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("위치 권한 거부시 앱을 사용할 수 없습니다.")
.setPositiveButton("권한 설정하러 가기", new DialogInterface.OnClickListener() {
// 권한 설정하러 가기 버튼 클릭시 해당 앱의 상세 설정 화면으로 이동합니다.
@Override
public void onClick(DialogInterface dialogInterface, int i) {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
startActivity(intent);
} finally {
finish();
}
}
})
.setNegativeButton("앱 종료하기", new DialogInterface.OnClickListener() {
// 앱 종료하기 버튼 클릭시 앱을 종료합니다.
@Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();
}
})
.setCancelable(false)
.show();
}
4. FusedLocationProviderClient로 현재 위치 가져와 카카오 지도에 시작하기
이제 FusedLocationProviderClient의 인스턴스를 만든 후, getCurrentLocation() 메서드를 사용하여 현재 기기 위치를 가져옵니다.
private FusedLocationProviderClient fusedLocationClient;
private LatLng startPosition = null;
private ProgressBar progressBar;
private MapView mapView;
private Label centerLabel;
// 카카오지도가 정상적으로 시작되었을때 실행되는 콜백 메서드 입니다.
private KakaoMapReadyCallback readyCallback = new KakaoMapReadyCallback() {
@Override
public void onMapReady(@NonNull KakaoMap kakaoMap) {
progressBar.setVisibility(View.GONE);
// 현재 위치를 나타낼 label를 그리기 위해 kakaomap 인스턴스에서 LabelLayer를 가져옵니다.
LabelLayer layer = kakaoMap.getLabelManager().getLayer();
// LabelLayer에 라벨을 추가합니다. 카카오 지도 API 공식 문서에 지도에서 사용하는 이미지는 drawable-nodpi/ 에 넣는 것을 권장합니다.
centerLabel = layer.addLabel(LabelOptions.from("centerLabel", startPosition)
.setStyles(LabelStyle.from(R.drawable.red_dot_marker).setAnchorPoint(0.5f, 0.5f))
.setRank(1));
//라벨의 위치가 변하더라도 항상 화면 중앙에 위치할 수 있도록 trackingManager를 통해 tracking을 시작합니다.
TrackingManager trackingManager = kakaoMap.getTrackingManager();
trackingManager.startTracking(centerLabel);
// 위치 업데이트 메서드 입니다. 5번 항목 참조
startLocationUpdates();
}
// 카카오 지도의 초기 위치를 반환하는 메서드입니다.
@NonNull
@Override
public LatLng getPosition() {
return startPosition;
}
// 카카오 지도의 초기 줌레벨을 설정하는 메서드 입니다.
@NonNull
@Override
public int getZoomLevel() {
return 17;
}
};
//...
@Override
protected void onCreate(Bundle savedInstanceState) {
//..
//FusedLocationProviderClient 인스턴스를 가져옵니다.
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
}
//...
// 해당 메서드를 위치 권한 획득한 이후에 실행되도록 합니다.
@SuppressLint("MissingPermission")
private void getCurLocation() {
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY,null).addOnSuccessListener(this, location -> {
if (location != null) {
// 구해진 현재 위치를 카카오 지도의 시작 위치를 설정할 때 사용하기 위해 LatLng 객체로 변환합니다.
startPosition = LatLng.from(location.getLatitude(), location.getLongitude());
// 카카오 지도를 시작합니다.
mapView.start(readyCallback);
}
});
}
5. 위치 업데이트 요청하여 기기 위치가 지도에 계속 반영되도록 하기
// 위치 업데이트가 요청된 적 있는지 여부를 저장합니다.
private boolean requestingLocationUpdates = false;
private LocationRequest locationRequest;
private LocationCallback locationCallback;
// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
//...
// 2초마다 최고 정확도로 업데이트를 요청하는 LocationRequest 객체를 생성합니다.
locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000L).build();
// 위치가 업데이트 될때마다 실행될 LocationCallback 메서드를 생성합니다.
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
for (Location location : locationResult.getLocations()) {
// 업데이트된 위치로 라벨을 이동시킵니다.
centerLabel.moveTo(LatLng.from(location.getLatitude(), location.getLongitude()));
}
}
};
}
// 위치 업데이트 요청을 시작하는 메서드입니다.
@SuppressLint("MissingPermission")
private void startLocationUpdates() {
requestingLocationUpdates = true;
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
}
// 액티비티가 onPause 되었다가 onResume될때, requestingLocationUpdates의 값이 true면 위치 업데이트가 다시 시작되도록 합니다.
@Override
protected void onResume() {
super.onResume();
if (requestingLocationUpdates) {
startLocationUpdates();
}
}
// 액티비티가 onPause 상태가 될때, 위치업데이트가 중단시켜서 불필요한 배터리 및 리소스 낭비를 막습니다.
@Override
protected void onPause() {
super.onPause();
fusedLocationClient.removeLocationUpdates(locationCallback);
}
6. 전체 코드
1. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.kakao.vectormap.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
</com.kakao.vectormap.MapView>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2. MainActivity
package com.example.kakaomaptest;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.view.View;
import android.widget.ProgressBar;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.Priority;
import com.kakao.vectormap.KakaoMap;
import com.kakao.vectormap.KakaoMapReadyCallback;
import com.kakao.vectormap.LatLng;
import com.kakao.vectormap.MapView;
import com.kakao.vectormap.label.Label;
import com.kakao.vectormap.label.LabelLayer;
import com.kakao.vectormap.label.LabelOptions;
import com.kakao.vectormap.label.LabelStyle;
import com.kakao.vectormap.label.TrackingManager;
public class MainActivity extends AppCompatActivity {
private final int LOCATION_PERMISSION_REQUEST_CODE = 1001;
private final String[] locationPermissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
private FusedLocationProviderClient fusedLocationClient;
private LatLng startPosition = null;
private ProgressBar progressBar;
private MapView mapView;
private Label centerLabel;
private boolean requestingLocationUpdates = false;
private LocationRequest locationRequest;
private LocationCallback locationCallback;
private KakaoMapReadyCallback readyCallback = new KakaoMapReadyCallback() {
@Override
public void onMapReady(@NonNull KakaoMap kakaoMap) {
progressBar.setVisibility(View.GONE);
LabelLayer layer = kakaoMap.getLabelManager().getLayer();
centerLabel = layer.addLabel(LabelOptions.from("centerLabel", startPosition)
.setStyles(LabelStyle.from(R.drawable.red_dot_marker).setAnchorPoint(0.5f, 0.5f))
.setRank(1));
TrackingManager trackingManager = kakaoMap.getTrackingManager();
trackingManager.startTracking(centerLabel);
startLocationUpdates();
}
@NonNull
@Override
public LatLng getPosition() {
return startPosition;
}
@NonNull
@Override
public int getZoomLevel() {
return 17;
}
};
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mapView = findViewById(R.id.map_view);
progressBar = findViewById(R.id.progressBar);
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000L).build();
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
for (Location location : locationResult.getLocations()) {
centerLabel.moveTo(LatLng.from(location.getLatitude(), location.getLongitude()));
}
}
};
if (ContextCompat.checkSelfPermission(this, locationPermissions[0]) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, locationPermissions[1]) == PackageManager.PERMISSION_GRANTED) {
getStartLocation();
} else {
ActivityCompat.requestPermissions(this, locationPermissions, LOCATION_PERMISSION_REQUEST_CODE);
}
}
@Override
protected void onResume() {
super.onResume();
if (requestingLocationUpdates) {
startLocationUpdates();
}
}
@Override
protected void onPause() {
super.onPause();
fusedLocationClient.removeLocationUpdates(locationCallback);
}
@SuppressLint("MissingPermission")
private void getStartLocation() {
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY,null)
.addOnSuccessListener(this, location -> {
if (location != null) {
startPosition = LatLng.from(location.getLatitude(), location.getLongitude());
mapView.start(readyCallback);
}
});
}
@SuppressLint("MissingPermission")
private void startLocationUpdates() {
requestingLocationUpdates = true;
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getStartLocation();
} else {
showPermissionDeniedDialog();
}
}
}
private void showPermissionDeniedDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("위치 권한 거부시 앱을 사용할 수 없습니다.")
.setPositiveButton("권한 설정하러 가기", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Intent intent = new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
startActivity(intent);
} finally {
finish();
}
}
})
.setNegativeButton("앱 종료하기", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
finish();
}
})
.setCancelable(false)
.show();
}
}
'안드로이드 자바' 카테고리의 다른 글
[JAVA][Android] ChatGPT API로 챗봇 만들기 - (1) ChatGPT API 사용하기 (0) | 2024.05.03 |
---|---|
[JAVA][Android] SharedPreferences에 객체 저장하기 (0) | 2024.04.27 |
[JAVA][Android] 카카오 지도 API Android v2 사용하기 (0) | 2024.04.13 |
[Android][Java] 플래시 라이트 (2) | 2024.02.28 |
[Android][Java] NumberPicker 넘버피커 (0) | 2024.02.19 |