최근 사이드 프로젝트로 차량 관리 어플리케이션을 만들고 있다.
해당 어플리케이션 기능 중 경로찾기 기능이 필요하여 카카오맵 SDk를 이용하여 기능을 구현하였다.
이번 길찾기 기능은 카카오맵 SDK와 컴포즈로 구현하였다.
1부에서는 기본 카카오 SDK 구성하기를 중심으로 작성해보려고 한다.
Jetpack Compose에서 XML 뷰(MapView) 통합 및 라이프사이클 관리 방법
카카오맵 SDK는 XML로 제공되고 있기 때문에 Compose에서 XML 뷰를 통합하고, 라이프사이클을 관리하는 방법으로 적용하여야 한다.
MapScreen Composable 함수
@Composable
fun MapScreen() {
val mapView = rememberMapViewWithLifecycle()
AndroidView({ mapView }) { view ->
// 추가적인 뷰 설정이 필요하다면 이곳에 작성합니다.
}
}
MapScreen 함수는 Compose에서 MapView를 표시하기 위한 Composable 함수이다.
여기서 핵심은 AndroidView를 사용하여 기존의 XML 뷰를 Compose에 통합하는 것이다.
• rememberMapViewWithLifecycle(): 이 함수는 MapView를 생성하고, Compose의 라이프사이클과 연결하여 관리.
• AndroidView: Compose에서 기존의 Android View를 표시하기 위해 사용.
rememberMapViewWithLifecycle 함수
@Composable
fun rememberMapViewWithLifecycle(): View {
val context = LocalContext.current
val mapView = remember { MapView(context) }
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycle) {
val observer = object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
mapView.start(
object : MapLifeCycleCallback() {
override fun onMapDestroy() {
// 지도 API가 정상적으로 종료될 때 호출됩니다.
}
override fun onMapError(error: Exception) {
// 인증 실패 및 지도 사용 중 에러가 발생할 때 호출됩니다.
}
},
object : KakaoMapReadyCallback() {
override fun getPosition(): LatLng {
return userLocation
}
override fun onMapReady(kakaoMap: KakaoMap) {
// 지도 초기화 및 설정 작업
}
}
)
}
override fun onResume(owner: LifecycleOwner) {
mapView.resume()
}
override fun onPause(owner: LifecycleOwner) {
mapView.pause()
}
// 다른 라이프사이클 메서드들도 필요에 따라 오버라이드합니다.
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
return mapView
}
이 함수는 MapView를 생성하고, Compose의 라이프사이클에 맞게 관리하기 위한 로직을 담고 있다.
주요 구성 요소
• remember: Compose에서 상태를 유지하기 위해 사용됩니다. 여기서는 MapView 인스턴스를 기억.
• DisposableEffect: 사이드 이펙트를 관리하고, Composable이 처분될 때 정리 작업을 수행.
• DefaultLifecycleObserver: 라이프사이클 이벤트를 감지하여 MapView의 상태를 관리.
라이프사이클 관리
DefaultLifecycleObserver를 구현하여 MapView의 라이프사이클 메서드를 호출하여 처리한다.
• onCreate: mapView.start()를 호출하여 지도를 초기화.
• onResume: mapView.resume()을 호출하여 지도를 다시 표시.
• onPause: mapView.pause()를 호출하여 지도의 업데이트를 중지.
• onDestroy: 필요한 경우 mapView를 정리합니다.
지도 초기화 및 콜백 처리
지도 초기화 시 MapLifeCycleCallback과 KakaoMapReadyCallback을 사용하여 지도 상태 및 준비 완료 이벤트를 처리한다.
MapLifeCycleCallback
object : MapLifeCycleCallback() {
override fun onMapDestroy() {
// 지도 API가 정상적으로 종료될 때 호출됩니다.
}
override fun onMapError(error: Exception) {
// 인증 실패 및 지도 사용 중 에러가 발생할 때 호출됩니다.
}
}
• onMapDestroy: 지도 리소스가 정상적으로 해제될 때 호출된다.
• onMapError: 지도 로드 중 에러가 발생하면 호출된다.
KakaoMapReadyCallback
object : KakaoMapReadyCallback() {
override fun getPosition(): LatLng {
return userLocation
}
override fun onMapReady(kakaoMap: KakaoMap) {
// 지도 준비가 완료되면 호출됩니다.
// 여기서 지도 설정 및 마커 추가 등의 작업을 수행합니다.
}
}
• getPosition: 지도의 초기 위치를 설정합니다.
• onMapReady: 지도가 준비되면 호출되며, 지도에 대한 설정을 이곳에서 진행합니다.
메인 화면에 지도 노출이 성공했으면 지도 기본기능이라 할 수 있는 현재 위치표시와 카메라 이동 기능을 추가해보겠다
지도에 요소 추가 및 애니메이션 처리
onMapReady 메서드 내에서 지도에 라벨, 폴리곤 등을 추가하고 애니메이션을 설정한다.
// 중심 라벨 생성
centerLabel = kakaoMap.labelManager?.layer?.addLabel(
LabelOptions.from("dotLabel", userLocation)
.setStyles(
LabelStyle.from(R.drawable.c).setAnchorPoint(0.5f, 0.5f)
)
.setRank(1)
)
// 애니메이션 폴리곤 생성
animationPolygon = kakaoMap.shapeManager?.layer?.addPolygon(
PolygonOptions.from("circlePolygon")
.setDotPoints(DotPoints.fromCircle(userLocation, 1.0f))
.setStylesSet(
PolygonStylesSet.from(
PolygonStyles.from(Color(0xFFff722b).toArgb())
)
)
)
• 라벨 추가: 사용자 위치에 중심 라벨을 추가.
• 폴리곤 추가: 원형 폴리곤을 추가하여 지도에 표시.
위 코드에서 사용한 중심라벨 요소 (R.drawable.c) 나 색상은 사용할 요소를 넣어주면 된다. 다만 나의 경우에는 백터요소 사용시 지도에 표시되지 않는 이슈가 있어 png 파일을 이용했다.
애니메이션 설정
val circleWaves: CircleWaves = CircleWaves.from(
"circleWaveAnim",
CircleWave.from(1F, 0F, 0F, 100F)
)
.setHideShapeAtStop(false)
.setInterpolation(Interpolation.CubicInOut)
.setDuration(1500)
.setRepeatCount(500)
val shapeAnimator: ShapeAnimator? = kakaoMap.shapeManager?.addAnimator(circleWaves)
shapeAnimator?.addPolygons(animationPolygon)
shapeAnimator?.start()
• CircleWaves: 원형 웨이브 애니메이션을 생성합니다.
• ShapeAnimator: 생성된 애니메이션을 폴리곤에 적용하고 시작합니다.
이후 생성된 라벨과 trackingManager 의 사용자 위치를 갱신해서 넣어주면 해당 위치로 이동된다.
centerLabel?.moveTo(userLocation)
trackingManager?.startTracking(centerLabel)
다만 이렇게 할 경우 위치가 갱신될 때 마다 moveTo 처리를 하면 카메라가 이동하여 사용자 인터랙션 처리가 필요하다.
kakaoMap.setOnCameraMoveStartListener { kakaoMap, gestureType ->
viewModel.setCameraTracking(false)
}
setOnCameraMoveStartListener를 이용하여 뷰모델에 카메라 트래킹 상태를 관찰하였다.
사용자 위치 이동 토글버튼 구현
// FloatingActionButton으로 트래킹 버튼 구현
FloatingActionButton(
onClick = {
// 트래킹 상태 토글
viewModel.setCameraTracking(!cameraState)
},
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp),
shape = CircleShape,
containerColor = if(cameraState) Color.Blue else Color.Gray // 상태에 따른 색상 변경
) {
Icon(
imageVector = Icons.Default.LocationOn,
contentDescription = "내 위치",
tint = Color.White
)
}
FloatingActionButton을 이용하여 현제 사용자 트레킹 유/무를 설정하고 상태를 확인할 수 있는 버튼을 구현하였다.
최종 코드를 보면 라벨, 에니메이션, 사용자 인터렉션이 잘 적용되였다.
2부에서는 지도기능의 핵심 서비스 구현코드를 작성해보겠다.