You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin实现MapBox自定义聚类图标:多色边框+数量显示

在Android Kotlin中实现MapBox自定义分色边框聚类图标

核心思路

要实现带分色边框和数量显示的自定义聚类图标,需要替换MapBox默认的聚类渲染逻辑,通过Canvas手动绘制图标,并利用ClusterIconProvider接口将自定义图标注入到聚类系统中。


步骤1:创建聚类图标生成工具类

这个类负责绘制带分色边框、深色背景和数量文本的Bitmap图标:

class ClusterIconGenerator(private val context: Context) {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.WHITE
        textSize = context.resources.getDimension(R.dimen.cluster_text_size)
        textAlign = Paint.Align.CENTER
        typeface = Typeface.DEFAULT_BOLD
    }
    private val iconSize = context.resources.getDimensionPixelSize(R.dimen.cluster_icon_size)
    private val borderWidth = context.resources.getDimensionPixelSize(R.dimen.cluster_border_width)
    private val innerRadius = (iconSize / 2) - borderWidth

    fun generateClusterIcon(colors: List<Int>, count: Int): Bitmap {
        val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)

        // 绘制分色边框
        drawSegmentedBorder(canvas, colors)

        // 绘制内部深色背景
        paint.color = Color.parseColor("#333333")
        canvas.drawCircle(iconSize/2f, iconSize/2f, innerRadius.toFloat(), paint)

        // 绘制数量文本
        val text = count.toString()
        val textBounds = Rect()
        textPaint.getTextBounds(text, 0, text.length, textBounds)
        val textY = (iconSize / 2) + (textBounds.height() / 2)
        canvas.drawText(text, iconSize/2f, textY, textPaint)

        return bitmap
    }

    private fun drawSegmentedBorder(canvas: Canvas, colors: List<Int>) {
        if (colors.isEmpty()) return
        val segmentAngle = 360f / colors.size
        val outerRadius = iconSize / 2f

        colors.forEachIndexed { index, color ->
            paint.color = color
            val startAngle = index * segmentAngle
            // 绘制扇形边框段
            canvas.drawArc(
                borderWidth.toFloat(),
                borderWidth.toFloat(),
                iconSize - borderWidth.toFloat(),
                iconSize - borderWidth.toFloat(),
                startAngle,
                segmentAngle,
                true,
                paint.apply { style = Paint.Style.STROKE; strokeWidth = borderWidth.toFloat() }
            )
        }
    }
}

步骤2:实现自定义ClusterIconProvider

这个接口是MapBox聚类系统和自定义图标之间的桥梁:

class CustomClusterIconProvider(private val context: Context) : ClusterIconProvider {
    private val iconGenerator = ClusterIconGenerator(context)

    override fun getIcon(cluster: Cluster): Bitmap {
        // 提取聚类中所有标注的颜色
        val colors = cluster.members.mapNotNull { member ->
            (member as? PointAnnotation)?.getStringProperty("color")?.let { colorStr ->
                try {
                    Color.parseColor(colorStr)
                } catch (e: IllegalArgumentException) {
                    Color.GRAY // 颜色解析失败时用默认灰色
                }
            }
        }
        return iconGenerator.generateClusterIcon(colors, cluster.pointCount)
    }
}

步骤3:修改PointAnnotationManager配置

禁用MapBox默认的聚类样式,替换为自定义图标提供器:

private fun onMapReady() {
    if (username.isNotEmpty()) {
        val annotationPlugin = binding.mapView.annotations

        // 禁用默认聚类的circle和text渲染,避免和自定义图标冲突
        val clusterOptions = ClusterOptions(
            circleColor = Color.TRANSPARENT,
            textColor = Color.TRANSPARENT
        )

        val annotationConfig = AnnotationConfig(
            annotationSourceOptions = AnnotationSourceOptions(
                clusterOptions = clusterOptions
            ),
            // 设置自定义聚类图标提供器
            clusterIconProvider = CustomClusterIconProvider(requireContext())
        )

        pointAnnotationManager = annotationPlugin.createPointAnnotationManager(annotationConfig)

        val destinations = listOf(
            Point.fromLngLat(77.6396767, 12.9754017),
            Point.fromLngLat(77.5912, 12.9797),
            Point.fromLngLat(77.088590, 13.339040),
            Point.fromLngLat(77.6212, 12.9797)
        )

        viewModel.getDeviceList().observe(viewLifecycleOwner) { cariTagDevices ->
            if (cariTagDevices.isNotEmpty()) {
                savedDeviceList = cariTagDevices
                for (i in cariTagDevices.indices) {
                    val device = cariTagDevices[i]
                    if (i < destinations.size) {
                        val iconBitmap = CariTagUtils.getMarkerBitmapFromView(
                            requireContext(),
                            CariTagUtils.getIcon(device.iconId),
                            device.colorValue
                        )
                        if (iconBitmap != null) {
                            prepareAnnotationMarker(iconBitmap, device, destinations[i])
                        }
                    }
                }
            }
        }

        mapboxMap.loadStyleUri(Style.MAPBOX_STREETS) {
            viewAnnotationManager = binding.mapView.viewAnnotationManager
            circleAnnotationManager = mapView.annotations.createCircleAnnotationManager()
            polylineAnnotationManager = mapView.annotations.createPolylineAnnotationManager()
        }
    }
}

步骤4:修改标注创建逻辑,存储颜色信息

确保每个PointAnnotation携带对应设备的颜色属性,方便聚类时提取:

private fun prepareAnnotationMarker(iconBitmap: Bitmap, device: CariTagDevice, point: Point) {
    val pointAnnotation = PointAnnotation().apply {
        geometry = point
        icon = IconFactory.getInstance(requireContext()).fromBitmap(iconBitmap)
        // 将设备颜色存储到标注属性中,格式为十六进制字符串(如"#FF0000")
        setStringProperty("color", device.colorValue)
    }
    pointAnnotationManager.create(pointAnnotation)
}

步骤5:添加尺寸资源

在res/values/dimens.xml中定义图标相关尺寸:

<dimen name="cluster_icon_size">50dp</dimen>
<dimen name="cluster_border_width">6dp</dimen>
<dimen name="cluster_text_size">18sp</dimen>

关键说明

  • 分色边框会根据聚类内标注的数量自动均分角度,每个角度段对应一个标注的颜色
  • 自定义图标完全替代了MapBox默认的聚类圆圈和文本,避免样式冲突
  • 标注的颜色通过属性存储,聚类时动态提取,确保边框颜色和原始标注一致

内容的提问来源于stack exchange,提问作者megha venkat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:37:28