fornewid / naver-map-compose

NAVER Map Android SDK for Jetpack Compose 🗺
https://fornewid.github.io/naver-map-compose/
Apache License 2.0
115 stars 7 forks source link

Clustering Marker 에 대한 Icon Custom Design 지원 #111

Open easyhooon opened 6 days ago

easyhooon commented 6 days ago

우선 저번에 만들어주셨던 MarkerComposable 를 잘 사용하고 있습니다 감사합니다ㅎ

혹시 Clustering Marker 도 Composable 함수를 통해 커스텀이 가능한지, 지원이 가능한지 문의드립니다.

일반적은 마커의 경우 컴포저블 함수인 MarkerComposable 사용하여 원하는 형태의 마커를 구현할 수 있었으나, 클러스터링 마커의 경우 .cluserMarkerUpdate(), .leafMarkerUpdater 내에 updateClusterMarker 함수 혹은 updateLeafMarker 함수 내에서 marker 에 대한 설정을 할 수 있으므로, 컴포저블 함수인 MarkerComposable 함수를 사용할 수 없었습니다.

@OptIn(ExperimentalNaverMapApi::class)
@Composable
fun MapContent(
    uiState: MapUiState,
    cameraPositionState: CameraPositionState,
    rotationState: Float,
    pagerState: PagerState,
    onMapUiAction: (MapUiAction) -> Unit,
    onFestivalUiAction: (FestivalUiAction) -> Unit,
) {
    // val context = LocalContext.current
    Box {
        // TODO 같은 속성(주점, 이벤트)의 Marker 들만 클러스터링 되도록 구현
        // TODO 클러스터링 마커 커스텀
        NaverMap(
            cameraPositionState = cameraPositionState,
            locationSource = rememberFusedLocationSource(),
            uiSettings = MapUiSettings(
                isZoomControlEnabled = false,
                isScaleBarEnabled = false,
                isLogoClickEnabled = false,
            ),
            properties = MapProperties(
                locationTrackingMode = LocationTrackingMode.NoFollow,
            ),
        ) {
            PolygonOverlay(
                coords = uiState.outerCords,
                color = Color.Gray.copy(alpha = 0.3f),
                outlineColor = Color.Gray,
                outlineWidth = 1.dp,
                holes = persistentListOf(uiState.innerHole),
            )
            uiState.filteredBoothsList.forEach { booth ->
                Marker(
                    state = MarkerState(position = booth.position),
                    icon = MarkerCategory.fromString(booth.category).getMarkerIcon(booth.isSelected),
                    onClick = {
                        // onMapUiAction(MapUiAction.OnBoothMarkerClick(booth))
                        true
                    },
                )
                MarkerComposable {}
            }

            var clusterManager by remember { mutableStateOf<Clusterer<BoothMapModel>?>(null) }
            DisposableMapEffect(uiState.boothList) { map ->
                if (clusterManager == null) {
                    clusterManager = Clusterer.Builder<BoothMapModel>()
                        .clusterMarkerUpdater(
                            object : DefaultClusterMarkerUpdater() {
                                override fun updateClusterMarker(info: ClusterMarkerInfo, marker: Marker) {
                                    super.updateClusterMarker(info, marker)
                                    MarkerComposable {} // <- error - @Composable invocations can only happen from the context of a @Composable functio
                                }
                            },
                            MarkerComposable {} // <- error - @Composable invocations can only happen from the context of a @Composable functio
                        )
                      .leafMarkerUpdater(
                            object : DefaultLeafMarkerUpdater() {
                                override fun updateLeafMarker(info: LeafMarkerInfo, marker: Marker) {
                                    super.updateLeafMarker(info, marker)
                                    marker.apply {
                                        icon = MarkerCategory.fromString((info.key as BoothMapModel).category)
                                            .getMarkerIcon((info.key as BoothMapModel).isSelected)
                                        onClickListener = Overlay.OnClickListener {
                                            onMapUiAction(MapUiAction.OnBoothMarkerClick(listOf(info.key as BoothMapModel)))
                                            true
                                        }
                                    }
                                    MarkerComposable {} // error - @Composable invocations can only happen from the context of a @Composable function
                                }
                            },
                            MarkerComposable {} // error - @Composable invocations can only happen from the context of a @Composable function
                        )
                        .build()
                        .apply { this.map = map }
                }
                val boothListMap = buildMap(uiState.boothList.size) {
                    uiState.boothList.forEachIndexed { index, booth ->
                        put(booth, index)
                    }
                }
                clusterManager?.addAll(boothListMap)
                onDispose {
                    clusterManager?.clear()
                }
            }

//            var clusterManager by remember { mutableStateOf<TedNaverClustering<BoothMapModel>?>(null) }
//            DisposableMapEffect(uiState.filteredBoothsList) { map ->
//                if (clusterManager == null) {
//                    clusterManager = TedNaverClustering.with<BoothMapModel>(context, map)
//                        .customMarker {
//                            Marker().apply {
//                                icon = MarkerCategory.fromString(it.category).getMarkerIcon(it.isSelected)
//                            }
//                        }
//                        .markerClickListener { booth ->
//                            onMapUiAction(MapUiAction.OnBoothMarkerClick(listOf(booth)))
//                        }
//                        .clusterClickListener { booths ->
//                            onMapUiAction(MapUiAction.OnBoothMarkerClick(booths.items.toList()))
//                        }
//                        // 마커를 클릭 했을 경우 마커의 위치로 카메라 이동 비활성화
//                        .clickToCenter(false)
//                        .make()
//                }
//                clusterManager?.addItems(uiState.filteredBoothsList)
//                onDispose {
//                    clusterManager?.clearItems()
//                }
//            }
        }

목적은 아래의 사진과 같이 Clustering Marker 를 목적에 맞게 커스텀해서 구현하기 위함입니다.

image (captionText 가 marker의 Design 내에 포함되는 경우, clustering marker 의 info.size 파라미터를 Marker View 의 파라미터로 추가해줘야하는 케이스)

읽어주셔서 감사합니다!

fornewid commented 5 days ago

기능 제안 감사합니다. 다만 제가 당분간은 작업이 어려울 것 같습니다. 여력이 생기면 검토해보겠습니다.

P.S. 지난 주말 행사에서 뵈서 반가웠습니다. 🙇