티스토리 뷰
1) API 키 발급
이번에 역시 키 발급 방법에 관련해서는 생략하겠습니다.
Naver Colud Platform 사이트에 서비스 > Application Services > Maps에서 활용 신청을 하시면 됩니다.
활용 신청 후 아래 링크의 인증 정보를 확인해서 Client Id와 Client Secret의 값들을 저장해 둡니다.
2) 라이브러리 설치
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url 'https://naver.jfrog.io/artifactory/maven/'
}
}
}
implementation 'com.naver.maps:map-sdk:3.15.0'
implementation 'io.github.fornewid:naver-map-compose:1.2.1'
네이버에서 공식으로 지원하는 것은 com.naver.maps:map-sdk:3.15.0입니다.
Github에 Sangyoung An 님께서 올려주신 라이브러리가 있어서 사용해 보려고 합니다.
2-1) 오류
네이버 맵 라이브러리를 추가 시 저의 경우 Duplicate class found 오류가 발생하였습니다.
해당 문제가 발생 시 해결 방안에 관해서 참고용으로 링크 첨부합니다.
3) API 설정 세팅
Api 키를 발급받아서 저장해 둔 Client Id와 Client Secret가 있을 것입니다.
client_id=35r...
client_secret=okhgI...
local.properties에 위와 같은 형식으로 저장해 줍니다.
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def naver_map_client_id = properties.getProperty('client_id')
def naver_map_client_secret = properties.getProperty('client_secret')
android {
...
defaultConfig {
...
manifestPlaceholders= [
naverClientId: naver_map_client_id,
naverClientSecret: naver_map_client_secret
]
...
}
}
1
2줄은 local.properties 안에 있는 가져오기 위한 코드이고, 3
4줄은 그 안에 방금 전에 넣은 값을 불러오기 위한 코드입니다.
android → defaultConfing 안에 manifestPlaceholders를 이용해서 변수처럼 사용할 수 있도록 키 밸류 형식으로 넣어줍니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.composestudy">
<application
...>
<meta-data
android:name="com.naver.maps.map.CLIENT_ID"
android:value="${naverClientId}" />
</application>
</manifest>
application 태그 안에 meta-data를 만들고 name은 동일하게 작성하고 value 값에 위에서 만든 키값을 넣어주시면 됩니다.
이렇게 네이버 지도를 사용하기 위한 준비는 완료되었습니다.
4) 지도 표시
NaverMap()
가장 기본적인 지도 표시는 NaverMap을 사용하면 끝입니다.
4-1) CameraPositionState
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition(latLng, 15.0)
}
NaverMap(
cameraPositionState = cameraPositionState
)
CameraPositionState는 지도에 사용되는 카메라의 상태를 제어 및 관찰을 위한 객체로 rememberCameraPositionState를 이용하여 생성할 수 있습니다.
생성 시 position의 값을 주면 맵 뷰 실행 시 초기 위치를 설정할 수 있습니다.
CameraPosition은 LatLng(위도, 경도)와 줌의 크기를 받습니다.
CameraPositionState를 코드로 변경을 하고자 할 때에는
cameraPositionState.move()
와 cameraPositionState.animate()
가 있습니다.
둘의 차이는 애니메이션이 들어간다는 것과 애니메이션 적용을 위해 CoroutineScope가 필요하다는 차이점이 있습니다.
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
cameraPositionState.animate(
update = CameraUpdate.scrollTo(LatLng(37.740111,127.0475821)),
animation = CameraAnimation.Fly,
)
}
}
예시로 특정 위치로 좌표를 이동하는 코드입니다.
좌표 이동 외에도 카메라 포지션을 통한 이동, 줌 크기 변경, 줌과 좌표 동시에 변경 등이 있습니다.
animation에는 Linear, Easing, Fly, None이 있고 기본 값은 Easing이 있습니다.
4-2) UiSettings
NaverMap(
uiSettings = mapUiSettings
)
UiSettings는 위와 같이 등록할 수 있습니다.
var mapUiSettings by remember {
mutableStateOf(
MapUiSettings(
isLocationButtonEnabled = false,
isZoomControlEnabled = false,
logoGravity = Gravity.TOP or Gravity.END,
)
)
}
변수 생성은 위와 같이 할 수 있습니다.
이번 예시에서 사용한 내용은
isLocationButtionEnableds는 위치 아이콘 버튼 표시 여부입니다. (클릭 시 동작은 추가 구현 필요)
isZoomControlEnabled는 버튼을 통한 줌 컨트롤러 표시 여부입니다.
logoGravity는 네이버 로고가 들어갈 위치를 조정합니다.
이 외에도 여러 가지 속성들이 있는데 양이 많고 코드로 확인하면 한글로 주석이 잘 되어있으니 확인해 보시기 바랍니다.
public data class MapUiSettings(
public val pickTolerance: Dp = NaverMapConstants.DefaultPickTolerance,
public val isScrollGesturesEnabled: Boolean = true,
public val isZoomGesturesEnabled: Boolean = true,
public val isTiltGesturesEnabled: Boolean = true,
public val isRotateGesturesEnabled: Boolean = true,
public val isStopGesturesEnabled: Boolean = true,
public val scrollGesturesFriction: Float = NaverMapConstants.DefaultScrollGesturesFriction,
public val zoomGesturesFriction: Float = NaverMapConstants.DefaultZoomGesturesFriction,
public val rotateGesturesFriction: Float = NaverMapConstants.DefaultRotateGesturesFriction,
public val isCompassEnabled: Boolean = true,
public val isScaleBarEnabled: Boolean = true,
public val isZoomControlEnabled: Boolean = true,
public val isIndoorLevelPickerEnabled: Boolean = false,
public val isLocationButtonEnabled: Boolean = false,
public val isLogoClickEnabled: Boolean = true,
public val logoGravity: Int = Gravity.BOTTOM or Gravity.START,
public val logoMargin: PaddingValues = PaddingValues(horizontal = 12.dp, vertical = 16.dp),
)
4-3) Properties
NaverMap(
properties = mapProperties
)
사용은 역시 위와 동일합니다.
var mapProperties by remember {
mutableStateOf(
MapProperties(
maxZoom = 20.0,
minZoom = 5.0,
isBicycleLayerGroupEnabled = true,
isCadastralLayerGroupEnabled = true,
isIndoorEnabled = true
)
)
}
역시 많은 종류 중 일부만 사용해 보았습니다.
maxZoom/minZoom 최소/최대 줌 크기 설정
isBicycleLayerGroupEnabled는 자전거 도로 표시 여부입니다.
isCadastralLayerGroupEnabled는 지적편집도 표시 여부입니다.
isIndoorEnabled는 실내지도 활성화 여부입니다.
public data class MapProperties(
public val mapType: MapType = MapType.Basic,
public val extent: LatLngBounds? = null,
public val minZoom: Double = NaverMapConstants.MinZoom,
public val maxZoom: Double = NaverMapConstants.MaxZoom,
public val maxTilt: Double = NaverMapConstants.DefaultMaxTilt,
public val defaultCameraAnimationDuration: Int = NaverMapConstants.DefaultCameraAnimationDuration,
public val fpsLimit: Int = 0,
public val isBuildingLayerGroupEnabled: Boolean = true,
public val isTransitLayerGroupEnabled: Boolean = false,
public val isBicycleLayerGroupEnabled: Boolean = false,
public val isTrafficLayerGroupEnabled: Boolean = false,
public val isCadastralLayerGroupEnabled: Boolean = false,
public val isMountainLayerGroupEnabled: Boolean = false,
public val isLiteModeEnabled: Boolean = false,
public val isNightModeEnabled: Boolean = false,
public val isIndoorEnabled: Boolean = false,
public val indoorFocusRadius: Dp = NaverMapConstants.DefaultIndoorFocusRadius,
public val buildingHeight: Float = 1f,
public val lightness: Float = 0f,
public val symbolScale: Float = 1f,
public val symbolPerspectiveRatio: Float = 1f,
public val backgroundColor: Color = NaverMapConstants.DefaultBackgroundColorLight,
@DrawableRes
public val backgroundResource: Int = NaverMapConstants.DefaultBackgroundDrawableLight,
public val locationTrackingMode: LocationTrackingMode = LocationTrackingMode.None,
)
역시 주석이 잘 나와있으니 직접 참고해 보시기 바랍니다.
5) Overay
제공되는 Overay의 종류는 다음과 같습니다.
ArrowheadPathOverlay
CircleOverlay
GroundOverlay
Marker
MultipartPathOverlay
PathOverlay
PolygonOverlay
PolylineOverlay
5-1) Marker
Marker(
state = MarkerState(LatLng(37.740111, 127.0475821)),
captionText = "스타벅스 의정부공원점",
subCaptionText = "카페",
captionOffset = 10.dp,
captionColor = Color.Magenta,
captionRequestedWidth = 10.dp
)
Marker(
state = MarkerState(LatLng(37.7378009,127.0461568)),
captionText = "스타벅스 신세계의정부",
subCaptionText = "카페",
captionOffset = 10.dp,
captionColor = Color.White,
captionHaloColor = Color.Black,
captionRequestedWidth = 10.dp,
icon = OverlayImage.fromResource(R.drawable.ic_round_star_24),
height = 40.dp,
width = 40.dp
)
captionRequestedWidth는 캡션의 넓이를 지정해 주는데 넓이 보다 텍스트가 길어지면 어절 단위로 다음 라인으로 넘어가게 됩니다.
captionOffset는 마커의 아이콘과 캡션과의 거리입니다.
오른쪽 상단에 있는 것이 기본 아이콘을 사용한 마커이고, 왼쪽 하단의 노란색 별이 아이콘을 변경한 마커입니다.
5-2) ArrowheadPathOverlay
ArrowheadPathOverlay(
coords = listOf(
LatLng(37.740111,127.0475821),
LatLng(37.7378009,127.0461568),
LatLng(37.733071,127.0388841),
),
width = 5.dp
)
ArrowheadPathOverlay는 지도에 화살표를 그려줍니다.
경로 탐색 기능이 있는 건 아니고 지정한 좌표와 좌표 사이를 연결한 화살표입니다.
5-3) CircleOverlay
CircleOverlay(
center = LatLng(37.7378009,127.0461568),
color = Color(0x4DFFEA00),
outlineColor = Color.White,
outlineWidth = 10.dp
)
CircleOverlay는 center의 좌표 기준으로 radius(기본 값 1000) 만큼 원을 그려줍니다.
5-4) GroundOverlay
val builder = LatLngBounds.Builder()
builder.include(LatLng(37.7378009,127.0461568))
builder.include(LatLng(37.733071,127.0388841))
GroundOverlay(
bounds = builder.build(),
image = OverlayImage.fromResource(R.drawable.ic_launcher_background),
alpha = .5f
)
GroundOverlay는 특정 LatLngBounds에 이미지를 표시해 주는 Overlay입니다.
5-5) PathOverlay
PathOverlay(
coords = listOf(
LatLng(37.740111, 127.0475821),
LatLng(37.7378009, 127.0461568),
LatLng(37.733071, 127.0388841),
),
progress = 0.4,
color = Color(0xFFFF1744),
passedColor = Color(0xFFFFEA00),
outlineWidth = 10.dp
)
PathOverlay는 지나온 경로를 표시해 주는 Overlay입니다.
5-6) MultipartPathOverlay
MultipartPathOverlay(
coordParts = listOf(
listOf(
LatLng(37.740111, 127.0475821),
LatLng(37.7378009, 127.0461568),
LatLng(37.733071, 127.0388841),
),
listOf(
LatLng(37.7378009, 127.0461568),
LatLng(37.733071, 127.0388841),
)
),
colorParts = listOf(
ColorPart(
color = Color(0xFFFF1744),
passedColor = Color(0xFFFFEA00)
),
),
progress = 0.8,
)
MultipartPathOverlay는 PathOverlay를 리스트를 통해 여러 개 등록할 수 있는 Overlay입니다.
6) 활용
우선 데이터입니다.
data class NaverMapItem(
val latLng: LatLng,
val title: String,
val image: String
)
fun getNaverMapItemList() = listOf(
NaverMapItem(
latLng = LatLng(37.740111, 127.0475821),
title = "스타벅스 의정부공원점",
image = "https://ldb-phinf.pstatic.net/20200825_200/1598301949474sBbkY_JPEG/3509_20171121080426_cnkfi.jpg"
),
NaverMapItem(
latLng = LatLng(37.7378009, 127.0461568),
title = "스타벅스 신세계의정부",
image = "https://ldb-phinf.pstatic.net/20200825_242/1598302003990GfJlO_JPEG/9698_20180615072632_2qg34.jpg"
),
NaverMapItem(
latLng = LatLng(37.733071, 127.0388841),
title = "스타벅스 의정부예술의전당DT점",
image = "https://ldb-phinf.pstatic.net/20210804_47/1628023521693AOigS_JPEG/3571_20210803091352_qzsc3.jpg"
),
)
다음은 뷰페이저입니다.
@OptIn(ExperimentalPagerApi::class)
@Composable
fun NaverMapViewPager(
list: List<NaverMapItem>,
state: PagerState,
modifier: Modifier = Modifier
) {
HorizontalPager(
count = list.size,
state = state,
contentPadding = PaddingValues(start = 25.dp, end = 50.dp),
modifier = modifier
) { index ->
NaverMapCard(
item = list[index],
modifier = Modifier
.graphicsLayer {
val pageOffset = calculateCurrentOffsetForPage(index).absoluteValue
lerp(
start = 0.85f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
).also { scale ->
scaleX = scale
scaleY = scale
}
}
)
}
}
@Composable
fun NaverMapCard(
item: NaverMapItem,
modifier: Modifier = Modifier
) {
Card(
shape = RoundedCornerShape(10.dp),
modifier = modifier
.fillMaxWidth()
.padding(end = 10.dp)
) {
Column(
modifier = Modifier.padding(10.dp)
) {
AsyncImage(
model = item.image,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.clip(RoundedCornerShape(10.dp))
)
Spacer(modifier = Modifier.height(10.dp))
Text(text = item.title, fontSize = 16.sp)
}
}
}
뷰페이저 관련해서는 아래 링크에서 다루었습니다.
@OptIn(ExperimentalNaverMapApi::class, ExperimentalPagerApi::class)
@Composable
fun NaverMapTest() {
val list = getNaverMapItemList()
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition(list[1].latLng, 14.0)
}
val pagerState = rememberPagerState()
val scope = rememberCoroutineScope()
Box(modifier = Modifier.fillMaxSize()) {
NaverMap(
cameraPositionState = cameraPositionState
) {
list.forEachIndexed { index, item ->
Marker(
state = MarkerState(position = item.latLng),
onClick = {
scope.launch {
cameraPositionState.animate(update = CameraUpdate.scrollTo(list[index].latLng))
pagerState.animateScrollToPage(index)
}
true
}
)
}
}
NaverMapViewPager(
list = list,
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 10.dp, max = 200.dp)
.align(Alignment.BottomCenter)
)
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }.collect { page ->
cameraPositionState.animate(update = CameraUpdate.scrollTo(list[page].latLng))
}
}
}
}
코드는 단순합니다.
NaverMap을 통해 네이버 지도를 그리고
위에서 만든 데이터를 통해 Marker를 그립니다.
Marker를 클릭 시 socpe.lauch 블록을 실행하여 뷰페이저의 페이지를 이동시킵니다.
cameraPositionState.animate는 안 넣어도 화면이 움직이지만 뷰페이저의 이동이 끝난 후에 지도 이동하는 게 어색해서 넣었습니다.
LaunchedEffect를 통해 뷰페이저의 변화를 감지하고 카메라를 이동시킵니다.
결과 화면입니다.
'안드로이드 > 코드' 카테고리의 다른 글
Compose MotionLayout (0) | 2022.11.02 |
---|---|
Compose ConstraintLayout (0) | 2022.11.02 |
Compose GoogleMap (0) | 2022.10.27 |
Compose LaunchedEffect (0) | 2022.10.27 |
Compose GraphicsLayer (0) | 2022.09.02 |
- Total
- Today
- Yesterday
- Compose ConstraintLayout
- 포켓몬 도감
- Retrofit
- Kotlin
- Compose Naver Map
- Worker
- Android Compose
- 안드로이드
- Duplicate class fond 에러
- Compose ModalBottomSheetLayout
- Duplicate class found error
- WebView
- column
- WorkManager
- 안드로이드 구글 지도
- Android
- Compose QRCode Scanner
- Row
- Compose BottomSheetDialog
- 웹뷰
- Gradient
- Compose MotionLayout
- Compose 네이버 지도
- Pokedex
- Compose BottomSheetScaffold
- Compose 네이버 지도 api
- Fast api
- LazyColumn
- compose
- Compose BottomSheet
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |