Android - Google Map API 설치부터 심화 활용까지 (커스텀 클러스터링) (4) (Kotlin)

업데이트:

  • 연구주제 : Android - Google Map 활용 (Kotlin)
  • 연구목적 : 안드로이드에서의 코틀린 활용
  • 연구일시 : 2020년 04월 09일 09:00~17:00
  • 연구자 : 이재환 ljh951103@naver.com
  • 연구장비 : HP EliteDesk 800 G4 TWR, Kotlin, Android studio, IntelliJ
  • 관련연구 : Java, Android, Kotlin, Google, API


서론

이번 포스팅에서는 지난 포스팅에서 구현했던 커스텀 마커와 클러스터링을 동시에 사용하여 다음과 같은 화면을 구현해보도록 하겠다.

image


본론

boundmap

다음의 함수를 사용하면 클러스터링에 삽입된 마커들을 모아준다.
즉, 마커들의 좌표들을 모으고 그 마커들을 다 보일 수 있도록 카메라 줌을 초기화한다.

fun boundmap() {
        val bounds: LatLngBounds = builder.build()
        mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, ZoomLevel))
        val zoom: Float = mMap.getCameraPosition().zoom - 0.5f
        mMap.animateCamera(CameraUpdateFactory.zoomTo(zoom))
    }


addLatLNgData

addLatLNgData 는 클러스터링에 정의한 데이터들을 추가해준다.

fun addLatLNgData(id : Long, latlng : LatLng) {
        val data = LatLngData(index++, id, latlng)
        mClusterManager.addItem(data)
        builder.include(data.latlng)
    }


MarkerClusterRenderer

MarkerClusterRenderer 클래스는 클러스터링의 여러가지 옵션을 정의한다.
onBeforeClusterItemRendered 함수의 경우, markerOption을 통하여 커스텀 마커가 가능하다.
createDrawableFromView의 경우 저번 포스팅에서 커스텀 마커를 구현했을 때의 함수와 같이 뷰를 비트맵으로 변환시켜주는 함수이다.
onClustersChanged는 마커를 클릭한 후, 마커가 클러스터로 변환될 경우, 적용된 렌더링이 풀리기 때문에 정의해준다.

class MarkerClusterRenderer(context: Context?, map: GoogleMap?, clusterManager: ClusterManager<LatLngData>?,
                            marker_view: View) : DefaultClusterRenderer<LatLngData>(context, map, clusterManager) {
    private val context = context
    private var marker_view = marker_view
    private lateinit var tag_marker: TextView

    override fun onBeforeClusterItemRendered(item: LatLngData, markerOptions: MarkerOptions) { // 5
        tag_marker = marker_view.findViewById(R.id.tag_marker) as TextView
        tag_marker.text = MediaStore_Dao.getNameById(context!!, item.id)
        markerOptions.icon( BitmapDescriptorFactory.fromBitmap(createDrawableFromView(context, marker_view)) )
    }

    override fun onClustersChanged(clusters: MutableSet<out Cluster<LatLngData>>?) {
        super.onClustersChanged(clusters)

        if(selectedMarker != null) {
            selectedMarker!!.setIcon(BitmapDescriptorFactory.fromBitmap(createDrawableFromView(context, marker_view)))
            selectedMarker = null
        }
    }

    companion object {
        fun createDrawableFromView(context: Context?, view: View): Bitmap {
            val displayMetrics = DisplayMetrics()
            (context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics)
            view.layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
            view.measure(displayMetrics.widthPixels, displayMetrics.heightPixels)
            view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)
            view.buildDrawingCache()
            val bitmap: Bitmap = Bitmap.createBitmap(
                view.measuredWidth,
                view.measuredHeight,
                Bitmap.Config.ARGB_8888
            )
            val canvas = Canvas(bitmap)
            view.draw(canvas)
            return bitmap
        }
    }
}


changeRenderer

여기서는 마커를 클릭했을 때, 렌더링을 서로 달리하기위함이다.
커스텀 마커를 구현했을 때와 같다.

