티스토리 뷰

안드로이드/코드

Compose GoogleMap

알렌보이스 2022. 10. 27. 21:17
728x90

1) 라이브러리 설치

implementation 'com.google.maps.android:maps-compose:2.5.3'
implementation 'com.google.android.gms:play-services-maps:18.1.0'

2) API 키 발급

 

API 키 사용  |  Android용 Maps SDK  |  Google Developers

의견 보내기 API 키 사용 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 플랫폼 선택: Android iOS 자바스크립트 여기에서는 Android용 Maps SDK에 사용할 API 키를 만

developers.google.com

api 발급은 어렵지 않으므로 생략하겠습니다.

3) API 셋팅

발급받은 api key는 local.properties에 다음과 같이 저장해 줍니다.

google_map_key=AI...
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def google_map_key = properties.getProperty('google_map_key')

android {
    ...
    defaultConfig {
                ...
        manifestPlaceholders= [googleMapKey: google_map_key]
                ...
    }
}

발급받은 api key를 Manifest에 사용해야 하므로 설정을 해줍니다.

manifestPlaceholders의 들어가는 키값을 사용할 예정이므로 오타가 없는지 확인 후 복사해 줍니다.

<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.google.android.geo.API_KEY" 
                    android:value="${googleMapKey}"/>

    </application>

</manifest>

meta-data 안에 위에 저장한 키값을 등록해서 api를 설정하게 됩니다.

여기까지 하면 구글 맵을 사용하기 위한 준비가 완료됩니다.

4) 구글 맵 표시

val latLng = LatLng(37.7387295, 127.0458908)
val cameraPositionState = rememberCameraPositionState {
    position = CameraPosition.fromLatLngZoom(latLng, 15f)
}

GoogleMap(
    modifier = Modifier.fillMaxSize(),
    cameraPositionState = cameraPositionState
) {
    Marker(
        state = MarkerState(position = latLng),
        title = "의정부역",
        snippet = "Uijeongbu subway"
    )
}

GoogleMap을 사용하면 구글 맵을 앱에 띄울 수 있습니다.

cameraPositionState는 카메라 위치를 저장하는 변수로 position을 지정하여

GoogleMap의 cameraPositionState에 값을 넣어주면 지도의 최초 위치를 지정할 수 있습니다.

Marker를 이용하여 특정 좌표에 표시를 해줄 수 있습니다.

title과 snippet은 마커를 클릭 시 나오는 버블의 문구를 설정할 수 있습니다.

Screenshot_20221027_173729 1.png

4-1) MarkerInfoWindowContent

MarkerInfoWindowContent(
    state = MarkerState(position = latLng),
) { marker ->
    Text(marker.title ?: "의정부역", color = Color.Blue)
}

기본 Marker 대신 MarkerInfoWindowContent를 사용하면 버블 내부의 컨탠츠를 꾸밀 수 있습니다.

예시에서는 간단하게 Text를 넣고 색상을 지정해 주었습니다.

이 외에도 Image 등을 넣어서 사용할 수 있습니다.

Screenshot_20221027_191833 1.png

4-2) MarkerInfoWindow

MarkerInfoWindow(
    state = MarkerState(position = latLng)
) { marker ->
    Button(
        contentPadding = PaddingValues(10.dp),
        onClick = {},
        shape = RoundedCornerShape(10.dp),
        colors = ButtonDefaults.buttonColors(
            backgroundColor = Color.Yellow
        ),
    ) {
        Column {
            Image(painter = painterResource(id = R.drawable.ic_launcher_background), contentDescription = null)
            Text(marker.title ?: "의정부역", color = Color.Blue)
        }
    }
}

MarkerInfoWindow는 모든 것을 다 커스텀 해서 제작할 수 있는 방식입니다.

Screenshot_20221027_194020 1.png

4-3) properties와 uiSettings

var mapProperties by remember {
    mutableStateOf(
        MapProperties(maxZoomPreference = 15f, minZoomPreference = 5f)
    )
}
var mapUiSettings by remember {
    mutableStateOf(
        MapUiSettings(mapToolbarEnabled = false)
    )
}

GoogleMap(
    properties = mapProperties,
    uiSettings = mapUiSettings,
    cameraPositionState = cameraPositionState
)

properties는 줌의 최대 크기와 최소 크기를 지정할 수 있습니다.

uiSettings는 각종 맵의 UI의 세팅을 설정할 수 있습니다.

예시로 amppToolbarEnabled의 경우 Marker를 선택 시 아래와 같은 아이콘이 등장합니다.

Screenshot_20221027_194020 1.png

이 아이콘의 표시 여부를 설정할 수 있습니다.

이 외의 설정을 할 수 있는 종류는 다음과 같습니다.

public class MapUiSettings(
    public val compassEnabled: Boolean = true,
    public val indoorLevelPickerEnabled: Boolean = true,
    public val mapToolbarEnabled: Boolean = true,
    public val myLocationButtonEnabled: Boolean = true,
    public val rotationGesturesEnabled: Boolean = true,
    public val scrollGesturesEnabled: Boolean = true,
    public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true,
    public val tiltGesturesEnabled: Boolean = true,
    public val zoomControlsEnabled: Boolean = true,
    public val zoomGesturesEnabled: Boolean = true,
)

4) 포지션 이동

cameraPositionState.move(CameraUpdateFactory.newLatLng(latLng))

위에서 만든 cameraPositionState를 이용해서 카메라의 포지션을 이동시킬 수 있습니다.

이 외에도 줌 크기 변경, 새로운 포지션 세팅 설정 등이 가능합니다.

5) 활용

간단하게 뷰페이저와 맵을 이용하는 예제입니다.

우선 데이터입니다.

data class MapItem(
    val latLng: LatLng,
    val title: String,
    val image: String
)

fun getMapItemList() = listOf(
    MapItem(
        latLng = LatLng(37.740118399999744, 127.04342009999966),
        title = "스타벅스 의정부공원점",
        image = "https://ldb-phinf.pstatic.net/20200825_200/1598301949474sBbkY_JPEG/3509_20171121080426_cnkfi.jpg"
    ),
    MapItem(
        latLng = LatLng(37.73786099999975, 127.04186979999947),
        title = "스타벅스 신세계의정부",
        image = "https://ldb-phinf.pstatic.net/20200825_242/1598302003990GfJlO_JPEG/9698_20180615072632_2qg34.jpg"
    ),
    MapItem(
        latLng = LatLng(37.73305020000005, 127.03457659999995),
        title = "스타벅스 의정부예술의전당DT점",
        image = "https://ldb-phinf.pstatic.net/20210804_47/1628023521693AOigS_JPEG/3571_20210803091352_qzsc3.jpg"
    ),
)

간단한 예제이므로 데이터는 하드코딩으로 설정하였습니다.

@OptIn(ExperimentalPagerApi::class)
@Composable
fun MapViewPager(
    list: List<MapItem>,
    state: PagerState,
    modifier: Modifier = Modifier
) {
    HorizontalPager(
        count = list.size,
        state = state,
        contentPadding = PaddingValues(start = 25.dp, end = 50.dp),
        modifier = modifier
    ) { index ->
        MapCard(
            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 MapCard(
    item: MapItem,
    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)
        }
    }
}

다음은 뷰페이저 설정으로 뷰페이저 관련해서는 다른 글에서 다루었으므로 링크를 남기겠습니다.

 

Compose 기초 3 : ViewPager

이번 포스팅에서는 기존 XML 에서 ViewPager 의 기능을 Compose 로 유사하게 구현하는 방법에 대하여 알아보겠습니다. 시작하기 전에 다음의 내용을 build.gradle(:app) 에 추가해 주세요 implementation "com.go..

alanboyce.tistory.com

val list = getMapItemList()
val cameraPositionState = rememberCameraPositionState {
    position = CameraPosition.fromLatLngZoom(list[0].latLng, 14f)
}
val uiSettings by remember {
    mutableStateOf(
        MapUiSettings(
            zoomControlsEnabled = false
        )
    )
}
val pagerState = rememberPagerState()
val scope = rememberCoroutineScope()

우선 사용하는 변수 대해 간략한 설명을 하자면

list는 위에서 만든 하드코딩 된 데이터를 담은 리스트입니다.

cameraPositionState는 지도의 카메라 상태 관리로,
초기 팅은 리스트의 첫 번째에 담긴 주소를 기준으로 14배 줌을 한 지도를 표시합니다.

uiSettings는 지도의 UI 설정 중 확대/축소 관련 아이콘을 제거하기 위해 사용하였습니다.

pagerState는 Pager의 상태 관리하기 위해 사용하였습니다.

scope는 Pager 이동 시 Coroutine을 사용하기 위해 정의하였습니다.

Box(modifier = Modifier.fillMaxSize()) {

    GoogleMap(
        cameraPositionState = cameraPositionState,
        uiSettings = uiSettings
    ) {
        list.forEachIndexed { index, mapItem ->
            MarkerInfoWindow(
                state = MarkerState(position = mapItem.latLng),
                onClick = {
                    scope.launch {
                        pagerState.animateScrollToPage(index)
                    }
                    true
                }
            )
        }
    }

    MapViewPager(
        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.move(CameraUpdateFactory.newLatLng(list[page].latLng))
        }
    }
}

마커의 경우 MarkerInfoWindow를 사용한 뒤 Contents에는 아무것도 안 넣음으로써 버블이 발생하지 않습니다.

또한 클릭 시 좌표 위치에 맞는 Pager의 페이지로 이동하게 설정하였습니다.

LaunchedEffect는 Pager 글에는 없던 내용입니다.
Pager의 변화를 감지하기 위해 사용하였습니다.

PagerState의 값이 변경될 때마다 코드 블록이 실행되며, Pager의 현재 위치를 파악한 뒤 지도의 위치를 변경하게 됩니다.

7731f3f1518a90ef0d9c.gif

실행 화면입니다.

728x90

'안드로이드 > 코드' 카테고리의 다른 글

Compose ConstraintLayout  (0) 2022.11.02
Compose Naver Map  (1) 2022.10.29
Compose LaunchedEffect  (0) 2022.10.27
Compose GraphicsLayer  (0) 2022.09.02
Compose 기초 4 : Gradient  (0) 2022.09.01
댓글