private fun changeRenderer(item: LatLngData) {
        if(selectedMarker != clusterRenderer.getMarker(item)) {
            if (selectedMarker != null) {
                tag_marker.setTextColor(Color.BLACK)
                tag_marker.setBackgroundResource(R.drawable.ic_marker_phone)
                selectedMarker!!.setIcon(
                    BitmapDescriptorFactory.fromBitmap(
                        createDrawableFromView(this, marker_view)
                    )
                )
            }

            if (clusterRenderer.getMarker(item) != null) {
                tag_marker.setTextColor(Color.WHITE)
                tag_marker.setBackgroundResource(R.drawable.ic_marker_phone_blue)
                tag_marker.text = MediaStore_Dao.getNameById(this, item.id)
                clusterRenderer.getMarker(item).setIcon(
                    BitmapDescriptorFactory.fromBitmap(
                        createDrawableFromView(
                            this,
                            marker_view
                        )
                    )
                )
                selectedMarker = clusterRenderer.getMarker(item)
                tag_marker.setTextColor(Color.BLACK)
                tag_marker.setBackgroundResource(R.drawable.ic_marker_phone)
            }
        }
    }


clusterClick

클러스터를 클릭했을 때의 리스너를 정해준다.
이것을 bound() 와 마찬가지로 카메라 줌을 맞춰준다.

private fun clusterClick(mMap: GoogleMap) {
        mClusterManager.setOnClusterClickListener { cluster ->
            // 클러스터 클릭 리스너
            val builder_c: LatLngBounds.Builder = LatLngBounds.builder()
            for (item in cluster.items) {
                if (item != null) {
                    builder_c.include(item.position)
                }
            }
            val bounds_c: LatLngBounds = builder_c.build()
            mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds_c, ZoomLevel))
            val zoom: Float = mMap.cameraPosition.zoom - 0.5f
            mMap.animateCamera(CameraUpdateFactory.zoomTo(zoom))
            true
        }
    }


clusterItemClick

그리고 마커를 클릭했을 때의 리스너도 달아준다.

    private fun clusterItemClick(mMap: GoogleMap) {
        mClusterManager.setOnClusterItemClickListener { p0 ->
            // // 클러스터 아이템 클릭 리스너
            val center: CameraUpdate = CameraUpdateFactory.newLatLng(p0?.position)
            mMap!!.animateCamera(center)

            Log.d("qweqwe","wqe")
            changeRenderer(p0)

            true
        }
    }


onMapReady

마지막으로 onMapReady 를 재정의 해준다.

override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        mMap.uiSettings.isMyLocationButtonEnabled = true;
        mMap.uiSettings.isZoomControlsEnabled = true;
        boundmap()

        mClusterManager = ClusterManager<LatLngData>(this, mMap)
        clusterRenderer = MarkerClusterRenderer(this,mMap, mClusterManager, marker_view)
        mClusterManager.setRenderer(clusterRenderer)

        mMap.setOnCameraChangeListener (mClusterManager)
        mMap.setOnMarkerClickListener(mClusterManager)

        mMap.setOnInfoWindowClickListener { }
        mMap.setOnMapClickListener {
            card_view.visibility = View.GONE
            if(selectedMarker != null) {
                selectedMarker!!.setIcon(BitmapDescriptorFactory.fromBitmap(createDrawableFromView(this, marker_view)))
                selectedMarker = null
            }
        }

        clusterItemClick(mMap)
        clusterClick(mMap)       
    }


이제 맵을 각자의 프로젝트에 맞게 설정해서 실행시키면 이런식으로 클러스터링에 커스텀 마커를 적용시켜서 사용할 수 있다.


결론

이제 클러스터링에 커스팀마커를 적용해보았다.
이제 추가적으로 구글맵에서 검색하여 마커를 클릭할 때, 나오는 하단의 카드뷰를 만들어보고 최종적으로 지도를 완성해보겠다.

향후과제

지도 하단 카드뷰 추가


참고자료

https://milkissboy.tistory.com/36


Writer: Jae-Hwan Lee

댓글남기기